carbonate 0.1

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.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'carbonate'
5
+
6
+ require 'pry'
7
+ Pry.start
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,34 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'carbonate/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'carbonate'
7
+ spec.version = Carbonate::VERSION
8
+ spec.authors = ['Vsevolod Romashov']
9
+ spec.email = ['7@7vn.ru']
10
+
11
+ spec.summary = %q{Clojure-inspired Lisp that compiles to Ruby}
12
+ spec.description = %q{A Clojure-inspired Lisp dialect that aims to mirror full Ruby functionality. Includes a transpiler that can produce equivalent Ruby code or eval it right away.}
13
+ spec.homepage = 'https://github.com/7even/carbonate'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
17
+ spec.bindir = 'exe'
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'rly'
22
+ spec.add_dependency 'unparser'
23
+
24
+ spec.add_development_dependency 'bundler', '~> 1.10'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'rspec'
27
+
28
+ spec.add_development_dependency 'guard-rspec'
29
+ spec.add_development_dependency 'rb-fsevent'
30
+ spec.add_development_dependency 'terminal-notifier'
31
+ spec.add_development_dependency 'terminal-notifier-guard'
32
+
33
+ spec.add_development_dependency 'awesome_pry'
34
+ end
@@ -0,0 +1,10 @@
1
+ (defclass User
2
+ (@attr-reader :first-name :last-name :age)
3
+ (defmethod initialize [first-name last-name age]
4
+ (def @first-name first-name)
5
+ (def @last-name last-name)
6
+ (def @age age))
7
+ (defmethod full-name []
8
+ (join [@first-name @last-name]))
9
+ (defmethod age []
10
+ (to-i @age)))
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'optparse'
5
+ require 'pathname'
6
+ require 'carbonate'
7
+
8
+ options = {}
9
+
10
+ OptionParser.new do |opts|
11
+ opts.banner = <<-BANNER
12
+ Transpiles a Carbonate source file to a Ruby source file.
13
+ Usage: crb2rb -i source.crb -o target.rb
14
+ | crb2rb < source.crb > target.rb
15
+ BANNER
16
+
17
+ opts.on '-i FILENAME', '--input', 'Input file (optional, defaults to STDIN)' do |filename|
18
+ options[:input] = Pathname.new(filename)
19
+ end
20
+
21
+ opts.on '-o FILENAME', '--output', 'Output file (optional, defaults to STDOUT)' do |filename|
22
+ options[:output] = Pathname.new(filename)
23
+ end
24
+
25
+ opts.on '-f', '--force', 'Silently overwrite existing files' do
26
+ options[:overwrite] = true
27
+ end
28
+
29
+ opts.on('-h', '--help', 'Print this help') do
30
+ puts opts
31
+ exit
32
+ end
33
+ end.parse!
34
+
35
+ if options.key?(:input)
36
+ if options[:input].exist?
37
+ source = options[:input].read
38
+ else
39
+ puts "Input file #{options[:input]} doesn't exist!"
40
+ exit 127
41
+ end
42
+ else
43
+ source = STDIN.read
44
+ end
45
+
46
+ begin
47
+ result = Carbonate.process(source)
48
+ rescue Carbonate::Parser::FormatError => e
49
+ STDERR.puts e.message
50
+ exit 1
51
+ end
52
+
53
+ if options.key?(:output)
54
+ if options[:overwrite] || !options[:output].exist?
55
+ options[:output].write(result)
56
+ else
57
+ puts "File #{options[:output]} already exists!"
58
+ exit 127
59
+ end
60
+ else
61
+ STDOUT.write(result)
62
+ end
@@ -0,0 +1,57 @@
1
+ require 'carbonate/parser'
2
+ require 'unparser'
3
+ require 'carbonate/version'
4
+
5
+ module Carbonate
6
+ class << self
7
+ def process(source)
8
+ ast = Parser.new.parse(source)
9
+ with_trailing_newline(Unparser.unparse(ast))
10
+ end
11
+
12
+ def require(filename)
13
+ absolute_path = find_required_file(filename)
14
+ fail LoadError, "cannot load such file -- #{filename}" if absolute_path.nil?
15
+ return false if $LOADED_FEATURES.include?(absolute_path.to_s)
16
+
17
+ ruby_code = process(absolute_path.read)
18
+ Object.class_eval(ruby_code)
19
+
20
+ $LOADED_FEATURES.push(absolute_path.to_s)
21
+ true
22
+ end
23
+
24
+ def require_relative(relative_path)
25
+ dir = Pathname.new(caller_locations(1, 1).first.path).dirname
26
+ absolute_path = dir + relative_path
27
+
28
+ require(absolute_path.to_s)
29
+ end
30
+
31
+ private
32
+ def with_trailing_newline(text)
33
+ if text.end_with?(?\n)
34
+ text
35
+ else
36
+ text + ?\n
37
+ end
38
+ end
39
+
40
+ def find_required_file(filename)
41
+ ([Dir.pwd] + $LOAD_PATH).each do |path|
42
+ absolute_path = Pathname.new(path).join(append_extension(filename.to_s))
43
+ return absolute_path if absolute_path.exist?
44
+ end
45
+
46
+ nil
47
+ end
48
+
49
+ def append_extension(filename)
50
+ if filename.end_with?('.crb')
51
+ filename
52
+ else
53
+ "#{filename}.crb"
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,749 @@
1
+ require 'rly'
2
+ require 'ast'
3
+ require 'parser/ast/node'
4
+
5
+ module Carbonate
6
+ class Parser < Rly::Yacc
7
+ def without_spaces(elements)
8
+ elements.reject do |element|
9
+ element.type == :S
10
+ end
11
+ end
12
+
13
+ def wrap_in_begin(nodes)
14
+ if nodes.one?
15
+ nodes.first
16
+ else
17
+ s(:begin, nodes)
18
+ end
19
+ end
20
+
21
+ def destructure_arguments(arguments)
22
+ if arguments[0..-2].any? { |arg| [:pseudo_block, :block_pass].include?(arg.type) }
23
+ raise FormatError, 'You can specify only one block per method call (as the last argument)'
24
+ end
25
+
26
+ if !arguments.empty? && arguments.last.type == :pseudo_block
27
+ [arguments[0..-2], arguments.last]
28
+ else
29
+ [arguments, nil]
30
+ end
31
+ end
32
+
33
+ def s(type, children)
34
+ self.class.s(type, children)
35
+ end
36
+
37
+ class << self
38
+ def s(type, children = [])
39
+ ::Parser::AST::Node.new(type, children)
40
+ end
41
+
42
+ def identifier(value)
43
+ value.gsub('-', '_').to_sym
44
+ end
45
+
46
+ def handle_string(string, range)
47
+ replacements = {
48
+ '\\n' => "\n",
49
+ '\\r' => "\r",
50
+ '\\t' => "\t",
51
+ '\\"' => '"'
52
+ }
53
+ string[range].gsub(/\\[nrt"]/, replacements)
54
+ end
55
+ end
56
+
57
+ lexer do
58
+ literals '+-*/%=<>&|^~!()[]{}#@.'
59
+
60
+ token :FLOAT, /-?\d+\.\d+/ do |t|
61
+ t.value = Parser.s(:float, [t.value.to_f])
62
+ t
63
+ end
64
+
65
+ token :INTEGER, /-?\d+/ do |t|
66
+ t.value = Parser.s(:int, [t.value.to_i])
67
+ t
68
+ end
69
+
70
+ token :SYMBOL, /:([A-Za-z0-9_-]+[!?=]?|[+\-*\/])/ do |t|
71
+ t.value = Parser.s(:sym, [Parser.identifier(t.value[1..-1])])
72
+ t
73
+ end
74
+
75
+ token :REGEXP, /#"([^"\\]|\\[nrt"])*"/ do |t|
76
+ t.value = Parser.s(:regexp,
77
+ [
78
+ Parser.s(:str, [Parser.handle_string(t.value, 2..-2)]),
79
+ Parser.s(:regopt)
80
+ ]
81
+ )
82
+ t
83
+ end
84
+
85
+ token :STRING, /"([^"\\]|\\[nrt"])*"/ do |t|
86
+ t.value = Parser.s(:str, [Parser.handle_string(t.value, 1..-2)])
87
+ t
88
+ end
89
+
90
+ token :TRUE, /true/ do |t|
91
+ t.value = Parser.s(:true)
92
+ t
93
+ end
94
+
95
+ token :FALSE, /false/ do |t|
96
+ t.value = Parser.s(:false)
97
+ t
98
+ end
99
+
100
+ # nil (without special characters at the end)
101
+ token :NIL, /nil(?![!?=])/
102
+
103
+ # characters treated as whitespace
104
+ token :S, /[\s,]+/ do |t|
105
+ t.lexer.lineno += t.value.count("\n")
106
+ t
107
+ end
108
+
109
+ # keywords
110
+ token :DEFCLASS, /defclass/
111
+ token :DEFMODULE, /defmodule/
112
+ token :DEFMETHOD, /defmethod/
113
+ token :DEF_OR, /def-or/
114
+ token :DEF, /def/
115
+ token :RETURN, /return/
116
+ token :SUPER, /super/
117
+ token :ZSUPER, /zsuper/
118
+ token :TRY, /try/
119
+ token :RESCUE, /rescue/
120
+ token :ENSURE, /ensure/
121
+
122
+ token :DO, /do/
123
+ token :IF, /if/
124
+ token :UNLESS, /unless/
125
+ token :CASE, /case/
126
+ token :WHILE, /while/
127
+ token :UNTIL, /until/
128
+ token :AND, /and/
129
+ token :OR, /or/
130
+
131
+ token :CONST, /\.?([A-Z][A-Za-z0-9_-]+\.)*[A-Z][A-Za-z0-9_-]+/ do |t|
132
+ parts = t.value.split('.').reject(&:empty?)
133
+ top_namespace = Parser.s(:cbase) if t.value.start_with?('.')
134
+
135
+ t.value = parts.inject(top_namespace) do |namespace, part|
136
+ Parser.s(:const, [namespace, Parser.identifier(part)])
137
+ end
138
+ t
139
+ end
140
+
141
+ # method name with '!', '?' or '=' at the end
142
+ token :METHOD_NAME, /[a-z][A-Za-z0-9_-]*[!?=]/ do |t|
143
+ t.value = Parser.identifier(t.value)
144
+ t
145
+ end
146
+
147
+ # local variable or method name without special chars at the end
148
+ token :LVAR, /[a-z][A-Za-z0-9_-]*/ do |t|
149
+ t.value = Parser.identifier(t.value)
150
+ t
151
+ end
152
+
153
+ # method name with '!', '?' or '=' at the end, called without an explicit receiver
154
+ token :SELF_METHOD_NAME, /@[a-z][A-Za-z0-9_-]*[!?=]/ do |t|
155
+ t.value = Parser.identifier(t.value)
156
+ t
157
+ end
158
+
159
+ # instance variable or method name without special chars, called without an explicit receiver
160
+ token :IVAR, /@[a-z][A-Za-z0-9_-]*/ do |t|
161
+ t.value = Parser.identifier(t.value)
162
+ t
163
+ end
164
+
165
+ on_error do |t|
166
+ fail FormatError, "Unknown character '#{t.value}' at line #{t.lexer.lineno.succ}"
167
+ end
168
+ end
169
+
170
+ # outermost scope
171
+ rule 'source : forms S | forms' do |source, forms|
172
+ source.value = wrap_in_begin(forms.value)
173
+ end
174
+
175
+ # multiple forms
176
+ rule 'forms : forms S form | form' do |forms, *forms_array|
177
+ forms.value = without_spaces(forms_array).flat_map(&:value)
178
+ end
179
+
180
+ # form can be a local variable
181
+ # user
182
+ rule 'form : LVAR' do |form, lvar|
183
+ form.value = s(:lvar, [lvar.value])
184
+ end
185
+
186
+ # form can be an instance variable
187
+ # @user
188
+ rule 'form : IVAR' do |form, ivar|
189
+ form.value = s(:ivar, [ivar.value])
190
+ end
191
+
192
+ # form can also be an S-expression, a literal value, a constant, self or a collection of forms
193
+ rule 'form : INTEGER
194
+ | FLOAT
195
+ | STRING
196
+ | SYMBOL
197
+ | REGEXP
198
+ | TRUE
199
+ | FALSE
200
+ | nil
201
+ | array
202
+ | hash
203
+ | set
204
+ | irange
205
+ | erange
206
+ | CONST
207
+ | self
208
+ | sexp' do |form, element|
209
+ form.value = element.value
210
+ end
211
+
212
+ # array
213
+ # [1 2 3]
214
+ rule 'array : "[" forms "]"' do |array, _, sexps, _|
215
+ array.value = s(:array, sexps.value)
216
+ end
217
+
218
+ # empty array
219
+ # []
220
+ rule 'array : "[" "]"' do |array, _, _|
221
+ array.value = s(:array, [])
222
+ end
223
+
224
+ # hash
225
+ # {:first-name "Rich", :last-name "Hickey"}
226
+ # commas are optional, elements count must be even
227
+ rule 'hash : "{" forms "}"' do |hash, _, forms, _|
228
+ raise FormatError.new('Odd number of elements in a hash') if forms.value.count.odd?
229
+
230
+ pairs = forms.value.each_slice(2).map { |pair| s(:pair, pair) }
231
+ hash.value = s(:hash, pairs)
232
+ end
233
+
234
+ # empty hash
235
+ # {}
236
+ rule 'hash : "{" "}"' do |hash, _, _|
237
+ hash.value = s(:hash, [])
238
+ end
239
+
240
+ # set
241
+ # #{123 "string" :symbol}
242
+ rule 'set : "#" "{" forms "}"' do |set, _, _, forms, _|
243
+ set.value = s(:send,
244
+ [
245
+ s(:const, [nil, :Set]),
246
+ :new,
247
+ s(:array, forms.value)
248
+ ]
249
+ )
250
+ end
251
+
252
+ # inclusive range
253
+ # 1..10
254
+ rule 'irange : form "." "." form' do |range, first, _, _, last|
255
+ range.value = s(:irange, [first.value, last.value])
256
+ end
257
+
258
+ # exclusive range
259
+ # 1...11
260
+ rule 'erange : form "." "." "." form' do |range, first, _, _, _, last|
261
+ range.value = s(:erange, [first.value, last.value])
262
+ end
263
+
264
+ # self
265
+ # @
266
+ rule 'self : "@"' do |self_node, _|
267
+ self_node.value = s(:self, [])
268
+ end
269
+
270
+ # nil
271
+ # nil
272
+ rule 'nil : NIL' do |nil_node, _|
273
+ nil_node.value = s(:nil, [])
274
+ end
275
+
276
+ # multiple S-expressions
277
+ rule 'sexps : sexps S sexp | sexp' do |sexps, *sexps_array|
278
+ sexps.value = without_spaces(sexps_array).flat_map(&:value)
279
+ end
280
+
281
+ # do statement - combines several forms into one
282
+ # so you can fit several statements in a place for just one
283
+ rule 'sexp : "(" DO S forms ")"' do |sexp, _, _, _, forms, _|
284
+ sexp.value = s(:begin, forms.value)
285
+ end
286
+
287
+ # if statement
288
+ # can have only "then" clause:
289
+ # (if (valid? user) (save user))
290
+ # or both "then" and "else" clauses:
291
+ # (if (> a b) a b)
292
+ rule 'sexp : "(" IF S form S form ")"
293
+ | "(" IF S form S form S form ")"' do |sexp, _, _, _, condition, _, then_clause, _, else_clause, _|
294
+ sexp.value = s(:if, [condition.value, then_clause.value, else_clause && else_clause.value])
295
+ end
296
+
297
+ # unless statement
298
+ # (unless (persisted? user) (save user))
299
+ rule 'sexp : "(" UNLESS S form S form ")"' do |sexp, _, _, _, condition, _, then_clause, _|
300
+ sexp.value = s(:if, [condition.value, nil, then_clause.value])
301
+ end
302
+
303
+ # case statement
304
+ # (case x
305
+ # 1 "one"
306
+ # 2 "two")
307
+ # can have an "else" clause at the end:
308
+ # (case lang
309
+ # "clojure" "great!"
310
+ # "ruby" "cool"
311
+ # "crap")
312
+ rule 'sexp : "(" CASE S form S forms ")"' do |sexp, _, _, _, form, _, forms, _|
313
+ elements = if forms.value.count.even?
314
+ [*forms.value, nil]
315
+ else
316
+ forms.value
317
+ end
318
+
319
+ when_clauses = elements[0..-2].each_slice(2).map do |value, expr|
320
+ s(:when, [value, expr])
321
+ end
322
+
323
+ sexp.value = s(:case, [form.value, *when_clauses, elements.last])
324
+ end
325
+
326
+ # while loop
327
+ # (while (< x 5) (def x (+ x 1)))
328
+ rule 'sexp : "(" WHILE S form S form ")"' do |sexp, _, _, _, condition, _, body, _|
329
+ sexp.value = s(:while, [condition.value, body.value])
330
+ end
331
+
332
+ # until loop
333
+ # (until (>= x 5) (def x (+ x 1)))
334
+ rule 'sexp : "(" UNTIL S form S form ")"' do |sexp, _, _, _, condition, _, body, _|
335
+ sexp.value = s(:until, [condition.value, body.value])
336
+ end
337
+
338
+ # instance method call with an explicit receiver
339
+ # (+ 2 2)
340
+ rule 'sexp : "(" func S form ")"
341
+ | "(" func S form S arguments_list ")"' do |sexp, _, func, _, receiver, _, arguments_list, _|
342
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
343
+ send_node = s(:send, [receiver.value, func.value, *arguments])
344
+
345
+ sexp.value = if block.nil?
346
+ send_node
347
+ else
348
+ s(:block, [send_node, *block.children])
349
+ end
350
+ end
351
+
352
+ # class method call with an explicit receiver
353
+ # (User/find-by {:first-name "John"})
354
+ rule 'sexp : "(" CONST "/" func ")"
355
+ | "(" CONST "/" func S arguments_list ")"' do |sexp, _, const, _, func, _, arguments_list, _|
356
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
357
+ send_node = s(:send, [const.value, func.value, *arguments])
358
+
359
+ sexp.value = if block.nil?
360
+ send_node
361
+ else
362
+ s(:block, [send_node, *block.children])
363
+ end
364
+ end
365
+
366
+ # method call with an implicit receiver
367
+ # (@attr-reader :first-name)
368
+ rule 'sexp : "(" IVAR ")"
369
+ | "(" IVAR S arguments_list ")"
370
+ | "(" SELF_METHOD_NAME ")"
371
+ | "(" SELF_METHOD_NAME S arguments_list ")"' do |sexp, _, method_name, _, arguments_list, _|
372
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
373
+ send_node = s(:send, [nil, method_name.value[1..-1].to_sym, *arguments])
374
+
375
+ sexp.value = if block.nil?
376
+ send_node
377
+ else
378
+ s(:block, [send_node, *block.children])
379
+ end
380
+ end
381
+
382
+ # class constructor call
383
+ # (User. {:name "John"})
384
+ rule 'sexp : "(" CONST "." ")"
385
+ | "(" CONST "." S arguments_list ")"' do |sexp, _, const, _, _, arguments_list, _|
386
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
387
+ send_node = s(:send, [const.value, :new, *arguments])
388
+
389
+ sexp.value = if block.nil?
390
+ send_node
391
+ else
392
+ s(:block, [send_node, *block.children])
393
+ end
394
+ end
395
+
396
+ # call to super with explicit parameters
397
+ # (super)
398
+ # (super "argument")
399
+ rule 'sexp : "(" SUPER ")"
400
+ | "(" SUPER S arguments_list ")"' do |sexp, _, _, _, arguments_list, _|
401
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
402
+ send_node = s(:super, arguments)
403
+
404
+ sexp.value = if block.nil?
405
+ send_node
406
+ else
407
+ s(:block, [send_node, *block.children])
408
+ end
409
+ end
410
+
411
+ # call to super with implicit parameters
412
+ # (zsuper)
413
+ rule 'sexp : "(" ZSUPER ")"
414
+ | "(" ZSUPER S arguments_list ")"' do |sexp, _, _, _, arguments_list, _|
415
+ arguments, block = destructure_arguments(arguments_list && arguments_list.value || [])
416
+ send_node = s(:zsuper, [])
417
+
418
+ sexp.value = if block.nil?
419
+ send_node
420
+ else
421
+ s(:block, [send_node, *block.children])
422
+ end
423
+ end
424
+
425
+ # multiple arguments (in method invocation)
426
+ rule 'arguments_list : arguments_list S argument | argument | empty' do |arguments_list, *arguments|
427
+ arguments_list.value = without_spaces(arguments).flat_map(&:value)
428
+ end
429
+
430
+ # an argument can be a simple form
431
+ rule 'argument : form' do |argument, form|
432
+ argument.value = form.value
433
+ end
434
+
435
+ # an argument can be splat
436
+ rule 'argument : "&" S form' do |argument, _, _, form|
437
+ argument.value = s(:splat, [form.value])
438
+ end
439
+
440
+ # the last argument can be passed as a block
441
+ rule 'argument : "#" S form' do |argument, _, _, form|
442
+ argument.value = s(:block_pass, [form.value])
443
+ end
444
+
445
+ # the last argument can be an inline block
446
+ rule 'argument : "#" "(" parameters_list S forms ")"' do |argument, _, _, params, _, forms, _|
447
+ block_body = wrap_in_begin(forms.value)
448
+ argument.value = s(:pseudo_block, [params.value, block_body])
449
+ end
450
+
451
+ # the last argument can also be an inline block without parameters
452
+ rule 'argument : "#" form
453
+ | "#" "(" forms ")"' do |argument, _, *forms_array|
454
+ body = forms_array.reject do |element|
455
+ ['(', ')'].include?(element.type)
456
+ end.flat_map(&:value)
457
+
458
+ argument.value = s(:pseudo_block, [s(:args, []), wrap_in_begin(body)])
459
+ end
460
+
461
+ # return statement without parameters
462
+ # (return)
463
+ rule 'sexp : "(" RETURN ")"' do |sexp, _, _, _|
464
+ sexp.value = s(:return, [])
465
+ end
466
+
467
+ # return statement with parameters
468
+ # (return 1)
469
+ rule 'sexp : "(" RETURN S forms ")"' do |sexp, _, _, _, forms, _|
470
+ sexp.value = s(:return, forms.value)
471
+ end
472
+
473
+ # try statement with a rescue clause
474
+ # (try (File/read path)
475
+ # (rescue Errno.ENOENT e
476
+ # (@puts (message e))))
477
+ rule 'sexp : "(" TRY S forms S rescues ")"' do |sexp, _, _, _, forms, _, rescue_clauses, _|
478
+ sexp.value = s(:kwbegin, [s(:rescue, [wrap_in_begin(forms.value), *rescue_clauses.value, nil])])
479
+ end
480
+
481
+ # try statement with an ensure clause
482
+ # (try (File/read path)
483
+ # (ensure (@puts "Tries to read a file.")))
484
+ rule 'sexp : "(" TRY S forms S ensure ")"' do |sexp, _, _, _, forms, _, ensure_clause|
485
+ sexp.value = s(:kwbegin, [s(:ensure, [wrap_in_begin(forms.value), ensure_clause.value.children.first])])
486
+ end
487
+
488
+ # try statement with rescue and ensure clauses
489
+ # (try (File/read path)
490
+ # (rescue Errno.ENOENT e (@puts (message e)))
491
+ # (ensure (@puts "Tried to read a file.")))
492
+ rule 'sexp : "(" TRY S forms S rescues S ensure ")"' do |sexp, _, _, _, forms, _, rescue_clauses, _, ensure_clause, _|
493
+ rescue_subexpression = s(:rescue, [wrap_in_begin(forms.value), *rescue_clauses.value, nil])
494
+ sexp.value = s(:kwbegin, [s(:ensure, [rescue_subexpression, ensure_clause.value.children.first])])
495
+ end
496
+
497
+ # multiple rescue clauses
498
+ rule 'rescues : rescues S rescue | rescue' do |rescues, *rescues_array|
499
+ rescues.value = without_spaces(rescues_array).flat_map(&:value)
500
+ end
501
+
502
+ # rescue clause
503
+ rule 'rescue : "(" RESCUE S CONST S LVAR S forms ")"' do |rescue_clause, _, _, _, const, _, lvar, _, forms, _|
504
+ rescue_clause.value = s(:resbody,
505
+ [
506
+ s(:array, [const.value]),
507
+ s(:lvasgn, [lvar.value]),
508
+ wrap_in_begin(forms.value)
509
+ ]
510
+ )
511
+ end
512
+
513
+ # ensure clause
514
+ rule 'ensure : "(" ENSURE S forms ")"' do |ensure_clause, _, _, _, forms, _|
515
+ ensure_clause.value = s(:pseudo_ensure, [wrap_in_begin(forms.value)])
516
+ end
517
+
518
+ # class definition w/o a parent class
519
+ # (defclass User (class body))
520
+ rule 'sexp : "(" DEFCLASS S CONST S forms ")"' do |sexp, _, _, _, const, _, forms, _|
521
+ class_body = wrap_in_begin(forms.value)
522
+ sexp.value = s(:class, [const.value, nil, class_body])
523
+ end
524
+
525
+ # class definition with a parent class
526
+ # (defclass User < Base (class body))
527
+ rule 'sexp : "(" DEFCLASS S CONST S "<" S form S forms ")"' do |sexp, _, _, _, const, _, _, _, form, _, forms|
528
+ class_body = wrap_in_begin(forms.value)
529
+ sexp.value = s(:class, [const.value, form.value, class_body])
530
+ end
531
+
532
+ # module definition
533
+ # (defmodule Enumerable (module body ...))
534
+ rule 'sexp : "(" DEFMODULE S CONST S forms ")"' do |sexp, _, _, _, const, _, forms, _|
535
+ module_body = wrap_in_begin(forms.value)
536
+ sexp.value = s(:module, [const.value, module_body])
537
+ end
538
+
539
+ # singleton class definition
540
+ # (<<- user (defmethod name [] @name)
541
+ rule 'sexp : "(" "<" "<" "-" S form S forms ")"' do |sexp, _, _, _, _, _, form, _, forms, _|
542
+ singleton_body = wrap_in_begin(forms.value)
543
+ sexp.value = s(:sclass, [form.value, singleton_body])
544
+ end
545
+
546
+ # method defition
547
+ # (defmethod full-name [] (join [first-name last-name]))
548
+ # (defmethod read-file [path]
549
+ # (File/read path)
550
+ # (rescue Errno.ENOENT e (@puts "No file found."))
551
+ # (ensure (@puts "Tried to read a file.")))
552
+ rule 'sexp : "(" DEFMETHOD S LVAR S parameters_list S method_body ")"
553
+ | "(" DEFMETHOD S METHOD_NAME S parameters_list S method_body ")"' do |sexp, _, _, _, method_name, _, params, _, method_body, _|
554
+ sexp.value = s(:def, [method_name.value.to_sym, params.value, method_body.value])
555
+ end
556
+
557
+ # method body consisting of forms
558
+ rule 'method_body : forms' do |method_body, forms|
559
+ method_body.value = wrap_in_begin(forms.value)
560
+ end
561
+
562
+ # method body consisting of forms and rescue clauses
563
+ rule 'method_body : forms S rescues' do |method_body, forms, _, rescue_clauses|
564
+ method_body.value = s(:rescue, [wrap_in_begin(forms.value), *rescue_clauses.value, nil])
565
+ end
566
+
567
+ # method body consisting of forms and an ensure clause
568
+ rule 'method_body : forms S ensure' do |method_body, forms, _, ensure_clause|
569
+ method_body.value = s(:ensure, [wrap_in_begin(forms.value), ensure_clause.value.children.first])
570
+ end
571
+
572
+ # method body consisting of forms and rescue and ensure clauses
573
+ rule 'method_body : forms S rescues S ensure' do |method_body, forms, _, rescue_clauses, _, ensure_clause|
574
+ rescue_subexpression = s(:rescue, [wrap_in_begin(forms.value), *rescue_clauses.value, nil])
575
+ method_body.value = s(:ensure, [rescue_subexpression, ensure_clause.value.children.first])
576
+ end
577
+
578
+ # lambda definition
579
+ # (-> [user] (email user))
580
+ rule 'sexp : "(" "-" ">" S parameters_list S forms ")"' do |sexp, _, _, _, _, params, _, forms, _|
581
+ lambda_body = wrap_in_begin(forms.value)
582
+ sexp.value = s(:block, [s(:send, [nil, :lambda]), params.value, lambda_body])
583
+ end
584
+
585
+ # lambda definition without parameters
586
+ # (-> (@puts "Hello world!"))
587
+ rule 'sexp : "(" "-" ">" S forms ")"' do |sexp, _, _, _, _, forms, _|
588
+ lambda_body = wrap_in_begin(forms.value)
589
+ sexp.value = s(:block, [s(:send, [nil, :lambda]), s(:args, []), lambda_body])
590
+ end
591
+
592
+ # local variable assignment
593
+ # (def username "7even")
594
+ rule 'sexp : "(" DEF S LVAR S form ")"' do |sexp, _, _, _, var_name, _, form, _|
595
+ sexp.value = s(:lvasgn, [var_name.value, form.value])
596
+ end
597
+
598
+ # conditional local variable assignment
599
+ # (def-or username "7even")
600
+ rule 'sexp : "(" DEF_OR S LVAR S form ")"' do |sexp, _, _, _, var_name, _, form, _|
601
+ sexp.value = s(:or_asgn, [s(:lvasgn, [var_name.value]), form.value])
602
+ end
603
+
604
+ # instance variable assignment
605
+ # (def @age 30)
606
+ rule 'sexp : "(" DEF S IVAR S form ")"' do |sexp, _, _, _, var_name, _, form, _|
607
+ sexp.value = s(:ivasgn, [var_name.value, form.value])
608
+ end
609
+
610
+ # conditional instance variable assignment
611
+ # (def-or @age 30)
612
+ rule 'sexp : "(" DEF_OR S IVAR S form ")"' do |sexp, _, _, _, var_name, _, form, _|
613
+ sexp.value = s(:or_asgn, [s(:ivasgn, [var_name.value]), form.value])
614
+ end
615
+
616
+ # object attribute assignment
617
+ # (def user.name "John")
618
+ rule 'sexp : "(" DEF S form "." LVAR S form ")"' do |sexp, _, _, _, object, _, lvar, _, form, _|
619
+ method_name = "#{lvar}=".to_sym
620
+ sexp.value = s(:send, [object.value, method_name, form.value])
621
+ end
622
+
623
+ # conditional object attribute assignment
624
+ # (def-or user.name "John")
625
+ rule 'sexp : "(" DEF_OR S form "." LVAR S form ")"' do |sexp, _, _, _, object, _, lvar, _, form, _|
626
+ sexp.value = s(:or_asgn, [s(:send, [object.value, lvar.value]), form.value])
627
+ end
628
+
629
+ # constant assignment
630
+ # (def DAYS-IN-WEEK 7)
631
+ rule 'sexp : "(" DEF S CONST S form ")"' do |sexp, _, _, _, const, _, form, _|
632
+ sexp.value = s(:casgn, [*const.value.children, form.value])
633
+ end
634
+
635
+ # method parameters list (in method definition)
636
+ # [a b c]
637
+ rule 'parameters_list : "[" parameters "]"' do |parameters_list, _, parameters, _|
638
+ parameters_list.value = s(:args, parameters.value)
639
+ end
640
+
641
+ # method parameters list with a block parameter at the end
642
+ # [a b c # d]
643
+ rule 'parameters_list : "[" parameters S block_parameter "]"' do |parameters_list, _, args, _, block_parameter, _|
644
+ parameters_list.value = s(:args, [*args.value, block_parameter.value])
645
+ end
646
+
647
+ # method parameters list consisting of one block parameter
648
+ # [# parameters]
649
+ rule 'parameters_list : "[" block_parameter "]"' do |parameters_list, _, block_parameter, _|
650
+ parameters_list.value = s(:args, [block_parameter.value])
651
+ end
652
+
653
+ # multiple parameters
654
+ rule 'parameters : parameters S parameter | parameter | empty' do |parameters, *parameters_array|
655
+ parameters.value = without_spaces(parameters_array).flat_map(&:value)
656
+ end
657
+
658
+ # a parameter can be a local variable
659
+ rule 'parameter : LVAR' do |parameter, identifier|
660
+ parameter.value = s(:arg, [identifier.value])
661
+ end
662
+
663
+ # a parameter can have a default value
664
+ # [base 10]
665
+ rule 'parameter : "[" LVAR S form "]"' do |parameter, _, identifier, _, default_value, _|
666
+ parameter.value = s(:optarg, [identifier.value, default_value.value])
667
+ end
668
+
669
+ # a parameter can be a splat
670
+ rule 'parameter : "&" S LVAR' do |parameter, _, _, identifier|
671
+ parameter.value = s(:restarg, [identifier.value])
672
+ end
673
+
674
+ # the last parameter can be a block
675
+ rule 'block_parameter : "#" S LVAR' do |block_parameter, _, _, identifier|
676
+ block_parameter.value = s(:blockarg, [identifier.value])
677
+ end
678
+
679
+ # empty rule
680
+ rule('empty :') do |empty|
681
+ empty.value = []
682
+ end
683
+
684
+ # method name (may be operator)
685
+ rule 'func : operator | METHOD_NAME | LVAR' do |func, function|
686
+ func.value = function.value
687
+ end
688
+
689
+ # operator
690
+ rule 'operator : "+" | "-" | "*" | "/" | "%" | "*" "*"
691
+ | "=" | "!" "=" | "<" | ">" | "<" "=" | ">" "=" | "<" "=" ">" | "=" "=" "="
692
+ | "&" | "|" | "^" | "~" | "<" "<" | ">" ">" | "!"' do |operator, *operator_chars|
693
+ operator_name = operator_chars.map(&:value).join
694
+
695
+ operator.value = if operator_name == '='
696
+ :==
697
+ else
698
+ operator_name.to_sym
699
+ end
700
+ end
701
+
702
+ # logical 'and' & 'or'
703
+ # (and a b)
704
+ # (or a b)
705
+ rule 'sexp : "(" AND S forms ")"
706
+ | "(" OR S forms ")"' do |sexp, _, operator, _, forms, _|
707
+ sexp.value = s(operator.value, forms.value)
708
+ end
709
+
710
+ # collection member reader
711
+ # hash[:key]
712
+ # array[1, 5]
713
+ rule 'sexp : form "[" forms "]"' do |sexp, collection, _, arguments, _|
714
+ sexp.value = s(:send, [collection.value, :[], *arguments.value])
715
+ end
716
+
717
+ # collection member writer
718
+ # (def hash[:key] value)
719
+ # (def array[1 2] 3)
720
+ rule 'sexp : "(" DEF S form "[" forms "]" S form ")"' do |sexp, _, _, _, collection, _, arguments, _, _, value, _|
721
+ sexp.value = s(:send, [collection.value, :[]=, *arguments.value, value.value])
722
+ end
723
+
724
+ # conditional collection member writer
725
+ # (def-or hash[:key] value)
726
+ rule 'sexp : "(" DEF_OR S form "[" forms "]" S form ")"' do |sexp, _, _, _, collection, _, arguments, _, _, value, _|
727
+ sexp.value = s(:or_asgn, [s(:send, [collection.value, :[], *arguments.value]), value.value])
728
+ end
729
+
730
+ on_error -> (token) do
731
+ if token.nil?
732
+ fail FormatError, 'Input ends unexpectedly'
733
+ else
734
+ token_string = if token.value.respond_to?(:children)
735
+ token.value.children.first
736
+ else
737
+ token.value
738
+ end
739
+
740
+ message = "Unexpected token '#{token_string}'"
741
+ message << " at line #{token.location_info[:lineno].succ}"
742
+
743
+ fail FormatError, message
744
+ end
745
+ end
746
+
747
+ class FormatError < RuntimeError; end
748
+ end
749
+ end