nydp 0.4.2 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +44 -0
  3. data/lib/lisp/core-010-precompile.nydp +13 -16
  4. data/lib/lisp/core-012-utils.nydp +3 -2
  5. data/lib/lisp/core-015-documentation.nydp +54 -23
  6. data/lib/lisp/core-017-builtin-dox.nydp +14 -12
  7. data/lib/lisp/core-020-utils.nydp +5 -5
  8. data/lib/lisp/core-030-syntax.nydp +166 -72
  9. data/lib/lisp/core-035-flow-control.nydp +38 -11
  10. data/lib/lisp/core-037-list-utils.nydp +12 -0
  11. data/lib/lisp/core-039-module.nydp +24 -0
  12. data/lib/lisp/core-040-utils.nydp +32 -12
  13. data/lib/lisp/core-041-string-utils.nydp +25 -1
  14. data/lib/lisp/core-042-date-utils.nydp +21 -1
  15. data/lib/lisp/core-043-list-utils.nydp +96 -64
  16. data/lib/lisp/core-070-prefix-list.nydp +1 -1
  17. data/lib/lisp/core-080-pretty-print.nydp +57 -17
  18. data/lib/lisp/core-090-hook.nydp +35 -1
  19. data/lib/lisp/core-100-utils.nydp +82 -2
  20. data/lib/lisp/core-110-hash-utils.nydp +56 -2
  21. data/lib/lisp/core-120-settings.nydp +16 -5
  22. data/lib/lisp/core-130-validations.nydp +51 -0
  23. data/lib/lisp/core-900-benchmarking.nydp +78 -20
  24. data/lib/lisp/tests/accum-examples.nydp +28 -1
  25. data/lib/lisp/tests/aif-examples.nydp +8 -3
  26. data/lib/lisp/tests/andify-examples.nydp +7 -0
  27. data/lib/lisp/tests/at-syntax-examples.nydp +17 -0
  28. data/lib/lisp/tests/best-examples.nydp +9 -0
  29. data/lib/lisp/tests/builtin-tests.nydp +19 -0
  30. data/lib/lisp/tests/case-examples.nydp +14 -0
  31. data/lib/lisp/tests/date-examples.nydp +54 -1
  32. data/lib/lisp/tests/destructuring-examples.nydp +46 -14
  33. data/lib/lisp/tests/detect-examples.nydp +12 -0
  34. data/lib/lisp/tests/dp-examples.nydp +24 -0
  35. data/lib/lisp/tests/each-tests.nydp +5 -0
  36. data/lib/lisp/tests/empty-examples.nydp +1 -1
  37. data/lib/lisp/tests/error-tests.nydp +4 -4
  38. data/lib/lisp/tests/explain-mac-examples.nydp +1 -1
  39. data/lib/lisp/tests/filter-forms-examples.nydp +15 -0
  40. data/lib/lisp/tests/hash-examples.nydp +25 -1
  41. data/lib/lisp/tests/list-grep-examples.nydp +40 -0
  42. data/lib/lisp/tests/list-tests.nydp +58 -1
  43. data/lib/lisp/tests/map-hash-examples.nydp +11 -0
  44. data/lib/lisp/tests/module-examples.nydp +10 -0
  45. data/lib/lisp/tests/multi-assign-examples.nydp +6 -0
  46. data/lib/lisp/tests/parser-tests.nydp +25 -0
  47. data/lib/lisp/tests/pretty-print-tests.nydp +17 -14
  48. data/lib/lisp/tests/set-difference-examples.nydp +8 -0
  49. data/lib/lisp/tests/settings-examples.nydp +17 -1
  50. data/lib/lisp/tests/string-tests.nydp +70 -1
  51. data/lib/lisp/tests/syntax-tests.nydp +5 -1
  52. data/lib/lisp/tests/to-integer-examples.nydp +16 -0
  53. data/lib/lisp/tests/validation-examples.nydp +15 -0
  54. data/lib/nydp.rb +10 -3
  55. data/lib/nydp/assignment.rb +10 -3
  56. data/lib/nydp/builtin.rb +1 -1
  57. data/lib/nydp/builtin/abs.rb +8 -0
  58. data/lib/nydp/builtin/date.rb +15 -1
  59. data/lib/nydp/builtin/error.rb +1 -1
  60. data/lib/nydp/builtin/hash.rb +24 -1
  61. data/lib/nydp/builtin/inspect.rb +1 -1
  62. data/lib/nydp/builtin/plus.rb +10 -2
  63. data/lib/nydp/builtin/random_string.rb +2 -2
  64. data/lib/nydp/builtin/{car.rb → regexp.rb} +2 -2
  65. data/lib/nydp/builtin/ruby_wrap.rb +72 -0
  66. data/lib/nydp/builtin/string_match.rb +2 -2
  67. data/lib/nydp/builtin/string_pad_left.rb +7 -0
  68. data/lib/nydp/builtin/string_pad_right.rb +7 -0
  69. data/lib/nydp/builtin/string_replace.rb +3 -3
  70. data/lib/nydp/builtin/string_split.rb +4 -3
  71. data/lib/nydp/builtin/to_integer.rb +23 -0
  72. data/lib/nydp/builtin/to_string.rb +2 -9
  73. data/lib/nydp/builtin/type_of.rb +9 -6
  74. data/lib/nydp/closure.rb +0 -3
  75. data/lib/nydp/cond.rb +23 -1
  76. data/lib/nydp/context_symbol.rb +14 -6
  77. data/lib/nydp/core.rb +36 -28
  78. data/lib/nydp/core_ext.rb +21 -5
  79. data/lib/nydp/date.rb +26 -18
  80. data/lib/nydp/function_invocation.rb +34 -26
  81. data/lib/nydp/helper.rb +35 -3
  82. data/lib/nydp/interpreted_function.rb +68 -40
  83. data/lib/nydp/literal.rb +1 -1
  84. data/lib/nydp/pair.rb +22 -5
  85. data/lib/nydp/parser.rb +11 -7
  86. data/lib/nydp/string_atom.rb +3 -4
  87. data/lib/nydp/symbol_lookup.rb +7 -7
  88. data/lib/nydp/tokeniser.rb +2 -2
  89. data/lib/nydp/truth.rb +10 -10
  90. data/lib/nydp/version.rb +1 -1
  91. data/lib/nydp/vm.rb +7 -0
  92. data/nydp.gemspec +2 -4
  93. data/spec/date_spec.rb +93 -0
  94. data/spec/embedded_spec.rb +12 -12
  95. data/spec/foreign_hash_spec.rb +14 -2
  96. data/spec/hash_non_hash_behaviour_spec.rb +7 -7
  97. data/spec/hash_spec.rb +24 -2
  98. data/spec/nydp_spec.rb +14 -2
  99. data/spec/pair_spec.rb +3 -1
  100. data/spec/parser_spec.rb +31 -20
  101. data/spec/rand_spec.rb +3 -3
  102. data/spec/spec_helper.rb +10 -1
  103. metadata +24 -37
  104. data/lib/nydp/builtin/cdr.rb +0 -7
  105. data/lib/nydp/builtin/cons.rb +0 -9
@@ -13,9 +13,9 @@
13
13
  `(ensuring (fn () ,protection)
14
14
  (fn () ,@body)))
15
15
 
16
+ ;; tests 'test, as long as 'test is non-nil,
17
+ ;; repeatedly executes 'body
16
18
  (mac while (test . body)
17
- ; tests 'test, as long as 'test is non-nil,
18
- ; repeatedly executes 'body
19
19
  (w/uniq (rfname pred)
20
20
  `(rfnwith ,rfname (,pred ,test)
21
21
  (when ,pred
@@ -40,12 +40,21 @@
40
40
  (loop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1))
41
41
  ,@body))))
42
42
 
43
- (mac curry (func . args1)
44
- ; return a new function which is the original function with
45
- ; the given args1 already applied
46
- ; arguments to the new function are whatever arguments remain
47
- ; for the old function
48
- `(fn args (apply ,func ,@args1 args)))
43
+ ;; return a new function which is the original function with
44
+ ;; the given args1 already applied
45
+ ;; arguments to the new function are whatever arguments remain
46
+ ;; for the old function
47
+ ;; Could be (mac curry things `(fn args (apply ,@things args))) but less readable
48
+ (mac curry (f . args0)
49
+ `(fn args
50
+ (apply ,f ,@args0 args)))
51
+
52
+ ;; like curry, but the returned function takes only a single arg (assumes all
53
+ ;; args but one are provided here)
54
+ ;; Could be (mac curry1 things `(fn (arg) (,@things arg))) but less readable
55
+ (mac curry1 (f . args)
56
+ `(fn (arg)
57
+ (,f ,@args arg)))
49
58
 
50
59
  (mac cache-get (hsh key val)
51
60
  ; if ,key is already in ,hsh - return the associated value.
@@ -56,13 +65,31 @@
56
65
  (let ,v (hash-get ,h ,k)
57
66
  (or ,v (returnlet ,v ,val (hash-set ,h ,k ,v)))))))
58
67
 
68
+ ;; same as 'def, but caches the result, keyed on args, so for a given set of args the result
69
+ ;; is only ever calculated once
70
+ ;;
71
+ ;; WARNING: in current incarnation, won't work with destructuring args
59
72
  (mac defmemo (name args . body)
60
- ; same as 'def, but caches the result, keyed on args, so for a given set of args the result
61
- ; is only ever calculated once
62
- (let forms (filter-forms (build-def-hash (hash)) body)
73
+ (let forms (filter-forms (build-def-hash) body)
63
74
  (w/uniq h
64
75
  `(let ,h (hash)
65
76
  (def ,name ,args
66
77
  ,@(map (fn (c) (cons 'comment c)) forms.comment)
67
78
  ,@(map (fn (c) (cons 'chapter c)) forms.chapter)
68
79
  (cache-get ,h (list ,@args) (do ,@(hash-get forms nil))))))))
80
+
81
+ ;; memoises a function expression
82
+ ;; args: the function arguments
83
+ ;; body: a list of function body expressions
84
+ ;; next: a function to assemble a function expression from 'args and 'body
85
+ ;; returns whatever 'next returns, where 'body is memoised based on the value of 'args
86
+ (def memoise (args body next)
87
+ (let (memo newbody) (filter-remove '#memoise body)
88
+ (if memo
89
+ (w/uniq h
90
+ `(let ,h (hash) ,(next args `((cache-get ,h (list ,@args) (do ,@newbody))))))
91
+ (next args body))))
92
+
93
+ (assign fun/expanders
94
+ (cons
95
+ (cons 'memoise memoise) fun/expanders))
@@ -13,6 +13,17 @@
13
13
  (f (car things))
14
14
  (eachl f (cdr things))))
15
15
 
16
+ ;; if things is a pair,
17
+ ;; if (cdr things) is nil, return (car things)
18
+ ;; else recurse on (cdr things)
19
+ ;; else return things
20
+ (def list/last (things)
21
+ (if (pair? things)
22
+ (aif (cdr things)
23
+ (list/last it)
24
+ (car things))
25
+ things))
26
+
16
27
  ; invokes 'f for each element of 'things, last element processed first
17
28
  ; ( "r" in "eachr" = "rightmost first" )
18
29
  (def eachr (f things)
@@ -29,6 +40,7 @@
29
40
  (rfnwith flattenize (x things)
30
41
  (if (pair? x)
31
42
  (eachr flattenize x)
43
+ x
32
44
  (push (f x) acc)))
33
45
  acc))
34
46
 
@@ -0,0 +1,24 @@
1
+ ;; creates a private namespace for support functions for one or more explicitly exported/public functions
2
+ ;;
3
+ ;; (module foo
4
+ ;; (def h ...)
5
+ ;; (def u ...)
6
+ ;; (export bar (x y) (h u x y)))
7
+ ;;
8
+ ;; results in 'foo/bar being universally available, but 'h and 'u are visible only within the module and override
9
+ ;; any other 'h or 'u defined elsewhere, in the scope of the module.
10
+ (mac module (module-name . forms)
11
+ (let private-names nil
12
+ (let module-forms
13
+ { def (fn (name args . body)
14
+ (push nil private-names)
15
+ (push name private-names)
16
+ `(assign ,name (fn ,args ,@body)))
17
+ export-def macs.def
18
+ export (fn (name args . body)
19
+ `(export-def
20
+ ,(sym (+ (to-string module-name) "/" (to-string name)))
21
+ ,args
22
+ ,@body)) }
23
+ (let module-body (pre-compile-each module-forms forms)
24
+ `(with (,@private-names) ,@module-body)))))
@@ -11,21 +11,20 @@
11
11
  (def num? (arg) (comment "true if arg is a number") (isa 'number arg))
12
12
  (def string? (arg) (comment "true if arg is a string") (isa 'string arg))
13
13
 
14
- (mac just (expr)
15
- ; this is useful sometimes when 'expr can't stand on its own due to lexical ambiguity, most often in string interpolations
16
- ; for example, in "hello ~person, how are you", the parser will try to interpolate the symbol "person," rather than the
17
- ; expected "person". In this case, use "hello ~(just person), how are you"
18
- expr)
14
+ ;; this is useful sometimes when 'expr can't stand on its own due to lexical ambiguity, most often in string interpolations
15
+ ;; for example, in "hello ~person, how are you", the parser will try to interpolate the symbol "person," rather than the
16
+ ;; expected "person". In this case, use "hello ~(just person), how are you"
17
+ (mac just (expr) expr)
19
18
 
20
19
  (def quotify (arg) `(quote ,arg))
21
20
 
22
- ; return a function that always returns 'arg, similar to K in SKI calculus
21
+ ;; return a function that always returns 'arg, similar to K in SKI calculus
23
22
  (defmemo k (arg) (fn nil arg))
24
23
 
24
+ ;; return the length of 'things where 'things may be nil, a string, list or hash
25
+ ;; length of nil is zero, length of hash is number of keys, length of string
26
+ ;; is number of characters, length of list is number of direct items - no recursive counting
25
27
  (def len (things)
26
- ; return the length of 'things where 'things may be nil, a string, list or hash
27
- ; length of nil is zero, length of hash is number of keys, length of string
28
- ; is number of characters, length of list is number of direct items - no recursive counting
29
28
  (chapter list-manipulation)
30
29
  (chapter string-manipulation)
31
30
  (chapter hash-manipulation)
@@ -37,8 +36,8 @@
37
36
 
38
37
  (assign dynamics (hash))
39
38
 
39
+ ;; creates a dynamic variable.
40
40
  (mac dynamic (name initial)
41
- ; creates a dynamic variable.
42
41
  (let with-mac-name (sym:+ "w/" name)
43
42
  (w/uniq prev
44
43
  `(do
@@ -53,10 +52,10 @@
53
52
  (def ,name () (hash-get (thread-locals) ',name))))))
54
53
 
55
54
 
56
- ; overrides 'privately defined earlier in documentation manager
55
+ ;; overrides 'privately defined earlier in documentation manager
57
56
  (dynamic privately)
58
57
 
59
- ; suppress documentation of anything defined in 'body
58
+ ;; suppress documentation of anything defined in 'body
60
59
  (mac in-private body
61
60
  `(w/privately t ,@body))
62
61
 
@@ -91,3 +90,24 @@
91
90
  (chapter string-manipulation)
92
91
  (chapter hash-manipulation)
93
92
  (!empty? thing))
93
+
94
+ ;; returns the first non-empty item in 'args
95
+ ;; mac equivalent of (detect present? args)
96
+ (mac dp args
97
+ (if args
98
+ (w/uniq nearg
99
+ `(let ,nearg ,(car args)
100
+ (if (empty? ,nearg)
101
+ (dp ,@(cdr args))
102
+ ,nearg)))
103
+ nil))
104
+
105
+ ;; returns a function that returns a number sequence. Example:
106
+ ;; (let c (counter)
107
+ ;; (p (c)) ;;=> 0
108
+ ;; (p (c)) ;;=> 1
109
+ ;; (p (c))) ;;=> 2
110
+ ;;
111
+ (def counter ()
112
+ (let i -1
113
+ (fn () (++ i))))
@@ -2,7 +2,7 @@
2
2
 
3
3
  ; return a new string with leading and trailing whitespace removed
4
4
  (def string-strip (txt)
5
- (string-replace "(^\\s+|\\s+$)" "" txt))
5
+ (string-replace "(\\A\\s+|\\s+\\z)" "" txt))
6
6
 
7
7
  (def joinstr (txt . things)
8
8
  ; flatten 'things into a single list (ie unnest lists)
@@ -30,3 +30,27 @@
30
30
  ; return the first 'length chars of string 'str
31
31
  (def string-truncate (str length)
32
32
  (string-replace "(.{~|length|}).*" "\\1" str))
33
+
34
+ ;; returns a function with args 'args whose body is 'str. 'str should be a string,
35
+ ;; 'args should correspond to interpolations within 'str
36
+ ;;
37
+ ;; example: (string-eval-fn "hello \~u.firstname" 'u)
38
+ ;; returns (fn (u) (string-pieces "hello " u.firstname))
39
+ (defmemo string-eval-fn (str args)
40
+ (eval `(fn ,args
41
+ ,(parse-in-string str))))
42
+
43
+ ;; assigns 'args respectively to 'arg-names and evals 'str in that context.
44
+ ;; Assumes 'str contains interpolations which reference 'arg-names.
45
+ ;; Useful for evaluating user-supplied strings ; dangerous for the same reason.
46
+ ;;
47
+ ;; example: (string/eval-with-args "\~x + \~y is \~(+ x y)" '(x y) 2 3)
48
+ ;; returns "2 + 3 is 5"
49
+ ;;
50
+ (def string/eval-with-args (str arg-names . args)
51
+ (on-err
52
+ (error (j "error evaluating " (inspect str)
53
+ "\nwith arg names " (inspect arg-names)
54
+ "\nand args " (inspect args)))
55
+ (apply (string-eval-fn str arg-names)
56
+ args)))
@@ -1,5 +1,25 @@
1
1
  (chapter-start 'date-time "utilities for retrieving and manipulating dates and times")
2
2
 
3
+ ;; return a date for the current day
3
4
  (def today ()
4
- ; return a date for the current day
5
5
  (date))
6
+
7
+ ;; return a Time object representing the time 's seconds ago
8
+ (def seconds-ago (s)
9
+ (- (time) s))
10
+
11
+ (def anniversary/previous (anchor anniv)
12
+ (let d (date anchor.year
13
+ anniv.month
14
+ anniv.day)
15
+ (if (< d anchor)
16
+ d
17
+ d.last-year)))
18
+
19
+ (def anniversary/next (anchor anniv)
20
+ (let d (date anchor.year
21
+ anniv.month
22
+ anniv.day)
23
+ (if (> d anchor)
24
+ d
25
+ d.next-year)))
@@ -23,76 +23,72 @@
23
23
  (intersperse inbetween (cdr things)))
24
24
  things))
25
25
 
26
+ ;; expects 'things a list of lists, joins the lists
27
+ ;; returns (a b X c d X e f)
28
+ ;; placing 'inbetween in between each list.
29
+ ;; For example (intersperse-splicing 'X '((a b) (c d) (e f)))
26
30
  (def intersperse-splicing (inbetween things)
27
- ; expects 'things a list of lists, joins the lists
28
- ; placing 'inbetween in between each list.
29
- ; For example (intersperse-splicing 'X '((a b) (c d) (e f)))
30
- ; returns (a b X c d X e f)
31
31
  (apply joinlists (intersperse (list inbetween) things)))
32
32
 
33
- (def collect (f things)
34
- ; if 'things is a list, return all the items in the list for which 'f returns non-nil
35
- ; otherwise, return 'things if (f things) is non-nil
36
- ; otherwise, nil
37
- ; note that this preserves improper lists and may return only the lastcdr if all else fails...
38
- (rfnwith collector (items things)
39
- (if (no items)
40
- nil
41
- (pair? items)
42
- (if (f (car items))
43
- (cons (car items)
44
- (collector (cdr items)))
45
- (collector (cdr items)))
46
- (f items)
47
- items)))
33
+ ;; if 'things is a list, return all the items in the list for which 'f returns non-nil
34
+ ;; otherwise, return 'things if (f things) is non-nil
35
+ ;; otherwise, nil
36
+ ;; note that this preserves improper lists and may return only the lastcdr if all else fails...
37
+ (def collect (f items)
38
+ (if (pair? items)
39
+ (if (f (car items))
40
+ (cons (car items)
41
+ (collect f (cdr items)))
42
+ (collect f (cdr items)))
43
+ items
44
+ (if (f items)
45
+ items)))
48
46
 
49
47
  (assign select collect)
50
48
 
51
- (def compact (things)
52
- ; return a new list containing only non-nil items from the given list
53
- (collect present? things))
49
+ ;; return a new list containing only 'present? items from the given list
50
+ (def compact (things) (collect present? things))
54
51
 
52
+ ;; return the sum of all non-nil values (consider nil as zero)
55
53
  (def +nz args
56
- ; return the sum of all non-nil values (consider nil as zero)
57
54
  (apply + (compact args)))
58
55
 
56
+ ;; return all the items in 'things for which 'f returns nil
59
57
  (def reject (f things)
60
- ; return all the items in 'things for which 'f returns nil
61
58
  (collect !f things))
62
59
 
63
- (def nth (n things)
64
- ; returns the n-th item in the list 'things
65
- (if (eq? n 0)
66
- (car things)
67
- (nth (- n 1) (cdr things))))
68
-
69
- (mac each (var things . body)
70
- ; repeatedly assigns an element of 'things to 'var,
71
- ; and executes 'body each time
60
+ (def each/build-expression (var things body othervars otherparams)
72
61
  (w/uniq (xs c)
73
- `((rfn ,c (,xs)
74
- (if (pair? ,xs)
75
- (do (let ,var (car ,xs) ,@body)
76
- (,c (cdr ,xs)))))
77
- ,things)))
62
+ `(rfnwith ,c (,xs ,things ,@othervars)
63
+ (if (pair? ,xs)
64
+ (let ,var (car ,xs)
65
+ ,@body
66
+ (,c (cdr ,xs) ,@otherparams))))))
67
+
68
+ ;; repeatedly assigns an element of 'things to 'var,
69
+ ;; and executes 'body each time
70
+ (mac each (var things . body)
71
+ (each/build-expression var things body))
78
72
 
79
- (def reduce (f things)
80
- ((rfn rd (acc list)
81
- (if (pair? list)
82
- (rd (f acc (car list))
83
- (cdr list))
84
- acc))
85
- (car things) (cdr things)))
73
+ (mac each-with-index (ivar var things . body)
74
+ (each/build-expression var things body `(,ivar 0) `((+ ,ivar 1))))
86
75
 
76
+ (def reduce (f things)
77
+ (rfnwith rd (acc (car things) list (cdr things))
78
+ (if (pair? list)
79
+ (rd (f acc (car list))
80
+ (cdr list))
81
+ acc)))
82
+
83
+ ;; t if this is a proper list (last cdr is nil)
84
+ ;; nil otherwise (last cdr is neither cons nor nil)
87
85
  (def proper? (list)
88
- ; t if this is a proper list (last cdr is nil)
89
- ; nil otherwise (last cdr is neither cons nor nil)
90
86
  (or (no list)
91
87
  (and (pair? list)
92
88
  (proper? (cdr list)))))
93
89
 
90
+ ;; returns the first 'n items in the list 'things
94
91
  (def firstn (n things)
95
- ; returns the first 'n items in the list 'things
96
92
  (if (eq? n 0) nil
97
93
  things (cons (car things)
98
94
  (firstn (- n 1)
@@ -140,25 +136,39 @@
140
136
  (detect (curry eq? f)
141
137
  things)))
142
138
 
139
+ ;; split things into a list of lists each n long
143
140
  (def tuples (n things)
144
- ;; split things into a list of lists each n long
145
141
  (rfnwith _ (list things)
146
142
  (if (no list)
147
143
  nil
148
144
  (cons (firstn n list) (_ (nthcdr n list))))))
149
145
 
150
- (def best (f things)
151
- (if (no things)
152
- nil
153
- (let winner (car things)
154
- (each thing (cdr things)
155
- (if (f thing winner)
156
- (= winner thing)))
157
- winner)))
146
+ ;; iterates through 'things pairwise (a,b then b,c then c,d etc), returns whichever is preferred by 'better-p
147
+ ;; example (best > '(3 1 4 1 5 9 2)) returns 9
148
+ ;; "better-p" -> takes two args a and b, return t if a is "better" than b, nil otherwise
149
+ (def best (better-p things)
150
+ (reduce (fn (a b)
151
+ (if (better-p a b) a b))
152
+ things))
158
153
 
159
154
  (def min things (best < things))
160
155
  (def max things (best > things))
161
156
 
157
+ ;; returns a function taking two args a and b, that compares attributes of two objects
158
+ ;; 'map-f takes one arg a returning a1
159
+ ;; 'compare-p takes two args a1 and b1, returns t if a1 is "better" than b1
160
+ ;;
161
+ ;; useful in conjunction with 'best : (best (map-compare-f > &size) (list { size 1 } { size 7 } { size 3 })) returns { size 7 }
162
+ (def map-compare-f (compare-p map-f)
163
+ (fn (a b) (compare-p (map-f a) (map-f b))))
164
+
165
+ ;; iterate over 'things, calling 'on-atom for each non-list element, and calling
166
+ ;; 'on-list for each list element.
167
+ ;; 'on-atom takes one parameter, the element in question;
168
+ ;; 'on-list takes two parameters: a function to call for recursing, and the list in question.
169
+ ;; 'on-list should be something like (fn (rec xs) (foo xs) (map rec xs)) to construct a new list,
170
+ ;; or (fn (rec xs) (foo xs) (eachl rec xs)) to iterate without constructing a new list
171
+ ;; see 'list-gsub for an example of constructing a new list using this.
162
172
  (def map-recurse (on-atom on-list things)
163
173
  ((afn (xs)
164
174
  (if (pair? xs)
@@ -167,8 +177,20 @@
167
177
  (on-atom xs)))
168
178
  things))
169
179
 
180
+ ;; like map-recurse, but doesn't depend on caller to initiate recursion
181
+ ;; 'on-atom and 'on-list are functions each taking one parameter
182
+ ;; return value is last returned item from on-atom or on-list
183
+ (def list/traverse (on-atom on-list things)
184
+ (map-recurse
185
+ (fn (s)
186
+ (on-atom s))
187
+ (fn (rec xs)
188
+ (on-list xs)
189
+ (eachl rec xs))
190
+ things))
191
+
192
+ ;; recursively replaces 'old with 'new inside 'list
170
193
  (def list-gsub (list old new)
171
- ; recursively replaces 'old with 'new inside 'list
172
194
  (map-recurse (fn (s) (if (eq? s old) new s))
173
195
  (fn (m things)
174
196
  (if (eq? things old)
@@ -177,8 +199,8 @@
177
199
  list))
178
200
 
179
201
  (def all? (f things)
180
- ; if 'things is a list, true when all items are non-nil
181
- ; if 'things is an atom, true when non-nil
202
+ ; if 'things is a list, true when f(thing) is non-nil for each thing in things
203
+ ; if 'things is an atom, true when f(things) is non-nil
182
204
  (if (pair? things)
183
205
  (and (f:car things)
184
206
  (or (no:cdr things)
@@ -186,8 +208,8 @@
186
208
  (f things)))
187
209
 
188
210
  (def any? (f things)
189
- ; if 'things is a list, true when at least one item is non-nil
190
- ; if 'things is an atom, true when non-nil
211
+ ; if 'things is a list, true when f(thing) is non-nil for at least one thing in things
212
+ ; if 'things is an atom, true when f(thing) is non-nil
191
213
  (if (pair? things)
192
214
  (or (f:car things)
193
215
  (and (cdr things)
@@ -195,8 +217,8 @@
195
217
  (f things)))
196
218
 
197
219
  (def none? (f things)
198
- ; if 'things is a list, true when all items are nil
199
- ; if 'things is an atom, true when nil
220
+ ; if 'things is a list, true when f(thing) is nil for each thing in things
221
+ ; if 'things is an atom, true when f(things) is nil
200
222
  (if (pair? things)
201
223
  (and (no:f:car things)
202
224
  (none? f (cdr things)))
@@ -215,3 +237,13 @@
215
237
 
216
238
  ; given an arg 'f, invoke 'f with no args
217
239
  (def self-invoke (f) (f))
240
+
241
+ ;; returns the first element of 'things iff it is the only element of 'things
242
+ (def list-single-element (things)
243
+ (if (no (cdr things)) (car things)))
244
+
245
+ ;; like map, but function 'f takes two arguments: the thing and the 0-based index of the thing
246
+ (def map-with-index (f things)
247
+ (let c (counter)
248
+ (map (fn (thing) (f thing (c)))
249
+ things)))