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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/Guardfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +669 -0
- data/Rakefile +6 -0
- data/bin/console +7 -0
- data/bin/setup +7 -0
- data/carbonate.gemspec +34 -0
- data/examples/user.crb +10 -0
- data/exe/crb2rb +62 -0
- data/lib/carbonate.rb +57 -0
- data/lib/carbonate/parser.rb +749 -0
- data/lib/carbonate/version.rb +3 -0
- metadata +203 -0
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/carbonate.gemspec
ADDED
|
@@ -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
|
data/examples/user.crb
ADDED
|
@@ -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)))
|
data/exe/crb2rb
ADDED
|
@@ -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
|
data/lib/carbonate.rb
ADDED
|
@@ -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
|