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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c6fb8d2312d17d1d15be5e17ad26569f839789b3
4
- data.tar.gz: 7c473de16a4e3c017af92fa423ef976c04b369ce
3
+ metadata.gz: 1eca69fd40314669d431772d434b59de33dc9695
4
+ data.tar.gz: 4a99615567d3b848585e9e330f3cebaa1a1f6fc8
5
5
  SHA512:
6
- metadata.gz: b4a30a116ecfeefb43ea5f02c2dd11c4686da9d338625d70ecaa11cad37284b3de0a4e7110d80fda135da9ece294902a839b715676dfc3084eaba8863ca89c6a
7
- data.tar.gz: 2d39bb873b1698e27f462c141185561582cf8749a8cc920b97041658b657207018ae7f28b2d485afe6c189c1ee5f63f2f80833222f4ac0f0c6a4ff9958763ab0
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.7.0
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
- ; --- Basic function calls ---
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 takes any number of arguments
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
- ; --- List operations ---
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)) ;; => [2, 3]
57
- (rest (list 1 2 3)) ;; => [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)) ;; => [0, 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") ;; => ["one", 2, 3]
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)) ;; => [1, 2]
74
- (drop 2 (list 1 2 3)) ;; => [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)) ;; => [3, 2, 1]
81
+ (reverse (list 1 2 3)) ;; => (3 2 1)
79
82
 
80
83
  ; Ranges
81
- (range 1 10) ;; => [1, 2, 3, 4, 5, 6, 7, 8, 9]
84
+ (range 1 10) ;; => (1 2 3 4 5 6 7 8 9)
85
+ ```
82
86
 
83
- ; --- Hash-maps ---
87
+ ## Dictionaries
84
88
 
85
- ; Create a hash-map
86
- (hash-map :one 1 :two 2) ;; => {"one"=>1, "two"=>2}
89
+ ```lisp
90
+ ; Create a dict
91
+ (dict :one 1 :two 2) ;; => {"one" 1, "two" 2}
87
92
 
88
- ; get also works with hash-maps
89
- (get :one (hash-map :one 1 :two 2)) ;; => 1
93
+ ; get also works with dicts
94
+ (get :one (dict :one 1 :two 2)) ;; => 1
90
95
 
91
- ; assoc works with hash-maps too
92
- (assoc (hash-map :one 1 :two 2) :three 3) ;; => {"one"=>1, "two"=>2, "three"=>3}
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 (hash-map :one 1 :two 2) :one) ;; => {"two"=>2}
100
+ (dissoc (dict :one 1 :two 2) :one) ;; => {"two" 2}
101
+ ```
96
102
 
97
- ; --- More complex functions ---
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)) ;; => [2, 3, 4]
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)) ;; => [1, 3]
121
+ (filter odd? (list 1 2 3)) ;; => (1 3)
122
+ ```
115
123
 
116
- ; --- Variables ---
124
+ ## Variables
117
125
 
126
+ ```lisp
118
127
  ; Define and evaluate a variable
119
128
  (def x (list 1 2 3))
120
- x ;; => [1, 2, 3]
129
+ x ;; => (1 2 3)
121
130
 
122
131
  ; Use it in a function
123
132
  (sum x) ;; => 6
133
+ ```
124
134
 
125
- --- Misc ---
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
- ; --- Creating functions ---
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 yourself
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
- ; --- Count the amount of 5:s in a list ---
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
- ; --- Print a beautiful pyramid ---
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
- ; --- Interoperability ---
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 very simple programming language implemented in Ruby.
3
+ A Lisp implementation in Ruby.
4
4
 
5
- It is just a toy Lisp implementation and should not be used seriously by anyone.
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
- - hash-map
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
- - `hash-map`
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
- :"hash-map" => -> (*args) { Hash[*args] },
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
@@ -0,0 +1,3 @@
1
+ module Lasp
2
+ SyntaxError = Class.new(StandardError)
3
+ end
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 then def_special_form(tail, env)
20
- when :fn then fn_special_form(tail, env)
21
- when :do then do_special_form(tail, env)
22
- when :if then if_special_form(tail, env)
23
- when Proc then head.(*tail)
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
- fn.(*args.map { |form| Lasp::eval(form, env) })
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
- -> (*args) {
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,8 @@
1
+ require "lasp/fn"
2
+
3
+ module Lasp
4
+ class Macro < Fn
5
+ # All that's needed is the type marker to let eval
6
+ # know to pass in the arguments unevaluated.
7
+ end
8
+ end
@@ -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
@@ -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
- module_function
2
+ class Parser
3
+ def parse(program)
4
+ build_ast(tokenize(sanitize(program)))
5
+ end
3
6
 
4
- def parse(program)
5
- build_ast(tokenize(sanitize(program)))
6
- end
7
+ def tokenize(string)
8
+ string.scan(/(?:(?:[^\s"()']|"[^"]*")+|[()'])/)
9
+ end
7
10
 
8
- def build_ast(tokens)
9
- return if tokens.empty?
10
- token = tokens.shift
11
+ private
11
12
 
12
- if token == "("
13
- form = []
14
- while tokens.first != ")"
15
- form << build_ast(tokens)
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
- def tokenize(string)
25
- string
26
- .gsub("(", " ( ")
27
- .gsub(")", " ) ")
28
- .scan(/(?:[^\s"]|"[^"]*")+/)
29
- end
30
-
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
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
- def sanitize(string)
45
- string.gsub(/;.*$/, "")
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
- num_opens = input.chars.select { |c| c == "(" }.count
26
- num_closes = input.chars.select { |c| c == ")" }.count
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
@@ -0,0 +1,17 @@
1
+ class Array
2
+ def inspect
3
+ "(#{ map(&:inspect).join(" ") })"
4
+ end
5
+ end
6
+
7
+ class Symbol
8
+ def inspect
9
+ to_s
10
+ end
11
+ end
12
+
13
+ class Hash
14
+ def inspect
15
+ "{#{ map { |pair| pair.map(&:inspect).join(" ")}.join(", ") }}"
16
+ end
17
+ end
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
- (= nil (head coll))))
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 (= nil (head coll))
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
- (pipe ((head fns) item) (tail fns)))))
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 (list str->list reverse list->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
@@ -1,3 +1,3 @@
1
1
  module Lasp
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
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(Lasp::parse(program), env)
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.7.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-01-18 00:00:00.000000000 Z
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: