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.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +35 -0
  3. data/CHANGELOG.md +15 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +51 -0
  6. data/Rakefile +92 -0
  7. data/bin/peruby +9 -0
  8. data/doc/COMPAT.md +54 -0
  9. data/doc/CONTRIBUTING.md +21 -0
  10. data/doc/DESIGN.md +25 -0
  11. data/doc/INCOMPATIBILITIES.md +30 -0
  12. data/doc/PERF.md +51 -0
  13. data/doc/ROADMAP.md +18 -0
  14. data/examples/hello.pl +1 -0
  15. data/examples/json.pl +2 -0
  16. data/examples/object.pl +5 -0
  17. data/examples/word_count.pl +6 -0
  18. data/lib/peruby/cli.rb +224 -0
  19. data/lib/peruby/compile_unit.rb +103 -0
  20. data/lib/peruby/compiler.rb +224 -0
  21. data/lib/peruby/errors.rb +40 -0
  22. data/lib/peruby/lexer/heredoc.rb +8 -0
  23. data/lib/peruby/lexer/keywords.rb +63 -0
  24. data/lib/peruby/lexer/number.rb +32 -0
  25. data/lib/peruby/lexer/quote_like.rb +72 -0
  26. data/lib/peruby/lexer/source_scanner.rb +104 -0
  27. data/lib/peruby/lexer/state.rb +46 -0
  28. data/lib/peruby/lexer/structure_scanner.rb +149 -0
  29. data/lib/peruby/lexer/term_scanner.rb +301 -0
  30. data/lib/peruby/lexer/token.rb +16 -0
  31. data/lib/peruby/lexer.rb +123 -0
  32. data/lib/peruby/node.rb +88 -0
  33. data/lib/peruby/op/assign.rb +128 -0
  34. data/lib/peruby/op/builtin.rb +903 -0
  35. data/lib/peruby/op/call.rb +378 -0
  36. data/lib/peruby/op/control.rb +256 -0
  37. data/lib/peruby/op/element.rb +136 -0
  38. data/lib/peruby/op/expression.rb +342 -0
  39. data/lib/peruby/op/io.rb +113 -0
  40. data/lib/peruby/op/list.rb +102 -0
  41. data/lib/peruby/op/literal.rb +84 -0
  42. data/lib/peruby/op/loop.rb +158 -0
  43. data/lib/peruby/op/regexp.rb +288 -0
  44. data/lib/peruby/op/variable.rb +534 -0
  45. data/lib/peruby/op.rb +47 -0
  46. data/lib/peruby/parser/grammar.rb +5797 -0
  47. data/lib/peruby/parser/grammar.y +576 -0
  48. data/lib/peruby/parser.rb +14 -0
  49. data/lib/peruby/runtime/code.rb +21 -0
  50. data/lib/peruby/runtime/conv.rb +140 -0
  51. data/lib/peruby/runtime/directory_handle.rb +18 -0
  52. data/lib/peruby/runtime/env.rb +129 -0
  53. data/lib/peruby/runtime/glob.rb +24 -0
  54. data/lib/peruby/runtime/interpolation.rb +223 -0
  55. data/lib/peruby/runtime/io_handle.rb +37 -0
  56. data/lib/peruby/runtime/local_stack.rb +90 -0
  57. data/lib/peruby/runtime/match_state.rb +62 -0
  58. data/lib/peruby/runtime/module_loader.rb +133 -0
  59. data/lib/peruby/runtime/mro.rb +94 -0
  60. data/lib/peruby/runtime/perl_array.rb +81 -0
  61. data/lib/peruby/runtime/perl_hash.rb +57 -0
  62. data/lib/peruby/runtime/ref.rb +43 -0
  63. data/lib/peruby/runtime/regexp_compiler.rb +75 -0
  64. data/lib/peruby/runtime/scalar.rb +27 -0
  65. data/lib/peruby/runtime/sprintf.rb +54 -0
  66. data/lib/peruby/runtime/stash.rb +50 -0
  67. data/lib/peruby/runtime/test_builder.rb +47 -0
  68. data/lib/peruby/runtime.rb +325 -0
  69. data/lib/peruby/validator.rb +236 -0
  70. data/lib/peruby/version.rb +5 -0
  71. data/lib/peruby.rb +31 -0
  72. data/t/00-basic.t +5 -0
  73. data/t/lib/MiniTest.pm +22 -0
  74. 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