rlsl 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.
@@ -0,0 +1,373 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ module IR
6
+ class Node
7
+ attr_accessor :type
8
+
9
+ def accept(visitor)
10
+ raise NotImplementedError
11
+ end
12
+ end
13
+
14
+ class Block < Node
15
+ attr_reader :statements
16
+
17
+ def initialize(statements = [])
18
+ super()
19
+ @statements = statements
20
+ end
21
+
22
+ def accept(visitor)
23
+ visitor.visit_block(self)
24
+ end
25
+ end
26
+
27
+ class VarDecl < Node
28
+ attr_reader :name, :initializer
29
+
30
+ def initialize(name, initializer, type = nil)
31
+ super()
32
+ @name = name
33
+ @initializer = initializer
34
+ @type = type
35
+ end
36
+
37
+ def accept(visitor)
38
+ visitor.visit_var_decl(self)
39
+ end
40
+ end
41
+
42
+ class VarRef < Node
43
+ attr_reader :name
44
+
45
+ def initialize(name, type = nil)
46
+ super()
47
+ @name = name
48
+ @type = type
49
+ end
50
+
51
+ def accept(visitor)
52
+ visitor.visit_var_ref(self)
53
+ end
54
+ end
55
+
56
+ class Literal < Node
57
+ attr_reader :value
58
+
59
+ def initialize(value, type = nil)
60
+ super()
61
+ @value = value
62
+ @type = type || (value.is_a?(Float) ? :float : :int)
63
+ end
64
+
65
+ def accept(visitor)
66
+ visitor.visit_literal(self)
67
+ end
68
+ end
69
+
70
+ class BoolLiteral < Node
71
+ attr_reader :value
72
+
73
+ def initialize(value)
74
+ super()
75
+ @value = value
76
+ @type = :bool
77
+ end
78
+
79
+ def accept(visitor)
80
+ visitor.visit_bool_literal(self)
81
+ end
82
+ end
83
+
84
+ class BinaryOp < Node
85
+ attr_reader :operator, :left, :right
86
+
87
+ def initialize(operator, left, right, type = nil)
88
+ super()
89
+ @operator = operator
90
+ @left = left
91
+ @right = right
92
+ @type = type
93
+ end
94
+
95
+ def accept(visitor)
96
+ visitor.visit_binary_op(self)
97
+ end
98
+ end
99
+
100
+ class UnaryOp < Node
101
+ attr_reader :operator, :operand
102
+
103
+ def initialize(operator, operand, type = nil)
104
+ super()
105
+ @operator = operator
106
+ @operand = operand
107
+ @type = type
108
+ end
109
+
110
+ def accept(visitor)
111
+ visitor.visit_unary_op(self)
112
+ end
113
+ end
114
+
115
+ class FuncCall < Node
116
+ attr_reader :name, :args, :receiver
117
+
118
+ def initialize(name, args = [], receiver = nil, type = nil)
119
+ super()
120
+ @name = name
121
+ @args = args
122
+ @receiver = receiver
123
+ @type = type
124
+ end
125
+
126
+ def accept(visitor)
127
+ visitor.visit_func_call(self)
128
+ end
129
+ end
130
+
131
+ class FieldAccess < Node
132
+ attr_reader :receiver, :field
133
+
134
+ def initialize(receiver, field, type = nil)
135
+ super()
136
+ @receiver = receiver
137
+ @field = field
138
+ @type = type
139
+ end
140
+
141
+ def accept(visitor)
142
+ visitor.visit_field_access(self)
143
+ end
144
+ end
145
+
146
+ class Swizzle < Node
147
+ attr_reader :receiver, :components
148
+
149
+ def initialize(receiver, components, type = nil)
150
+ super()
151
+ @receiver = receiver
152
+ @components = components
153
+ @type = type
154
+ end
155
+
156
+ def accept(visitor)
157
+ visitor.visit_swizzle(self)
158
+ end
159
+ end
160
+
161
+ class IfStatement < Node
162
+ attr_reader :condition, :then_branch, :else_branch
163
+
164
+ def initialize(condition, then_branch, else_branch = nil, type = nil)
165
+ super()
166
+ @condition = condition
167
+ @then_branch = then_branch
168
+ @else_branch = else_branch
169
+ @type = type
170
+ end
171
+
172
+ def accept(visitor)
173
+ visitor.visit_if_statement(self)
174
+ end
175
+ end
176
+
177
+ class Return < Node
178
+ attr_reader :expression
179
+
180
+ def initialize(expression)
181
+ super()
182
+ @expression = expression
183
+ @type = expression&.type
184
+ end
185
+
186
+ def accept(visitor)
187
+ visitor.visit_return(self)
188
+ end
189
+ end
190
+
191
+ class Assignment < Node
192
+ attr_reader :target, :value
193
+
194
+ def initialize(target, value)
195
+ super()
196
+ @target = target
197
+ @value = value
198
+ @type = value&.type
199
+ end
200
+
201
+ def accept(visitor)
202
+ visitor.visit_assignment(self)
203
+ end
204
+ end
205
+
206
+ class ForLoop < Node
207
+ attr_reader :variable, :range_start, :range_end, :body
208
+
209
+ def initialize(variable, range_start, range_end, body)
210
+ super()
211
+ @variable = variable
212
+ @range_start = range_start
213
+ @range_end = range_end
214
+ @body = body
215
+ @type = nil
216
+ end
217
+
218
+ def accept(visitor)
219
+ visitor.visit_for_loop(self)
220
+ end
221
+ end
222
+
223
+ class WhileLoop < Node
224
+ attr_reader :condition, :body
225
+
226
+ def initialize(condition, body)
227
+ super()
228
+ @condition = condition
229
+ @body = body
230
+ @type = nil
231
+ end
232
+
233
+ def accept(visitor)
234
+ visitor.visit_while_loop(self)
235
+ end
236
+ end
237
+
238
+ class Break < Node
239
+ def initialize
240
+ super()
241
+ @type = nil
242
+ end
243
+
244
+ def accept(visitor)
245
+ visitor.visit_break(self)
246
+ end
247
+ end
248
+
249
+ class Constant < Node
250
+ attr_reader :name
251
+
252
+ def initialize(name, type = :float)
253
+ super()
254
+ @name = name
255
+ @type = type
256
+ end
257
+
258
+ def accept(visitor)
259
+ visitor.visit_constant(self)
260
+ end
261
+ end
262
+
263
+ class Parenthesized < Node
264
+ attr_reader :expression
265
+
266
+ def initialize(expression)
267
+ super()
268
+ @expression = expression
269
+ @type = expression&.type
270
+ end
271
+
272
+ def accept(visitor)
273
+ visitor.visit_parenthesized(self)
274
+ end
275
+ end
276
+
277
+ class ArrayLiteral < Node
278
+ attr_reader :elements
279
+
280
+ def initialize(elements, type = nil)
281
+ super()
282
+ @elements = elements
283
+ @type = type
284
+ end
285
+
286
+ def accept(visitor)
287
+ visitor.visit_array_literal(self)
288
+ end
289
+ end
290
+
291
+ class ArrayIndex < Node
292
+ attr_reader :array, :index
293
+
294
+ def initialize(array, index, type = nil)
295
+ super()
296
+ @array = array
297
+ @index = index
298
+ @type = type
299
+ end
300
+
301
+ def accept(visitor)
302
+ visitor.visit_array_index(self)
303
+ end
304
+ end
305
+
306
+ class GlobalDecl < Node
307
+ attr_reader :name, :initializer
308
+ attr_accessor :is_const, :is_static, :array_size, :element_type
309
+
310
+ def initialize(name, initializer, type: nil, is_const: false, is_static: true, array_size: nil, element_type: nil)
311
+ super()
312
+ @name = name
313
+ @initializer = initializer
314
+ @type = type
315
+ @is_const = is_const
316
+ @is_static = is_static
317
+ @array_size = array_size
318
+ @element_type = element_type
319
+ end
320
+
321
+ def accept(visitor)
322
+ visitor.visit_global_decl(self)
323
+ end
324
+ end
325
+
326
+ class FunctionDefinition < Node
327
+ attr_reader :name, :params, :body
328
+ attr_accessor :return_type, :param_types
329
+
330
+ def initialize(name, params, body, return_type: nil, param_types: {})
331
+ super()
332
+ @name = name
333
+ @params = params
334
+ @body = body
335
+ @return_type = return_type
336
+ @param_types = param_types
337
+ @type = return_type
338
+ end
339
+
340
+ def accept(visitor)
341
+ visitor.visit_function_definition(self)
342
+ end
343
+ end
344
+
345
+ class MultipleAssignment < Node
346
+ attr_reader :targets, :value
347
+
348
+ def initialize(targets, value)
349
+ super()
350
+ @targets = targets
351
+ @value = value
352
+ @type = nil
353
+ end
354
+
355
+ def accept(visitor)
356
+ visitor.visit_multiple_assignment(self)
357
+ end
358
+ end
359
+
360
+ class TupleType
361
+ attr_reader :types
362
+
363
+ def initialize(*types)
364
+ @types = types
365
+ end
366
+
367
+ def to_sym
368
+ :"tuple_#{types.map(&:to_s).join('_')}"
369
+ end
370
+ end
371
+ end
372
+ end
373
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class SourceExtractor
6
+ class SourceNotAvailable < StandardError; end
7
+
8
+ def extract(block)
9
+ file, line_num = block.source_location
10
+ raise SourceNotAvailable, "Block source location not available" unless file && File.exist?(file)
11
+
12
+ lines = File.readlines(file)
13
+ extract_block_source(lines, line_num - 1)
14
+ end
15
+
16
+ def extract_from_string(source)
17
+ source
18
+ end
19
+
20
+ private
21
+
22
+ def extract_block_source(lines, start_line)
23
+ source = +""
24
+ depth = 0
25
+ in_block = false
26
+ block_start_found = false
27
+
28
+ lines[start_line..].each_with_index do |line, idx|
29
+ tokens = tokenize_for_blocks(line)
30
+
31
+ tokens.each do |token|
32
+ case token
33
+ when :do, :brace_open
34
+ if !block_start_found
35
+ block_start_found = true
36
+ in_block = true
37
+ end
38
+ depth += 1
39
+ when :block_start
40
+ depth += 1
41
+ when :end, :brace_close
42
+ depth -= 1
43
+ end
44
+ end
45
+
46
+ if block_start_found
47
+ if idx == 0
48
+ source << extract_first_line(line)
49
+ else
50
+ source << line
51
+ end
52
+ end
53
+
54
+ break if in_block && depth == 0
55
+ end
56
+
57
+ clean_block_source(source)
58
+ end
59
+
60
+ def tokenize_for_blocks(line)
61
+ tokens = []
62
+ in_string = nil
63
+ i = 0
64
+
65
+ while i < line.length
66
+ char = line[i]
67
+
68
+ if in_string
69
+ if char == in_string && (i == 0 || line[i - 1] != "\\")
70
+ in_string = nil
71
+ end
72
+ i += 1
73
+ next
74
+ end
75
+
76
+ if char == '"' || char == "'"
77
+ in_string = char
78
+ i += 1
79
+ next
80
+ end
81
+
82
+ break if char == "#"
83
+
84
+ prev_is_boundary = i == 0 || !line[i - 1].match?(/[a-zA-Z0-9_]/)
85
+
86
+ if char == "{"
87
+ tokens << :brace_open
88
+ elsif char == "}"
89
+ tokens << :brace_close
90
+ elsif prev_is_boundary && line[i..].match?(/\Ado\b/)
91
+ tokens << :do
92
+ i += 1
93
+ elsif prev_is_boundary && line[i..].match?(/\Aelsif\b/)
94
+ i += 4
95
+ elsif prev_is_boundary && line[i..].match?(/\Aelse\b/)
96
+ i += 3
97
+ elsif prev_is_boundary && (m = line[i..].match(/\A(if|unless|while|for|case|def|class|module)\b/))
98
+ tokens << :block_start
99
+ i += m[1].length - 1
100
+ elsif prev_is_boundary && line[i..].match?(/\Aend\b/)
101
+ tokens << :end
102
+ i += 2
103
+ end
104
+
105
+ i += 1
106
+ end
107
+
108
+ tokens
109
+ end
110
+
111
+ def extract_first_line(line)
112
+ if line.include?(" do")
113
+ match = line.match(/do\s*(\|[^|]*\|)?\s*(.*)$/)
114
+ if match
115
+ params = match[1] || ""
116
+ rest = match[2] || ""
117
+ "#{params}\n#{rest}\n"
118
+ else
119
+ "\n"
120
+ end
121
+ elsif line.include?("{")
122
+ match = line.match(/\{\s*(\|[^|]*\|)?\s*(.*)$/)
123
+ if match
124
+ params = match[1] || ""
125
+ rest = match[2] || ""
126
+ "#{params}\n#{rest}\n"
127
+ else
128
+ "\n"
129
+ end
130
+ else
131
+ line
132
+ end
133
+ end
134
+
135
+ def clean_block_source(source)
136
+ lines = source.lines
137
+ return "" if lines.empty?
138
+
139
+ last_line = lines.last.strip
140
+ if last_line == "end" || last_line == "}"
141
+ lines.pop
142
+ elsif last_line.end_with?("end") || last_line.end_with?("}")
143
+ lines[-1] = lines[-1].sub(/\s*(end|\})\s*$/, "\n")
144
+ end
145
+
146
+ lines.shift if lines.first&.strip&.empty?
147
+
148
+ lines.join
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "ir/nodes"
6
+ require_relative "source_extractor"
7
+ require_relative "builtins"
8
+ require_relative "ast_visitor"
9
+ require_relative "type_inference"
10
+ require_relative "emitters/base_emitter"
11
+ require_relative "emitters/c_emitter"
12
+ require_relative "emitters/msl_emitter"
13
+ require_relative "emitters/wgsl_emitter"
14
+ require_relative "emitters/glsl_emitter"
15
+
16
+ module RLSL
17
+ module Prism
18
+ class Transpiler
19
+ TARGETS = {
20
+ c: Emitters::CEmitter,
21
+ msl: Emitters::MSLEmitter,
22
+ wgsl: Emitters::WGSLEmitter,
23
+ glsl: Emitters::GLSLEmitter
24
+ }.freeze
25
+
26
+ attr_reader :ir, :uniforms, :custom_functions
27
+
28
+ def initialize(uniforms = {}, custom_functions = {})
29
+ @uniforms = uniforms
30
+ @custom_functions = custom_functions
31
+ @source_extractor = SourceExtractor.new
32
+ @ir = nil
33
+ end
34
+
35
+ def parse_block(block)
36
+ source = @source_extractor.extract(block)
37
+ parse_source(source)
38
+ end
39
+
40
+ def parse_source(source)
41
+ params, body = extract_block_body(source)
42
+
43
+ visitor = ASTVisitor.new(uniforms: @uniforms, params: params)
44
+ @ir = visitor.parse(body)
45
+
46
+ inference = TypeInference.new(@uniforms, @custom_functions)
47
+ inference.register(:frag_coord, :vec2)
48
+ inference.register(:resolution, :vec2)
49
+ inference.infer(@ir)
50
+
51
+ @ir
52
+ end
53
+
54
+ def emit(target, needs_return: true)
55
+ raise "No IR parsed yet. Call parse_block or parse_source first." unless @ir
56
+
57
+ emitter_class = TARGETS[target.to_sym]
58
+ raise "Unknown target: #{target}" unless emitter_class
59
+
60
+ emitter = emitter_class.new
61
+ emitter.emit(@ir, needs_return: needs_return)
62
+ end
63
+
64
+ def transpile(block, target)
65
+ parse_block(block)
66
+ emit(target)
67
+ end
68
+
69
+ def transpile_source(source, target)
70
+ parse_source(source)
71
+ emit(target)
72
+ end
73
+
74
+ def transpile_helpers(block, target, function_signatures = {})
75
+ source = @source_extractor.extract(block)
76
+ _, body = extract_block_body(source)
77
+
78
+ visitor = ASTVisitor.new(uniforms: @uniforms)
79
+ @ir = visitor.parse(body)
80
+
81
+ apply_function_signatures(@ir, function_signatures)
82
+
83
+ inference = TypeInference.new(@uniforms, @custom_functions)
84
+ inference.infer(@ir)
85
+
86
+ emit(target, needs_return: false)
87
+ end
88
+
89
+ private
90
+
91
+ def apply_function_signatures(ir, signatures)
92
+ return unless ir.is_a?(IR::Block)
93
+
94
+ ir.statements.each do |stmt|
95
+ next unless stmt.is_a?(IR::FunctionDefinition)
96
+
97
+ sig = signatures[stmt.name]
98
+ next unless sig
99
+
100
+ stmt.return_type = sig[:returns]
101
+ stmt.param_types = sig[:params] || {}
102
+ end
103
+ end
104
+
105
+ def extract_block_body(source)
106
+ lines = source.strip.lines
107
+ params = []
108
+
109
+ first_line = lines.first&.strip || ""
110
+
111
+ if first_line.start_with?("|")
112
+ param_end = first_line.index("|", 1)
113
+ if param_end
114
+ param_str = first_line[1...param_end]
115
+ params = param_str.split(",").map { |p| p.strip.to_sym }
116
+ lines[0] = first_line[(param_end + 1)..]
117
+ end
118
+ end
119
+
120
+ lines.shift while lines.first&.strip&.empty?
121
+ lines.pop while lines.last&.strip&.empty?
122
+
123
+ body = lines.join.strip
124
+ [params, body]
125
+ end
126
+ end
127
+ end
128
+ end