nydp 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +77 -56
  4. data/lib/lisp/core-000.nydp +1 -1
  5. data/lib/lisp/core-010-precompile.nydp +49 -29
  6. data/lib/lisp/core-012-utils.nydp +12 -8
  7. data/lib/lisp/core-015-documentation.nydp +41 -15
  8. data/lib/lisp/core-017-builtin-dox.nydp +621 -100
  9. data/lib/lisp/core-020-utils.nydp +33 -6
  10. data/lib/lisp/core-025-warnings.nydp +1 -1
  11. data/lib/lisp/core-030-syntax.nydp +64 -48
  12. data/lib/lisp/core-035-flow-control.nydp +20 -28
  13. data/lib/lisp/core-037-list-utils.nydp +84 -21
  14. data/lib/lisp/core-040-utils.nydp +8 -5
  15. data/lib/lisp/core-041-string-utils.nydp +17 -11
  16. data/lib/lisp/core-043-list-utils.nydp +140 -77
  17. data/lib/lisp/core-045-dox-utils.nydp +1 -0
  18. data/lib/lisp/core-050-test-runner.nydp +8 -12
  19. data/lib/lisp/core-070-prefix-list.nydp +19 -15
  20. data/lib/lisp/core-080-pretty-print.nydp +13 -5
  21. data/lib/lisp/core-090-hook.nydp +11 -11
  22. data/lib/lisp/core-100-utils.nydp +51 -66
  23. data/lib/lisp/core-110-hash-utils.nydp +34 -7
  24. data/lib/lisp/core-120-settings.nydp +14 -9
  25. data/lib/lisp/core-130-validations.nydp +28 -13
  26. data/lib/lisp/core-900-benchmarking.nydp +420 -47
  27. data/lib/lisp/tests/000-empty-args-examples.nydp +5 -0
  28. data/lib/lisp/tests/andify-examples.nydp +1 -1
  29. data/lib/lisp/tests/auto-hash-examples.nydp +6 -1
  30. data/lib/lisp/tests/best-examples.nydp +1 -1
  31. data/lib/lisp/tests/boot-tests.nydp +1 -1
  32. data/lib/lisp/tests/date-examples.nydp +129 -102
  33. data/lib/lisp/tests/destructuring-examples.nydp +1 -1
  34. data/lib/lisp/tests/dox-tests.nydp +2 -2
  35. data/lib/lisp/tests/hash-examples.nydp +58 -33
  36. data/lib/lisp/tests/list-tests.nydp +137 -1
  37. data/lib/lisp/tests/pretty-print-tests.nydp +12 -0
  38. data/lib/lisp/tests/rotate-2d-array-examples.nydp +26 -0
  39. data/lib/lisp/tests/sort-examples.nydp +5 -5
  40. data/lib/lisp/tests/string-tests.nydp +16 -5
  41. data/lib/lisp/tests/syntax-tests.nydp +10 -2
  42. data/lib/lisp/tests/time-examples.nydp +8 -1
  43. data/lib/lisp/tests/unparse-tests.nydp +13 -7
  44. data/lib/nydp/assignment.rb +15 -28
  45. data/lib/nydp/builtin/abs.rb +4 -3
  46. data/lib/nydp/builtin/apply.rb +8 -10
  47. data/lib/nydp/builtin/cdr_set.rb +1 -1
  48. data/lib/nydp/builtin/comment.rb +1 -3
  49. data/lib/nydp/builtin/date.rb +11 -28
  50. data/lib/nydp/builtin/divide.rb +3 -10
  51. data/lib/nydp/builtin/ensuring.rb +6 -21
  52. data/lib/nydp/builtin/error.rb +2 -4
  53. data/lib/nydp/builtin/eval.rb +9 -4
  54. data/lib/nydp/builtin/greater_than.rb +7 -8
  55. data/lib/nydp/builtin/handle_error.rb +10 -34
  56. data/lib/nydp/builtin/hash.rb +24 -45
  57. data/lib/nydp/builtin/inspect.rb +1 -3
  58. data/lib/nydp/builtin/is_equal.rb +4 -7
  59. data/lib/nydp/builtin/less_than.rb +6 -7
  60. data/lib/nydp/builtin/log.rb +7 -0
  61. data/lib/nydp/builtin/math_ceiling.rb +1 -3
  62. data/lib/nydp/builtin/math_floor.rb +1 -3
  63. data/lib/nydp/builtin/math_power.rb +1 -3
  64. data/lib/nydp/builtin/math_round.rb +2 -2
  65. data/lib/nydp/builtin/minus.rb +7 -14
  66. data/lib/nydp/builtin/parse.rb +5 -5
  67. data/lib/nydp/builtin/parse_in_string.rb +5 -7
  68. data/lib/nydp/builtin/plus.rb +14 -31
  69. data/lib/nydp/builtin/pre_compile.rb +1 -3
  70. data/lib/nydp/builtin/puts.rb +4 -8
  71. data/lib/nydp/builtin/quit.rb +1 -1
  72. data/lib/nydp/builtin/rand.rb +6 -11
  73. data/lib/nydp/builtin/random_string.rb +2 -4
  74. data/lib/nydp/builtin/rng.rb +25 -0
  75. data/lib/nydp/builtin/ruby_wrap.rb +27 -14
  76. data/lib/nydp/builtin/script_run.rb +1 -3
  77. data/lib/nydp/builtin/set_intersection.rb +3 -4
  78. data/lib/nydp/builtin/set_union.rb +3 -4
  79. data/lib/nydp/builtin/sort.rb +2 -7
  80. data/lib/nydp/builtin/string_match.rb +5 -13
  81. data/lib/nydp/builtin/string_replace.rb +2 -7
  82. data/lib/nydp/builtin/string_split.rb +3 -8
  83. data/lib/nydp/builtin/sym.rb +2 -9
  84. data/lib/nydp/builtin/thread_locals.rb +2 -2
  85. data/lib/nydp/builtin/time.rb +38 -44
  86. data/lib/nydp/builtin/times.rb +6 -15
  87. data/lib/nydp/builtin/to_integer.rb +8 -14
  88. data/lib/nydp/builtin/to_string.rb +2 -13
  89. data/lib/nydp/builtin/type_of.rb +10 -16
  90. data/lib/nydp/builtin/vm_info.rb +2 -10
  91. data/lib/nydp/builtin.rb +15 -37
  92. data/lib/nydp/compiler.rb +29 -19
  93. data/lib/nydp/cond.rb +95 -88
  94. data/lib/nydp/context_symbol.rb +11 -9
  95. data/lib/nydp/core.rb +74 -73
  96. data/lib/nydp/core_ext.rb +87 -26
  97. data/lib/nydp/date.rb +22 -19
  98. data/lib/nydp/error.rb +2 -3
  99. data/lib/nydp/function_invocation.rb +76 -289
  100. data/lib/nydp/helper.rb +18 -9
  101. data/lib/nydp/interpreted_function.rb +159 -25
  102. data/lib/nydp/lexical_context.rb +9 -8
  103. data/lib/nydp/lexical_context_builder.rb +1 -1
  104. data/lib/nydp/literal.rb +3 -7
  105. data/lib/nydp/loop.rb +72 -0
  106. data/lib/nydp/namespace.rb +52 -0
  107. data/lib/nydp/pair.rb +146 -50
  108. data/lib/nydp/parser.rb +9 -11
  109. data/lib/nydp/plugin.rb +88 -19
  110. data/lib/nydp/runner.rb +141 -23
  111. data/lib/nydp/symbol.rb +16 -26
  112. data/lib/nydp/symbol_lookup.rb +3 -2
  113. data/lib/nydp/tokeniser.rb +1 -1
  114. data/lib/nydp/truth.rb +2 -37
  115. data/lib/nydp/version.rb +1 -1
  116. data/lib/nydp.rb +33 -44
  117. data/nydp.gemspec +2 -1
  118. data/spec/date_spec.rb +26 -32
  119. data/spec/embedded_spec.rb +22 -22
  120. data/spec/error_spec.rb +12 -16
  121. data/spec/foreign_hash_spec.rb +21 -36
  122. data/spec/hash_non_hash_behaviour_spec.rb +12 -29
  123. data/spec/hash_spec.rb +36 -49
  124. data/spec/literal_spec.rb +6 -6
  125. data/spec/nydp_spec.rb +14 -14
  126. data/spec/pair_spec.rb +8 -8
  127. data/spec/parser_spec.rb +41 -37
  128. data/spec/rand_spec.rb +1 -4
  129. data/spec/spec_helper.rb +3 -3
  130. data/spec/string_atom_spec.rb +15 -16
  131. data/spec/symbol_spec.rb +27 -52
  132. data/spec/thread_local_spec.rb +23 -8
  133. data/spec/time_spec.rb +4 -10
  134. data/spec/tokeniser_spec.rb +10 -10
  135. metadata +25 -13
  136. data/lib/nydp/builtin/modulo.rb +0 -11
  137. data/lib/nydp/builtin/regexp.rb +0 -7
  138. data/lib/nydp/builtin/sqrt.rb +0 -7
  139. data/lib/nydp/builtin/string_pad_left.rb +0 -7
  140. data/lib/nydp/builtin/string_pad_right.rb +0 -7
  141. data/lib/nydp/hash.rb +0 -9
  142. data/lib/nydp/image_store.rb +0 -21
  143. data/lib/nydp/vm.rb +0 -129
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: adb6cc0345ed64c1e0e7ac99c06d9aa1328482e7d51d771cd77b844e1c5a6e14
4
- data.tar.gz: 997c1f318ed518f3d2100699c1532db9a140f7109d6e4ddef6e68055466eb23a
3
+ metadata.gz: 2163d395501072ea321375834496bc7228aa3b19f8991c0ccc9ac69f3e951426
4
+ data.tar.gz: 3a3b17042f42b1c2e0653a27e4aebf5000f248d59fc56a1e39f065df3a2fc97b
5
5
  SHA512:
6
- metadata.gz: 4a80109cc7bf73c177c6f89697e386a0a12d3645512bd14db2ed036711ef2c0b11d80fef67cff8dbc6f7bfed23d103220b9bc909aa9b34590323375db699c5e3
7
- data.tar.gz: 25864fdef654329baef7d494a54b926f6dd236399f131e5943505752784713b03dc9e79640407c1df6af17ac380c483d82e2fae5e35fa3f3636b1226384eaa58
6
+ metadata.gz: 5c1ff800db3fed15b1a541c4926ffe53a8d2962dc54969265d472c4fefb1eb453db4097aaf10750c39ccc86174967665689f38f63c557f42a9fe2a575a268fcf
7
+ data.tar.gz: 4c87fb842105b2614643f31d5f1c965129dec628310007b3cc6054756917d54c6a794a06234aa5bf6b658285fc6a0c7feaacef87d8c7dd778640fdabb9b38e76
data/.gitignore CHANGED
@@ -18,3 +18,4 @@ tmp
18
18
  lib/lisp/scratch
19
19
  .#*
20
20
  bm.txt
21
+ rubycode
data/README.md CHANGED
@@ -1,16 +1,14 @@
1
1
  # `'nydp`
2
2
 
3
- `'nydp` is a new LISP dialect, much inspired by [Arc](http://arclanguage.org/), and hence indirectly by all of `'arc`'s ancestors,
4
- and implemented in Ruby.
3
+ `'nydp` is a new LISP dialect, much inspired by [Arc](http://arclanguage.org/), and hence indirectly by all of `'arc`'s ancestors, and implemented in Ruby.
5
4
 
6
- `'nydp` is "Not Your Daddy's Parentheses", a reference to [Xkcd 297](http://xkcd.com/297/) (itself a reference
7
- to Star Wars), as well as to the meme [Not Your Daddy's Q](http://tvtropes.org/pmwiki/pmwiki.php/Main/NotYourDaddysX), where Q is a
8
- modern, improved Q quite unlike the Q your daddy used. `'nydp` also shamelessly piggypacks on the
9
- catchiness and popularity of the [NYPD](https://en.wikipedia.org/wiki/NYPD_Blue) abbreviation ("New York Police Department",
10
- for those who have no interest in popular US politics or TV).
5
+ `'nydp` is "Not Your Daddy's Parentheses", a reference to [Xkcd 297](http://xkcd.com/297/) (itself a reference to Star Wars), as well as to the meme [Not Your Daddy's Q](http://tvtropes.org/pmwiki/pmwiki.php/Main/NotYourDaddysX), where Q is a modern, improved Q quite unlike the Q your daddy used. `'nydp` also shamelessly piggypacks on the catchiness and popularity of the [NYPD](https://en.wikipedia.org/wiki/NYPD_Blue) abbreviation ("New York Police Department").
11
6
 
12
- We do not wish to suggest by "Not Your Daddy's Parentheses" that Common Lisp, Scheme, Racket, Arc, Clojure or your favourite
13
- other lisp are somehow old-fashioned, inferior, or in need of improvement in any way.
7
+ We do not wish to suggest by "Not Your Daddy's Parentheses" that Common Lisp, Scheme, Racket, Arc, Clojure or your favourite other lisp are somehow old-fashioned, inferior, or in need of improvement in any way.
8
+
9
+ Further, we deplore and lament the gender bias and the age bias implicit in the use of the word "Daddy". We hereby declare that `'nydp` is also not your mommy's parentheses, and, in fact, is not the parentheses of any other family member, including gender-neutral and nonbinary members, deceased ancestors and as-yet unconceived descendants. `'nydp` is equivalently and unequivocally not their parentheses either, and neither more or less so than the parentheses of any male or masculine or father-role family member. It is possible that in due course this project may be renamed "Not Your Parents' Parentheses" even if `'nypp` doesn't have quite the same ring to it and the 6-tuple "Parent" appears twice in the name.
10
+
11
+ Yet more deplorable and lamentable is the fact that "Daddy" appears to suggest that the age-gender axis is the only relevant axis under consideration. We hereby assert and declare that `'nydp` is also not any parentheses you may obtain (in any manner legal or otherwise) from any race, religion, nationality, sexual orientation, or choice of text editor.
14
12
 
15
13
  The goal of `'nydp` is to allow untrusted users run sandboxed server-side scripts. By default, `'nydp` provides no system access :
16
14
 
@@ -23,8 +21,7 @@ The goal of `'nydp` is to allow untrusted users run sandboxed server-side script
23
21
 
24
22
  [Peruse `'nydp`'s features here](lib/lisp/tests) in the `tests` directory.
25
23
 
26
- Pronunciation guide: there is no fixed canonical pronunciation of `'nydp`. Just keep in mind that if you find yourself
27
- wading _knee-deep_ through raw sewage, it's still better than working on a java project in a bank.
24
+ Pronunciation guide: there is no fixed canonical pronunciation of `'nydp`. Just keep in mind that if you find yourself wading _knee-deep_ through raw sewage, it's still better than working on a java or C# project in a bank. Or insurance company, if that's your thing.
28
25
 
29
26
  ## Running
30
27
 
@@ -37,7 +34,7 @@ welcome to nydp
37
34
  nydp >
38
35
  ```
39
36
 
40
- The REPL uses the readline library so you can use up- and down-arrows to navigate history.
37
+ The REPL uses the readline library so you can use up- and down-arrows to navigate history. IRB also uses readline and command history may get a little bit mixed-up if you start nydp inside an irb session.
41
38
 
42
39
  #### Invoking from Ruby
43
40
 
@@ -46,15 +43,14 @@ Suppose you want to invoke the function named `question` with some arguments. Do
46
43
  ```ruby
47
44
  ns = Nydp.build_nydp # keep this for later re-use, it's expensive to set up
48
45
 
49
- answer = Nydp.apply_function ns, :question, :life, ["The Universe", and_also(everything)]
46
+ answer = ns.apply :question, :life, ["The Universe", and_also(everything)]
50
47
 
51
48
  ==> 42
52
49
  ```
53
50
 
54
- `ns` is just a plain old ruby hash, mapping ruby symbols to nydp symbols for quick lookup at nydp compile-time. The nydp symbols maintain the values of global variables, including all builtin functions and any other functions defined using `def`.
55
-
56
- You can maintain multiple `ns` instances without mutual interference. In other words, assigning global variables while one `ns` is in scope will not affect the values of variables in any other `ns` (unless you've specifically arranged it to be so by duplicating namespaces or some such sorcery).
51
+ `ns` is a Nydp::Namespace object, mapping ruby symbols to nydp symbols for quick lookup at nydp compile-time. The nydp symbols maintain the values of global variables, including all builtin functions and any other functions defined using `def`.
57
52
 
53
+ Nydp is designed to operate in a multi-tenant architectural context, so you can instantiate multiple Namespace instances, where each tenant applies their own custom scripts ; such scripts will not interfere with one another (unless you've specifically arranged it to be so by duplicating namespaces or some such sorcery).
58
54
 
59
55
  #### Facing the Truth
60
56
 
@@ -77,7 +73,7 @@ In conditional statements, nil is false, anything else is true
77
73
 
78
74
  After parsing its input, `'nydp` passes the result as an argument to the `pre-compile` function. This is where things get a little bit circular: initially, `pre-compile` is a builtin function that just returns its argument. `pre-compile` bootstraps itself into existence in [core-010-precompile.nydp](lib/lisp/core-010-precompile.nydp).
79
75
 
80
- You can override `pre-compile` to transform the expression in any way you wish. By default, the `core-010-precompile.nydp` implementation of `pre-compile` performs macro-expansion.
76
+ You can override `pre-compile` to transform the expression in any way you wish. By default, the `core-010-precompile.nydp` implementation of `pre-compile` performs macro-expansion by searching for keywords in the `macs` global variable and applying the corresponding function to the given expression if a function is found.
81
77
 
82
78
 
83
79
  ```lisp
@@ -177,14 +173,16 @@ nydp > (map %td:&lastname german-composers)
177
173
 
178
174
  ```
179
175
 
180
- So, @%td@ expands to @(percent-syntax || td)@, @&lastname@ to @(ampersand-syntax || lastname)@, and the whole @%td:&lastname@
181
- to @(colon-syntax (percent-syntax || td) (ampersand-syntax || lastname))@. Luckily for you, there's a fine @colon-syntax@ macro
182
- that knows how to build a function out of these bits and pieces.
176
+ So, `%td` expands to `(percent-syntax || td)`, `&lastname` to `(ampersand-syntax || lastname)`,
177
+ and the whole `%td:&lastname` to `(colon-syntax (percent-syntax || td) (ampersand-syntax || lastname))`.
178
+ Luckily for you, there's a fine `colon-syntax` macro that knows how to build a function out of these bits
179
+ and pieces.
183
180
 
181
+ Look for `SYMBOL_OPERATORS` in [parser.rb](lib/nydp/parser.rb) to see which syntax is recognised and in
182
+ which order. The order of these definitions defines special-syntax-operator precedence.
184
183
 
185
- Look for `SYMBOL_OPERATORS` in [parser.rb](lib/nydp/parser.rb) to see which syntax is recognised and in which order. The order of these definitions defines special-syntax-operator precedence.
186
-
187
- Any character that is not special syntax will be recognised as part of a symbol. At time of writing, this includes the plus sign, hyphen, and slash.
184
+ Any character that is not special syntax will be recognised as part of a symbol. At time of writing, this
185
+ includes the plus sign, hyphen, and slash.
188
186
 
189
187
  ```lisp
190
188
 
@@ -208,7 +206,7 @@ nydp > (parse "{ a 1 b 2 }")
208
206
 
209
207
  ```
210
208
 
211
- `brace-list` is a macro that expands to create a hash literal. It assumes every item 0 is a literal symbol key, item 1 is the corresponding value which is evaluated at run time, and so on for each following item-pair.
209
+ `brace-list` is a macro that expands to create a hash literal. It assumes every item 0 is a literal key (symbol, string or number), item 1 is the corresponding value which is evaluated at run time, and so on for each following item-pair.
212
210
 
213
211
  ```lisp
214
212
 
@@ -233,9 +231,9 @@ nydp > (let bar "Mister Nice Guy" "hello, ~bar")
233
231
 
234
232
  ; this is a more tricky example because we need to make a string with an interpolation token in it
235
233
 
236
- nydp > (let s (joinstr "" "\"hello, " '~ "world\"") (parse s))
234
+ nydp > (let s "\"hello, \~world\"" (parse s))
237
235
 
238
- ==> (string-pieces "hello, " world "") ; "hello, ", followed by the interpolation 'world, followed by the empty string after 'world
236
+ ==> (string-pieces "hello, " world) ; "hello, ", followed by the interpolation `world`
239
237
 
240
238
  ; It is possible to nest interpolations. Note that as with many popular language features, just because you can do something, does not mean you should:
241
239
 
@@ -250,22 +248,37 @@ nydp > (with (a 1 b 2)
250
248
  ==> AND ALSO, Consider 3, the third (and final) thing
251
249
  ```
252
250
 
253
- By default, `string-pieces` is a function that just concatenates the string value of its arguments. You can redefine it as a macro to
254
- perform more fun stuff, or you can detect it within another macro to do extra-special stuff with it. The 'nydp-html gem detects
255
- 'string-pieces and gives it special treatment in order to render haml and textile efficiently, and also to capture and report errors
256
- inside interpolations and report them correctly.
257
-
251
+ By default, `string-pieces` is a function that just concatenates the string value of its arguments. You can redefine it as a macro to perform more fun stuff, or you can detect it within another macro to do extra-special stuff with it. The 'nydp-html gem detects 'string-pieces and gives it special powers in order to render haml and textile efficiently, and also to capture and report errors inside interpolations and report them correctly.
258
252
 
259
253
  #### 5. No continuations.
260
254
 
261
255
  Sorry. While technically possible ... why bother?
262
256
 
257
+ #### 6. No tail-call optimisation / tail-call elimination
258
+
259
+ This isn't completely true. For performance reasons, the nydp stack maps directly to the underlying ruby stack, and it is possible to enable TCO in your ruby VM. Nydp doesn't enable it for you though, you'll need to take care of this yourself. So basically nydp doesn't guarantee you TCO, but if you're in control of your deployment environment, you can still have it.
260
+
261
+ As a result, nydp has an extra keyword, 'loop, so that recursive solutions can be rewritten as iterative ones. For example
262
+
263
+ ```lisp
264
+ (def elegant-recursive-solution (n f things)
265
+ (if (> n 0)
266
+ (elegant-recursive-solution (- n 1) (cdr things))
267
+ (f things)))
268
+
269
+ ;; can be rewritten as
270
+ (def stupid-iterative-solution (n f things)
271
+ (loop (> n 0)
272
+ (= things (cdr things)
273
+ n (- n 1)))
274
+ (f things))
275
+ ```
263
276
 
264
277
  ## Besides that, what can Nydp do?
265
278
 
266
279
  #### 1. Functions and variables exist in the same namespace.
267
280
  #### 2. Macros are maintained in a hash called 'macs in the main namespace.
268
- #### 3. General [tail call elimination](https://en.wikipedia.org/wiki/Tail_call) allowing recursion without stack overflow in some cases.
281
+ #### 3. Loop construct (see above under 'no tail-call optimisation')
269
282
  #### 4. 'if like Arc:
270
283
 
271
284
  ```lisp
@@ -355,8 +368,7 @@ nydp > (parse "%w(a b c)")
355
368
  ==> (prefix-list "%w" (a b c))
356
369
  ```
357
370
 
358
- This allows for preprocessing lists in a way not possible for macros. nydp uses this feature to implement shortcut oneline
359
- functions, as in
371
+ This allows for preprocessing lists in a way not possible for macros. nydp uses this feature to implement shortcut monoexpression functions with single-letter arguments, as in
360
372
 
361
373
  ```lisp
362
374
  nydp > (parse "λx(len x)")
@@ -380,6 +392,13 @@ nydp > (pre-compile '(prefix-list "λxy" (* x y)))
380
392
  ==> (fn (x y) (* x y))
381
393
  ```
382
394
 
395
+ Example:
396
+
397
+ ```lisp
398
+ nydp > (map λx(* x x) '(1 2 3 4))
399
+
400
+ ==> (1 4 9 16)
401
+
383
402
  Use 'define-prefix-list-macro to define a new handler for a prefix-list. Here's the code for the 'λ shortcut:
384
403
 
385
404
  ```lisp
@@ -395,11 +414,11 @@ In this case, the regex matches an initial 'λ ; there is no constraint however
395
414
 
396
415
  Once the 'dox system is bootstrapped, any further use of 'mac or 'def will create documentation.
397
416
 
398
- Any comments at the start of the form body will be used to generate help text. For example:
417
+ Comments immediately prior to the start of the form body will be used to generate help text. For example:
399
418
 
400
419
  ```lisp
401
- nydp > (def foo (x y)
402
- ; return the foo of x and y
420
+ nydp > ; return the foo of x and y
421
+ (def foo (x y)
403
422
  (* x y))
404
423
 
405
424
  nydp > (dox foo)
@@ -421,14 +440,11 @@ nydp > (dox-lookup 'foo)
421
440
  ((foo def ("return the foo of x and y") (x y) (def foo (x y) (* x y))))
422
441
  ```
423
442
 
424
- Not as friendly, but more amenable to programmatic manipulation. Each subsequent definition of 'foo (if you override it, define
425
- it as a macro, or define it again in some other context) will generate a new documentation structure, which will simply be preprended to
426
- the existing list.
443
+ Not as friendly, but more amenable to programmatic manipulation. Each subsequent definition of 'foo (if you override it, define it as a macro, or define it again in some other context) will generate a new documentation structure, which will simply be preprended to the existing list.
427
444
 
428
445
  #### 11. Pretty-Printing
429
446
 
430
- 'dox above uses the pretty printer to display code source. The pretty-printer is hard-coded to handle some special cases,
431
- so it will unparse special syntax, prefix-lists, quote, quasiquote, unquote, and unquote-splicing.
447
+ 'dox above uses the pretty printer to display code source. The pretty-printer is hard-coded to handle some special cases, so it will unparse special syntax, prefix-lists, quote, quasiquote, unquote, and unquote-splicing.
432
448
 
433
449
  You can examine its behaviour at the repl:
434
450
 
@@ -448,13 +464,11 @@ nydp > (p:pp:dox-src 'pp/find-breaks)
448
464
  (list form))))
449
465
  ```
450
466
 
451
- The pretty-printer is still rather primitive in that it only indents according to some hard-coded rules, and according to argument-count
452
- for documented macros. It has no means of wrapping forms that get too long, or that extend beyond a certain predefined margin or column number.
467
+ The pretty-printer is still rather primitive in that it only indents according to some hard-coded rules, and according to argument-count for documented macros. It has no means of wrapping forms that get too long, or that extend beyond a certain predefined margin or column number.
453
468
 
454
469
  #### 12. DSLs
455
470
 
456
- The `pre-compile` system (described earlier in "Macro-expansion runs in lisp") is available for implementing local, mini-languages. To do this, use `pre-compile-with`
457
- in a macro. `pre-compile-with` expects a hash with expansion rules, and an expression to expand using these rules. For example, to build a "describe" dsl :
471
+ The `pre-compile` system (described earlier in "Macro-expansion runs in lisp") is available for implementing local, mini-languages. To do this, use `pre-compile-with` in a macro. `pre-compile-with` expects a hash with expansion rules, and an expression to expand using these rules. For example, to build a "describe" dsl :
458
472
 
459
473
  ```lisp
460
474
  (= describe-dsl (hash))
@@ -467,20 +481,27 @@ in a macro. `pre-compile-with` expects a hash with expansion rules, and an expre
467
481
  ',outcome))
468
482
  ```
469
483
 
470
- In this example, `describe-dsl` establishes rules for expanding `def` and for `p` which will shadow their usual meanings, but only in the context
471
- of a `describe` form.
484
+ In this example, `describe-dsl` establishes rules for expanding `def` and for `p` which will shadow their usual meanings, but only in the context of a `describe` form.
472
485
 
473
- This technique is useful to avoid cluttering the global macro-namespace with rules that are used only in a specific context. A word of warning:
474
- if you shadow a popular name, for example `let`, `map` or `do`, you are likely to run into trouble when some non-shadowed macro inside your
475
- dsl context expands into something you have shadowed; in this case your shadowy definitions will be used to further expand the result.
486
+ This technique is useful to avoid cluttering the global macro-namespace with rules that are used only in a specific context. A word of warning: if you shadow a popular name, for example `let`, `map` or `do`, you are likely to run into trouble when some non-shadowed macro inside your dsl context expands into something you have shadowed; in this case your shadowy definitions will be used to further expand the result.
476
487
 
477
- For example, if you shadow `with`, but not `let`, and then you use a `let` form within a sample of the dsl you are specifying ; the `let`
478
- form expands into a `with` form, thinking the `with` will expand in the usual way, but in fact no, your private dsl is going to expand
479
- this `with` in your private special way, possibly in a way that's incompatible with the form produced by `let`.
488
+ For example, if you shadow `with`, but not `let`, and then you use a `let` form within a sample of the dsl you are specifying ; the `let` form expands into a `with` form, thinking the `with` will expand in the usual way, but in fact no, your private dsl is going to expand this `with` in your private special way, possibly in a way that's incompatible with the form produced by `let`.
480
489
 
481
- If, notwithstanding the aforementioned, the outcome works the way you expected it to, then you have the wrong kind of genius and your
482
- license to program should be revoked.
490
+ If, notwithstanding the aforementioned, you do this anyway and the outcome works the way you expected it to, then you have the wrong kind of genius and your license to program should be revoked.
483
491
 
492
+ ## Extending NYDP
493
+
494
+ You can add any object implementing #call (for example, a ruby Proc) to your `ns` instance:
495
+
496
+ ```ruby
497
+ ns.assign(:"send-mail", -> {|props| Net::SMTP.deliver_the_letter(props).the_sooner_the_better }) # imaginary example
498
+ ```
499
+
500
+ In your lisp,
501
+
502
+ ```lisp
503
+ (send-mail { from "myself@example.com" to "yourself@example.com" subject "SPECIAL OFFER" body (mail-body blah-blah) })
504
+ ```
484
505
 
485
506
  ## Installation
486
507
 
@@ -2,7 +2,7 @@
2
2
  ;;
3
3
  ;; Acknowledgements to Paul Graham, Robert Morris, and their ancestor programmers.
4
4
  ;; nydp's main inspiration is arc, and many nydp features (including, but not limited
5
- ;; to, 'do, 'rfn, 'loop, 'for) were directly inspired by (aka stolen from) arc.arc. See
5
+ ;; to, 'do, 'rfn, 'for) were directly inspired by (aka stolen from) arc.arc. See
6
6
  ;; README.md however for some significant differences
7
7
 
8
8
  (assign noop (fn))
@@ -1,9 +1,11 @@
1
+ (assign uniq-counter 0)
2
+
1
3
  (assign mac-expand
2
4
  (fn (names macfn expr)
3
5
  (cond macfn
4
6
  (handle-error
5
- (fn (errors)
6
- (error "expanding" (inspect expr) "with" (inspect macfn)))
7
+ (fn (errors traces)
8
+ (error "expanding" (inspect expr) "with" macfn))
7
9
  (fn ()
8
10
  (pre-compile-with names (apply macfn (cdr expr)))))
9
11
  expr)))
@@ -11,37 +13,41 @@
11
13
  (assign macs (hash))
12
14
 
13
15
  (assign pre-compile-each
14
- (fn (names args)
15
- (cond args
16
- (cond (pair? args)
16
+ (fn (names exprs)
17
+ (cond exprs
18
+ (cond (pair? exprs)
17
19
  (cons (pre-compile-with names
18
- (car args))
20
+ (car exprs))
19
21
  (pre-compile-each names
20
- (cdr args)))
21
- args))))
22
+ (cdr exprs)))
23
+ exprs))))
22
24
 
23
25
  (assign pre-compile-msg
24
26
  (fn (src compiled)
25
- (p "pre-compile" src "\n -> " compiled)
26
27
  compiled))
27
28
 
28
29
  (assign pre-compile-raw
29
- (fn (names arg)
30
- (cond (pair? arg)
31
- (cond (eq? (car arg) 'quote)
32
- arg
33
- (pre-compile-each names
34
- (mac-expand names
35
- (hash-get names
36
- (car arg))
37
- arg)))
38
- arg)))
30
+ (fn (names expr)
31
+ (cond (pair? expr)
32
+ (cond (eq? (car expr) 'quote)
33
+ expr
34
+ (cond (isa 'symbol (car expr))
35
+ (pre-compile-each
36
+ names
37
+ (mac-expand names
38
+ (hash-get names
39
+ (car expr))
40
+ expr))
41
+ (pre-compile-each
42
+ names
43
+ expr)))
44
+ expr)))
39
45
 
40
46
  (assign pre-compile-debug
41
- (fn (names arg)
42
- (pre-compile-msg arg
47
+ (fn (names expr)
48
+ (pre-compile-msg expr
43
49
  (pre-compile-raw names
44
- arg))))
50
+ expr))))
45
51
 
46
52
  (assign debug-pre-compile
47
53
  (fn (arg)
@@ -54,8 +60,17 @@
54
60
 
55
61
  ; builtin pre-compile does nothing; override here to provide macro-expansion
56
62
  (assign pre-compile
57
- (fn (arg)
58
- (pre-compile-with macs arg)))
63
+ (fn (expr)
64
+ (pre-compile-with macs expr)))
65
+
66
+ ;; this is the entry point for the interpreter ; we need to reset uniq-counter for each new expression
67
+ ;; (for performance purposes only - to ensure identical nydp code produces identical ruby code) ; but
68
+ ;; we can't reset uniq-counter in the middle of pre-compiling an expression, otherwise @uniq@ won't be
69
+ ;; even slightly unique any more
70
+ (assign pre-compile-new-expression
71
+ (fn (expr)
72
+ (assign uniq-counter 0)
73
+ (pre-compile-with macs expr)))
59
74
 
60
75
  ; we override this later to provide argument deconstruction
61
76
  (hash-set macs 'fun
@@ -81,28 +96,33 @@
81
96
  (qq-quasiquote rest level))
82
97
  arg))
83
98
 
99
+ (def qq-build-cons (a b)
100
+ (cond b
101
+ (list 'cons a b)
102
+ (list 'cons a)))
103
+
84
104
  (def qq-skip-unquote-splicing (arg rest level)
85
- (list 'cons
105
+ (qq-build-cons
86
106
  (list 'list ''unquote-splicing (qq-quasiquote arg (- level 1)))
87
107
  (qq-quasiquote rest level)))
88
108
 
89
109
  (def qq-handle-quasiquote (arg rest level)
90
- (list 'cons
110
+ (qq-build-cons
91
111
  (list 'list ''quasiquote (qq-quasiquote arg (+ level 1)))
92
112
  (qq-quasiquote rest level)))
93
113
 
94
114
  (def qq-handle-unquote (arg rest level)
95
- (list 'cons
115
+ (qq-build-cons
96
116
  (qq-maybe-unquote arg level)
97
117
  (qq-quasiquote rest level)))
98
118
 
99
119
  (def qq-unquote-recurse (arg rest level)
100
- (list 'cons
120
+ (qq-build-cons
101
121
  (qq-quasiquote arg level)
102
122
  (qq-quasiquote rest level)))
103
123
 
104
124
  (def qq-handle-plain (arg rest level)
105
- (list 'cons
125
+ (qq-build-cons
106
126
  (list 'quote arg)
107
127
  (qq-quasiquote rest level)))
108
128
 
@@ -9,9 +9,11 @@
9
9
  nil)))
10
10
 
11
11
  (def map-helper-0 (f things lc)
12
- (if (pair? things)
13
- (map-helper-0 f (cdr things) (cdr-set lc (cons (f (car things)))))
14
- things
12
+ (loop (pair? things)
13
+ ((fn nil
14
+ (assign lc (cdr-set lc (cons (f (car things)))))
15
+ (assign things (cdr things)))))
16
+ (if things
15
17
  (cdr-set lc (f things))))
16
18
 
17
19
  (def map-helper-1 (f things acc)
@@ -35,9 +37,11 @@
35
37
  (def hash-cons (h k v)
36
38
  (hash-set h k (cons v (hash-get h k))))
37
39
 
40
+ ;; 'things - the list to be reversed
41
+ ;; 'last-cdr - (normally nil) - an item (atom, list, nil, anything) to be consed to the end of the reversed list.
38
42
  (def rev (things last-cdr)
39
- ; 'things - the list to be reversed
40
- ; 'last-cdr - (normally nil) - an item (atom, list, nil, anything) to be consed to the end of the reversed list.
41
- (if (pair? things)
42
- (rev (cdr things) (cons (car things) last-cdr))
43
- last-cdr))
43
+ (loop (pair? things)
44
+ ((fn nil
45
+ (assign last-cdr (cons (car things) last-cdr))
46
+ (assign things (cdr things)))))
47
+ last-cdr)
@@ -77,9 +77,6 @@
77
77
  (hash-set hsh 'texts texts )
78
78
  (hash-set hsh 'args args )
79
79
  (hash-set hsh 'src src )
80
- (hash-set hsh 'chapters (cons (chapter-current) chapters))
81
- (hash-set hsh 'file this-script )
82
- (hash-set hsh 'plugin this-plugin )
83
80
  hsh)
84
81
 
85
82
  (def dox-new (item)
@@ -87,11 +84,19 @@
87
84
  (hash-cons types (hash-get item 'what) item)
88
85
  (dox-add-to-chapters item (hash-get item 'what) (hash-get item 'chapters) (hash)))
89
86
 
90
- (def dox-add-doc (name what texts args src chapters more)
91
- (cond (no (privately))
92
- (dox-new (dox-build (if more more (hash)) name what texts args src chapters))))
87
+ (def dox-add-doc (name what texts args src etc)
88
+ (handle-error
89
+ (fn (errors traces) (error "failed to add dox for " name " - " what))
90
+ (fn ()
91
+ (cond (no etc)
92
+ (error (inspect name) " : please provide arguments (name what texts args src etc) where etc is a hash with at least keys (chapters plugin file)"))
93
+ (cond (no (privately))
94
+ (dox-new
95
+ (dox-build etc name what texts args src))))))
93
96
 
94
97
  (def dox-add-to-chapters (item type chapters already)
98
+ (cond (pair? (car chapters))
99
+ (error "dox-add-to-chapters : expected symbol, got " (inspect (car chapters)) " for " (inspect item)))
95
100
  (cond chapters
96
101
  (cond (no (hash-get already (car chapters)))
97
102
  (do (hash-set already (car chapters) t)
@@ -131,7 +136,7 @@
131
136
  (def plugin-start (name) (assign this-plugin name) (chapter-end))
132
137
  (def plugin-end (name) (assign this-plugin nil ) (chapter-end))
133
138
  (def script-start (name) (assign this-script name) (chapter-end))
134
- (def script-end (name) (assign this-script nil ) (chapter-end))
139
+ (def script-end (name) (assign this-script nil ) (fetch-and-clear-comments) (chapter-end))
135
140
 
136
141
  (def script-run (event name)
137
142
  (cond (eq? event 'plugin-start)
@@ -180,14 +185,27 @@
180
185
 
181
186
  (assign DEF-FORMS '(comment chapter))
182
187
 
188
+ (def hash-init (h keys v)
189
+ (if keys
190
+ (do (hash-set h (car keys) v)
191
+ (hash-init h (cdr keys) v)))
192
+ h)
193
+
183
194
  (def build-def-hash ()
184
- ((fn (h)
185
- (map (fn (k) (hash-set h k nil)) DEF-FORMS)
186
- h)
187
- (hash)))
195
+ (hash-init (hash) DEF-FORMS nil))
188
196
 
189
197
  (def dox-build-def-name (name) name)
190
198
 
199
+ (def dox-chapters-expr-helper (chaps)
200
+ (cond chaps
201
+ `(quote (,@chaps))))
202
+
203
+ (def dox-chapters-expr (chaps)
204
+ (cond (chapter-current)
205
+ (dox-chapters-expr-helper (cons (chapter-current) (apply + nil chaps)))
206
+ (cond chaps
207
+ (dox-chapters-expr-helper (apply + chaps)))))
208
+
191
209
  ;; used internally by 'mac
192
210
  (def define-mac-expr (name args body-forms)
193
211
  `(do (hash-set macs ',name (fun ,args ,@(hash-get body-forms nil)))
@@ -196,7 +214,9 @@
196
214
  ',(+ (fetch-and-clear-comments) (map car (hash-get body-forms 'comment)))
197
215
  ',args
198
216
  '(mac ,name ,args ,@(hash-get body-forms nil))
199
- ',(map car (hash-get body-forms 'chapter)))))
217
+ (hash 'chapters ,(dox-chapters-expr (hash-get body-forms 'chapter))
218
+ 'plugin ',this-plugin
219
+ 'file ',this-script))))
200
220
 
201
221
  (hash-set macs 'mac
202
222
  (fn (name args . body)
@@ -207,14 +227,18 @@
207
227
  '("define a new global macro")
208
228
  '(name args . body)
209
229
  '`(hash-set macs ',name (fn ,args ,@body))
210
- '(nydp-core))
230
+ (hash 'chapters '(nydp-core)
231
+ 'plugin this-plugin
232
+ 'file this-script))
211
233
 
212
234
  (dox-add-doc 'do
213
235
  'mac
214
236
  '("perform a series of operations")
215
237
  'args
216
238
  '`((fn nil ,@args))
217
- '(nydp-core))
239
+ (hash 'chapters '(nydp-core)
240
+ 'plugin this-plugin
241
+ 'file this-script))
218
242
 
219
243
  ;; override later to use '= instead of 'assign, giving us hash-assignment and other goodies for free
220
244
  (mac def-assign args `(assign ,@args))
@@ -230,7 +254,9 @@
230
254
  ',(+ (fetch-and-clear-comments) (map car (hash-get body-forms 'comment)))
231
255
  ',args
232
256
  '(def ,name ,args ,@(hash-get body-forms nil))
233
- ',(map car (hash-get body-forms 'chapter)))))
257
+ (hash 'chapters ,(dox-chapters-expr (hash-get body-forms 'chapter))
258
+ 'plugin ',this-plugin
259
+ 'file ',this-script))))
234
260
 
235
261
  ;; define a new function in the global namespace
236
262
  (mac def (name args . body)