rbl 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f79f3d05ec02a277eb9cc2b52e66cfb0bc167df
4
- data.tar.gz: a1e2bd7b861f7bddd40b47092e508d542b78a89a
3
+ metadata.gz: 7e9f93f61446acfe3a434444b82dd743f9a1b4fc
4
+ data.tar.gz: f3d8e18c04933b302bfc2a54950f09b474815bf8
5
5
  SHA512:
6
- metadata.gz: e5517a472833aad9bd8670c0aceb5b7fac368bf5cc0c96a12d1eed9352d563bd6599fcdceb006e4d6565567dfc1bd1a761ed395758b5448b3285b28e7b82aa51
7
- data.tar.gz: ca25909f9d314a751ce37b51c5b82ca61e9cb26706bc68d60dddf7af896d8fb0c09ff3c2cbf07301397eae7cd1239024929c526b5a30225ff4b3a5b31b9ae6b3
6
+ metadata.gz: c8601be4ccdfc976d27a12a75b1215b2ea906b46418e6dfd4a85f281f37a5c7b367f076920e215d9b584900d75651b34fa7b6ee96668c84d8f60c68053faa876
7
+ data.tar.gz: 37c7beccfaae69fd0711941a2d0b274475df20ae8f1f3b36ae90f9910c62ca3cd0f60ca17930e17590a77dbe04afc3c3c93a521d9d12a4284ad765bf4594a7c7
data/README.md CHANGED
@@ -29,8 +29,9 @@ I think I was right. Check it out, everyone -- you can write your Ruby scripts i
29
29
  * Fire up a REPL or run it in a script.
30
30
  * Immutable linked lists, vectors, and hashes courtesy of the [Hamster][hamster]
31
31
  library.
32
- * _(TODO)_ Macros, Clojure-style.
33
- * _(TODO)_ Namespaces, Clojure-style.
32
+ * Clojure-style atoms, courtesy of [concurrent-ruby][concurrent].
33
+ * _(TODO)_ Clojure-style macros.
34
+ * _(TODO)_ Clojure-style namespaces.
34
35
  * _(TODO)_ Dependency management / the ability to use some sort of build tool to
35
36
  include Ruby libraries and use them via inter-op.
36
37
 
@@ -158,18 +159,18 @@ user> (:species barbara)
158
159
  But that's not all -- you can also use keywords to get the value of an instance variable:
159
160
 
160
161
  ```clojure
161
- ; FIXME: This is a contrived example because it is not yet possible to easily
162
- define a class in RubyLisp.
162
+ ;; FIXME: This is a contrived example because it is not yet possible to easily
163
+ ;; define a class in RubyLisp.
163
164
 
164
- ; rbl.core/=@ is provided as a convenient way to set instance variables on any
165
- object... even a string!
165
+ ;; rbl.core/=@ is provided as a convenient way to set instance variables on any
166
+ ;; object... even a string!
166
167
  user> (def s "my string")
167
168
  "my string"
168
169
 
169
170
  user> (=@ s :object_level 9001)
170
171
  9001
171
172
 
172
- ; instance variables can then be retrieved by using a keyword as a function
173
+ ;; instance variables can then be retrieved by using a keyword as a function
173
174
  user> (:object_level s)
174
175
  9001
175
176
  ```
@@ -271,3 +272,4 @@ The gem is available as open source under the terms of the [MIT License](http://
271
272
  [ruby]: http://ruby-lang.org
272
273
  [mal]: https://github.com/kanaka/mal
273
274
  [hamster]: https://github.com/hamstergem/hamster
275
+ [concurrent]: https://github.com/ruby-concurrency/concurrent-ruby
data/bin/rbl CHANGED
@@ -13,13 +13,13 @@ USAGE = <<~HEREDOC
13
13
  #{File.basename $0} <filename>
14
14
  HEREDOC
15
15
 
16
- case ARGV.length
17
- when 0
16
+ if ARGV.empty?
18
17
  RubyLisp::REPL::start
19
- when 1
18
+ elsif ARGV[0] == '--help'
19
+ puts USAGE
20
+ else
20
21
  file_contents = File.read ARGV[0]
22
+ ARGV.shift
21
23
  input = file_contents.gsub(/\A#!.*\n/, '')
22
24
  puts RubyLisp::Parser.parse(input)
23
- else
24
- puts USAGE
25
25
  end
@@ -2,14 +2,28 @@ require 'rubylisp/parser'
2
2
 
3
3
  module RubyLisp
4
4
  class Environment
5
- attr_accessor :namespace, :vars, :outer
5
+ attr_accessor :namespace, :vars, :outer, :is_namespace, :out_env
6
6
 
7
- def initialize(outer: nil, namespace: nil)
7
+ def initialize(outer: nil, namespace: nil, is_namespace: false, out_env: nil)
8
8
  @vars = {'*ns*' => (outer or self)}
9
9
  @outer = outer
10
10
  @namespace = namespace or
11
11
  (outer.namespace if outer) or
12
12
  "__ns_#{rand 10000}"
13
+ @is_namespace = is_namespace
14
+ @out_env = if out_env
15
+ out_env
16
+ else
17
+ find_namespace
18
+ end
19
+ end
20
+
21
+ def find_namespace
22
+ env = self
23
+ while !env.is_namespace && !env.outer.nil?
24
+ env = env.outer
25
+ end
26
+ env
13
27
  end
14
28
 
15
29
  def set key, val
@@ -31,7 +45,7 @@ module RubyLisp
31
45
  if env
32
46
  env.vars[key]
33
47
  else
34
- raise RubyLisp::RuntimeError, "Unable to resolve symbol: #{key}"
48
+ raise RuntimeError, "Unable to resolve symbol: #{key}"
35
49
  end
36
50
  end
37
51
 
@@ -55,8 +69,8 @@ module RubyLisp
55
69
  root = File.expand_path '../..', File.dirname(__FILE__)
56
70
  input = File.read "#{root}/#{path}"
57
71
 
58
- namespace = RubyLisp::Environment.new
59
- RubyLisp::Parser.parse input, namespace
72
+ namespace = Environment.new
73
+ Parser.parse input, namespace
60
74
  namespace
61
75
  end
62
76
 
@@ -1,226 +1,257 @@
1
1
  require 'rubylisp/environment'
2
2
  require 'rubylisp/printer'
3
3
  require 'rubylisp/types'
4
+ require 'rubylisp/util'
5
+
6
+ include RubyLisp::Util
4
7
 
5
8
  module RubyLisp
6
9
  module Evaluator
7
10
  module_function
8
11
 
9
- def assert_number_of_args sexp, num_args
10
- fn, *args = sexp.value
11
- fn_name = fn.value
12
- unless args.count == num_args
13
- raise RubyLisp::RuntimeError,
14
- "Wrong number of arguments passed to `#{fn_name}`; " +
15
- "got #{args.count}, expected #{num_args}."
16
- end
12
+ def macro_call? ast, env
13
+ return false unless ast.is_a? Hamster::List
14
+ symbol = ast[0]
15
+ return false unless symbol.class == Symbol
16
+ return false unless env.find(symbol.value)
17
+ value = symbol.resolve(env)
18
+ value.class == Function && value.is_macro
17
19
  end
18
20
 
19
- def assert_at_least_n_args sexp, num_args
20
- fn, *args = sexp.value
21
- fn_name = fn.value
22
- unless args.count >= num_args
23
- raise RubyLisp::RuntimeError,
24
- "Wrong number of arguments passed to `#{fn_name}`; " +
25
- "got #{args.count}, expected at least #{num_args}."
21
+ def macroexpand input, env
22
+ while macro_call? input, env
23
+ macro = input[0].resolve(env)
24
+ input = macro.call(*input[1..-1])
26
25
  end
26
+ input
27
27
  end
28
28
 
29
- def assert_arg_type sexp, arg_number, arg_type
30
- fn = sexp.value[0]
31
- fn_name = fn.value
32
- arg = if arg_number == 'last'
33
- sexp.value.last
34
- else
35
- sexp.value[arg_number]
36
- end
29
+ def pair? form
30
+ sequential?(form) && form.count > 0
31
+ end
37
32
 
38
- arg_description = if arg_number == 'last'
39
- 'The last argument'
40
- else
41
- "Argument ##{arg_number}"
42
- end
43
-
44
- arg_types = if arg_type.class == Array
45
- arg_type
46
- else
47
- [arg_type]
48
- end
49
-
50
- expected = case arg_types.count
51
- when 1
52
- arg_types.first
53
- when 2
54
- arg_types.join(' or ')
55
- else
56
- last_arg_type = arg_types.pop
57
- arg_types.join(', ') + " or #{last_arg_type}"
58
- end
59
-
60
- if arg_types.none? {|type| arg.is_a? type}
61
- raise RubyLisp::RuntimeError,
62
- "#{arg_description} to `#{fn_name}` must be a " +
63
- "#{expected}; got a #{arg.class}."
33
+ def quasiquote ast
34
+ if !pair?(ast)
35
+ list [Symbol.new('quote'), ast]
36
+ elsif vector? ast
37
+ list [Symbol.new('vec'), quasiquote(ast.to_list)]
38
+ else
39
+ elem_a, *elems = ast
40
+
41
+ if elem_a.class == Symbol && elem_a.value == 'unquote'
42
+ elems[0]
43
+ elsif pair?(elem_a) &&
44
+ elem_a[0].class == Symbol &&
45
+ elem_a[0].value == 'splice-unquote'
46
+ list [Symbol.new('concat'), elem_a[1], quasiquote(elems)]
47
+ else
48
+ list [Symbol.new('cons'),
49
+ quasiquote(elem_a),
50
+ quasiquote(elems.to_list)]
51
+ end
64
52
  end
65
53
  end
66
54
 
55
+ def special_form? input, name
56
+ input[0].class == Symbol && input[0].value == name
57
+ end
58
+
67
59
  def eval_ast input, env
68
- case input
69
- when Array # of ASTs to be evaluated, e.g. multiple expressions in a file
70
- input.compact.map {|form| eval_ast(form, env)}.last
71
- when RubyLisp::HashMap
72
- input.value.map {|k, v| [eval_ast(k, env), eval_ast(v, env)]}
73
- when RubyLisp::List
74
- if input.value.empty?
75
- input.value
76
- elsif input.value[0].value == 'apply'
77
- coll = eval_ast(input.value[-1], env) || Hamster::Vector[]
78
-
79
- # evaluate the last argument
80
- sexp = RubyLisp::List.new(input.value.fill(coll, -1, 1))
81
- # assert that it's enumerable
82
- assert_arg_type sexp, 'last', Enumerable
83
-
84
- # drop `apply` and pull the last form's contents into the sexp as
85
- # multiple arguments
86
- all_but_last_arg = sexp.value[1..-2].map {|arg| eval_ast arg, env}
87
- fn, *args = all_but_last_arg + coll.to_list
88
- sexp = RubyLisp::List.new([RubyLisp::Symbol.new("apply"), fn])
89
- assert_arg_type sexp, 1, Proc
90
- fn.call(*args)
91
- elsif input.value[0].value == 'def'
92
- assert_arg_type input, 1, RubyLisp::Symbol
93
- key, val = input.value[1..-1]
94
- env.set key.value, eval_ast(val, env)
95
- elsif input.value[0].value == 'do'
96
- body = input.value[1..-1]
97
- body.map {|form| eval_ast(form, env)}.last
98
- elsif input.value[0].value == 'fn'
99
- if input.value[1].class == RubyLisp::Symbol
100
- fn_name = input.value[1].value
101
- bindings, *body = input.value[2..-1]
102
- else
103
- fn_name = "__fn_#{rand 10000}"
104
- bindings, *body = input.value[1..-1]
105
- end
60
+ # loop forever until a value is returned;
61
+ # this is for tail call optimization
62
+ while true
63
+ # (no-op if this is not a macro call)
64
+ input = macroexpand(input, env)
106
65
 
107
- unless bindings.class == RubyLisp::Vector
108
- raise RubyLisp::RuntimeError,
109
- "The bindings of `fn` must be a RubyLisp::Vector."
110
- end
66
+ case input
67
+ when Array # of ASTs to be evaluated, e.g. multiple expressions in a file
68
+ # discard nils that can be produced by the reader, e.g. when a comment
69
+ # is read
70
+ input = input.compact
111
71
 
112
- ampersand_indices = bindings.value.to_list.indices {|x| x.value == '&'}
113
- if ampersand_indices.any? {|i| i != bindings.value.count - 2}
114
- raise RubyLisp::RuntimeError,
115
- "An '&' can only occur right before the last binding."
116
- end
72
+ # if input is empty (e.g. only comments), there is nothing to evaluate
73
+ return nil if input.empty?
74
+
75
+ # discard the return value of all but the last form
76
+ input[0..-2].each {|form| eval_ast(form, env)}
77
+ # recur; evaluate and return the value of the last form
78
+ input = input.last
79
+ when Hamster::Hash
80
+ return input.map {|k, v| [eval_ast(k, env), eval_ast(v, env)]}
81
+ when Hamster::List
82
+ if input.empty?
83
+ return input
84
+ elsif special_form? input, 'apply'
85
+ # evaluate the last argument
86
+ coll = eval_ast(input[-1], env) || vector()
87
+ # assert that it's enumerable
88
+ sexp = list input.fill(coll, -1, 1)
89
+ assert_arg_type sexp, 'last', Enumerable
90
+
91
+ # drop `apply` and pull the last form's contents into the sexp as
92
+ # multiple arguments
93
+ all_but_last_arg = sexp[1..-2].map {|arg| eval_ast arg, env}
94
+ fn, *args = all_but_last_arg + coll.to_list
95
+ sexp = list [Symbol.new("apply"), fn]
96
+ assert_arg_type sexp, 1, [Function, Proc]
97
+ return fn.call(*args)
98
+ # Provides a way to call a Ruby method that expects a block. Blocks
99
+ # are not first-class in Ruby, but they have similar semantics to
100
+ # functions.
101
+ #
102
+ # The last argument to call-with-block can be a Function or
103
+ # Proc that takes any number of arguments; it will be provided to the
104
+ # instance method as a block.
105
+ #
106
+ # ruby: instance.method(arg1, arg2, arg3) { do_something }
107
+ # rbl: (call-with-block .method instance [arg1 arg2 arg3] fn)
108
+ elsif special_form? input, 'call-with-block'
109
+ assert_number_of_args input, 4
117
110
 
118
- fn = lambda do |*args|
119
- inner_env = RubyLisp::Environment.new(outer: env)
120
- inner_env.set(fn_name, fn) # self-referential lambda omg
111
+ assert_arg_type input, 1, Symbol
121
112
 
122
- sexp = RubyLisp::List.new([RubyLisp::Symbol.new(fn_name), *args])
123
- if bindings.value.any? {|binding| binding.value == '&'}
124
- required_args = bindings.value.count - 2
125
- assert_at_least_n_args sexp, required_args
113
+ sexp = input[2..-1].map {|form| eval_ast(form, env)}
114
+ .cons(input[1].value[1..-1].to_sym)
115
+ .cons(input[0])
126
116
 
127
- bindings.value[0..-3].zip(args.take(required_args)).each do |k, v|
128
- inner_env.set(k.value, v)
129
- end
117
+ assert_arg_type sexp, 3, [Hamster::List, Hamster::Vector]
118
+ assert_arg_type sexp, 4, [Function, Proc]
130
119
 
131
- rest_args = if args.count > required_args
132
- args[required_args..-1].to_list
133
- else
134
- nil
135
- end
120
+ method, receiver, method_args, fn = sexp[1..-1]
136
121
 
137
- inner_env.set(bindings.value[-1].value, rest_args)
122
+ return receiver.send(method, *method_args) do |*block_args|
123
+ fn.call(*block_args)
124
+ end
125
+ elsif special_form? input, 'def'
126
+ assert_arg_type input, 1, Symbol
127
+ k, v = input[1..-1]
128
+ key, val = [k.value, eval_ast(v, env)]
129
+ return env.out_env.set key, val
130
+ elsif special_form? input, 'defmacro*'
131
+ assert_arg_type input, 1, Symbol
132
+ key, val = input[1..-1]
133
+ macro = eval_ast(val, env)
134
+ macro.is_macro = true
135
+ return env.set key.value, macro
136
+ elsif special_form? input, 'do'
137
+ body = input[1..-1]
138
+ # discard the return values of all but the last form
139
+ body[0..-2].each {|form| eval_ast(form, env)}
140
+ # recur; evaluate and return the last form
141
+ input = body.last
142
+ elsif special_form? input, 'eval'
143
+ assert_number_of_args input, 1
144
+
145
+ form = eval_ast(input[1], env)
146
+ input = form
147
+ elsif special_form? input, 'fn'
148
+ if input[1].class == Symbol
149
+ fn_name = input[1].value
150
+ more = input[2..-1]
138
151
  else
139
- required_args = bindings.value.count
140
- assert_number_of_args sexp, required_args
152
+ fn_name = "__fn_#{rand 10000}"
153
+ more = input[1..-1]
154
+ end
141
155
 
142
- bindings.value.zip(args).each do |k, v|
143
- inner_env.set(k.value, v)
144
- end
156
+ arities = more[0].class == Hamster::Vector ? [list(more)] : more
157
+
158
+ return fn = Function.new(fn_name, env, arities) {|*fn_args|
159
+ arity = fn.get_arity(fn_args)
160
+ eval_ast arity.body, fn.gen_env(arity, fn_args, env)
161
+ }
162
+ elsif special_form? input, 'if'
163
+ unless input[1..-1].count > 1
164
+ raise RuntimeError,
165
+ "An `if` form must at least have a 'then' branch."
145
166
  end
146
167
 
147
- body.map {|form| eval_ast(form, inner_env)}.last
148
- end
149
- elsif input.value[0].value == 'if'
150
- cond, then_form, else_form = input.value[1..-1]
151
- unless then_form
152
- raise RubyLisp::RuntimeError,
153
- "An `if` form must at least have a 'then' branch."
154
- end
168
+ cond, then_form, else_form = input[1..-1]
155
169
 
156
- if (eval_ast cond, env)
157
- eval_ast(then_form, env)
158
- elsif else_form
159
- eval_ast(else_form, env)
160
- end
161
- elsif input.value[0].value == 'in-ns'
162
- assert_number_of_args input, 1
163
- input.value[1] = eval_ast(input.value[1], env)
164
- assert_arg_type input, 1, RubyLisp::Symbol
165
- ns_name = input.value[1].to_s
166
- # TODO: register ns and switch to it
167
- env.namespace = ns_name
168
- elsif input.value[0].value == 'let'
169
- assert_arg_type input, 1, RubyLisp::Vector
170
- inner_env = RubyLisp::Environment.new(outer: env)
171
- bindings, *body = input.value[1..-1]
172
- unless bindings.value.count.even?
173
- raise RubyLisp::RuntimeError,
174
- "The bindings vector of `let` must contain an even number " +
175
- " of forms."
176
- end
170
+ if (eval_ast cond, env)
171
+ input = then_form
172
+ elsif else_form
173
+ input = else_form
174
+ else
175
+ return nil
176
+ end
177
+ elsif special_form? input, 'in-ns'
178
+ assert_number_of_args input, 1
179
+ input[1] = eval_ast(input[1], env)
180
+ assert_arg_type input, 1, Symbol
181
+ ns_name = input[1].to_s
182
+ # TODO: register ns and switch to it
183
+ env.is_namespace = true
184
+ return env.namespace = ns_name
185
+ elsif special_form? input, 'let'
186
+ assert_arg_type input, 1, Hamster::Vector
187
+ inner_env = Environment.new(outer: env)
188
+ bindings, *body = input[1..-1]
189
+ unless bindings.count.even?
190
+ raise RuntimeError,
191
+ "The bindings vector of `let` must contain an even number " +
192
+ " of forms."
193
+ end
177
194
 
178
- bindings.value.each_slice(2) do |(k, v)|
179
- inner_env.set k.value, eval_ast(v, inner_env)
180
- end
195
+ bindings.each_slice(2) do |(binding, value)|
196
+ inner_env.set binding.value, eval_ast(value, inner_env)
197
+ end
181
198
 
182
- body.map {|form| eval_ast(form, inner_env)}.last
183
- elsif input.value[0].value == 'ns'
184
- # ns will be defined more robustly in rbl.core, but rbl.core also
185
- # needs the `ns` form in order to declare that it is rbl.core.
186
- #
187
- # defining it here in a minimal form where it is equivalent to in-ns,
188
- # except that you don't have to quote the namespace name
189
- assert_number_of_args input, 1
190
- assert_arg_type input, 1, RubyLisp::Symbol
191
- ns_name = input.value[1].to_s
192
- # TODO: register ns and switch to it
193
- env.namespace = ns_name
194
- elsif input.value[0].value == 'quote'
195
- assert_number_of_args input, 1
196
- fn, *args = input.value
197
- quoted_form = args[0]
198
-
199
- if [RubyLisp::HashMap, RubyLisp::List, RubyLisp::Vector].member? quoted_form.class
200
- quoted_form.quote.value
199
+ # discard the return values of all but the last form
200
+ body[0..-2].each {|expr| eval_ast(expr, inner_env)}
201
+ # recur; evaluate and return the last form's value
202
+ env = inner_env
203
+ input = body.last
204
+ elsif special_form? input, 'macroexpand'
205
+ assert_number_of_args input, 1
206
+ form = eval_ast(input[1], env)
207
+ return macroexpand(form, env)
208
+ elsif special_form? input, 'ns'
209
+ # ns will be defined more robustly in rbl.core, but rbl.core also
210
+ # needs the `ns` form in order to declare that it is rbl.core.
211
+ #
212
+ # defining it here in a minimal form where it is equivalent to in-ns,
213
+ # except that you don't have to quote the namespace name
214
+ assert_number_of_args input, 1
215
+ assert_arg_type input, 1, Symbol
216
+ ns_name = input[1].to_s
217
+ # TODO: register ns and switch to it
218
+ env.is_namespace = true
219
+ return env.namespace = ns_name
220
+ elsif special_form? input, 'quasiquote'
221
+ assert_number_of_args input, 1
222
+ ast = input[1]
223
+ input = quasiquote(ast)
224
+ elsif special_form? input, 'quote'
225
+ assert_number_of_args input, 1
226
+ return input[1]
227
+ elsif special_form? input, 'resolve'
228
+ assert_number_of_args input, 1
229
+ symbol = eval_ast(input[1], env)
230
+ sexp = list input.fill(symbol, 1, 1)
231
+ assert_arg_type sexp, 1, Symbol
232
+ return symbol.resolve(env)
201
233
  else
202
- quoted_form.quote
234
+ fn, *args = input.map {|value| eval_ast(value, env)}
235
+ if fn.class == Function
236
+ # recur with input and env set to the body of the function and an
237
+ # inner environment where the function's bindings are set to the
238
+ # values of the arguments
239
+ arity = fn.get_arity(args)
240
+ env = fn.gen_env(arity, args, env)
241
+ input = arity.body
242
+ else
243
+ return fn.call(*args)
244
+ end
203
245
  end
204
- elsif input.value[0].value == 'resolve'
205
- assert_number_of_args input, 1
206
- symbol = eval_ast(input.value[1], env)
207
- sexp = RubyLisp::List.new(input.value.fill(symbol, 1, 1))
208
- assert_arg_type sexp, 1, RubyLisp::Symbol
209
- symbol.resolve(env)
210
- else
211
- fn, *args = input.value.map {|value| eval_ast(value, env)}
212
- fn.call(*args)
213
- end
214
- when RubyLisp::Symbol
215
- if input.quoted
216
- input
246
+ when Hamster::Vector
247
+ return input.map {|value| eval_ast(value, env)}
248
+ when Symbol
249
+ return input.resolve(env)
250
+ when Value
251
+ return input.value
217
252
  else
218
- input.resolve(env)
253
+ return input
219
254
  end
220
- when RubyLisp::Vector
221
- input.value.map {|value| eval_ast(value, env)}
222
- else
223
- input.value
224
255
  end
225
256
  end
226
257
  end