peruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rubocop.yml +35 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +51 -0
- data/Rakefile +92 -0
- data/bin/peruby +9 -0
- data/doc/COMPAT.md +54 -0
- data/doc/CONTRIBUTING.md +21 -0
- data/doc/DESIGN.md +25 -0
- data/doc/INCOMPATIBILITIES.md +30 -0
- data/doc/PERF.md +51 -0
- data/doc/ROADMAP.md +18 -0
- data/examples/hello.pl +1 -0
- data/examples/json.pl +2 -0
- data/examples/object.pl +5 -0
- data/examples/word_count.pl +6 -0
- data/lib/peruby/cli.rb +224 -0
- data/lib/peruby/compile_unit.rb +103 -0
- data/lib/peruby/compiler.rb +224 -0
- data/lib/peruby/errors.rb +40 -0
- data/lib/peruby/lexer/heredoc.rb +8 -0
- data/lib/peruby/lexer/keywords.rb +63 -0
- data/lib/peruby/lexer/number.rb +32 -0
- data/lib/peruby/lexer/quote_like.rb +72 -0
- data/lib/peruby/lexer/source_scanner.rb +104 -0
- data/lib/peruby/lexer/state.rb +46 -0
- data/lib/peruby/lexer/structure_scanner.rb +149 -0
- data/lib/peruby/lexer/term_scanner.rb +301 -0
- data/lib/peruby/lexer/token.rb +16 -0
- data/lib/peruby/lexer.rb +123 -0
- data/lib/peruby/node.rb +88 -0
- data/lib/peruby/op/assign.rb +128 -0
- data/lib/peruby/op/builtin.rb +903 -0
- data/lib/peruby/op/call.rb +378 -0
- data/lib/peruby/op/control.rb +256 -0
- data/lib/peruby/op/element.rb +136 -0
- data/lib/peruby/op/expression.rb +342 -0
- data/lib/peruby/op/io.rb +113 -0
- data/lib/peruby/op/list.rb +102 -0
- data/lib/peruby/op/literal.rb +84 -0
- data/lib/peruby/op/loop.rb +158 -0
- data/lib/peruby/op/regexp.rb +288 -0
- data/lib/peruby/op/variable.rb +534 -0
- data/lib/peruby/op.rb +47 -0
- data/lib/peruby/parser/grammar.rb +5797 -0
- data/lib/peruby/parser/grammar.y +576 -0
- data/lib/peruby/parser.rb +14 -0
- data/lib/peruby/runtime/code.rb +21 -0
- data/lib/peruby/runtime/conv.rb +140 -0
- data/lib/peruby/runtime/directory_handle.rb +18 -0
- data/lib/peruby/runtime/env.rb +129 -0
- data/lib/peruby/runtime/glob.rb +24 -0
- data/lib/peruby/runtime/interpolation.rb +223 -0
- data/lib/peruby/runtime/io_handle.rb +37 -0
- data/lib/peruby/runtime/local_stack.rb +90 -0
- data/lib/peruby/runtime/match_state.rb +62 -0
- data/lib/peruby/runtime/module_loader.rb +133 -0
- data/lib/peruby/runtime/mro.rb +94 -0
- data/lib/peruby/runtime/perl_array.rb +81 -0
- data/lib/peruby/runtime/perl_hash.rb +57 -0
- data/lib/peruby/runtime/ref.rb +43 -0
- data/lib/peruby/runtime/regexp_compiler.rb +75 -0
- data/lib/peruby/runtime/scalar.rb +27 -0
- data/lib/peruby/runtime/sprintf.rb +54 -0
- data/lib/peruby/runtime/stash.rb +50 -0
- data/lib/peruby/runtime/test_builder.rb +47 -0
- data/lib/peruby/runtime.rb +325 -0
- data/lib/peruby/validator.rb +236 -0
- data/lib/peruby/version.rb +5 -0
- data/lib/peruby.rb +31 -0
- data/t/00-basic.t +5 -0
- data/t/lib/MiniTest.pm +22 -0
- metadata +130 -0
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Peruby::Parser::Grammar
|
|
4
|
+
token NUMBER VERSION STRING ISTRING HEREDOC BACKTICK MATCH QR SUBST TRANS BAREWORD PACKAGE_NAME FUNC LISTOP SORT_SUB
|
|
5
|
+
MAP_EXPR GREP_EXPR NAMED_UNARY PROTOTYPE
|
|
6
|
+
SCALAR_VAR ARRAY_VAR HASH_VAR CODE_VAR GLOB_VAR SPECIAL_VAR ARRAY_LAST
|
|
7
|
+
SCALAR_DEREF ARRAY_DEREF HASH_DEREF CODE_DEREF
|
|
8
|
+
SCALAR_DEREF_OPEN ARRAY_DEREF_OPEN HASH_DEREF_OPEN CODE_DEREF_OPEN
|
|
9
|
+
HASHREF_LBRACE SUBSCRIPT_LBRACE FILEHANDLE
|
|
10
|
+
PRINT PRINTF SAY MY OUR LOCAL STATE RETURN IF ELSIF ELSE UNLESS WHILE UNTIL SUB SUB_NAME FOR FOREACH PACKAGE GOTO
|
|
11
|
+
LAST NEXT REDO CONTINUE DO
|
|
12
|
+
READLINE FILETEST
|
|
13
|
+
SORT MAP GREP FATCOMMA
|
|
14
|
+
POSTFIX
|
|
15
|
+
USE NO REQUIRE QW
|
|
16
|
+
BEGIN CHECK INIT END EVAL
|
|
17
|
+
CONSTANT
|
|
18
|
+
ASSIGNOP POW REPEAT LSHIFT RSHIFT EQ NE LE GE CMP SEQ SNE SLT SGT SLE SGE SCMP
|
|
19
|
+
ANDAND OROR DORDOR AND OR XOR NOT MATCHOP NOTMATCHOP DOTDOT DOTDOTDOT
|
|
20
|
+
INC DEC REFGEN ARROW
|
|
21
|
+
LBRACE RBRACE
|
|
22
|
+
|
|
23
|
+
# perldoc perlop: Terms and List Operators through logical or/xor, highest first.
|
|
24
|
+
prechigh
|
|
25
|
+
right POW
|
|
26
|
+
left INC DEC ARROW '[' SUBSCRIPT_LBRACE
|
|
27
|
+
left POSTFIX
|
|
28
|
+
right '!' '~' UMINUS REFGEN
|
|
29
|
+
left MATCHOP NOTMATCHOP
|
|
30
|
+
left '*' '/' '%' REPEAT
|
|
31
|
+
left '+' '-' '.'
|
|
32
|
+
left LSHIFT RSHIFT
|
|
33
|
+
nonassoc '<' '>' LE GE SLT SGT SLE SGE
|
|
34
|
+
nonassoc EQ NE CMP SEQ SNE SCMP
|
|
35
|
+
left '&'
|
|
36
|
+
left '|' '^'
|
|
37
|
+
left ANDAND
|
|
38
|
+
left OROR DORDOR
|
|
39
|
+
nonassoc DOTDOT DOTDOTDOT
|
|
40
|
+
right '?' ':'
|
|
41
|
+
right '=' ASSIGNOP
|
|
42
|
+
left ',' FATCOMMA
|
|
43
|
+
right LISTOP
|
|
44
|
+
right NOT
|
|
45
|
+
left AND
|
|
46
|
+
left OR XOR
|
|
47
|
+
preclow
|
|
48
|
+
|
|
49
|
+
rule
|
|
50
|
+
program
|
|
51
|
+
: stmtseq { result = node(:Program, val[0], token: first_token(val[0])) }
|
|
52
|
+
|
|
53
|
+
stmtseq
|
|
54
|
+
: /* empty */ { result = [] }
|
|
55
|
+
| stmtseq statement { result = val[0] << val[1] }
|
|
56
|
+
|
|
57
|
+
statement
|
|
58
|
+
: ';' { result = node(:Nop, token: val[0]) }
|
|
59
|
+
| modifiable_statement ';' { result = val[0] }
|
|
60
|
+
| modifiable_statement IF expr ';' {
|
|
61
|
+
result = node(:ModifierCondition, val[0], val[2], :if, token: token_of(val[0]))
|
|
62
|
+
}
|
|
63
|
+
| modifiable_statement UNLESS expr ';' {
|
|
64
|
+
result = node(:ModifierCondition, val[0], val[2], :unless, token: token_of(val[0]))
|
|
65
|
+
}
|
|
66
|
+
| modifiable_statement WHILE expr ';' {
|
|
67
|
+
result = node(:ModifierCondition, val[0], val[2], :while, token: token_of(val[0]))
|
|
68
|
+
}
|
|
69
|
+
| modifiable_statement UNTIL expr ';' {
|
|
70
|
+
result = node(:ModifierCondition, val[0], val[2], :until, token: token_of(val[0]))
|
|
71
|
+
}
|
|
72
|
+
| modifiable_statement FOR expr ';' { result = node(:ModifierFor, val[0], val[2], token: val[1]) }
|
|
73
|
+
| modifiable_statement FOREACH expr ';' { result = node(:ModifierFor, val[0], val[2], token: val[1]) }
|
|
74
|
+
| MY variable initializer_opt ';' { result = node(:My, val[1], val[2], token: val[0]) }
|
|
75
|
+
| STATE variable initializer_opt ';' { result = node(:State, val[1], val[2], token: val[0]) }
|
|
76
|
+
| OUR variable initializer_opt ';' { result = node(:Our, package_variable(val[1]), val[2], token: val[0]) }
|
|
77
|
+
| LOCAL postfix initializer_opt ';' { result = node(:Local, val[1], val[2], token: val[0]) }
|
|
78
|
+
| PACKAGE package_name ';' { @package = val[1]; result = node(:Package, val[1], token: val[0]) }
|
|
79
|
+
| package_block_start stmtseq RBRACE {
|
|
80
|
+
@package = val[0][1]
|
|
81
|
+
body = node(:Block, val[1], token: val[0][0])
|
|
82
|
+
@unit&.leave_compile_scope
|
|
83
|
+
result = node(:PackageBlock, val[0][2], body, token: val[0][0])
|
|
84
|
+
}
|
|
85
|
+
| USE package_name use_args ';' {
|
|
86
|
+
register_module_imports(val[1], val[2])
|
|
87
|
+
register_mro(val[1], val[2])
|
|
88
|
+
result = compile_time_use(node(:Use, val[1], val[2], false, @package, token: val[0]))
|
|
89
|
+
}
|
|
90
|
+
| NO package_name use_args ';' {
|
|
91
|
+
result = compile_time_use(node(:Use, val[1], val[2], true, @package, token: val[0]))
|
|
92
|
+
}
|
|
93
|
+
| USE package_name VERSION use_args ';' {
|
|
94
|
+
imports = ['__VERSION__', val[2].value, *val[3]]
|
|
95
|
+
register_module_imports(val[1], val[3])
|
|
96
|
+
result = compile_time_use(node(:Use, val[1], imports, false, @package, token: val[0]))
|
|
97
|
+
}
|
|
98
|
+
| USE package_name NUMBER use_args ';' {
|
|
99
|
+
imports = ['__VERSION__', val[2].value, *val[3]]
|
|
100
|
+
register_module_imports(val[1], val[3])
|
|
101
|
+
result = compile_time_use(node(:Use, val[1], imports, false, @package, token: val[0]))
|
|
102
|
+
}
|
|
103
|
+
| USE VERSION ';' { result = node(:Nop, token: val[0]) }
|
|
104
|
+
| USE NUMBER ';' { result = node(:Nop, token: val[0]) }
|
|
105
|
+
| USE CONSTANT STRING FATCOMMA assignment ';' {
|
|
106
|
+
@known_subs[val[2].value] = ''
|
|
107
|
+
result = node(:Constant, qualify(val[2].value), val[4], token: val[0])
|
|
108
|
+
@unit&.run_at_compile_time(result)
|
|
109
|
+
result = node(:Nop, token: val[0]) if @unit
|
|
110
|
+
}
|
|
111
|
+
| USE package_name STRING FATCOMMA assignment overload_tail ';' {
|
|
112
|
+
register_module_imports(val[1], [])
|
|
113
|
+
imports = [literal_value(val[2]), literal_option(val[4]), *val[5]]
|
|
114
|
+
result = compile_time_use(node(:Use, val[1], imports, false, @package, token: val[0]))
|
|
115
|
+
}
|
|
116
|
+
| BEGIN block { phase(:begin, val[1], val[0], @package); result = node(:Nop, token: val[0]) }
|
|
117
|
+
| CHECK block { phase(:check, val[1], val[0], @package); result = node(:Nop, token: val[0]) }
|
|
118
|
+
| INIT block { phase(:init, val[1], val[0], @package); result = node(:Nop, token: val[0]) }
|
|
119
|
+
| END block { phase(:end, val[1], val[0], @package); result = node(:Nop, token: val[0]) }
|
|
120
|
+
| GOTO CODE_VAR ';' { result = node(:Goto, val[1].value, token: val[0]) }
|
|
121
|
+
| SUB SUB_NAME prototype_opt block {
|
|
122
|
+
@known_subs[val[1].value] = val[2]&.start_with?('&') ? :listop : val[2]
|
|
123
|
+
result = node(:SubDef, qualify(val[1].value), val[2], val[3], token: val[0])
|
|
124
|
+
}
|
|
125
|
+
| SUB SUB_NAME prototype_opt ';' {
|
|
126
|
+
@known_subs[val[1].value] = val[2]&.start_with?('&') ? :listop : val[2]
|
|
127
|
+
result = node(:Nop, token: val[0])
|
|
128
|
+
}
|
|
129
|
+
| MY '(' expr ')' initializer_opt ';' { result = node(:My, ensure_list(val[2]), val[4], token: val[0]) }
|
|
130
|
+
| BAREWORD ':' while_statement { result = val[2].with(label: val[0].value) }
|
|
131
|
+
| BAREWORD ':' for_statement { result = val[2].with(label: val[0].value) }
|
|
132
|
+
| BAREWORD ':' block { result = node(:BareBlock, val[2], val[0].value, token: val[0]) }
|
|
133
|
+
| if_statement
|
|
134
|
+
| while_statement
|
|
135
|
+
| for_statement
|
|
136
|
+
| block { result = node(:BareBlock, val[0], nil, token: token_of(val[0])) }
|
|
137
|
+
|
|
138
|
+
modifiable_statement
|
|
139
|
+
: expr
|
|
140
|
+
| RETURN expr_opt { result = node(:Return, val[1], token: val[0]) }
|
|
141
|
+
| loop_jump
|
|
142
|
+
| PRINT filehandle_opt expr_opt { result = output_statement(:Print, val[1], val[2], val[0]) }
|
|
143
|
+
| PRINTF filehandle_opt expr_opt { result = output_statement(:Printf, val[1], val[2], val[0]) }
|
|
144
|
+
| SAY filehandle_opt expr_opt { result = output_statement(:Say, val[1], val[2], val[0]) }
|
|
145
|
+
|
|
146
|
+
initializer_opt
|
|
147
|
+
: /* empty */ { result = nil }
|
|
148
|
+
| '=' assignment { result = val[1] }
|
|
149
|
+
|
|
150
|
+
if_statement
|
|
151
|
+
: IF '(' expr ')' block else_opt { result = node(:If, val[2], val[4], val[5], token: val[0]) }
|
|
152
|
+
| UNLESS '(' expr ')' block else_opt { result = node(:If, node(:Unary, :not, val[2], token: val[0]), val[4], val[5], token: val[0]) }
|
|
153
|
+
|
|
154
|
+
else_opt
|
|
155
|
+
: /* empty */ { result = nil }
|
|
156
|
+
| ELSE block { result = val[1] }
|
|
157
|
+
| ELSIF '(' expr ')' block else_opt { result = node(:If, val[2], val[4], val[5], token: val[0]) }
|
|
158
|
+
|
|
159
|
+
while_statement
|
|
160
|
+
: WHILE '(' expr ')' block continue_opt { result = node(:While, val[2], val[4], false, val[5], nil, token: val[0]) }
|
|
161
|
+
| UNTIL '(' expr ')' block continue_opt { result = node(:While, val[2], val[4], true, val[5], nil, token: val[0]) }
|
|
162
|
+
|
|
163
|
+
for_statement
|
|
164
|
+
: for_my_start block continue_opt {
|
|
165
|
+
result = node(:ForEach, val[0][1], val[0][2], val[1], val[2], nil, true, token: val[0][0])
|
|
166
|
+
@unit&.leave_compile_scope
|
|
167
|
+
}
|
|
168
|
+
| FOR SCALAR_VAR '(' expr ')' block continue_opt {
|
|
169
|
+
result = node(:ForEach, variable(:scalar, val[1]), val[3], val[5], val[6], nil, false, token: val[0])
|
|
170
|
+
}
|
|
171
|
+
| FOREACH SCALAR_VAR '(' expr ')' block continue_opt {
|
|
172
|
+
result = node(:ForEach, variable(:scalar, val[1]), val[3], val[5], val[6], nil, false, token: val[0])
|
|
173
|
+
}
|
|
174
|
+
| FOR '(' expr ')' block continue_opt {
|
|
175
|
+
result = node(:ForEach, node(:Variable, :scalar, '_', token: val[0]), val[2], val[4], val[5], nil, false,
|
|
176
|
+
token: val[0])
|
|
177
|
+
}
|
|
178
|
+
| FOREACH '(' expr ')' block continue_opt {
|
|
179
|
+
result = node(:ForEach, node(:Variable, :scalar, '_', token: val[0]), val[2], val[4], val[5], nil, false,
|
|
180
|
+
token: val[0])
|
|
181
|
+
}
|
|
182
|
+
| FOR '(' for_initializer ';' expr_opt ';' expr_opt ')' block continue_opt {
|
|
183
|
+
result = node(:CFor, val[2][0], val[4], val[6], val[8], val[9], nil, token: val[0])
|
|
184
|
+
@unit&.leave_compile_scope if val[2][1]
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for_my_start
|
|
188
|
+
: FOR MY SCALAR_VAR '(' expr ')' {
|
|
189
|
+
variable = variable(:scalar, val[2])
|
|
190
|
+
@unit&.enter_compile_scope
|
|
191
|
+
@unit&.predeclare_variable(variable)
|
|
192
|
+
result = [val[0], variable, val[4]]
|
|
193
|
+
}
|
|
194
|
+
| FOREACH MY SCALAR_VAR '(' expr ')' {
|
|
195
|
+
variable = variable(:scalar, val[2])
|
|
196
|
+
@unit&.enter_compile_scope
|
|
197
|
+
@unit&.predeclare_variable(variable)
|
|
198
|
+
result = [val[0], variable, val[4]]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
for_initializer
|
|
202
|
+
: /* empty */ { result = [nil, false] }
|
|
203
|
+
| expr { result = [val[0], false] }
|
|
204
|
+
| MY variable initializer_opt {
|
|
205
|
+
@unit&.enter_compile_scope
|
|
206
|
+
result = [node(:My, val[1], val[2], token: val[0]), true]
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
continue_opt
|
|
210
|
+
: /* empty */ { result = nil }
|
|
211
|
+
| CONTINUE block { result = val[1] }
|
|
212
|
+
|
|
213
|
+
loop_jump
|
|
214
|
+
: LAST label_opt { result = node(:LoopJump, :last, val[1], token: val[0]) }
|
|
215
|
+
| NEXT label_opt { result = node(:LoopJump, :next, val[1], token: val[0]) }
|
|
216
|
+
| REDO label_opt { result = node(:LoopJump, :redo, val[1], token: val[0]) }
|
|
217
|
+
|
|
218
|
+
label_opt
|
|
219
|
+
: /* empty */ { result = nil }
|
|
220
|
+
| BAREWORD { result = val[0].value }
|
|
221
|
+
|
|
222
|
+
block_start
|
|
223
|
+
: LBRACE { @unit&.enter_compile_scope; result = [val[0], @package] }
|
|
224
|
+
|
|
225
|
+
package_block_start
|
|
226
|
+
: PACKAGE package_name LBRACE {
|
|
227
|
+
@unit&.enter_compile_scope
|
|
228
|
+
result = [val[2], @package, val[1]]
|
|
229
|
+
@package = val[1]
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
block
|
|
233
|
+
: block_start stmtseq RBRACE {
|
|
234
|
+
@package = val[0][1]
|
|
235
|
+
result = node(:Block, val[1], token: val[0][0])
|
|
236
|
+
@unit&.leave_compile_scope
|
|
237
|
+
}
|
|
238
|
+
| block_start stmtseq expr RBRACE {
|
|
239
|
+
@package = val[0][1]
|
|
240
|
+
result = node(:Block, val[1] << val[2], token: val[0][0])
|
|
241
|
+
@unit&.leave_compile_scope
|
|
242
|
+
}
|
|
243
|
+
| block_start stmtseq RETURN expr RBRACE {
|
|
244
|
+
@package = val[0][1]
|
|
245
|
+
result = node(:Block, val[1] << node(:Return, val[3], token: val[2]), token: val[0][0])
|
|
246
|
+
@unit&.leave_compile_scope
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
expr_opt
|
|
250
|
+
: /* empty */ { result = nil }
|
|
251
|
+
| expr
|
|
252
|
+
|
|
253
|
+
expr
|
|
254
|
+
: assignment
|
|
255
|
+
| expr ',' assignment { result = append_list(val[0], val[2]) }
|
|
256
|
+
| expr FATCOMMA assignment { result = append_list(val[0], val[2]) }
|
|
257
|
+
|
|
258
|
+
assignment
|
|
259
|
+
: ternary
|
|
260
|
+
| LISTOP expr { result = node(:Call, val[0].value, list_items(val[1]), token: val[0]) }
|
|
261
|
+
| unary '=' assignment { result = node(:Assign, :'=', val[0], val[2], token: token_of(val[0])) }
|
|
262
|
+
| unary ASSIGNOP assignment { result = node(:Assign, val[1].value.to_sym, val[0], val[2], token: val[1]) }
|
|
263
|
+
|
|
264
|
+
ternary
|
|
265
|
+
: binary
|
|
266
|
+
| binary '?' assignment ':' assignment { result = node(:Ternary, val[0], val[2], val[4], token: token_of(val[0])) }
|
|
267
|
+
|
|
268
|
+
binary
|
|
269
|
+
: unary
|
|
270
|
+
| binary '+' binary { result = binary(:+, val) }
|
|
271
|
+
| binary '-' binary { result = binary(:-, val) }
|
|
272
|
+
| binary '*' binary { result = binary(:*, val) }
|
|
273
|
+
| binary '/' binary { result = binary(:divide, val) }
|
|
274
|
+
| binary '%' binary { result = binary(:modulo, val) }
|
|
275
|
+
| binary '.' binary { result = binary(:concat, val) }
|
|
276
|
+
| binary LSHIFT binary { result = binary(:left_shift, val) }
|
|
277
|
+
| binary RSHIFT binary { result = binary(:right_shift, val) }
|
|
278
|
+
| binary POW binary { result = binary(:pow, val) }
|
|
279
|
+
| binary REPEAT binary { result = binary(:repeat, val) }
|
|
280
|
+
| binary EQ binary { result = binary(:num_eq, val) }
|
|
281
|
+
| binary NE binary { result = binary(:num_ne, val) }
|
|
282
|
+
| binary '<' binary { result = binary(:num_lt, val) }
|
|
283
|
+
| binary '>' binary { result = binary(:num_gt, val) }
|
|
284
|
+
| binary LE binary { result = binary(:num_le, val) }
|
|
285
|
+
| binary GE binary { result = binary(:num_ge, val) }
|
|
286
|
+
| binary CMP binary { result = binary(:num_cmp, val) }
|
|
287
|
+
| binary SEQ binary { result = binary(:str_eq, val) }
|
|
288
|
+
| binary SNE binary { result = binary(:str_ne, val) }
|
|
289
|
+
| binary SLT binary { result = binary(:str_lt, val) }
|
|
290
|
+
| binary SGT binary { result = binary(:str_gt, val) }
|
|
291
|
+
| binary SLE binary { result = binary(:str_le, val) }
|
|
292
|
+
| binary SGE binary { result = binary(:str_ge, val) }
|
|
293
|
+
| binary SCMP binary { result = binary(:str_cmp, val) }
|
|
294
|
+
| binary ANDAND binary { result = binary(:and, val) }
|
|
295
|
+
| binary OROR binary { result = binary(:or, val) }
|
|
296
|
+
| binary DORDOR binary { result = binary(:defined_or, val) }
|
|
297
|
+
| binary '&' binary { result = binary(:bit_and, val) }
|
|
298
|
+
| binary '|' binary { result = binary(:bit_or, val) }
|
|
299
|
+
| binary '^' binary { result = binary(:bit_xor, val) }
|
|
300
|
+
| binary AND binary { result = binary(:and, val) }
|
|
301
|
+
| binary OR binary { result = binary(:or, val) }
|
|
302
|
+
| binary XOR binary { result = binary(:xor, val) }
|
|
303
|
+
| binary DOTDOT binary { result = binary(:range, val) }
|
|
304
|
+
| binary DOTDOTDOT binary { result = binary(:range_exclusive, val) }
|
|
305
|
+
| binary MATCHOP binary { result = node(:Bind, val[0], val[2], false, token: val[1]) }
|
|
306
|
+
| binary NOTMATCHOP binary { result = node(:Bind, val[0], val[2], true, token: val[1]) }
|
|
307
|
+
|
|
308
|
+
unary
|
|
309
|
+
: postfix =POSTFIX
|
|
310
|
+
| '-' unary =UMINUS { result = node(:Unary, :negate, val[1], token: val[0]) }
|
|
311
|
+
| '+' unary =UMINUS { result = node(:Unary, :positive, val[1], token: val[0]) }
|
|
312
|
+
| '!' unary { result = node(:Unary, :not, val[1], token: val[0]) }
|
|
313
|
+
| NOT unary { result = node(:Unary, :not, val[1], token: val[0]) }
|
|
314
|
+
| '~' unary { result = node(:Unary, :bit_not, val[1], token: val[0]) }
|
|
315
|
+
| REFGEN unary { result = node(:Unary, :reference, val[1], token: val[0]) }
|
|
316
|
+
| INC unary { result = node(:Prefix, :increment, val[1], token: val[0]) }
|
|
317
|
+
| DEC unary { result = node(:Prefix, :decrement, val[1], token: val[0]) }
|
|
318
|
+
| NAMED_UNARY unary { result = node(:Call, val[0].value, list_items(val[1]), token: val[0]) }
|
|
319
|
+
| FILETEST unary { result = node(:FileTest, val[0].value, val[1], token: val[0]) }
|
|
320
|
+
| REQUIRE unary { result = node(:Require, val[1], token: val[0]) }
|
|
321
|
+
| EVAL block { result = node(:Eval, val[1], false, token: val[0]) }
|
|
322
|
+
| EVAL unary { result = node(:Eval, val[1], true, token: val[0]) }
|
|
323
|
+
| DO block { result = val[1] }
|
|
324
|
+
|
|
325
|
+
postfix
|
|
326
|
+
: term
|
|
327
|
+
| postfix '[' expr ']' { result = node(:Element, val[0], val[2], :array, false, token: token_of(val[0])) }
|
|
328
|
+
| postfix SUBSCRIPT_LBRACE expr RBRACE { result = node(:Element, val[0], val[2], :hash, false, token: token_of(val[0])) }
|
|
329
|
+
| postfix ARROW '[' expr ']' { result = node(:Element, val[0], val[3], :array, true, token: token_of(val[0])) }
|
|
330
|
+
| postfix ARROW SUBSCRIPT_LBRACE expr RBRACE { result = node(:Element, val[0], val[3], :hash, true, token: token_of(val[0])) }
|
|
331
|
+
| postfix ARROW BAREWORD '(' expr_opt ')' { result = node(:MethodCall, val[0], val[2].value, list_items(val[4]), token: token_of(val[0])) }
|
|
332
|
+
| postfix ARROW FUNC '(' expr_opt ')' { result = node(:MethodCall, val[0], val[2].value, list_items(val[4]), token: token_of(val[0])) }
|
|
333
|
+
| postfix ARROW PACKAGE_NAME '(' expr_opt ')' { result = node(:MethodCall, val[0], val[2].value, list_items(val[4]), token: token_of(val[0])) }
|
|
334
|
+
| postfix ARROW SCALAR_VAR '(' expr_opt ')' { result = node(:MethodCall, val[0], variable(:scalar, val[2]), list_items(val[4]), token: token_of(val[0])) }
|
|
335
|
+
| postfix ARROW '(' expr_opt ')' { result = node(:CodeCall, val[0], list_items(val[3]), token: token_of(val[0])) }
|
|
336
|
+
| postfix INC { result = node(:Postfix, :increment, val[0], token: val[1]) }
|
|
337
|
+
| postfix DEC { result = node(:Postfix, :decrement, val[0], token: val[1]) }
|
|
338
|
+
| postfix ARROW BAREWORD { result = node(:MethodCall, val[0], val[2].value, [], token: token_of(val[0])) }
|
|
339
|
+
| postfix ARROW PACKAGE_NAME { result = node(:MethodCall, val[0], val[2].value, [], token: token_of(val[0])) }
|
|
340
|
+
| postfix ARROW ARRAY_VAR { result = postfix_dereference(val[0], val[2], :array) }
|
|
341
|
+
| postfix ARROW HASH_VAR { result = postfix_dereference(val[0], val[2], :hash) }
|
|
342
|
+
|
|
343
|
+
term
|
|
344
|
+
: NUMBER { result = node(:Literal, val[0].value, token: val[0]) }
|
|
345
|
+
| VERSION { result = node(:Literal, val[0].value, token: val[0]) }
|
|
346
|
+
| STRING { result = node(:Literal, literal_value(val[0]), token: val[0]) }
|
|
347
|
+
| ISTRING { result = node(:Literal, val[0].value, token: val[0]) }
|
|
348
|
+
| HEREDOC { result = node(:Literal, val[0].value, token: val[0]) }
|
|
349
|
+
| BACKTICK { result = node(:Literal, val[0].value, token: val[0]) }
|
|
350
|
+
| MATCH { result = node(:Match, val[0].value, token: val[0]) }
|
|
351
|
+
| QR { result = node(:Regexp, val[0].value, token: val[0]) }
|
|
352
|
+
| SUBST { result = node(:Substitute, val[0].value, token: val[0]) }
|
|
353
|
+
| TRANS { result = node(:Transliterate, val[0].value, token: val[0]) }
|
|
354
|
+
| READLINE { result = node(:Readline, val[0].value, token: val[0]) }
|
|
355
|
+
| QW {
|
|
356
|
+
words = val[0].value.parts.first.split.map { |word| node(:Literal, word, token: val[0]) }
|
|
357
|
+
result = node(:List, words, token: val[0])
|
|
358
|
+
}
|
|
359
|
+
| BAREWORD { result = node(:Bareword, val[0].value, token: val[0]) }
|
|
360
|
+
| PACKAGE_NAME { result = node(:Literal, val[0].value.delete_suffix('::'), token: val[0]) }
|
|
361
|
+
| variable
|
|
362
|
+
| SCALAR_DEREF_OPEN expr RBRACE { result = node(:DereferenceExpr, :scalar, val[1], token: val[0]) }
|
|
363
|
+
| ARRAY_DEREF_OPEN expr RBRACE { result = node(:DereferenceExpr, :array, val[1], token: val[0]) }
|
|
364
|
+
| HASH_DEREF_OPEN expr RBRACE { result = node(:DereferenceExpr, :hash, val[1], token: val[0]) }
|
|
365
|
+
| CODE_DEREF_OPEN expr RBRACE { result = node(:DereferenceExpr, :code, val[1], token: val[0]) }
|
|
366
|
+
| '(' expr ')' { result = node(:Group, val[1], token: val[0]) }
|
|
367
|
+
| '[' expr_opt ']' { result = node(:ArrayLiteral, list_items(val[1]), token: val[0]) }
|
|
368
|
+
| HASHREF_LBRACE expr_opt RBRACE { result = node(:HashLiteral, list_items(val[1]), token: val[0]) }
|
|
369
|
+
| FUNC '(' expr_opt ')' { result = node(:Call, val[0].value, list_items(val[2]), token: val[0]) }
|
|
370
|
+
| FUNC { result = node(:Call, qualify(val[0].value), [], token: val[0]) }
|
|
371
|
+
| LISTOP { result = node(:Call, qualify(val[0].value), [], token: val[0]) }
|
|
372
|
+
| SUB prototype_opt block { result = node(:AnonSub, val[1], val[2], token: val[0]) }
|
|
373
|
+
| LISTOP '(' MY SCALAR_VAR ',' expr ')' {
|
|
374
|
+
declaration = node(:My, variable(:scalar, val[3]), nil, token: val[2])
|
|
375
|
+
result = node(:Call, val[0].value, [declaration, *list_items(val[5])], token: val[0])
|
|
376
|
+
}
|
|
377
|
+
| FUNC '(' MY SCALAR_VAR ',' expr ')' {
|
|
378
|
+
declaration = node(:My, variable(:scalar, val[3]), nil, token: val[2])
|
|
379
|
+
result = node(:Call, val[0].value, [declaration, *list_items(val[5])], token: val[0])
|
|
380
|
+
}
|
|
381
|
+
| LISTOP block unary {
|
|
382
|
+
block = node(:AnonSub, nil, val[1], token: val[0])
|
|
383
|
+
result = node(:Call, qualify(val[0].value), [block, val[2]], token: val[0])
|
|
384
|
+
}
|
|
385
|
+
| SORT unary { result = node(:Sort, nil, val[1], @package, token: val[0]) }
|
|
386
|
+
| SORT block unary { result = node(:Sort, val[1], val[2], @package, token: val[0]) }
|
|
387
|
+
| SORT SORT_SUB unary { result = node(:Sort, qualify(val[1].value), val[2], @package, token: val[0]) }
|
|
388
|
+
| MAP block unary { result = node(:Map, val[1], val[2], false, token: val[0]) }
|
|
389
|
+
| GREP block unary { result = node(:Map, val[1], val[2], true, token: val[0]) }
|
|
390
|
+
| MAP_EXPR unary { result = node(:Map, embedded_expression(val[0]), val[1], false, token: val[0]) }
|
|
391
|
+
| GREP_EXPR unary { result = node(:Map, embedded_expression(val[0]), val[1], true, token: val[0]) }
|
|
392
|
+
|
|
393
|
+
variable
|
|
394
|
+
: SCALAR_VAR { result = variable(:scalar, val[0]) }
|
|
395
|
+
| ARRAY_VAR { result = variable(:array, val[0]) }
|
|
396
|
+
| HASH_VAR { result = variable(:hash, val[0]) }
|
|
397
|
+
| CODE_VAR { result = variable(:code, val[0]) }
|
|
398
|
+
| GLOB_VAR { result = variable(:glob, val[0]) }
|
|
399
|
+
| SPECIAL_VAR { result = variable(:scalar, val[0]) }
|
|
400
|
+
| ARRAY_LAST { result = variable(:array_last, val[0]) }
|
|
401
|
+
| SCALAR_DEREF { result = node(:Dereference, :scalar, val[0].value, token: val[0]) }
|
|
402
|
+
| ARRAY_DEREF { result = node(:Dereference, :array, val[0].value, token: val[0]) }
|
|
403
|
+
| HASH_DEREF { result = node(:Dereference, :hash, val[0].value, token: val[0]) }
|
|
404
|
+
| CODE_DEREF { result = node(:Dereference, :code, val[0].value, token: val[0]) }
|
|
405
|
+
|
|
406
|
+
filehandle_opt
|
|
407
|
+
: /* empty */ { result = nil }
|
|
408
|
+
| FILEHANDLE { result = val[0].value }
|
|
409
|
+
|
|
410
|
+
package_name
|
|
411
|
+
: BAREWORD { result = val[0].value }
|
|
412
|
+
| PACKAGE_NAME { result = val[0].value }
|
|
413
|
+
|
|
414
|
+
prototype_opt
|
|
415
|
+
: /* empty */ { result = nil }
|
|
416
|
+
| PROTOTYPE { result = val[0].value }
|
|
417
|
+
|
|
418
|
+
use_args
|
|
419
|
+
: /* empty */ { result = [] }
|
|
420
|
+
| '(' ')' { result = ['__NO_IMPORT__'] }
|
|
421
|
+
| QW { result = val[0].value.parts.first.split }
|
|
422
|
+
| STRING { result = [literal_value(val[0])] }
|
|
423
|
+
| ISTRING { result = [literal_value(val[0])] }
|
|
424
|
+
|
|
425
|
+
overload_tail
|
|
426
|
+
: /* empty */ { result = [] }
|
|
427
|
+
| ',' assignment FATCOMMA assignment overload_tail {
|
|
428
|
+
result = [literal_option(val[1]), literal_option(val[3]), *val[4]]
|
|
429
|
+
}
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
---- header
|
|
433
|
+
require 'racc/parser'
|
|
434
|
+
require_relative '../node'
|
|
435
|
+
require_relative '../lexer'
|
|
436
|
+
|
|
437
|
+
module Peruby
|
|
438
|
+
module Parser
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
|
|
442
|
+
---- inner
|
|
443
|
+
def parse(source, file: '-e', known_subs: {}, unit: nil, package: 'main')
|
|
444
|
+
@package = package
|
|
445
|
+
@unit = unit
|
|
446
|
+
@known_subs = known_subs
|
|
447
|
+
@tokens = Lexer.new(source, file:, known_subs:, implicit_semicolon: true).each
|
|
448
|
+
do_parse
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def register_module_imports(module_name, imports)
|
|
452
|
+
defaults = { 'Test::More' => %w[plan ok is isnt like is_deeply pass fail subtest done_testing diag],
|
|
453
|
+
'Test::Simple' => %w[plan ok],
|
|
454
|
+
'List::Util' => %w[first sum sum0 min max minstr maxstr reduce any all none shuffle uniq pairs],
|
|
455
|
+
'Scalar::Util' => %w[blessed reftype refaddr weaken dualvar looks_like_number] }
|
|
456
|
+
names = imports.reject { |name| name.to_s.start_with?('__') }
|
|
457
|
+
names = defaults.fetch(module_name, []) if names.empty? && !imports.include?('__NO_IMPORT__')
|
|
458
|
+
names.each do |name|
|
|
459
|
+
prototype = ModuleLoader.prototype(module_name, name)
|
|
460
|
+
@known_subs[name] = prototype&.start_with?('&') ? :listop : (prototype || :listop)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def compile_time_use(use_node)
|
|
465
|
+
return use_node unless @unit
|
|
466
|
+
|
|
467
|
+
@unit.run_at_compile_time(use_node)
|
|
468
|
+
use_node.imports.reject { |name| name.to_s.start_with?('__') || name.is_a?(Numeric) }.each do |name|
|
|
469
|
+
prototype = @unit.runtime.stash.fetch("#{use_node.module_name}::#{name}")&.code&.prototype
|
|
470
|
+
@known_subs[name] = prototype&.start_with?('&') ? :listop : (prototype || :listop)
|
|
471
|
+
end
|
|
472
|
+
%w[strict warnings utf8 feature integer bytes vars].include?(use_node.module_name) ? use_node : node(:Nop, token: use_node)
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
def phase(kind, block, token, package)
|
|
476
|
+
@unit&.register_phase(kind, block, token, package)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def qualify(name)
|
|
480
|
+
@package == 'main' || name.include?('::') ? name : "#{@package}::#{name}"
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
def package_variable(variable)
|
|
484
|
+
Node::Variable.new(variable.sigil, qualify(variable.name), variable.file, variable.line)
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
def next_token
|
|
488
|
+
token = @tokens.next
|
|
489
|
+
type = { :'{' => :LBRACE, :'}' => :RBRACE }.fetch(token.type, token.type)
|
|
490
|
+
type = type.to_s if type.to_s.length == 1
|
|
491
|
+
[type, token]
|
|
492
|
+
rescue StopIteration
|
|
493
|
+
[false, false]
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
def on_error(_token_id, token, _stack)
|
|
497
|
+
near = token ? token.value.inspect : 'EOF'
|
|
498
|
+
line = token.respond_to?(:line) ? token.line : 1
|
|
499
|
+
file = token.respond_to?(:file) ? token.file : '-e'
|
|
500
|
+
raise CompileError, "syntax error at #{file} line #{line}, near #{near}"
|
|
501
|
+
end
|
|
502
|
+
|
|
503
|
+
def node(name, *values, token:)
|
|
504
|
+
if name == :Call && values[0] == 'bless' && values[1].one?
|
|
505
|
+
values[1] << Node::Literal.new(@package, token&.file, token&.line)
|
|
506
|
+
end
|
|
507
|
+
result = Node.const_get(name).new(*values, token&.file, token&.line)
|
|
508
|
+
@unit&.predeclare(result) if %i[My State].include?(name)
|
|
509
|
+
result
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
def register_mro(module_name, imports)
|
|
513
|
+
return unless module_name == 'mro' && imports.first
|
|
514
|
+
|
|
515
|
+
@unit&.runtime&.mro&.set(@package, imports.first)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def embedded_expression(token)
|
|
519
|
+
self.class.new.parse("#{token.value};", file: token.file, known_subs: @known_subs).statements.first
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def variable(sigil, token)
|
|
523
|
+
node(:Variable, sigil, token.value, token:)
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def postfix_dereference(expression, token, kind)
|
|
527
|
+
raise CompileError, "syntax error at #{token.file} line #{token.line}, near #{token.value.inspect}" unless token.value == '*'
|
|
528
|
+
|
|
529
|
+
node(:DereferenceExpr, kind, expression, token: token_of(expression))
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
def output_statement(kind, handle, arguments, token)
|
|
533
|
+
return node(kind, handle, arguments, token:) unless arguments.is_a?(Node::List) &&
|
|
534
|
+
arguments.items.first.is_a?(Node::Group)
|
|
535
|
+
|
|
536
|
+
output = node(kind, handle, arguments.items.first, token:)
|
|
537
|
+
node(:List, [output, *arguments.items.drop(1)], token:)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def binary(operator, values)
|
|
541
|
+
node(:Binary, operator, values[0], values[2], token: token_of(values[0]))
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def append_list(left, right)
|
|
545
|
+
items = left.is_a?(Node::List) ? left.items : [left]
|
|
546
|
+
node(:List, items + [right], token: token_of(left))
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def list_items(value)
|
|
550
|
+
return [] unless value
|
|
551
|
+
|
|
552
|
+
value = value.expression if value.is_a?(Node::Group)
|
|
553
|
+
value.is_a?(Node::List) ? value.items : [value]
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def ensure_list(value)
|
|
557
|
+
return value if value.is_a?(Node::List)
|
|
558
|
+
|
|
559
|
+
node(:List, [value], token: token_of(value))
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def literal_value(token)
|
|
563
|
+
token.value.is_a?(Lexer::Quote) ? token.value.parts.first : token.value
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def literal_option(value)
|
|
567
|
+
value.respond_to?(:value) ? value.value : nil
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def token_of(value)
|
|
571
|
+
Struct.new(:file, :line).new(value.file, value.line)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def first_token(values)
|
|
575
|
+
values.empty? ? nil : token_of(values.first)
|
|
576
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'parser/grammar'
|
|
4
|
+
|
|
5
|
+
module Peruby
|
|
6
|
+
# Public parser facade.
|
|
7
|
+
module Parser
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def parse(source, file: '-e', known_subs: {}, unit: nil, package: 'main')
|
|
11
|
+
Grammar.new.parse(source, file:, known_subs:, unit:, package:)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Peruby
|
|
4
|
+
# Compiled Perl subroutine and its captured lexical environment.
|
|
5
|
+
Code = Data.define(:name, :body, :environment, :package, :prototype)
|
|
6
|
+
GotoRequest = Data.define(:code)
|
|
7
|
+
|
|
8
|
+
# Adapter for core-module functions implemented in Ruby.
|
|
9
|
+
class NativeBody
|
|
10
|
+
def initialize(&block)
|
|
11
|
+
@block = block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def run_subroutine(env, context)
|
|
15
|
+
result = @block.call(env, env.fetch(:array, '_').cells, context)
|
|
16
|
+
return Array(result).map { |value| value.is_a?(Scalar) ? value : Scalar.new(value) } if context == :list
|
|
17
|
+
|
|
18
|
+
result.is_a?(Scalar) ? result.get : result
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|