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,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Faust2Ruby
4
+ # AST nodes for Faust DSP programs.
5
+ # These represent the parsed structure before conversion to Ruby2Faust IR.
6
+ module AST
7
+ # Base class for all AST nodes
8
+ class Node
9
+ attr_reader :line, :column
10
+
11
+ def initialize(line: nil, column: nil)
12
+ @line = line
13
+ @column = column
14
+ end
15
+ end
16
+
17
+ # Complete Faust program
18
+ class Program < Node
19
+ attr_reader :statements
20
+
21
+ def initialize(statements, **opts)
22
+ super(**opts)
23
+ @statements = statements
24
+ end
25
+ end
26
+
27
+ # Import statement: import("stdfaust.lib");
28
+ class Import < Node
29
+ attr_reader :path
30
+
31
+ def initialize(path, **opts)
32
+ super(**opts)
33
+ @path = path
34
+ end
35
+ end
36
+
37
+ # Declare statement: declare name "author";
38
+ class Declare < Node
39
+ attr_reader :key, :value
40
+
41
+ def initialize(key, value, **opts)
42
+ super(**opts)
43
+ @key = key
44
+ @value = value
45
+ end
46
+ end
47
+
48
+ # Definition: name = expression;
49
+ class Definition < Node
50
+ attr_reader :name, :params, :expression
51
+
52
+ def initialize(name, expression, params: [], **opts)
53
+ super(**opts)
54
+ @name = name
55
+ @params = params
56
+ @expression = expression
57
+ end
58
+ end
59
+
60
+ # Binary operation: left OP right
61
+ class BinaryOp < Node
62
+ attr_reader :op, :left, :right
63
+
64
+ def initialize(op, left, right, **opts)
65
+ super(**opts)
66
+ @op = op
67
+ @left = left
68
+ @right = right
69
+ end
70
+ end
71
+
72
+ # Unary operation: OP operand (e.g., negation)
73
+ class UnaryOp < Node
74
+ attr_reader :op, :operand
75
+
76
+ def initialize(op, operand, **opts)
77
+ super(**opts)
78
+ @op = op
79
+ @operand = operand
80
+ end
81
+ end
82
+
83
+ # Function call: func(arg1, arg2, ...)
84
+ class FunctionCall < Node
85
+ attr_reader :name, :args
86
+
87
+ def initialize(name, args, **opts)
88
+ super(**opts)
89
+ @name = name
90
+ @args = args
91
+ end
92
+ end
93
+
94
+ # Qualified name: os.osc, fi.lowpass, etc.
95
+ class QualifiedName < Node
96
+ attr_reader :parts
97
+
98
+ def initialize(parts, **opts)
99
+ super(**opts)
100
+ @parts = parts
101
+ end
102
+
103
+ def to_s
104
+ parts.join(".")
105
+ end
106
+ end
107
+
108
+ # Identifier reference
109
+ class Identifier < Node
110
+ attr_reader :name
111
+
112
+ def initialize(name, **opts)
113
+ super(**opts)
114
+ @name = name
115
+ end
116
+ end
117
+
118
+ # Integer literal
119
+ class IntLiteral < Node
120
+ attr_reader :value
121
+
122
+ def initialize(value, **opts)
123
+ super(**opts)
124
+ @value = value
125
+ end
126
+ end
127
+
128
+ # Float literal
129
+ class FloatLiteral < Node
130
+ attr_reader :value
131
+
132
+ def initialize(value, **opts)
133
+ super(**opts)
134
+ @value = value
135
+ end
136
+ end
137
+
138
+ # String literal
139
+ class StringLiteral < Node
140
+ attr_reader :value
141
+
142
+ def initialize(value, **opts)
143
+ super(**opts)
144
+ @value = value
145
+ end
146
+ end
147
+
148
+ # Wire primitive: _
149
+ class Wire < Node
150
+ end
151
+
152
+ # Cut primitive: !
153
+ class Cut < Node
154
+ end
155
+
156
+ # Iteration: par(i, n, expr), seq(i, n, expr), etc.
157
+ class Iteration < Node
158
+ attr_reader :type, :var, :count, :body
159
+
160
+ def initialize(type, var, count, body, **opts)
161
+ super(**opts)
162
+ @type = type # :par, :seq, :sum, :prod
163
+ @var = var
164
+ @count = count
165
+ @body = body
166
+ end
167
+ end
168
+
169
+ # Lambda: \(x, y).(body)
170
+ class Lambda < Node
171
+ attr_reader :params, :body
172
+
173
+ def initialize(params, body, **opts)
174
+ super(**opts)
175
+ @params = params
176
+ @body = body
177
+ end
178
+ end
179
+
180
+ # With clause: expr with { defs }
181
+ class With < Node
182
+ attr_reader :expression, :definitions
183
+
184
+ def initialize(expression, definitions, **opts)
185
+ super(**opts)
186
+ @expression = expression
187
+ @definitions = definitions
188
+ end
189
+ end
190
+
191
+ # Letrec clause: letrec { defs }
192
+ class Letrec < Node
193
+ attr_reader :definitions, :expression
194
+
195
+ def initialize(definitions, expression, **opts)
196
+ super(**opts)
197
+ @definitions = definitions
198
+ @expression = expression
199
+ end
200
+ end
201
+
202
+ # UI element: hslider, vslider, nentry, button, checkbox
203
+ class UIElement < Node
204
+ attr_reader :type, :label, :init, :min, :max, :step
205
+
206
+ def initialize(type, label, init: nil, min: nil, max: nil, step: nil, **opts)
207
+ super(**opts)
208
+ @type = type
209
+ @label = label
210
+ @init = init
211
+ @min = min
212
+ @max = max
213
+ @step = step
214
+ end
215
+ end
216
+
217
+ # UI group: hgroup, vgroup, tgroup
218
+ class UIGroup < Node
219
+ attr_reader :type, :label, :content
220
+
221
+ def initialize(type, label, content, **opts)
222
+ super(**opts)
223
+ @type = type
224
+ @label = label
225
+ @content = content
226
+ end
227
+ end
228
+
229
+ # Waveform: waveform{v1, v2, ...}
230
+ class Waveform < Node
231
+ attr_reader :values
232
+
233
+ def initialize(values, **opts)
234
+ super(**opts)
235
+ @values = values
236
+ end
237
+ end
238
+
239
+ # Table operations: rdtable, rwtable
240
+ class Table < Node
241
+ attr_reader :type, :args
242
+
243
+ def initialize(type, args, **opts)
244
+ super(**opts)
245
+ @type = type
246
+ @args = args
247
+ end
248
+ end
249
+
250
+ # Delay with prime: expr'
251
+ class Prime < Node
252
+ attr_reader :operand
253
+
254
+ def initialize(operand, **opts)
255
+ super(**opts)
256
+ @operand = operand
257
+ end
258
+ end
259
+
260
+ # Access with brackets: expr[n]
261
+ class Access < Node
262
+ attr_reader :operand, :index
263
+
264
+ def initialize(operand, index, **opts)
265
+ super(**opts)
266
+ @operand = operand
267
+ @index = index
268
+ end
269
+ end
270
+
271
+ # Parenthesized expression
272
+ class Paren < Node
273
+ attr_reader :expression
274
+
275
+ def initialize(expression, **opts)
276
+ super(**opts)
277
+ @expression = expression
278
+ end
279
+ end
280
+
281
+ # Route: route(ins, outs, connections)
282
+ class Route < Node
283
+ attr_reader :ins, :outs, :connections
284
+
285
+ def initialize(ins, outs, connections, **opts)
286
+ super(**opts)
287
+ @ins = ins
288
+ @outs = outs
289
+ @connections = connections
290
+ end
291
+ end
292
+
293
+ # Case expression: case { (pattern) => expr; ... }
294
+ # Each branch is a CaseBranch with pattern and result
295
+ class CaseExpr < Node
296
+ attr_reader :branches
297
+
298
+ def initialize(branches, **opts)
299
+ super(**opts)
300
+ @branches = branches
301
+ end
302
+ end
303
+
304
+ # A single branch in a case expression
305
+ class CaseBranch < Node
306
+ attr_reader :pattern, :result
307
+
308
+ def initialize(pattern, result, **opts)
309
+ super(**opts)
310
+ @pattern = pattern # Can be IntLiteral, Identifier (variable), or Paren
311
+ @result = result
312
+ end
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,413 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ast"
4
+ require_relative "library_mapper"
5
+ require_relative "../ruby2faust/ir"
6
+
7
+ module Faust2Ruby
8
+ # Converts Faust AST nodes to Ruby2Faust IR nodes.
9
+ # This enables semantic analysis and Ruby code generation.
10
+ class IRBuilder
11
+ Node = Ruby2Faust::Node
12
+ NodeType = Ruby2Faust::NodeType
13
+
14
+ def initialize
15
+ @definitions = {} # name => AST::Definition
16
+ @errors = []
17
+ end
18
+
19
+ attr_reader :errors
20
+
21
+ # Build IR from a parsed program
22
+ # Returns a hash with :process (the main process IR), :imports, :declares
23
+ def build(program)
24
+ # First pass: collect all definitions
25
+ program.statements.each do |stmt|
26
+ case stmt
27
+ when AST::Definition
28
+ @definitions[stmt.name] = stmt
29
+ end
30
+ end
31
+
32
+ # Find process definition
33
+ process_def = @definitions["process"]
34
+ unless process_def
35
+ @errors << "No 'process' definition found"
36
+ return nil
37
+ end
38
+
39
+ # Build IR for process
40
+ process_ir = build_expression(process_def.expression)
41
+
42
+ # Collect imports and declares
43
+ imports = program.statements.select { |s| s.is_a?(AST::Import) }.map(&:path)
44
+ declares = program.statements.select { |s| s.is_a?(AST::Declare) }.to_h { |d| [d.key, d.value] }
45
+
46
+ {
47
+ process: process_ir,
48
+ imports: imports,
49
+ declares: declares,
50
+ definitions: @definitions.reject { |k, _| k == "process" }
51
+ }
52
+ end
53
+
54
+ private
55
+
56
+ def build_expression(node)
57
+ case node
58
+ when AST::IntLiteral
59
+ Node.new(type: NodeType::LITERAL, args: [node.value.to_s])
60
+
61
+ when AST::FloatLiteral
62
+ Node.new(type: NodeType::LITERAL, args: [node.value.to_s])
63
+
64
+ when AST::StringLiteral
65
+ # Strings in expressions are typically for UI labels
66
+ Node.new(type: NodeType::LITERAL, args: ["\"#{node.value}\""])
67
+
68
+ when AST::Wire
69
+ Node.new(type: NodeType::WIRE)
70
+
71
+ when AST::Cut
72
+ Node.new(type: NodeType::CUT, channels: 0)
73
+
74
+ when AST::Identifier
75
+ build_identifier(node)
76
+
77
+ when AST::QualifiedName
78
+ build_qualified_name(node)
79
+
80
+ when AST::BinaryOp
81
+ build_binary_op(node)
82
+
83
+ when AST::UnaryOp
84
+ build_unary_op(node)
85
+
86
+ when AST::FunctionCall
87
+ build_function_call(node)
88
+
89
+ when AST::UIElement
90
+ build_ui_element(node)
91
+
92
+ when AST::UIGroup
93
+ build_ui_group(node)
94
+
95
+ when AST::Iteration
96
+ build_iteration(node)
97
+
98
+ when AST::Lambda
99
+ build_lambda(node)
100
+
101
+ when AST::Waveform
102
+ build_waveform(node)
103
+
104
+ when AST::Table
105
+ build_table(node)
106
+
107
+ when AST::Route
108
+ build_route(node)
109
+
110
+ when AST::Prime
111
+ build_prime(node)
112
+
113
+ when AST::Access
114
+ build_access(node)
115
+
116
+ when AST::Paren
117
+ build_expression(node.expression)
118
+
119
+ when AST::With
120
+ build_with(node)
121
+
122
+ when AST::Letrec
123
+ build_letrec(node)
124
+
125
+ else
126
+ @errors << "Unknown AST node type: #{node.class}"
127
+ Node.new(type: NodeType::LITERAL, args: ["/* unknown */"])
128
+ end
129
+ end
130
+
131
+ def build_identifier(node)
132
+ name = node.name
133
+
134
+ # Check if it's a known primitive
135
+ if LibraryMapper::PRIMITIVES.key?(name)
136
+ mapping = LibraryMapper::PRIMITIVES[name]
137
+ if mapping[:args] == 0
138
+ # Zero-arg primitive (used as prefix operator)
139
+ Node.new(type: symbol_to_node_type(mapping[:dsl]))
140
+ else
141
+ # Primitive that needs args - return as literal
142
+ Node.new(type: NodeType::LITERAL, args: [name])
143
+ end
144
+ elsif @definitions.key?(name)
145
+ # Reference to a local definition
146
+ build_expression(@definitions[name].expression)
147
+ else
148
+ # Unknown identifier - output as literal
149
+ Node.new(type: NodeType::LITERAL, args: [name])
150
+ end
151
+ end
152
+
153
+ def build_qualified_name(node)
154
+ name = node.to_s
155
+
156
+ # Check library mapping
157
+ if LibraryMapper::MAPPINGS.key?(name)
158
+ mapping = LibraryMapper::MAPPINGS[name]
159
+ if mapping[:args] == 0
160
+ # Zero-arg library function
161
+ Node.new(type: symbol_to_node_type(mapping[:dsl]))
162
+ else
163
+ # Function that needs args - return as literal for now
164
+ Node.new(type: NodeType::LITERAL, args: [name])
165
+ end
166
+ else
167
+ Node.new(type: NodeType::LITERAL, args: [name])
168
+ end
169
+ end
170
+
171
+ def build_binary_op(node)
172
+ left = build_expression(node.left)
173
+ right = build_expression(node.right)
174
+
175
+ type = case node.op
176
+ when :SEQ then NodeType::SEQ
177
+ when :PAR then NodeType::PAR
178
+ when :SPLIT then NodeType::SPLIT
179
+ when :MERGE then NodeType::MERGE
180
+ when :REC then NodeType::FEEDBACK
181
+ when :ADD then NodeType::ADD
182
+ when :SUB then NodeType::SUB
183
+ when :MUL then NodeType::MUL
184
+ when :DIV then NodeType::DIV
185
+ when :MOD then NodeType::LITERAL # Handle modulo
186
+ when :POW then NodeType::POW
187
+ when :DELAY then NodeType::DELAY # @ operator
188
+ else
189
+ @errors << "Unknown binary operator: #{node.op}"
190
+ NodeType::LITERAL
191
+ end
192
+
193
+ if type == NodeType::LITERAL
194
+ # Fallback for unknown ops
195
+ op_str = node.op.to_s.downcase
196
+ Node.new(type: NodeType::LITERAL, args: ["(#{emit_ir(left)} #{op_str} #{emit_ir(right)})"])
197
+ elsif type == NodeType::SPLIT
198
+ # Split takes source + multiple targets, but binary op only has 2
199
+ Node.new(type: type, inputs: [left, right], channels: right.channels)
200
+ else
201
+ # Calculate channels for composition operators
202
+ channels = case type
203
+ when NodeType::SEQ then right.channels
204
+ when NodeType::PAR then (left.channels || 1) + (right.channels || 1)
205
+ when NodeType::MERGE then right.channels
206
+ when NodeType::FEEDBACK then left.channels
207
+ else 1
208
+ end
209
+ Node.new(type: type, inputs: [left, right], channels: channels)
210
+ end
211
+ end
212
+
213
+ def build_unary_op(node)
214
+ operand = build_expression(node.operand)
215
+
216
+ case node.op
217
+ when :NEG
218
+ Node.new(type: NodeType::NEG, inputs: [operand])
219
+ else
220
+ @errors << "Unknown unary operator: #{node.op}"
221
+ operand
222
+ end
223
+ end
224
+
225
+ def build_function_call(node)
226
+ name = node.name
227
+ args = node.args.map { |a| build_expression(a) }
228
+
229
+ # Check library mapping
230
+ mapping = LibraryMapper.lookup(name)
231
+ if mapping
232
+ build_mapped_function(mapping, args)
233
+ elsif LibraryMapper.ui_element?(name)
234
+ # This shouldn't happen - UI elements parsed separately
235
+ Node.new(type: NodeType::LITERAL, args: ["#{name}(...)"])
236
+ else
237
+ # Unknown function - emit as literal
238
+ args_str = args.map { |a| emit_ir(a) }.join(", ")
239
+ Node.new(type: NodeType::LITERAL, args: ["#{name}(#{args_str})"])
240
+ end
241
+ end
242
+
243
+ def build_mapped_function(mapping, args)
244
+ dsl_method = mapping[:dsl]
245
+ type = symbol_to_node_type(dsl_method)
246
+
247
+ if type
248
+ # Handle special cases with opts
249
+ if mapping[:opts]
250
+ # E.g., lowpass where first arg is order
251
+ opts = mapping[:opts]
252
+ order_idx = opts[:order]
253
+ if order_idx
254
+ order = args[order_idx]
255
+ remaining = args.dup
256
+ remaining.delete_at(order_idx)
257
+ Node.new(type: type, args: [emit_ir(order).to_i], inputs: remaining)
258
+ else
259
+ Node.new(type: type, inputs: args)
260
+ end
261
+ else
262
+ Node.new(type: type, inputs: args)
263
+ end
264
+ else
265
+ # No direct type mapping - use literal
266
+ Node.new(type: NodeType::LITERAL, args: [dsl_method.to_s])
267
+ end
268
+ end
269
+
270
+ def build_ui_element(node)
271
+ case node.type
272
+ when :hslider
273
+ init = build_expression(node.init)
274
+ min = build_expression(node.min)
275
+ max = build_expression(node.max)
276
+ step = build_expression(node.step)
277
+ Node.new(type: NodeType::SLIDER, args: [
278
+ node.label,
279
+ emit_ir(init),
280
+ emit_ir(min),
281
+ emit_ir(max),
282
+ emit_ir(step)
283
+ ])
284
+ when :vslider
285
+ init = build_expression(node.init)
286
+ min = build_expression(node.min)
287
+ max = build_expression(node.max)
288
+ step = build_expression(node.step)
289
+ Node.new(type: NodeType::VSLIDER, args: [
290
+ node.label,
291
+ emit_ir(init),
292
+ emit_ir(min),
293
+ emit_ir(max),
294
+ emit_ir(step)
295
+ ])
296
+ when :nentry
297
+ init = build_expression(node.init)
298
+ min = build_expression(node.min)
299
+ max = build_expression(node.max)
300
+ step = build_expression(node.step)
301
+ Node.new(type: NodeType::NENTRY, args: [
302
+ node.label,
303
+ emit_ir(init),
304
+ emit_ir(min),
305
+ emit_ir(max),
306
+ emit_ir(step)
307
+ ])
308
+ when :button
309
+ Node.new(type: NodeType::BUTTON, args: [node.label])
310
+ when :checkbox
311
+ Node.new(type: NodeType::CHECKBOX, args: [node.label])
312
+ end
313
+ end
314
+
315
+ def build_ui_group(node)
316
+ content = build_expression(node.content)
317
+ type = case node.type
318
+ when :hgroup then NodeType::HGROUP
319
+ when :vgroup then NodeType::VGROUP
320
+ when :tgroup then NodeType::TGROUP
321
+ end
322
+ Node.new(type: type, args: [node.label], inputs: [content], channels: content.channels)
323
+ end
324
+
325
+ def build_iteration(node)
326
+ type = case node.type
327
+ when :par then NodeType::FPAR
328
+ when :seq then NodeType::FSEQ
329
+ when :sum then NodeType::FSUM
330
+ when :prod then NodeType::FPROD
331
+ end
332
+
333
+ # Store the body AST for later evaluation
334
+ # We'll need to convert this to a block during Ruby generation
335
+ count = build_expression(node.count)
336
+
337
+ Node.new(
338
+ type: type,
339
+ args: [node.var.to_sym, emit_ir(count), node.body], # Keep AST body for later
340
+ channels: type == NodeType::FPAR ? emit_ir(count).to_i : 1
341
+ )
342
+ end
343
+
344
+ def build_lambda(node)
345
+ # Store params and body AST
346
+ Node.new(
347
+ type: NodeType::LAMBDA,
348
+ args: [node.params.map(&:to_sym), node.body] # Keep AST body for later
349
+ )
350
+ end
351
+
352
+ def build_waveform(node)
353
+ values = node.values.map { |v| emit_ir(build_expression(v)) }
354
+ Node.new(type: NodeType::WAVEFORM, args: values)
355
+ end
356
+
357
+ def build_table(node)
358
+ args = node.args.map { |a| build_expression(a) }
359
+ type = node.type == :rdtable ? NodeType::RDTABLE : NodeType::RWTABLE
360
+ Node.new(type: type, inputs: args)
361
+ end
362
+
363
+ def build_route(node)
364
+ ins = emit_ir(build_expression(node.ins))
365
+ outs = emit_ir(build_expression(node.outs))
366
+ connections = node.connections.map do |from, to|
367
+ [emit_ir(build_expression(from)), emit_ir(build_expression(to))]
368
+ end
369
+ Node.new(type: NodeType::ROUTE, args: [ins.to_i, outs.to_i, connections], channels: outs.to_i)
370
+ end
371
+
372
+ def build_prime(node)
373
+ # Prime (') is one-sample delay, equivalent to mem
374
+ operand = build_expression(node.operand)
375
+ Node.new(type: NodeType::SEQ, inputs: [operand, Node.new(type: NodeType::MEM)])
376
+ end
377
+
378
+ def build_access(node)
379
+ # Bracket access - often for component outputs
380
+ operand = build_expression(node.operand)
381
+ index = build_expression(node.index)
382
+ Node.new(type: NodeType::LITERAL, args: ["#{emit_ir(operand)}[#{emit_ir(index)}]"])
383
+ end
384
+
385
+ def build_with(node)
386
+ # With clauses define local scope - for now, inline
387
+ build_expression(node.expression)
388
+ end
389
+
390
+ def build_letrec(node)
391
+ # Letrec for recursive definitions - complex, emit as literal for now
392
+ Node.new(type: NodeType::REC, args: [node])
393
+ end
394
+
395
+ # Convert DSL method symbol to NodeType
396
+ def symbol_to_node_type(sym)
397
+ const_name = sym.to_s.upcase.gsub(/_$/, "")
398
+ Ruby2Faust::NodeType.const_get(const_name) rescue nil
399
+ end
400
+
401
+ # Simple IR to string for embedding in literals
402
+ def emit_ir(node)
403
+ return node.to_s unless node.is_a?(Node)
404
+
405
+ case node.type
406
+ when NodeType::LITERAL then node.args[0].to_s
407
+ when NodeType::WIRE then "_"
408
+ when NodeType::CUT then "!"
409
+ else node.args[0].to_s rescue "?"
410
+ end
411
+ end
412
+ end
413
+ end