nydp 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56ae5257ab6792eeaef112c3e81991dae7c43ce1
4
- data.tar.gz: 327b5770bc8f1c442202d340f5392288e58efdbe
3
+ metadata.gz: 1cb1c4582106367e04bc3ac5163db7401782331c
4
+ data.tar.gz: 3c8be4535a704cdbe02674796f4068d6f079880c
5
5
  SHA512:
6
- metadata.gz: 65b2df58e4d78c39e8ea8aacb1e3e0298269b5f2b03f6d9fb11e3ce44bf45012cd35b7b397a74de46d6e10f36a1b64be179c9efae28d5ca709959c50c9574e2b
7
- data.tar.gz: 9e787875c9011054dbaa9b416b84ef162f798bea1f5c33fde9a834322fc54e8435d537a37ce7a7e62e1972db95103aca67a3336b2e727bf7da07b94953af34f8
6
+ metadata.gz: 7523046a3da0fb43634736645674566d77c10a3178b911765d849503c9b66cdb32e9b17810365795bddc7f2bf5fe655d24c1e2f10b6253054987cdfdbe5a6a16
7
+ data.tar.gz: 82f1f13fb1e3badf6f80c8a9ca6077019b01c0f8e571c61b1e1ade4ba1c640c4c8945f9e3d77442d69e77382e5ecee60587d51b9010ff03ec10ee4bb422664c8
@@ -0,0 +1 @@
1
+ *.nydp linguist-language=NYDP
data/README.md CHANGED
@@ -1,35 +1,252 @@
1
- # Nydp
1
+ # NYDP
2
+
3
+ NYDP is a new LISP dialect, much inspired by [Arc](http://arclanguage.org/), and implemented in Ruby.
2
4
 
3
5
  NYDP is "Not Your Daddy's Parentheses", a reference to [Xkcd 297](http://xkcd.com/297/) (itself a reference
4
6
  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,
5
7
  improved Q unlike the Q your daddy used. "NYDP" also shamelessly piggypacks on the
6
8
  catchiness and popularity of the [NYPD](https://en.wikipedia.org/wiki/NYPD_Blue) abbreviation ("New York Police Department",
7
- for those who have no interest in popular US TV or authoritarian politics).
9
+ for those who have no interest in popular US politics or TV).
10
+
11
+ 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.
12
+
13
+ The goal of NYDP is to allow untrusted users run sandboxed server-side scripts. By default, NYDP provides no system access :
14
+
15
+ * no file functions
16
+ * no network functions
17
+ * no IO other than $stdin and $stdout
18
+ * no process functions
19
+ * no threading functions
20
+ * no ruby calls
21
+
22
+ [Peruse NYDP's features here](lib/lisp/tests) in the `tests` directory.
23
+
24
+ ## Running
25
+
26
+ #### Get a REPL :
27
+
28
+ ```Shell
29
+ $ bundle exec bin/nydp
30
+ welcome to nydp
31
+ ^D to exit
32
+ nydp >
33
+ ```
34
+
35
+ The REPL uses the readline library so you can use up- and down-arrows to navigate history.
36
+
37
+ #### Invoking from Ruby
38
+
39
+ Suppose you want to invoke the function named `question` with some arguments. Do this:
40
+
41
+ ```ruby
42
+ ns = Nydp.build_nydp # keep this for later re-use, it's expensive to set up
8
43
 
9
- Macro-expansion is not built-in to the interpreter; however, the compiler will invoke 'pre-compile before compiling, passing
10
- the expression to compile as an argument. You can override 'pre-compile to transform the expression in any way you wish. By default,
11
- nydp provides an implementation of 'pre-compile that performs macro-expansion.
44
+ answer = Nydp.apply_function ns, :question, :life, ("The Universe" and everything())
12
45
 
46
+ ==> 42
13
47
  ```
48
+
49
+ `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`.
50
+
51
+ 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).
52
+
53
+
54
+ ## Different from Arc :
55
+
56
+ #### 1. Macro-expansion runs in lisp
57
+
58
+ 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 [boot.nydp](lib/lisp/boot.nydp).
59
+
60
+ You can override `pre-compile` to transform the expression in any way you wish. By default, the `boot.nydp` implementation of `pre-compile` performs macro-expansion.
61
+
62
+
63
+
64
+ ```lisp
14
65
  (def pre-compile (expr)
15
66
  (map pre-compile
16
67
  (if (mac-names (car expr))
17
68
  (pre-compile (mac-expand (car expr) (cdr expr)))
18
69
  expr)))
70
+
71
+ (mac yoyo (thing) `(do-yoyo ,thing))
72
+
73
+ nydp > (pre-compile '(yoyo 42))
74
+
75
+ ==> (do-yoyo 42)
76
+ ```
77
+
78
+
79
+ #### 2. Special symbol syntax
80
+
81
+ The parser detects syntax embedded in smybol names and emits a form whose first element names the syntax used. Here's an example:
82
+
83
+ ```lisp
84
+
85
+ nydp > (parse "x.y")
86
+
87
+ ==> (dot-syntax x y)
88
+
89
+ nydp > (parse "$x x$ x$x $x$ $$")
90
+
91
+ ==> (dollar-syntax || x) ; '|| is the empty symbol.
92
+ ==> (dollar-syntax x ||)
93
+ ==> (dollar-syntax x x)
94
+ ==> (dollar-syntax || x ||)
95
+ ==> (dollar-syntax || || ||)
96
+
97
+ nydp > (parse "!foo")
98
+
99
+ ==> (bang-syntax || foo)
100
+
101
+ nydp > (parse "!x.$y")
102
+
103
+ ==> (bang-syntax || (dot-syntax x (dollar-syntax || y)))
104
+
105
+ ```
106
+
107
+ Nydp provides macros for some but not all possible special syntax
108
+
109
+ ```lisp
110
+ nydp > (pre-compile 'x.y)
111
+
112
+ ==> (hash-get x 'y) ; 'dot-syntax is a macro that expands to perform hash lookups
113
+
114
+ nydp > (pre-compile 'x.y.z)
115
+
116
+ ==> (hash-get (hash-get x 'y) 'z)
117
+
118
+
119
+ nydp > (pre-compile '!eq?)
120
+
121
+ ==> (fn args (no (apply eq? args)))
122
+
123
+ nydp > (pre-compile '(!eq? a b))
124
+
125
+ ==> ((fn args (no (apply eq? args))) a b) ; equivalent to (no (eq? a b))
19
126
  ```
20
127
 
21
- ; blah blah
128
+ 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.
129
+
130
+ #### 3. Special list syntax
131
+
132
+ The parser detects alternative list delimiters
133
+
134
+ ```lisp
135
+ nydp > (parse "{ a 1 b 2 }")
136
+
137
+ ==> (brace-list a 1 b 2)
22
138
 
23
139
  ```
24
- ==> (comment "blah blah")
25
140
 
26
- ==> (mac comment (txt) nil)
141
+ `brace-list` is a macro that expands to create a hash literal. It assumes every (2n+1)th items are literal symbol keys, and every (2(n+1))th item is the corresponding value which is evaluated at run time.
142
+
143
+ ```lisp
144
+
145
+ nydp > { a 1 b (author-name) }
146
+
147
+ ==> {a=>1, b=>"conanite"}
148
+
149
+ ```
150
+
151
+
152
+
153
+ #### 4. Sensible, nestable string interpolation
154
+
155
+ The parser detects lisp code inside strings. When this happens, instead of emitting a string literal, the parser emits a form whose car is the symbol `string-pieces`.
156
+
157
+ ```lisp
158
+ nydp > (parse "\"foo\"")
159
+
160
+ ==> "foo"
161
+
162
+ nydp > (let bar "Mister Nice Guy" "hello, ~bar")
163
+
164
+ ==> hello, Mister Nice Guy
165
+
166
+ ; this is a more tricky example because we need to make a string with an interpolation token in it
167
+
168
+ nydp > (let s (joinstr "" "\"hello, " '~ "world\"") (parse s))
169
+
170
+ ==> (string-pieces "hello, " world "") ; "hello, ", followed by the interpolation 'world, followed by the empty string after 'world
171
+
172
+ nydp > (def also (str) "\nAND ALSO, ~str")
173
+ nydp > (with (a 1 b 2)
174
+ (p "Consider ~a : the first thing,
175
+ ~(also "Consider ~b : the second thing,
176
+ ~(also "Consider ~(+ a b), the third (and final) thing")")"))
177
+
178
+ ==> Consider 1 : the first thing,
179
+ ==> AND ALSO, Consider 2 : the second thing,
180
+ ==> AND ALSO, Consider 3, the third (and final) thing
27
181
  ```
28
182
 
29
- We do not wish to suggest by "Not Your Daddy's Parentheses" that Common Lisp,
30
- Scheme, Racket, Arc, Clojure or your favourite other lisp are somehow
31
- old-fashioned, inferior, or in need of improvement in any way.
183
+ 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.
184
+
185
+
186
+ #### 5. No continuations.
187
+
188
+ Sorry. While technically possible ... why bother?
189
+
190
+ #### 6. No argument destructuring
32
191
 
192
+ However, this doesn't need to be built-in, it can be done with macros alone.
193
+
194
+
195
+ ## Besides that, what can Nydp do?
196
+
197
+ #### 1. Functions and variables exist in the same namespace.
198
+ #### 2. Macros are maintained in a hash called 'macs in the main namespace.
199
+ #### 3. General [tail call elimination](https://en.wikipedia.org/wiki/Tail_call) allowing recursion without stack overflow in some cases.
200
+ #### 4. 'if like Arc:
201
+
202
+ ```lisp
203
+ (if a b c d e) ; equivalent to ruby :
204
+ ```
205
+
206
+ ```ruby
207
+ if a
208
+ b
209
+ elsif c
210
+ d
211
+ else e
212
+ ```
213
+
214
+ #### 5. Lexically scoped, but with macros to define dynamic variables backed by ruby threadlocals.
215
+
216
+ ```lisp
217
+ nydp> (dynamic foo)
218
+
219
+ nydp> (def do-something () (+ (foo) 1))
220
+
221
+ nydp> (w/foo 99 (do-something))
222
+
223
+ ==> 100
224
+
225
+ nydp> (foo)
226
+
227
+ ==> nil
228
+ ```
229
+
230
+ #### 6. Basic error handling
231
+
232
+ ```lisp
233
+ nydp> (on-err (p "error")
234
+ (ensure (p "make sure this happens")
235
+ (/ 1 0)))
236
+
237
+ make sure this happens
238
+ error
239
+ ```
240
+
241
+ #### 7 Intercept comments
242
+
243
+ ```lisp
244
+ nydp > (parse "; blah blah")
245
+
246
+ ==> (comment "blah blah")
247
+
248
+ By default, `comment` is a macro that expands to nil. If you have a better idea, go for it. (doc-comments for example)
249
+ ```
33
250
 
34
251
  ## Installation
35
252
 
@@ -45,9 +262,6 @@ Or install it yourself as:
45
262
 
46
263
  $ gem install nydp
47
264
 
48
- ## Usage
49
-
50
- TODO: Write usage instructions here
51
265
 
52
266
  ## Contributing
53
267
 
@@ -0,0 +1,18 @@
1
+ (def bm-pythag ()
2
+ (for i 1 100
3
+ (for j 1 100
4
+ (sqrt (+ (* i i) (* j j))))))
5
+
6
+ (def bmf (f n)
7
+ (for b 1 n (f)))
8
+
9
+ (def bm (f n)
10
+ (let time (millisecs)
11
+ (bmf f n)
12
+ (let elapsed (- (millisecs) time)
13
+ (p "took: ~elapsed ms")
14
+ (p "~n iterations, ~(/ elapsed n) ms per iteration")))
15
+ nil)
16
+
17
+ (def rbs ()
18
+ (bm bm-pythag 20))
@@ -1,12 +1,17 @@
1
1
  ; -*- lisp -*-
2
2
 
3
+ ;;
4
+ ;; Acknowledgements to Paul Graham. Some nydp features defined in this file (including,
5
+ ;; but not limited to, 'do, 'rfn, 'loop, 'for) are stolen directly from arc.arc
6
+ ;;
7
+
3
8
  (assign last-cons (fn (xs)
4
9
  (cond (pair? (cdr xs))
5
10
  (last-cons (cdr xs))
6
11
  xs)))
7
12
 
8
13
 
9
- (assign append-list! (fn (list-1 list-2)
14
+ (assign append-list (fn (list-1 list-2)
10
15
  (cdr-set (last-cons list-1) list-2)
11
16
  list-1))
12
17
 
@@ -15,7 +20,7 @@
15
20
  (assign caar (fn (arg) (car (car arg))))
16
21
  (assign cadr (fn (arg) (car (cdr arg))))
17
22
  (assign cddr (fn (arg) (cdr (cdr arg))))
18
- (assign no (fn (arg) (eq? arg nil)))
23
+ (assign no (fn (arg) (cond arg nil t)))
19
24
  (assign just (fn (arg) arg))
20
25
  (assign pargs (fn args (apply p args) (last args)))
21
26
 
@@ -145,6 +150,14 @@
145
150
  `(cond ,(car args) ,(cadr args)))
146
151
  (car args))))
147
152
 
153
+ (mac bang-syntax (pfx . rest)
154
+ (if (no (eq? pfx '||))
155
+ (error "Irregular ! syntax: got prefix ~(inspect pfx) in ~(joinstr "!" (cons pfx rest))"))
156
+ (if (cdr rest)
157
+ (error "Irregular ! syntax: got suffix ~(inspect (cdr rest)) in ~(joinstr "!" (cons pfx rest))"))
158
+ `(fn args
159
+ (no (apply ,(car rest) args))))
160
+
148
161
  (mac and args
149
162
  (if args
150
163
  (if (cdr args)
@@ -215,13 +228,12 @@
215
228
  (flattenize things))
216
229
  acc))
217
230
 
218
- (def joinstr (txt things)
219
- (if (no (pair? things))
220
- (error "joinstr : 'things is a %%(type-of things) expected a list : %%(inspect things)"))
221
- (apply +
222
- (to-string (car things))
223
- (flatten (zip (map (fn (_) txt) (cdr things))
224
- (map to-string (cdr things))))))
231
+ (def joinstr (txt . things)
232
+ (let joinables (flatten things)
233
+ (apply +
234
+ (to-string (car joinables))
235
+ (flatten (zip (map (fn (_) txt) (cdr joinables))
236
+ (map to-string (cdr joinables)))))))
225
237
 
226
238
  (let uniq-counter 0
227
239
  (def uniq (prefix)
@@ -263,6 +275,7 @@
263
275
  (iso (cdr x) (cdr y)))))
264
276
 
265
277
  (def isa (type obj) (eq? (type-of obj) type))
278
+ (def sym? (arg) (isa 'symbol arg))
266
279
  (mac just (arg) arg)
267
280
  (def quotify (arg) `(quote ,arg))
268
281
 
@@ -317,5 +330,51 @@
317
330
  (mac = (name value)
318
331
  (if (isa 'symbol name)
319
332
  `(assign ,name ,value)
320
- (caris 'dot-syntax name)
333
+ (caris 'dot-syntax name)
321
334
  (dot-syntax-assignment (cdr name) value)))
335
+
336
+ (def brace-list-hash-key (k)
337
+ (if (isa 'symbol k) `(quote ,k)
338
+ (caris 'unquote k) (cadr k)
339
+ k))
340
+
341
+ (def brace-list-build-hash (args)
342
+ (w/uniq hash
343
+ (let mappings (pairs args)
344
+ `(let ,hash (hash)
345
+ ,@(map (fn (m) `(hash-set ,hash ,(brace-list-hash-key (car m)) ,(cadr m))) mappings)
346
+ ,hash))))
347
+
348
+ (mac brace-list args
349
+ (if (no (cdr args))
350
+ (car args)
351
+ (brace-list-build-hash args)))
352
+
353
+ (mac on-err (handler . body)
354
+ `(handle-error (fn (err) ,handler)
355
+ (fn () ,@body)))
356
+
357
+ (mac ensure (protection . body)
358
+ `(ensuring (fn () ,protection)
359
+ (fn () ,@body)))
360
+
361
+ (mac rfn (name parms . body)
362
+ `(let ,name nil
363
+ (assign ,name (fn ,parms ,@body))))
364
+
365
+ (mac afn (parms . body)
366
+ `(rfn self ,parms ,@body))
367
+
368
+ (mac loop (start test update . body)
369
+ (w/uniq (gfn gparm)
370
+ `(do ,start
371
+ ((rfn ,gfn (,gparm)
372
+ (if ,gparm
373
+ (do ,@body ,update (,gfn ,test))))
374
+ ,test))))
375
+
376
+ (mac for (v init max . body)
377
+ (w/uniq (gi gm)
378
+ `(with (,v nil ,gi ,init ,gm (+ ,max 1))
379
+ (loop (assign ,v ,gi) (< ,v ,gm) (assign ,v (+ ,v 1))
380
+ ,@body))))
@@ -20,8 +20,39 @@
20
20
  (make-plus +seven 7)
21
21
  (make-plus +eleven 11)
22
22
 
23
+ ;
24
+ ; another contrived example to check deeply nested lexical scoping
25
+ ;
26
+ (let test-a0 "a0"
27
+ (def test-foo (f0 f1)
28
+ (with (w0 "w0" w1 "w1" w2 "w2" w3 "w3")
29
+ (let f (fn (x0) (joinstr " " test-a0 x0 f0 x0 f1))
30
+ (map f (list w0 w1 w2 w3))))))
31
+
23
32
  (register-test
24
33
  '(suite "Boot Tests"
34
+ (suite "hashtables"
35
+ ("build a hash table from brace-list syntax"
36
+ (let hsh { foo 1 bar 2 }
37
+ (list 'foo hsh.foo 'bar hsh.bar))
38
+ (foo 1 bar 2))
39
+
40
+ ("single-item brace list is just the thing itself"
41
+ (let zi 10 "finds the ~{zi}th item")
42
+ "finds the 10th item")
43
+
44
+ ("unquotes hash keys"
45
+ (with (zi 'foo chi 'bar yi 'grr)
46
+ (let hsh { ,zi 10 ,chi 11 ,yi 12 }
47
+ (list zi hsh.foo chi hsh.bar yi hsh.grr)))
48
+ (foo 10 bar 11 grr 12))
49
+
50
+ ("allows literal and invocation hash keys"
51
+ (with (zi "hello" chi "world")
52
+ (let hsh { (joinstr " " zi chi) 10 "yesterday" 11 }
53
+ (list "hello world" (hash-get hsh "hello world") "yesterday" (hash-get hsh "yesterday"))))
54
+ ("hello world" 10 "yesterday" 11)))
55
+
25
56
  (suite "list management"
26
57
  ("'pair breaks a list into pairs"
27
58
  (pairs '(1 a 2 b 3 c))
@@ -43,6 +74,18 @@
43
74
  (joinstr "" '("foo" "bar" "bax"))
44
75
  "foobarbax")
45
76
 
77
+ ("joins separate elements into a string"
78
+ (joinstr "/" "foo" "bar" "bax")
79
+ "foo/bar/bax")
80
+
81
+ ("joins a single thing"
82
+ (joinstr "/" "foo")
83
+ "foo")
84
+
85
+ ("joins nested and separate elements into a string"
86
+ (joinstr "/" "foo" "bar" '(twiddle diddle) "bax")
87
+ "foo/bar/twiddle/diddle/bax")
88
+
46
89
  ("joins elements into a string"
47
90
  (joinstr " - " '(1 2 3))
48
91
  "1 - 2 - 3")
@@ -54,9 +97,30 @@
54
97
  (suite "map"
55
98
  ("maps a function over a list of numbers"
56
99
  (map (fn (x) (* x x)) '(1 2 3))
57
- (1 4 9)))
100
+ (1 4 9))
101
+
102
+ ("maps a string join function over a list of strings"
103
+ (test-foo "x" "y")
104
+ ("a0 w0 x w0 y" "a0 w1 x w1 y" "a0 w2 x w2 y" "a0 w3 x w3 y"))
58
105
 
59
106
  (suite "pre-compile"
107
+ (suite "bang-syntax"
108
+ ("expansion"
109
+ (pre-compile '(!eq? a b))
110
+ ((fn args (no (apply eq? args))) a b))
111
+
112
+ ("bang-syntax for 'eq?"
113
+ (!eq? 1 2)
114
+ t)
115
+
116
+ ("bang-syntax for 'caris"
117
+ (!caris 'foo '(foo bar))
118
+ nil)
119
+
120
+ ("bang-syntax for 'caris"
121
+ (!caris 'foo '(zozo foo bar))
122
+ t))
123
+
60
124
  ("expands 'let"
61
125
  (do
62
126
  (def x+3*z (x y)