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,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
|