frausto 0.2.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/LICENSE.txt +21 -0
- data/README.md +45 -0
- data/bin/faust2ruby +124 -0
- data/bin/ruby2faust +129 -0
- data/faust2ruby.md +523 -0
- data/lib/faust2ruby/ast.rb +315 -0
- data/lib/faust2ruby/ir_builder.rb +413 -0
- data/lib/faust2ruby/lexer.rb +255 -0
- data/lib/faust2ruby/library_mapper.rb +249 -0
- data/lib/faust2ruby/parser.rb +596 -0
- data/lib/faust2ruby/ruby_generator.rb +708 -0
- data/lib/faust2ruby/version.rb +5 -0
- data/lib/faust2ruby.rb +82 -0
- data/lib/frausto.rb +8 -0
- data/lib/ruby2faust/dsl.rb +1332 -0
- data/lib/ruby2faust/emitter.rb +599 -0
- data/lib/ruby2faust/ir.rb +285 -0
- data/lib/ruby2faust/live.rb +82 -0
- data/lib/ruby2faust/version.rb +5 -0
- data/lib/ruby2faust.rb +27 -0
- data/ruby2faust.md +334 -0
- metadata +106 -0
|
@@ -0,0 +1,596 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lexer"
|
|
4
|
+
require_relative "ast"
|
|
5
|
+
|
|
6
|
+
module Faust2Ruby
|
|
7
|
+
# Recursive descent parser for Faust DSP programs.
|
|
8
|
+
# Grammar based on Faust's official grammar with simplified precedence.
|
|
9
|
+
class Parser
|
|
10
|
+
class ParseError < StandardError
|
|
11
|
+
attr_reader :line, :column
|
|
12
|
+
|
|
13
|
+
def initialize(message, line: nil, column: nil)
|
|
14
|
+
@line = line
|
|
15
|
+
@column = column
|
|
16
|
+
super("#{message} at line #{line}, column #{column}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Operator precedence (lowest to highest)
|
|
21
|
+
# In Faust: SEQ < PAR < SPLIT/MERGE < REC < arithmetic
|
|
22
|
+
PRECEDENCE = {
|
|
23
|
+
PAR: 1, # , (parallel - lowest, used as arg separator)
|
|
24
|
+
SEQ: 2, # : (sequential)
|
|
25
|
+
SPLIT: 3, # <:
|
|
26
|
+
MERGE: 3, # :>
|
|
27
|
+
REC: 4, # ~
|
|
28
|
+
OR: 5, # |
|
|
29
|
+
AND: 6, # &
|
|
30
|
+
LT: 7, GT: 7, LE: 7, GE: 7, EQ: 7, NEQ: 7, # comparison
|
|
31
|
+
ADD: 8, SUB: 8, # + -
|
|
32
|
+
MUL: 9, DIV: 9, MOD: 9, # * / %
|
|
33
|
+
POW: 10, # ^
|
|
34
|
+
DELAY: 11, # @
|
|
35
|
+
}.freeze
|
|
36
|
+
|
|
37
|
+
def initialize(source)
|
|
38
|
+
@lexer = Lexer.new(source)
|
|
39
|
+
@tokens = @lexer.tokenize
|
|
40
|
+
@pos = 0
|
|
41
|
+
@errors = @lexer.errors.dup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def parse
|
|
45
|
+
statements = []
|
|
46
|
+
until current_type == :EOF
|
|
47
|
+
stmt = parse_statement
|
|
48
|
+
statements << stmt if stmt
|
|
49
|
+
end
|
|
50
|
+
AST::Program.new(statements)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attr_reader :errors
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def current
|
|
58
|
+
@tokens[@pos]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def current_type
|
|
62
|
+
current&.type || :EOF
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def current_value
|
|
66
|
+
current&.value
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def peek(offset = 1)
|
|
70
|
+
@tokens[@pos + offset]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def advance
|
|
74
|
+
token = current
|
|
75
|
+
@pos += 1
|
|
76
|
+
token
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def expect(type)
|
|
80
|
+
if current_type == type
|
|
81
|
+
advance
|
|
82
|
+
else
|
|
83
|
+
error("Expected #{type}, got #{current_type}")
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def error(message)
|
|
89
|
+
token = current || @tokens.last
|
|
90
|
+
@errors << "#{message} at line #{token&.line}, column #{token&.column}"
|
|
91
|
+
# Skip to next statement boundary for recovery
|
|
92
|
+
advance until [:ENDDEF, :EOF].include?(current_type)
|
|
93
|
+
advance if current_type == :ENDDEF
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def parse_statement
|
|
98
|
+
case current_type
|
|
99
|
+
when :IMPORT
|
|
100
|
+
parse_import
|
|
101
|
+
when :DECLARE
|
|
102
|
+
parse_declare
|
|
103
|
+
when :IDENT, :PROCESS
|
|
104
|
+
# PROCESS is also a valid definition target
|
|
105
|
+
parse_definition
|
|
106
|
+
else
|
|
107
|
+
advance # skip unknown
|
|
108
|
+
nil
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parse_import
|
|
113
|
+
token = advance # consume 'import'
|
|
114
|
+
expect(:LPAREN)
|
|
115
|
+
path_token = expect(:STRING)
|
|
116
|
+
path = path_token&.value
|
|
117
|
+
expect(:RPAREN)
|
|
118
|
+
expect(:ENDDEF)
|
|
119
|
+
AST::Import.new(path, line: token.line, column: token.column)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def parse_declare
|
|
123
|
+
token = advance # consume 'declare'
|
|
124
|
+
key_token = expect(:IDENT)
|
|
125
|
+
key = key_token&.value
|
|
126
|
+
value_token = expect(:STRING)
|
|
127
|
+
value = value_token&.value
|
|
128
|
+
expect(:ENDDEF)
|
|
129
|
+
AST::Declare.new(key, value, line: token.line, column: token.column)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def parse_definition
|
|
133
|
+
token = current
|
|
134
|
+
name_token = advance # consume identifier or process keyword
|
|
135
|
+
name = name_token.type == :PROCESS ? "process" : name_token.value
|
|
136
|
+
|
|
137
|
+
# Check for parameters: name(x, y) = ... or name(0) = ... (pattern matching)
|
|
138
|
+
params = []
|
|
139
|
+
if current_type == :LPAREN
|
|
140
|
+
advance # consume (
|
|
141
|
+
until current_type == :RPAREN || current_type == :EOF
|
|
142
|
+
# Accept both identifiers and integer literals (for pattern matching)
|
|
143
|
+
if current_type == :IDENT
|
|
144
|
+
params << advance.value
|
|
145
|
+
elsif current_type == :INT
|
|
146
|
+
# Integer pattern - store as string so merge_to_case can detect it
|
|
147
|
+
params << advance.value.to_s
|
|
148
|
+
else
|
|
149
|
+
error("Expected identifier or integer pattern")
|
|
150
|
+
break
|
|
151
|
+
end
|
|
152
|
+
break unless current_type == :PAR
|
|
153
|
+
advance # consume ,
|
|
154
|
+
end
|
|
155
|
+
expect(:RPAREN)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
expect(:DEF)
|
|
159
|
+
expr = parse_expression
|
|
160
|
+
|
|
161
|
+
# Handle 'with' clause: expr with { definitions }
|
|
162
|
+
if current_type == :WITH
|
|
163
|
+
expr = parse_with_clause(expr)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
expect(:ENDDEF)
|
|
167
|
+
AST::Definition.new(name, expr, params: params, line: token.line, column: token.column)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def parse_with_clause(expr)
|
|
171
|
+
token = advance # consume 'with'
|
|
172
|
+
expect(:LBRACE)
|
|
173
|
+
definitions = []
|
|
174
|
+
until current_type == :RBRACE || current_type == :EOF
|
|
175
|
+
if current_type == :IDENT
|
|
176
|
+
def_token = current
|
|
177
|
+
name = advance.value
|
|
178
|
+
# Check for parameters
|
|
179
|
+
params = []
|
|
180
|
+
if current_type == :LPAREN
|
|
181
|
+
advance
|
|
182
|
+
until current_type == :RPAREN || current_type == :EOF
|
|
183
|
+
param_token = expect(:IDENT)
|
|
184
|
+
params << param_token.value if param_token
|
|
185
|
+
break unless current_type == :PAR
|
|
186
|
+
advance
|
|
187
|
+
end
|
|
188
|
+
expect(:RPAREN)
|
|
189
|
+
end
|
|
190
|
+
expect(:DEF)
|
|
191
|
+
def_expr = parse_expression
|
|
192
|
+
# Handle nested with
|
|
193
|
+
if current_type == :WITH
|
|
194
|
+
def_expr = parse_with_clause(def_expr)
|
|
195
|
+
end
|
|
196
|
+
expect(:ENDDEF)
|
|
197
|
+
definitions << AST::Definition.new(name, def_expr, params: params, line: def_token.line, column: def_token.column)
|
|
198
|
+
else
|
|
199
|
+
break
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
expect(:RBRACE)
|
|
203
|
+
AST::With.new(expr, definitions, line: token.line, column: token.column)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def parse_expression(min_prec = 0)
|
|
207
|
+
left = parse_unary
|
|
208
|
+
|
|
209
|
+
while binary_op?(current_type) && PRECEDENCE[current_type] >= min_prec
|
|
210
|
+
op_token = advance
|
|
211
|
+
op = op_token.type
|
|
212
|
+
# Right associativity for some operators
|
|
213
|
+
next_prec = right_associative?(op) ? PRECEDENCE[op] : PRECEDENCE[op] + 1
|
|
214
|
+
right = parse_expression(next_prec)
|
|
215
|
+
left = AST::BinaryOp.new(op, left, right, line: op_token.line, column: op_token.column)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
left
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def binary_op?(type)
|
|
222
|
+
PRECEDENCE.key?(type)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def right_associative?(op)
|
|
226
|
+
[:POW, :SEQ].include?(op)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def parse_unary
|
|
230
|
+
if current_type == :SUB
|
|
231
|
+
# Check if this is a prefix operator form: - (x) vs unary negation -x
|
|
232
|
+
# If followed by LPAREN, it's a prefix operator (subtract from input)
|
|
233
|
+
if peek&.type == :LPAREN
|
|
234
|
+
return parse_postfix # Will handle as prefix operator in parse_primary
|
|
235
|
+
end
|
|
236
|
+
token = advance
|
|
237
|
+
operand = parse_unary
|
|
238
|
+
return AST::UnaryOp.new(:NEG, operand, line: token.line, column: token.column)
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
parse_postfix
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def parse_postfix
|
|
245
|
+
expr = parse_primary
|
|
246
|
+
|
|
247
|
+
loop do
|
|
248
|
+
case current_type
|
|
249
|
+
when :PRIME
|
|
250
|
+
# Delay: expr'
|
|
251
|
+
token = advance
|
|
252
|
+
expr = AST::Prime.new(expr, line: token.line, column: token.column)
|
|
253
|
+
when :LBRACKET
|
|
254
|
+
# Access: expr[n]
|
|
255
|
+
token = advance
|
|
256
|
+
index = parse_expression
|
|
257
|
+
expect(:RBRACKET)
|
|
258
|
+
expr = AST::Access.new(expr, index, line: token.line, column: token.column)
|
|
259
|
+
when :LPAREN
|
|
260
|
+
# Function call when following an identifier
|
|
261
|
+
if expr.is_a?(AST::Identifier) || expr.is_a?(AST::QualifiedName)
|
|
262
|
+
name = expr.is_a?(AST::QualifiedName) ? expr.to_s : expr.name
|
|
263
|
+
args = parse_call_args
|
|
264
|
+
expr = AST::FunctionCall.new(name, args, line: expr.line, column: expr.column)
|
|
265
|
+
else
|
|
266
|
+
break
|
|
267
|
+
end
|
|
268
|
+
when :DOT
|
|
269
|
+
# Qualified name continuation
|
|
270
|
+
advance
|
|
271
|
+
if current_type == :IDENT
|
|
272
|
+
name_token = advance
|
|
273
|
+
parts = expr.is_a?(AST::QualifiedName) ? expr.parts.dup : [expr.name]
|
|
274
|
+
parts << name_token.value
|
|
275
|
+
expr = AST::QualifiedName.new(parts, line: expr.line, column: expr.column)
|
|
276
|
+
else
|
|
277
|
+
error("Expected identifier after '.'")
|
|
278
|
+
break
|
|
279
|
+
end
|
|
280
|
+
when :LETREC
|
|
281
|
+
# Postfix letrec: expr letrec { ... }
|
|
282
|
+
letrec = parse_letrec_expr
|
|
283
|
+
expr = AST::Letrec.new(letrec.definitions, expr, line: letrec.line, column: letrec.column)
|
|
284
|
+
else
|
|
285
|
+
break
|
|
286
|
+
end
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
expr
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def parse_call_args
|
|
293
|
+
args = []
|
|
294
|
+
expect(:LPAREN)
|
|
295
|
+
until current_type == :RPAREN || current_type == :EOF
|
|
296
|
+
# Parse argument with minimum precedence above PAR to stop at commas
|
|
297
|
+
args << parse_expression(PRECEDENCE[:PAR] + 1)
|
|
298
|
+
break unless current_type == :PAR
|
|
299
|
+
advance # consume ,
|
|
300
|
+
end
|
|
301
|
+
expect(:RPAREN)
|
|
302
|
+
args
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def parse_primary
|
|
306
|
+
token = current
|
|
307
|
+
|
|
308
|
+
case current_type
|
|
309
|
+
when :INT
|
|
310
|
+
advance
|
|
311
|
+
AST::IntLiteral.new(token.value, line: token.line, column: token.column)
|
|
312
|
+
|
|
313
|
+
when :FLOAT
|
|
314
|
+
advance
|
|
315
|
+
AST::FloatLiteral.new(token.value, line: token.line, column: token.column)
|
|
316
|
+
|
|
317
|
+
when :STRING
|
|
318
|
+
advance
|
|
319
|
+
AST::StringLiteral.new(token.value, line: token.line, column: token.column)
|
|
320
|
+
|
|
321
|
+
when :WIRE
|
|
322
|
+
advance
|
|
323
|
+
AST::Wire.new(line: token.line, column: token.column)
|
|
324
|
+
|
|
325
|
+
when :CUT
|
|
326
|
+
advance
|
|
327
|
+
AST::Cut.new(line: token.line, column: token.column)
|
|
328
|
+
|
|
329
|
+
when :MUL, :ADD, :SUB, :DIV, :MOD
|
|
330
|
+
# Could be prefix form *(0.5) or standalone primitive +
|
|
331
|
+
if peek&.type == :LPAREN
|
|
332
|
+
parse_prefix_operator
|
|
333
|
+
else
|
|
334
|
+
# Standalone primitive operator
|
|
335
|
+
token = advance
|
|
336
|
+
name = case token.type
|
|
337
|
+
when :MUL then "*"
|
|
338
|
+
when :ADD then "+"
|
|
339
|
+
when :SUB then "-"
|
|
340
|
+
when :DIV then "/"
|
|
341
|
+
when :MOD then "%"
|
|
342
|
+
end
|
|
343
|
+
AST::Identifier.new(name, line: token.line, column: token.column)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
when :IDENT
|
|
347
|
+
parse_identifier_or_call
|
|
348
|
+
|
|
349
|
+
when :LPAREN
|
|
350
|
+
parse_paren
|
|
351
|
+
|
|
352
|
+
when :LBRACE
|
|
353
|
+
parse_waveform_or_environment
|
|
354
|
+
|
|
355
|
+
when :LAMBDA
|
|
356
|
+
parse_lambda
|
|
357
|
+
|
|
358
|
+
when :PAR, :SEQ, :SUM, :PROD
|
|
359
|
+
parse_iteration
|
|
360
|
+
|
|
361
|
+
when :LETREC
|
|
362
|
+
parse_letrec_expr
|
|
363
|
+
|
|
364
|
+
when :CASE
|
|
365
|
+
parse_case_expr
|
|
366
|
+
|
|
367
|
+
else
|
|
368
|
+
error("Unexpected token #{current_type}")
|
|
369
|
+
nil
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def parse_prefix_operator
|
|
374
|
+
token = advance # consume the operator
|
|
375
|
+
op = token.type
|
|
376
|
+
args = parse_call_args # Parse arguments in parentheses
|
|
377
|
+
|
|
378
|
+
# Create a function call AST node for prefix operators
|
|
379
|
+
name = case op
|
|
380
|
+
when :MUL then "*"
|
|
381
|
+
when :ADD then "+"
|
|
382
|
+
when :SUB then "-"
|
|
383
|
+
when :DIV then "/"
|
|
384
|
+
end
|
|
385
|
+
AST::FunctionCall.new(name, args, line: token.line, column: token.column)
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
def parse_identifier_or_call
|
|
389
|
+
token = advance
|
|
390
|
+
name = token.value
|
|
391
|
+
|
|
392
|
+
# Check for UI elements
|
|
393
|
+
case name
|
|
394
|
+
when "hslider", "vslider", "nentry"
|
|
395
|
+
return parse_slider(name, token)
|
|
396
|
+
when "button", "checkbox"
|
|
397
|
+
return parse_button(name, token)
|
|
398
|
+
when "hgroup", "vgroup", "tgroup"
|
|
399
|
+
return parse_group(name, token)
|
|
400
|
+
when "rdtable", "rwtable"
|
|
401
|
+
return parse_table(name, token)
|
|
402
|
+
when "route"
|
|
403
|
+
return parse_route(token)
|
|
404
|
+
when "waveform"
|
|
405
|
+
return parse_waveform_call(token)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Simple identifier - qualified names handled in postfix
|
|
409
|
+
AST::Identifier.new(name, line: token.line, column: token.column)
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def parse_slider(type, token)
|
|
413
|
+
expect(:LPAREN)
|
|
414
|
+
label = expect(:STRING)&.value
|
|
415
|
+
expect(:PAR)
|
|
416
|
+
init = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
417
|
+
expect(:PAR)
|
|
418
|
+
min = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
419
|
+
expect(:PAR)
|
|
420
|
+
max = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
421
|
+
expect(:PAR)
|
|
422
|
+
step = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
423
|
+
expect(:RPAREN)
|
|
424
|
+
AST::UIElement.new(type.to_sym, label, init: init, min: min, max: max, step: step,
|
|
425
|
+
line: token.line, column: token.column)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
def parse_button(type, token)
|
|
429
|
+
expect(:LPAREN)
|
|
430
|
+
label = expect(:STRING)&.value
|
|
431
|
+
expect(:RPAREN)
|
|
432
|
+
AST::UIElement.new(type.to_sym, label, line: token.line, column: token.column)
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def parse_group(type, token)
|
|
436
|
+
expect(:LPAREN)
|
|
437
|
+
label = expect(:STRING)&.value
|
|
438
|
+
expect(:PAR)
|
|
439
|
+
content = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
440
|
+
expect(:RPAREN)
|
|
441
|
+
AST::UIGroup.new(type.to_sym, label, content, line: token.line, column: token.column)
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def parse_table(type, token)
|
|
445
|
+
args = parse_call_args
|
|
446
|
+
AST::Table.new(type.to_sym, args, line: token.line, column: token.column)
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
def parse_route(token)
|
|
450
|
+
expect(:LPAREN)
|
|
451
|
+
ins = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
452
|
+
expect(:PAR)
|
|
453
|
+
outs = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
454
|
+
connections = []
|
|
455
|
+
while current_type == :PAR
|
|
456
|
+
advance
|
|
457
|
+
expect(:LPAREN)
|
|
458
|
+
from = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
459
|
+
expect(:PAR)
|
|
460
|
+
to = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
461
|
+
expect(:RPAREN)
|
|
462
|
+
connections << [from, to]
|
|
463
|
+
end
|
|
464
|
+
expect(:RPAREN)
|
|
465
|
+
AST::Route.new(ins, outs, connections, line: token.line, column: token.column)
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def parse_waveform_call(token)
|
|
469
|
+
if current_type == :LBRACE
|
|
470
|
+
advance # consume {
|
|
471
|
+
values = []
|
|
472
|
+
until current_type == :RBRACE || current_type == :EOF
|
|
473
|
+
# Parse with minimum precedence above PAR to stop at commas
|
|
474
|
+
values << parse_expression(PRECEDENCE[:PAR] + 1)
|
|
475
|
+
break unless current_type == :PAR
|
|
476
|
+
advance
|
|
477
|
+
end
|
|
478
|
+
expect(:RBRACE)
|
|
479
|
+
AST::Waveform.new(values, line: token.line, column: token.column)
|
|
480
|
+
else
|
|
481
|
+
# Just an identifier named 'waveform'
|
|
482
|
+
AST::Identifier.new("waveform", line: token.line, column: token.column)
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
def parse_paren
|
|
487
|
+
token = advance # consume (
|
|
488
|
+
expr = parse_expression
|
|
489
|
+
expect(:RPAREN)
|
|
490
|
+
AST::Paren.new(expr, line: token.line, column: token.column)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def parse_waveform_or_environment
|
|
494
|
+
token = advance # consume {
|
|
495
|
+
values = []
|
|
496
|
+
until current_type == :RBRACE || current_type == :EOF
|
|
497
|
+
# Parse with minimum precedence above PAR to stop at commas
|
|
498
|
+
values << parse_expression(PRECEDENCE[:PAR] + 1)
|
|
499
|
+
break unless current_type == :PAR
|
|
500
|
+
advance
|
|
501
|
+
end
|
|
502
|
+
expect(:RBRACE)
|
|
503
|
+
AST::Waveform.new(values, line: token.line, column: token.column)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def parse_lambda
|
|
507
|
+
token = advance # consume \
|
|
508
|
+
expect(:LPAREN)
|
|
509
|
+
params = []
|
|
510
|
+
until current_type == :RPAREN || current_type == :EOF
|
|
511
|
+
param = expect(:IDENT)
|
|
512
|
+
params << param.value if param
|
|
513
|
+
break unless current_type == :PAR
|
|
514
|
+
advance
|
|
515
|
+
end
|
|
516
|
+
expect(:RPAREN)
|
|
517
|
+
expect(:DOT)
|
|
518
|
+
expect(:LPAREN)
|
|
519
|
+
body = parse_expression
|
|
520
|
+
expect(:RPAREN)
|
|
521
|
+
AST::Lambda.new(params, body, line: token.line, column: token.column)
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
def parse_iteration
|
|
525
|
+
token = advance # consume par/seq/sum/prod
|
|
526
|
+
type = token.value.to_sym
|
|
527
|
+
expect(:LPAREN)
|
|
528
|
+
var = expect(:IDENT)&.value
|
|
529
|
+
expect(:PAR)
|
|
530
|
+
count = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
531
|
+
expect(:PAR)
|
|
532
|
+
body = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
533
|
+
expect(:RPAREN)
|
|
534
|
+
AST::Iteration.new(type, var, count, body, line: token.line, column: token.column)
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
def parse_letrec_expr
|
|
538
|
+
token = advance # consume letrec
|
|
539
|
+
expect(:LBRACE)
|
|
540
|
+
definitions = []
|
|
541
|
+
until current_type == :RBRACE || current_type == :EOF
|
|
542
|
+
# Handle prime notation for state variables: 'x = expr;
|
|
543
|
+
has_prime = false
|
|
544
|
+
if current_type == :PRIME
|
|
545
|
+
has_prime = true
|
|
546
|
+
advance # consume '
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
if current_type == :IDENT
|
|
550
|
+
name = advance.value
|
|
551
|
+
name = "'#{name}" if has_prime # Mark as state variable
|
|
552
|
+
expect(:DEF)
|
|
553
|
+
expr = parse_expression
|
|
554
|
+
# Handle nested with
|
|
555
|
+
if current_type == :WITH
|
|
556
|
+
expr = parse_with_clause(expr)
|
|
557
|
+
end
|
|
558
|
+
expect(:ENDDEF)
|
|
559
|
+
definitions << AST::Definition.new(name, expr)
|
|
560
|
+
else
|
|
561
|
+
break
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
expect(:RBRACE)
|
|
565
|
+
AST::Letrec.new(definitions, nil, line: token.line, column: token.column)
|
|
566
|
+
end
|
|
567
|
+
|
|
568
|
+
# Parse case expression: case { (pattern) => expr; ... }
|
|
569
|
+
def parse_case_expr
|
|
570
|
+
token = advance # consume 'case'
|
|
571
|
+
expect(:LBRACE)
|
|
572
|
+
branches = []
|
|
573
|
+
|
|
574
|
+
until current_type == :RBRACE || current_type == :EOF
|
|
575
|
+
# Parse pattern: (pattern)
|
|
576
|
+
expect(:LPAREN)
|
|
577
|
+
pattern = parse_expression(PRECEDENCE[:PAR] + 1)
|
|
578
|
+
expect(:RPAREN)
|
|
579
|
+
|
|
580
|
+
# Parse arrow: =>
|
|
581
|
+
expect(:ARROW)
|
|
582
|
+
|
|
583
|
+
# Parse result expression
|
|
584
|
+
result = parse_expression
|
|
585
|
+
|
|
586
|
+
# End of branch: ;
|
|
587
|
+
expect(:ENDDEF)
|
|
588
|
+
|
|
589
|
+
branches << AST::CaseBranch.new(pattern, result, line: token.line, column: token.column)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
expect(:RBRACE)
|
|
593
|
+
AST::CaseExpr.new(branches, line: token.line, column: token.column)
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
end
|