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.
@@ -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