rbl 0.0.5 → 0.0.6

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: 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