lasp 0.7.0 → 0.8.0
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 +4 -4
- data/CHANGELOG.md +44 -10
- data/EXAMPLES.md +84 -46
- data/README.md +23 -4
- data/lib/lasp/corelib.rb +5 -1
- data/lib/lasp/errors.rb +3 -0
- data/lib/lasp/eval.rb +23 -13
- data/lib/lasp/fn.rb +49 -0
- data/lib/lasp/macro.rb +8 -0
- data/lib/lasp/parameters.rb +93 -0
- data/lib/lasp/params.rb +72 -0
- data/lib/lasp/parser.rb +37 -36
- data/lib/lasp/repl.rb +5 -2
- data/lib/lasp/representation.rb +17 -0
- data/lib/lasp/stdlib.lasp +22 -5
- data/lib/lasp/stdmacros.lasp +38 -0
- data/lib/lasp/version.rb +1 -1
- data/lib/lasp.rb +2 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1eca69fd40314669d431772d434b59de33dc9695
|
4
|
+
data.tar.gz: 4a99615567d3b848585e9e330f3cebaa1a1f6fc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42594879ac26a139dc2a39b1dd9990314cbd65efa6f44acbb8d99f303e359863eccac73ae59a06dd54c77921d4094b6a156ccf83dd7022caa54d0250956ae828
|
7
|
+
data.tar.gz: 8831f6fa41e43311dd348de39dda2876b129d1c09853c000f76a4ad2d5a9ff7e62c1b2334d309b9da0657ad5d330eee5e2461df5bacd2ce5decf8c08f63d9e34
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,40 @@
|
|
1
1
|
# Läsp changelog
|
2
2
|
|
3
|
-
## v0.
|
3
|
+
## v0.8.0 - 2016-02-04
|
4
|
+
|
5
|
+
### Added
|
6
|
+
|
7
|
+
- Macro system
|
8
|
+
- `quote` special form to defer evaluation
|
9
|
+
- `macro` special form that works like a function but receives its arguments before evaluation
|
10
|
+
- `'` syntax sugar for quoting, `'f` becomes `(quote f)`
|
11
|
+
- Variadic functions
|
12
|
+
- Both functions and macros can now accept rest-arguments with an `&`
|
13
|
+
- Additions to core library
|
14
|
+
- `require`
|
15
|
+
- `apply`
|
16
|
+
- Additions to standard library
|
17
|
+
- `nil?`
|
18
|
+
- `not=`
|
19
|
+
- `second`
|
20
|
+
- macros
|
21
|
+
- `defn`
|
22
|
+
- `defm`
|
23
|
+
- `macroexpand`
|
24
|
+
- Lispy looking datastructures
|
25
|
+
- The REPL now displays `[:f, 12, 34]` as `(f 12 34)`
|
26
|
+
|
27
|
+
### Changed
|
28
|
+
|
29
|
+
- Rename `hash-map` to `dict`
|
30
|
+
- `pipe` now uses rest-arguments instead of taking a list of functions
|
31
|
+
|
32
|
+
### Fixed
|
33
|
+
|
34
|
+
- Parentheses inside strings are now handled correctly
|
35
|
+
- Negative numbers parsed correctly instead of appearing as symbols
|
36
|
+
|
37
|
+
## v0.7.0 - 2016-01-18
|
4
38
|
|
5
39
|
- Arity is now enforced in function calls and will throw errors when mismatched
|
6
40
|
- Add methods to stdlib:
|
@@ -17,19 +51,19 @@
|
|
17
51
|
- Return nil when pressing Ctrl+D (this used to cause an error)
|
18
52
|
- Many refactorings
|
19
53
|
|
20
|
-
## v0.6.0
|
54
|
+
## v0.6.0 - 2016-01-17
|
21
55
|
|
22
56
|
- `<` and `>` now return false when given a list of equal items, they all **have to** increase or decrease.
|
23
57
|
- Add `<=` and `>=` to the core library.
|
24
58
|
- Fix stack overflowing when using `range` with an upper bound less than the lower bound, this now returns an empty list.
|
25
59
|
|
26
|
-
## v0.5.0
|
60
|
+
## v0.5.0 - 2015-09-24
|
27
61
|
|
28
62
|
- Merge `lasp-repl` command into `lasp`, it starts when not provided with a filename.
|
29
63
|
- Don't send env to functions implemented as Ruby procs, it was never used.
|
30
64
|
- Specs as default rake task.
|
31
65
|
|
32
|
-
## v0.4.0
|
66
|
+
## v0.4.0 - 2015-08-09
|
33
67
|
|
34
68
|
Implicit do-blocks around files. You can now do this...:
|
35
69
|
|
@@ -45,12 +79,12 @@ anything after would seemingly inexplicably not be run.
|
|
45
79
|
|
46
80
|
**This is only enabled in files, not every form of evaluation.**
|
47
81
|
|
48
|
-
## v0.3.2
|
82
|
+
## v0.3.2 - 2015-08-09
|
49
83
|
|
50
84
|
Fix bug in `do` - it accidentally returned part of the AST, now it correctly
|
51
85
|
returns the result of the last expression.
|
52
86
|
|
53
|
-
## v0.3.1
|
87
|
+
## v0.3.1 - 2015-08-09
|
54
88
|
|
55
89
|
Make readline support actually work once released to rubygems by implementing it directly in Ruby.
|
56
90
|
|
@@ -58,11 +92,11 @@ It does not seem to remember history between runs like rlwrap did, but trying
|
|
58
92
|
to deploy an interactive bash script to rubygems is just too much of a headache
|
59
93
|
and this is almost as nice with just a single line of Ruby.
|
60
94
|
|
61
|
-
## v0.3.0
|
95
|
+
## v0.3.0 - 2015-08-09
|
62
96
|
|
63
97
|
Add readline support in the REPL using rlwrap, this makes the REPL a **lot** nicer to use.
|
64
98
|
|
65
|
-
## v0.2.0
|
99
|
+
## v0.2.0 - 2015-08-08
|
66
100
|
|
67
101
|
Add support for hash-maps with the following added functions to the core library:
|
68
102
|
|
@@ -73,10 +107,10 @@ Add support for hash-maps with the following added functions to the core library
|
|
73
107
|
|
74
108
|
Also adds exit instructions to the REPL welcome message.
|
75
109
|
|
76
|
-
## v0.1.1
|
110
|
+
## v0.1.1 - 2015-08-02
|
77
111
|
|
78
112
|
Fix broken `lasp` executable.
|
79
113
|
|
80
|
-
## v0.1.0
|
114
|
+
## v0.1.0 - 2015-08-01
|
81
115
|
|
82
116
|
First release.
|
data/EXAMPLES.md
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
# Examples
|
2
2
|
|
3
|
+
A complete list of the available functions can be found in the README.
|
4
|
+
|
5
|
+
Since Läsp is very similar to Clojure, you can get pretty decent syntax
|
6
|
+
highlighting by just setting your editor to treat .lasp files as .clj
|
7
|
+
|
8
|
+
In this document output is shown with ;; and normal comments with ;.
|
9
|
+
|
10
|
+
## Data types
|
11
|
+
|
3
12
|
```lisp
|
4
|
-
; Notes:
|
5
|
-
;
|
6
|
-
; A complete list of the available functions can be found in the README.
|
7
|
-
;
|
8
|
-
; Since Läsp is very similar to Clojure, you can get pretty decent syntax
|
9
|
-
; highlighting by just setting your editor to treat .lasp files as .clj
|
10
|
-
;
|
11
|
-
; In this document output is shown with ;; and normal comments with ;.
|
12
|
-
|
13
|
-
; --- Data types ---
|
14
13
|
; Number types
|
15
14
|
1
|
16
15
|
1.5
|
@@ -29,13 +28,15 @@ nil
|
|
29
28
|
; Lists
|
30
29
|
(list 1 2 3) ;; => [1, 2, 3]
|
31
30
|
(list) ;; => []
|
31
|
+
```
|
32
32
|
|
33
|
-
|
33
|
+
## Basic function calls
|
34
34
|
|
35
|
+
```lisp
|
35
36
|
; inc is a function that increments its argument, and 1 is the single argument
|
36
37
|
(inc 1) ;; => 2
|
37
38
|
|
38
|
-
; Simple maths functions
|
39
|
+
; Simple maths functions take any number of arguments
|
39
40
|
(/ 20 2) ;; => 10
|
40
41
|
(+ 1 3 5) ;; => 9
|
41
42
|
(* 2 5 6 10) ;; => 600
|
@@ -47,16 +48,18 @@ nil
|
|
47
48
|
|
48
49
|
; Boolean inversion
|
49
50
|
(not true) ;; => false
|
51
|
+
```
|
50
52
|
|
51
|
-
|
53
|
+
## List operations
|
52
54
|
|
55
|
+
```lisp
|
53
56
|
(head (list 1 2 3)) ;; => 1
|
54
57
|
(first (list 1 2 3)) ;; => 1
|
55
58
|
|
56
|
-
(tail (list 1 2 3)) ;; =>
|
57
|
-
(rest (list 1 2 3)) ;; =>
|
59
|
+
(tail (list 1 2 3)) ;; => (2 3)
|
60
|
+
(rest (list 1 2 3)) ;; => (2 3)
|
58
61
|
|
59
|
-
(cons 0 (list 1 2 3)) ;; =>
|
62
|
+
(cons 0 (list 1 2 3)) ;; => (0 1 2 3)
|
60
63
|
|
61
64
|
(nth 1 (list 1 2 3)) ;; => 2
|
62
65
|
(nth 3 (list 1 2 3)) ;; => nil
|
@@ -66,38 +69,42 @@ nil
|
|
66
69
|
(get 3 (list 1 2 3)) ;; => nil
|
67
70
|
|
68
71
|
; assoc returns a new array with the specified index updated
|
69
|
-
(assoc (list 1 2 3) 0 "one") ;; =>
|
72
|
+
(assoc (list 1 2 3) 0 "one") ;; => ("one" 2 3)
|
70
73
|
|
71
74
|
(last (list 1 2 3)) ;; => 3
|
72
75
|
|
73
|
-
(take 2 (list 1 2 3)) ;; =>
|
74
|
-
(drop 2 (list 1 2 3)) ;; =>
|
76
|
+
(take 2 (list 1 2 3)) ;; => (1 2)
|
77
|
+
(drop 2 (list 1 2 3)) ;; => (3)
|
75
78
|
|
76
79
|
(max (list 1 3 2)) ;; => 3
|
77
80
|
|
78
|
-
(reverse (list 1 2 3)) ;; =>
|
81
|
+
(reverse (list 1 2 3)) ;; => (3 2 1)
|
79
82
|
|
80
83
|
; Ranges
|
81
|
-
(range 1 10) ;; =>
|
84
|
+
(range 1 10) ;; => (1 2 3 4 5 6 7 8 9)
|
85
|
+
```
|
82
86
|
|
83
|
-
|
87
|
+
## Dictionaries
|
84
88
|
|
85
|
-
|
86
|
-
|
89
|
+
```lisp
|
90
|
+
; Create a dict
|
91
|
+
(dict :one 1 :two 2) ;; => {"one" 1, "two" 2}
|
87
92
|
|
88
|
-
; get also works with
|
89
|
-
(get :one (
|
93
|
+
; get also works with dicts
|
94
|
+
(get :one (dict :one 1 :two 2)) ;; => 1
|
90
95
|
|
91
|
-
; assoc works with
|
92
|
-
(assoc (
|
96
|
+
; assoc works with dicts too
|
97
|
+
(assoc (dict :one 1 :two 2) :three 3) ;; => {"one" 1, "two" 2, "three" 3}
|
93
98
|
|
94
99
|
; dissoc removes values
|
95
|
-
(dissoc (
|
100
|
+
(dissoc (dict :one 1 :two 2) :one) ;; => {"two" 2}
|
101
|
+
```
|
96
102
|
|
97
|
-
|
103
|
+
## More complex functions
|
98
104
|
|
105
|
+
```lisp
|
99
106
|
; Apply a function to all items in a list
|
100
|
-
(map inc (list 1 2 3)) ;; =>
|
107
|
+
(map inc (list 1 2 3)) ;; => (2 3 4)
|
101
108
|
|
102
109
|
; Accumulate a value with a function
|
103
110
|
; The function (here +) will receive a memo (the running total)
|
@@ -111,19 +118,23 @@ nil
|
|
111
118
|
(reduce * 1 (list 1 2 3)) ;; => 6
|
112
119
|
|
113
120
|
; Filtering
|
114
|
-
(filter odd? (list 1 2 3)) ;; =>
|
121
|
+
(filter odd? (list 1 2 3)) ;; => (1 3)
|
122
|
+
```
|
115
123
|
|
116
|
-
|
124
|
+
## Variables
|
117
125
|
|
126
|
+
```lisp
|
118
127
|
; Define and evaluate a variable
|
119
128
|
(def x (list 1 2 3))
|
120
|
-
x ;; =>
|
129
|
+
x ;; => (1 2 3)
|
121
130
|
|
122
131
|
; Use it in a function
|
123
132
|
(sum x) ;; => 6
|
133
|
+
```
|
124
134
|
|
125
|
-
|
135
|
+
## Misc
|
126
136
|
|
137
|
+
```lisp
|
127
138
|
; Outputting to the terminal
|
128
139
|
(println "hello world!")
|
129
140
|
;; hello world!
|
@@ -141,25 +152,51 @@ x ;; => [1, 2, 3]
|
|
141
152
|
(println "nope!"))
|
142
153
|
;; nope!
|
143
154
|
;; => nil
|
155
|
+
```
|
144
156
|
|
145
|
-
|
157
|
+
## Creating functions
|
146
158
|
|
159
|
+
```lisp
|
147
160
|
; A function has 2 forms, one with the parameters and one with the body.
|
148
161
|
; Here's a function that adds 10 to its argument
|
149
162
|
(fn (x) (+ 10 x))
|
150
163
|
|
151
|
-
; You can call it just like one of the named functions
|
164
|
+
; You can call it just like one of the named functions by placing the entire
|
165
|
+
; fn-form at the first position
|
152
166
|
((fn (x) (+ 10 x)) 50) ;; => 60
|
153
167
|
|
154
|
-
; You can give it a name
|
168
|
+
; You can give it a name by defining it
|
155
169
|
(def add-ten (fn (x) (+ 10 x)))
|
156
170
|
(add-ten 50) ;; => 60
|
157
171
|
|
158
172
|
(def square (fn (x) (* x x)))
|
159
173
|
(square 5) ;; => 25
|
160
174
|
|
161
|
-
;
|
175
|
+
; Arity is enforced when calling a function, this yields an error:
|
176
|
+
(square 5 2) ;; !> wrong number of arguments (2 for 1)
|
177
|
+
|
178
|
+
|
179
|
+
; Rest arguments are passed in as a list to the binding after the &
|
180
|
+
(def last-argument
|
181
|
+
(fn (& args)
|
182
|
+
(last args)))
|
183
|
+
|
184
|
+
(last-argument 1 2) ;; => 2
|
185
|
+
(last-argument 1 2 3) ;; => 3
|
186
|
+
|
187
|
+
; You can also have mandatory positional arguments and rest arguments at the same time
|
188
|
+
(def add-first-two
|
189
|
+
(fn (a b & args)
|
190
|
+
(+ a b)))
|
191
|
+
|
192
|
+
(add-first-two 1 2) ;; => 3
|
193
|
+
(add-first-two 1 2 3) ;; => 3
|
194
|
+
(add-first-two 1) ;; !> wrong number of arguments (1 for 2+)
|
195
|
+
```
|
162
196
|
|
197
|
+
### Count the amount of 5:s in a list
|
198
|
+
|
199
|
+
```lisp
|
163
200
|
; The list we will operate on
|
164
201
|
(def fives (list 1 5 2 3 5 8 5)) ; 5 occurs 3 times.
|
165
202
|
|
@@ -171,19 +208,19 @@ x ;; => [1, 2, 3]
|
|
171
208
|
0 ; (2) We start counting from 0
|
172
209
|
fives) ; (3) We operate over the previously defined `fives` list.
|
173
210
|
;; => 3
|
211
|
+
```
|
174
212
|
|
175
|
-
|
213
|
+
### Print a beautiful pyramid
|
176
214
|
|
215
|
+
```lisp
|
177
216
|
; Print one row in the pyramid
|
178
217
|
(def print-row
|
179
|
-
(fn
|
180
|
-
(length)
|
218
|
+
(fn (length)
|
181
219
|
(println (* "#" length)))) ; We use the fact that the host platform (Ruby) can multiply strings
|
182
220
|
|
183
221
|
; Print the entire pyramid
|
184
222
|
(def print-pyramid
|
185
|
-
(fn
|
186
|
-
(height current-width)
|
223
|
+
(fn (height current-width)
|
187
224
|
(if (= current-width height)
|
188
225
|
nil
|
189
226
|
(do
|
@@ -201,10 +238,11 @@ x ;; => [1, 2, 3]
|
|
201
238
|
;; #######
|
202
239
|
;; ########
|
203
240
|
;; #########
|
241
|
+
```
|
204
242
|
|
243
|
+
## Interoperability
|
205
244
|
|
206
|
-
|
207
|
-
|
245
|
+
```lisp
|
208
246
|
; The . function allows for Ruby interoperability.
|
209
247
|
(. "01011101" :to_i 2) ;; => 93
|
210
248
|
|
data/README.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# Läsp
|
2
2
|
|
3
|
-
A
|
3
|
+
A Lisp implementation in Ruby.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Very concise [core library](lib/lasp/corelib.rb) written in Ruby
|
8
|
+
- [Standard library](lib/lasp/stdlib.lisp) written in Läsp itself
|
9
|
+
- Interactive REPL with auto-closing of missing trailing parentheses
|
10
|
+
- Fully functional macro system
|
11
|
+
- Interoperability with Ruby
|
6
12
|
|
7
13
|
## Installation
|
8
14
|
|
@@ -57,7 +63,7 @@ Supports these datatypes (implemented as their Ruby counterparts)
|
|
57
63
|
- nil
|
58
64
|
- string
|
59
65
|
- list
|
60
|
-
-
|
66
|
+
- dict
|
61
67
|
|
62
68
|
### Comments
|
63
69
|
|
@@ -85,13 +91,15 @@ Implemented as Ruby lambdas.
|
|
85
91
|
- `head`
|
86
92
|
- `tail`
|
87
93
|
- `cons`
|
88
|
-
- `
|
94
|
+
- `dict`
|
89
95
|
- `get`
|
90
96
|
- `assoc`
|
91
97
|
- `dissoc`
|
92
98
|
- `not`
|
93
99
|
- `println`
|
100
|
+
- `apply`
|
94
101
|
- `.`
|
102
|
+
- `require`
|
95
103
|
|
96
104
|
### Special forms
|
97
105
|
|
@@ -101,6 +109,8 @@ Implemented as special cases while evaluating.
|
|
101
109
|
- `fn`
|
102
110
|
- `do`
|
103
111
|
- `if`
|
112
|
+
- `quote`
|
113
|
+
- `macro`
|
104
114
|
|
105
115
|
### Functions in stdlib
|
106
116
|
|
@@ -110,7 +120,10 @@ Implemented in Läsp itself.
|
|
110
120
|
- `rest` (alias of `tail`)
|
111
121
|
- `inc`
|
112
122
|
- `dec`
|
123
|
+
- `nil?`
|
113
124
|
- `empty?`
|
125
|
+
- `not=`
|
126
|
+
- `second`
|
114
127
|
- `mod`
|
115
128
|
- `complement`
|
116
129
|
- `even?`
|
@@ -136,6 +149,12 @@ Implemented in Läsp itself.
|
|
136
149
|
- `pipe`
|
137
150
|
- `reverse-str`
|
138
151
|
|
152
|
+
## Macros in stdlib
|
153
|
+
|
154
|
+
- `defn`
|
155
|
+
- `defm`
|
156
|
+
- `macroexpand`
|
157
|
+
|
139
158
|
## Developing
|
140
159
|
|
141
160
|
### Run the specs
|
data/lib/lasp/corelib.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "lasp"
|
2
|
+
|
1
3
|
module Lasp
|
2
4
|
CORELIB = {
|
3
5
|
:+ => -> (*args) { args.reduce(:+) },
|
@@ -13,12 +15,14 @@ module Lasp
|
|
13
15
|
:head => -> (list) { list.first },
|
14
16
|
:tail => -> (list) { list.drop(1) },
|
15
17
|
:cons => -> (item, list) { [item] + list },
|
16
|
-
:
|
18
|
+
:dict => -> (*args) { Hash[*args] },
|
17
19
|
:get => -> (key, a) { a[key] },
|
18
20
|
:assoc => -> (a, key, val) { a.dup.tap { |a| a[key] = val } },
|
19
21
|
:dissoc => -> (a, key) { a.dup.tap { |a| a.delete(key) } },
|
20
22
|
:not => -> (arg) { !arg },
|
21
23
|
:println => -> (output) { puts output },
|
24
|
+
:apply => -> (f, list) { f.call(*list) },
|
22
25
|
:"." => -> (obj, m, *args) { obj.send(m, *args) },
|
26
|
+
:require => -> (path) { Lasp::execute_file(path) },
|
23
27
|
}
|
24
28
|
end
|
data/lib/lasp/errors.rb
ADDED
data/lib/lasp/eval.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
|
-
require "lasp/parser"
|
2
1
|
require "lasp/env"
|
2
|
+
require "lasp/fn"
|
3
|
+
require "lasp/macro"
|
3
4
|
|
4
5
|
module Lasp
|
5
6
|
module_function
|
@@ -16,18 +17,23 @@ module Lasp
|
|
16
17
|
head, *tail = *form
|
17
18
|
|
18
19
|
case head
|
19
|
-
when :def
|
20
|
-
when :fn
|
21
|
-
when :do
|
22
|
-
when :if
|
23
|
-
when
|
20
|
+
when :def then def_special_form(tail, env)
|
21
|
+
when :fn then fn_special_form(tail, env)
|
22
|
+
when :do then do_special_form(tail, env)
|
23
|
+
when :if then if_special_form(tail, env)
|
24
|
+
when :quote then quote_special_form(tail, env)
|
25
|
+
when :macro then macro_special_form(tail, env)
|
24
26
|
else call_function(head, tail, env)
|
25
27
|
end
|
26
28
|
end
|
27
29
|
|
28
30
|
def call_function(symbol, args, env)
|
29
31
|
fn = Lasp::eval(symbol, env)
|
30
|
-
|
32
|
+
|
33
|
+
case fn
|
34
|
+
when Macro then Lasp::eval(fn.(*args), env)
|
35
|
+
else fn.(*args.map { |form| Lasp::eval(form, env) })
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
def def_special_form(form, env)
|
@@ -37,12 +43,7 @@ module Lasp
|
|
37
43
|
|
38
44
|
def fn_special_form(form, env)
|
39
45
|
params, func = form
|
40
|
-
|
41
|
-
unless args.count == params.count
|
42
|
-
fail ArgumentError, "wrong number of arguments (#{args.count} for #{params.count})"
|
43
|
-
end
|
44
|
-
Lasp::eval(func, env.merge(Hash[params.zip(args)]))
|
45
|
-
}
|
46
|
+
Fn.new(params, func, env)
|
46
47
|
end
|
47
48
|
|
48
49
|
def do_special_form(form, env)
|
@@ -53,4 +54,13 @@ module Lasp
|
|
53
54
|
conditional, true_form, false_form = form
|
54
55
|
Lasp::eval(conditional, env) ? Lasp::eval(true_form, env) : Lasp::eval(false_form, env)
|
55
56
|
end
|
57
|
+
|
58
|
+
def quote_special_form(form, _)
|
59
|
+
form.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def macro_special_form(form, env)
|
63
|
+
params, func = form
|
64
|
+
Macro.new(params, func, env)
|
65
|
+
end
|
56
66
|
end
|
data/lib/lasp/fn.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
require "lasp/eval"
|
2
|
+
require "lasp/params"
|
3
|
+
require "lasp/errors"
|
4
|
+
|
5
|
+
module Lasp
|
6
|
+
class Fn
|
7
|
+
attr_reader :params, :body, :env
|
8
|
+
|
9
|
+
def initialize(params, body, env)
|
10
|
+
@params = Params.new(params)
|
11
|
+
@body = body
|
12
|
+
@env = env
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(*args)
|
16
|
+
Lasp::eval(body, env_with_args(args))
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
class_name = self.class.name.split("::").last
|
21
|
+
"#<#{class_name} #{params}>"
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def env_with_args(args)
|
27
|
+
enforce_arity!(args)
|
28
|
+
|
29
|
+
params_with_args = params
|
30
|
+
.ordered
|
31
|
+
.zip(args.take(params.length))
|
32
|
+
.to_h
|
33
|
+
|
34
|
+
if params.variadic?
|
35
|
+
params_with_args[params.rest] = args.drop(params.length)
|
36
|
+
end
|
37
|
+
|
38
|
+
env.merge(params_with_args)
|
39
|
+
end
|
40
|
+
|
41
|
+
def enforce_arity!(args)
|
42
|
+
wrong_number_of_args!(args) unless params.matches_arity?(args.length)
|
43
|
+
end
|
44
|
+
|
45
|
+
def wrong_number_of_args!(args)
|
46
|
+
fail ArgumentError, "wrong number of arguments (#{args.length} for #{params.arity})"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/lasp/macro.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
module Lasp
|
2
|
+
module Parameters
|
3
|
+
def self.new(params)
|
4
|
+
if params.include?(:&)
|
5
|
+
RestParameters.new(params)
|
6
|
+
else
|
7
|
+
FixedParameters.new(params)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class FixedParameters
|
12
|
+
attr_reader :parameter_list
|
13
|
+
|
14
|
+
def initialize(parameter_list)
|
15
|
+
@parameter_list = parameter_list
|
16
|
+
valid_signature!
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_h(args)
|
20
|
+
enforce_arity!(args)
|
21
|
+
Hash[parameter_list.zip(args)]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"(" + parameter_list.join(" ") + ")"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def enforce_arity!(args)
|
31
|
+
if args.count != arity
|
32
|
+
fail ArgumentError, "wrong number of arguments (#{args.count} for #{arity})"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def valid_signature!
|
37
|
+
if duplicate_parameter?
|
38
|
+
fail SyntaxError, "Parameter names have to be unique. a is used more than once"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def duplicate_parameter?
|
43
|
+
parameter_list.uniq.length != parameter_list.length
|
44
|
+
end
|
45
|
+
|
46
|
+
def arity
|
47
|
+
parameter_list.count
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class RestParameters < FixedParameters
|
52
|
+
def to_h(args)
|
53
|
+
enforce_arity!(args)
|
54
|
+
|
55
|
+
ordered = parameter_list[0, minimum_arguments]
|
56
|
+
rest = parameter_list.last
|
57
|
+
|
58
|
+
env = ordered.zip(args.take(ordered.length))
|
59
|
+
env << [ rest, args.drop(ordered.length) ]
|
60
|
+
|
61
|
+
Hash[env]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def valid_signature!
|
67
|
+
if multiple_rest_arguments? || ampersand_not_second_to_last?
|
68
|
+
fail SyntaxError, "Rest-arguments may only be used once, at the end, with a single binding."
|
69
|
+
end
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
def multiple_rest_arguments?
|
74
|
+
parameter_list.select { |b| b == :& }.length > 1
|
75
|
+
end
|
76
|
+
|
77
|
+
def ampersand_not_second_to_last?
|
78
|
+
parameter_list.find_index(:&) != minimum_arguments
|
79
|
+
end
|
80
|
+
|
81
|
+
def minimum_arguments
|
82
|
+
parameter_list.count - 2
|
83
|
+
end
|
84
|
+
|
85
|
+
def enforce_arity!(args)
|
86
|
+
if args.count < minimum_arguments
|
87
|
+
fail ArgumentError, "wrong number of arguments (#{args.count} for #{minimum_arguments}+)"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
end
|
data/lib/lasp/params.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Lasp
|
2
|
+
class Params
|
3
|
+
attr_reader :param_list
|
4
|
+
|
5
|
+
def initialize(param_list)
|
6
|
+
@param_list = param_list
|
7
|
+
|
8
|
+
validate_params!
|
9
|
+
end
|
10
|
+
|
11
|
+
def ordered
|
12
|
+
param_list.take_while { |p| p != :& }
|
13
|
+
end
|
14
|
+
|
15
|
+
def rest
|
16
|
+
unless variadic?
|
17
|
+
fail ArgumentError, "A non-variadic function does not have rest-arguments"
|
18
|
+
end
|
19
|
+
param_list.last
|
20
|
+
end
|
21
|
+
|
22
|
+
def variadic?
|
23
|
+
param_list.include?(:&)
|
24
|
+
end
|
25
|
+
|
26
|
+
def arity
|
27
|
+
ordered.length.to_s + (variadic? ? "+" : "")
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches_arity?(num_args)
|
31
|
+
if variadic?
|
32
|
+
num_args >= length
|
33
|
+
else
|
34
|
+
num_args == length
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def length
|
39
|
+
ordered.length
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
"(" + param_list.join(" ") + ")"
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def validate_params!
|
49
|
+
validate_single_ampersand!
|
50
|
+
validate_single_rest_parameter!
|
51
|
+
validate_unique_parameter_names!
|
52
|
+
end
|
53
|
+
|
54
|
+
def validate_unique_parameter_names!
|
55
|
+
unless param_list.uniq.length == param_list.length
|
56
|
+
fail ArgumentError, "Parameter names have to be unique."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def validate_single_ampersand!
|
61
|
+
unless param_list.select { |p| p == :& }.length <= 1
|
62
|
+
fail ArgumentError, "Rest-arguments may only be used once, at the end, with a single binding."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate_single_rest_parameter!
|
67
|
+
if variadic? && param_list[-2] != :&
|
68
|
+
fail ArgumentError, "Rest-arguments may only be used once, at the end, with a single binding."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/lasp/parser.rb
CHANGED
@@ -1,47 +1,48 @@
|
|
1
1
|
module Lasp
|
2
|
-
|
2
|
+
class Parser
|
3
|
+
def parse(program)
|
4
|
+
build_ast(tokenize(sanitize(program)))
|
5
|
+
end
|
3
6
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
+
def tokenize(string)
|
8
|
+
string.scan(/(?:(?:[^\s"()']|"[^"]*")+|[()'])/)
|
9
|
+
end
|
7
10
|
|
8
|
-
|
9
|
-
return if tokens.empty?
|
10
|
-
token = tokens.shift
|
11
|
+
private
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def build_ast(tokens)
|
14
|
+
return if tokens.empty?
|
15
|
+
token = tokens.shift
|
16
|
+
|
17
|
+
if token == "("
|
18
|
+
form = []
|
19
|
+
while tokens.first != ")"
|
20
|
+
form << build_ast(tokens)
|
21
|
+
end
|
22
|
+
tokens.shift
|
23
|
+
form
|
24
|
+
elsif token == "'"
|
25
|
+
[:quote] << build_ast(tokens)
|
26
|
+
else
|
27
|
+
atom(token)
|
16
28
|
end
|
17
|
-
tokens.shift
|
18
|
-
form
|
19
|
-
else
|
20
|
-
atom(token)
|
21
29
|
end
|
22
|
-
end
|
23
30
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
when "nil" then nil
|
36
|
-
when /\A\d+\z/ then Integer(token)
|
37
|
-
when /\A\d+.\d+\z/ then Float(token)
|
38
|
-
when /"(.*)"/ then String($1)
|
39
|
-
when /:(\w+)/ then String($1) # Symbol style strings are actually just strings
|
40
|
-
else token.to_sym
|
31
|
+
def atom(token)
|
32
|
+
case token
|
33
|
+
when "true" then true
|
34
|
+
when "false" then false
|
35
|
+
when "nil" then nil
|
36
|
+
when /\A-?\d+\z/ then Integer(token)
|
37
|
+
when /\A-?\d+.\d+\z/ then Float(token)
|
38
|
+
when /"(.*)"/ then String($1)
|
39
|
+
when /:(\w+)/ then String($1) # Symbol style strings are actually just strings
|
40
|
+
else token.to_sym
|
41
|
+
end
|
41
42
|
end
|
42
|
-
end
|
43
43
|
|
44
|
-
|
45
|
-
|
44
|
+
def sanitize(string)
|
45
|
+
string.gsub(/;.*$/, "")
|
46
|
+
end
|
46
47
|
end
|
47
48
|
end
|
data/lib/lasp/repl.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require "lasp"
|
2
|
+
require "lasp/parser"
|
3
|
+
require "lasp/representation"
|
2
4
|
require "readline"
|
3
5
|
|
4
6
|
module Lasp
|
@@ -22,8 +24,9 @@ module Lasp
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def autoclose_parentheses(input)
|
25
|
-
|
26
|
-
|
27
|
+
tokens = Parser.new.tokenize(input)
|
28
|
+
num_opens = tokens.select { |t| t == "(" }.count
|
29
|
+
num_closes = tokens.select { |t| t == ")" }.count
|
27
30
|
|
28
31
|
if num_opens > num_closes
|
29
32
|
missing_closes = num_opens - num_closes
|
data/lib/lasp/stdlib.lasp
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
(require "./lib/lasp/stdmacros.lasp")
|
2
|
+
|
1
3
|
; Aliases
|
2
4
|
(def first head)
|
3
5
|
(def rest tail)
|
@@ -8,10 +10,23 @@
|
|
8
10
|
; Decrement a number by one
|
9
11
|
(def dec (fn (x) (- x 1)))
|
10
12
|
|
13
|
+
; Is it nil?
|
14
|
+
(def nil? (fn (arg) (= nil arg)))
|
15
|
+
|
11
16
|
; If a list is empty
|
12
17
|
(def empty?
|
13
18
|
(fn (coll)
|
14
|
-
(
|
19
|
+
(nil? (head coll))))
|
20
|
+
|
21
|
+
; If all arguments are not equal
|
22
|
+
(def not=
|
23
|
+
(fn (& args)
|
24
|
+
(not (apply = args))))
|
25
|
+
|
26
|
+
; The second item in a list
|
27
|
+
(def second
|
28
|
+
(fn (coll)
|
29
|
+
(head (tail coll))))
|
15
30
|
|
16
31
|
; Modulus
|
17
32
|
(def mod
|
@@ -59,7 +74,7 @@
|
|
59
74
|
; Apply f to all items in list
|
60
75
|
(def map
|
61
76
|
(fn (f coll)
|
62
|
-
(if (
|
77
|
+
(if (nil? (head coll))
|
63
78
|
coll
|
64
79
|
(cons
|
65
80
|
(f (head coll))
|
@@ -140,12 +155,14 @@
|
|
140
155
|
|
141
156
|
; Pass a value in order through a list of functions
|
142
157
|
(def pipe
|
143
|
-
(fn (item fns)
|
158
|
+
(fn (item & fns)
|
144
159
|
(if (empty? fns)
|
145
160
|
item
|
146
|
-
|
161
|
+
; Note that you need to take special care when recursing with rest
|
162
|
+
; arguments, hence the use of apply.
|
163
|
+
(apply pipe (cons ((head fns) item) (tail fns))))))
|
147
164
|
|
148
165
|
; Reverses a string
|
149
166
|
(def reverse-str
|
150
167
|
(fn (str)
|
151
|
-
(pipe str
|
168
|
+
(pipe str str->list reverse list->str)))
|
@@ -0,0 +1,38 @@
|
|
1
|
+
; Shorthand for defining macros
|
2
|
+
;
|
3
|
+
; (defm m (form) (reverse form))
|
4
|
+
; expands to:
|
5
|
+
; (def m (macro (form) (reverse form)))
|
6
|
+
(def defm
|
7
|
+
(macro (name params body)
|
8
|
+
(list 'def name
|
9
|
+
(list 'macro params body))))
|
10
|
+
|
11
|
+
; Shorthand for defining functions
|
12
|
+
;
|
13
|
+
; (defn f (x) (+ x 1))
|
14
|
+
; expands to:
|
15
|
+
; (def f (fn (x) (do (+ x 1))))
|
16
|
+
(defm defn
|
17
|
+
(name params & body)
|
18
|
+
(list 'def name
|
19
|
+
(list 'fn params (cons 'do body))))
|
20
|
+
|
21
|
+
; See unevaluated form the macro expands to for debugging macros
|
22
|
+
;
|
23
|
+
; This simply uses the fact that `apply` already has this effect
|
24
|
+
; of expanding macros and makes the syntax a bit nicer, you can
|
25
|
+
; also just use `apply` directly as shown below.
|
26
|
+
;
|
27
|
+
; Example:
|
28
|
+
; (macroexpand (defn f (x) (+ 1 x)))
|
29
|
+
;
|
30
|
+
; expands to:
|
31
|
+
; (apply defn (quote (f (x) (+ 1 x))))
|
32
|
+
;
|
33
|
+
; which in is evaulated and returns this form:
|
34
|
+
; (def f (fn (x) (do (+ 1 x))))
|
35
|
+
(defm macroexpand
|
36
|
+
(form)
|
37
|
+
(list 'apply (first form)
|
38
|
+
(list 'quote (rest form))))
|
data/lib/lasp/version.rb
CHANGED
data/lib/lasp.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "lasp/version"
|
2
2
|
require "lasp/eval"
|
3
|
+
require "lasp/parser"
|
3
4
|
|
4
5
|
module Lasp
|
5
6
|
STDLIB_PATH = File.expand_path("../lasp/stdlib.lasp", __FILE__)
|
@@ -11,7 +12,7 @@ module Lasp
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def execute(program, env = global_env)
|
14
|
-
Lasp::eval(
|
15
|
+
Lasp::eval(Parser.new.parse(program), env)
|
15
16
|
end
|
16
17
|
|
17
18
|
def load_stdlib!
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lasp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jimmy Börjesson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,10 +58,17 @@ files:
|
|
58
58
|
- lib/lasp.rb
|
59
59
|
- lib/lasp/corelib.rb
|
60
60
|
- lib/lasp/env.rb
|
61
|
+
- lib/lasp/errors.rb
|
61
62
|
- lib/lasp/eval.rb
|
63
|
+
- lib/lasp/fn.rb
|
64
|
+
- lib/lasp/macro.rb
|
65
|
+
- lib/lasp/parameters.rb
|
66
|
+
- lib/lasp/params.rb
|
62
67
|
- lib/lasp/parser.rb
|
63
68
|
- lib/lasp/repl.rb
|
69
|
+
- lib/lasp/representation.rb
|
64
70
|
- lib/lasp/stdlib.lasp
|
71
|
+
- lib/lasp/stdmacros.lasp
|
65
72
|
- lib/lasp/version.rb
|
66
73
|
homepage: https://github.com/alcesleo/lasp
|
67
74
|
licenses:
|