rlsl 0.1.0 → 1.0.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.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -1
  3. data/README.md +18 -1
  4. data/lib/rlsl/base_translator/call_parser.rb +68 -0
  5. data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
  6. data/lib/rlsl/base_translator/code_scanner.rb +102 -0
  7. data/lib/rlsl/base_translator.rb +159 -63
  8. data/lib/rlsl/code_generator/math_prelude.rb +79 -0
  9. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +97 -0
  10. data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
  11. data/lib/rlsl/code_generator/template_context.rb +41 -0
  12. data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
  13. data/lib/rlsl/code_generator.rb +26 -201
  14. data/lib/rlsl/compiled_shader.rb +4 -12
  15. data/lib/rlsl/function_context.rb +31 -14
  16. data/lib/rlsl/glsl/translator.rb +19 -38
  17. data/lib/rlsl/msl/shader.rb +8 -38
  18. data/lib/rlsl/msl/translator.rb +19 -36
  19. data/lib/rlsl/msl/uniform_buffer_packer.rb +68 -0
  20. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +96 -0
  21. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +79 -0
  22. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +168 -0
  23. data/lib/rlsl/prism/ast_visitor/scope_context.rb +44 -0
  24. data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
  25. data/lib/rlsl/prism/ast_visitor.rb +54 -298
  26. data/lib/rlsl/prism/builtins/function_registry.rb +114 -0
  27. data/lib/rlsl/prism/builtins/operator_rules.rb +99 -0
  28. data/lib/rlsl/prism/builtins/swizzle_rules.rb +38 -0
  29. data/lib/rlsl/prism/builtins.rb +31 -148
  30. data/lib/rlsl/prism/compilation_unit.rb +7 -0
  31. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +83 -0
  32. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +104 -0
  33. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +92 -0
  34. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +89 -0
  35. data/lib/rlsl/prism/emitters/base_emitter.rb +68 -423
  36. data/lib/rlsl/prism/emitters/c_emitter.rb +78 -121
  37. data/lib/rlsl/prism/emitters/glsl_emitter.rb +30 -65
  38. data/lib/rlsl/prism/emitters/msl_emitter.rb +35 -62
  39. data/lib/rlsl/prism/emitters/target_emitter.rb +77 -0
  40. data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
  41. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +65 -61
  42. data/lib/rlsl/prism/ir/control_flow.rb +83 -0
  43. data/lib/rlsl/prism/ir/definitions.rb +67 -0
  44. data/lib/rlsl/prism/ir/expressions.rb +197 -0
  45. data/lib/rlsl/prism/ir/node.rb +21 -0
  46. data/lib/rlsl/prism/ir/nodes.rb +4 -371
  47. data/lib/rlsl/prism/ir/traversal.rb +62 -0
  48. data/lib/rlsl/prism/source_extractor/block_locator.rb +52 -0
  49. data/lib/rlsl/prism/source_extractor.rb +14 -133
  50. data/lib/rlsl/prism/source_unit/parser.rb +78 -0
  51. data/lib/rlsl/prism/source_unit.rb +42 -0
  52. data/lib/rlsl/prism/target_capability_validator.rb +85 -0
  53. data/lib/rlsl/prism/transpiler.rb +63 -60
  54. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +35 -0
  55. data/lib/rlsl/prism/type_inference/call_validator.rb +71 -0
  56. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +80 -0
  57. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +66 -0
  58. data/lib/rlsl/prism/type_inference/definition_inferer.rb +41 -0
  59. data/lib/rlsl/prism/type_inference/expression_inferer.rb +92 -0
  60. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +17 -0
  61. data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
  62. data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
  63. data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
  64. data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
  65. data/lib/rlsl/prism/type_inference.rb +114 -248
  66. data/lib/rlsl/runtime_shader.rb +47 -0
  67. data/lib/rlsl/shader_builder/build_service.rb +77 -0
  68. data/lib/rlsl/shader_builder/native_extension_compiler.rb +71 -0
  69. data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
  70. data/lib/rlsl/shader_builder/source_resolver.rb +91 -0
  71. data/lib/rlsl/shader_builder.rb +22 -113
  72. data/lib/rlsl/types/catalog.rb +47 -0
  73. data/lib/rlsl/types/target_resolver.rb +15 -0
  74. data/lib/rlsl/types/type_spec.rb +167 -0
  75. data/lib/rlsl/types/value_normalizer.rb +81 -0
  76. data/lib/rlsl/types.rb +11 -29
  77. data/lib/rlsl/uniform_context.rb +6 -12
  78. data/lib/rlsl/version.rb +1 -1
  79. data/lib/rlsl/wgsl/translator.rb +23 -39
  80. data/lib/rlsl.rb +14 -12
  81. metadata +55 -16
@@ -3,29 +3,22 @@
3
3
  module RLSL
4
4
  module MSL
5
5
  class Translator < BaseTranslator
6
- TYPE_MAP = {
7
- "vec2" => "float2",
8
- "vec3" => "float3",
9
- "vec4" => "float4"
10
- }.freeze
11
-
12
- FUNC_REPLACEMENTS = BaseTranslator.common_func_replacements(
13
- target_vec2: "float2",
14
- target_vec3: "float3",
15
- target_vec4: "float4"
16
- ).freeze
6
+ PROFILE = BaseTranslator.build_profile(
7
+ uniform_target: :msl,
8
+ identifier_replacements: {
9
+ "vec2" => "float2",
10
+ "vec3" => "float3",
11
+ "vec4" => "float4"
12
+ },
13
+ call_rewrites: BaseTranslator.common_call_rewrites(
14
+ target_vec2: "float2",
15
+ target_vec3: "float3",
16
+ target_vec4: "float4"
17
+ )
18
+ )
17
19
 
18
20
  protected
19
21
 
20
- def translate_code(c_code)
21
- result = super(c_code)
22
- return result if result.empty?
23
-
24
- result.gsub!(/\bstatic\s+/, "")
25
- result.gsub!(/\binline\s+/, "")
26
- result
27
- end
28
-
29
22
  def generate_shader(helpers, fragment)
30
23
  <<~MSL
31
24
  #include <metal_stdlib>
@@ -64,26 +57,16 @@ module RLSL
64
57
 
65
58
  private
66
59
 
60
+ def profile
61
+ PROFILE
62
+ end
63
+
67
64
  def generate_uniform_struct
68
- fields = ["float2 resolution;"]
69
- @uniforms.each do |name, type|
70
- msl_type = uniform_type_to_target(type)
71
- fields << "#{msl_type} #{name};"
65
+ fields = uniform_lines(resolution_line: "#{target_vec2_type} resolution;") do |name, msl_type|
66
+ "#{msl_type} #{name};"
72
67
  end
73
68
  fields.join("\n ")
74
69
  end
75
-
76
- def target_vec2_type
77
- "float2"
78
- end
79
-
80
- def target_vec3_type
81
- "float3"
82
- end
83
-
84
- def target_vec4_type
85
- "float4"
86
- end
87
70
  end
88
71
  end
89
72
  end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module MSL
5
+ class UniformBufferPacker
6
+ def initialize(shader_name, uniform_types, uniform_names)
7
+ @shader_name = shader_name
8
+ @uniform_types = uniform_types
9
+ @uniform_names = uniform_names
10
+ end
11
+
12
+ def pack(width, height, uniforms)
13
+ normalized_uniforms = UniformTypes.normalize_values(@uniform_types, uniforms, shader_name: @shader_name)
14
+ data = [width.to_f, height.to_f].pack("ff")
15
+ current_offset = 8
16
+
17
+ @uniform_names.each do |name|
18
+ value = normalized_uniforms[name]
19
+ spec = UniformTypes.metal_spec(@uniform_types[name])
20
+ current_offset, data = append_padding(data, current_offset, spec.metal_alignment)
21
+ data << pack_uniform_value(spec, value)
22
+ current_offset += spec.metal_size
23
+ end
24
+
25
+ data.ljust(256, "\x00")
26
+ end
27
+
28
+ private
29
+
30
+ def append_padding(data, current_offset, alignment)
31
+ padding_needed = (alignment - (current_offset % alignment)) % alignment
32
+ return [current_offset, data] if padding_needed.zero?
33
+
34
+ [current_offset + padding_needed, data + ("\x00" * padding_needed)]
35
+ end
36
+
37
+ def pack_uniform_value(spec, value)
38
+ case spec.wrapper_kind
39
+ when :float
40
+ [value.to_f].pack("f")
41
+ when :int
42
+ [value.to_i].pack("l")
43
+ when :bool
44
+ [value ? 1 : 0].pack("l")
45
+ when :vector
46
+ pack_vector_uniform(spec.vector_size, value)
47
+ else
48
+ raise ArgumentError, "Unsupported Metal uniform type: #{spec.c_type}"
49
+ end
50
+ end
51
+
52
+ def pack_vector_uniform(vector_size, value)
53
+ components = Array(value).map(&:to_f)
54
+
55
+ case vector_size
56
+ when 2
57
+ components.pack("ff")
58
+ when 3
59
+ (components + [0.0]).pack("ffff")
60
+ when 4
61
+ components.pack("ffff")
62
+ else
63
+ raise ArgumentError, "Unsupported vector size: #{vector_size}"
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class ASTVisitor
6
+ module ControlFlowVisiting
7
+ VISITORS = {}.tap do |visitors|
8
+ visitors[::Prism::BlockNode] = :visit_block if defined?(::Prism::BlockNode)
9
+ visitors[::Prism::LambdaNode] = :visit_lambda if defined?(::Prism::LambdaNode)
10
+ visitors[::Prism::IfNode] = :visit_if if defined?(::Prism::IfNode)
11
+ visitors[::Prism::ElseNode] = :visit_else if defined?(::Prism::ElseNode)
12
+ visitors[::Prism::UnlessNode] = :visit_unless if defined?(::Prism::UnlessNode)
13
+ visitors[::Prism::ReturnNode] = :visit_return if defined?(::Prism::ReturnNode)
14
+ visitors[::Prism::RangeNode] = :visit_range if defined?(::Prism::RangeNode)
15
+ visitors[::Prism::ForNode] = :visit_for if defined?(::Prism::ForNode)
16
+ visitors[::Prism::WhileNode] = :visit_while if defined?(::Prism::WhileNode)
17
+ visitors[::Prism::BreakNode] = :visit_break if defined?(::Prism::BreakNode)
18
+ end.freeze
19
+
20
+ private
21
+
22
+ def visit_block(node)
23
+ visit_with_scoped_vars(node.body, params: extract_block_params(node))
24
+ end
25
+
26
+ def visit_lambda(node)
27
+ visit_block(node)
28
+ end
29
+
30
+ def visit_if(node)
31
+ condition = visit(node.predicate)
32
+ then_branch = visit_with_scoped_vars(node.statements)
33
+ else_branch = node.subsequent ? visit_with_scoped_vars(node.subsequent) : nil
34
+
35
+ IR::IfStatement.new(condition, then_branch, else_branch)
36
+ end
37
+
38
+ def visit_else(node)
39
+ visit(node.statements)
40
+ end
41
+
42
+ def visit_elsif(node)
43
+ visit_if(node)
44
+ end
45
+
46
+ def visit_if_node(node)
47
+ visit_if(node)
48
+ end
49
+
50
+ def visit_unless(node)
51
+ condition = IR::UnaryOp.new("!", visit(node.predicate))
52
+ then_branch = visit_with_scoped_vars(node.statements)
53
+ else_branch = node.else_clause ? visit_with_scoped_vars(node.else_clause) : nil
54
+
55
+ IR::IfStatement.new(condition, then_branch, else_branch)
56
+ end
57
+
58
+ def visit_return(node)
59
+ expr = node.arguments ? normalize_expression(visit(node.arguments.arguments.first)) : nil
60
+ IR::Return.new(expr)
61
+ end
62
+
63
+ def visit_range(node)
64
+ [visit(node.left), visit(node.right)]
65
+ end
66
+
67
+ def visit_for(node)
68
+ range = visit(node.collection)
69
+ body = visit(node.statements)
70
+ IR::ForLoop.new(node.index.name.to_sym, range[0], range[1], body)
71
+ end
72
+
73
+ def visit_call_with_block(node)
74
+ return visit_plain_call(node) unless times_loop?(node)
75
+
76
+ count = visit(node.receiver)
77
+ block = visit(node.block)
78
+ var_name = extract_block_params(node.block).first || :i
79
+ IR::ForLoop.new(var_name, IR::Literal.new(0, :int), count, block)
80
+ end
81
+
82
+ def visit_while(node)
83
+ IR::WhileLoop.new(visit(node.predicate), visit(node.statements))
84
+ end
85
+
86
+ def visit_break(_node)
87
+ IR::Break.new
88
+ end
89
+
90
+ def times_loop?(node)
91
+ node.name.to_s == "times" && node.receiver
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class ASTVisitor
6
+ module DefinitionVisiting
7
+ VISITORS = {}.tap do |visitors|
8
+ visitors[::Prism::LocalVariableWriteNode] = :visit_local_variable_write if defined?(::Prism::LocalVariableWriteNode)
9
+ visitors[::Prism::LocalVariableOperatorWriteNode] = :visit_local_variable_operator_write if defined?(::Prism::LocalVariableOperatorWriteNode)
10
+ visitors[::Prism::LocalVariableReadNode] = :visit_local_variable_read if defined?(::Prism::LocalVariableReadNode)
11
+ visitors[::Prism::DefNode] = :visit_def if defined?(::Prism::DefNode)
12
+ visitors[::Prism::GlobalVariableWriteNode] = :visit_global_variable_write if defined?(::Prism::GlobalVariableWriteNode)
13
+ visitors[::Prism::ConstantWriteNode] = :visit_constant_write if defined?(::Prism::ConstantWriteNode)
14
+ visitors[::Prism::MultiWriteNode] = :visit_multi_write if defined?(::Prism::MultiWriteNode)
15
+ visitors[::Prism::LocalVariableTargetNode] = :visit_local_variable_target if defined?(::Prism::LocalVariableTargetNode)
16
+ end.freeze
17
+
18
+ private
19
+
20
+ def visit_local_variable_write(node)
21
+ name = node.name.to_sym
22
+ value = normalize_expression(visit(node.value))
23
+
24
+ if known_variable?(name)
25
+ IR::Assignment.new(IR::VarRef.new(name), value)
26
+ else
27
+ declare_variable(name)
28
+ IR::VarDecl.new(name, value)
29
+ end
30
+ end
31
+
32
+ def visit_local_variable_operator_write(node)
33
+ name = node.name.to_sym
34
+ operator = node.operator.to_s.delete_suffix("=")
35
+ value = normalize_expression(visit(node.value))
36
+
37
+ declare_variable(name)
38
+ target = IR::VarRef.new(name)
39
+ expr = IR::BinaryOp.new(operator, IR::VarRef.new(name), value)
40
+ IR::Assignment.new(target, expr)
41
+ end
42
+
43
+ def visit_local_variable_read(node)
44
+ IR::VarRef.new(node.name.to_sym, infer_param_type(node.name.to_sym))
45
+ end
46
+
47
+ def visit_def(node)
48
+ params = extract_required_params(node.parameters)
49
+ body = visit_with_scoped_vars(node.body, params: params)
50
+ IR::FunctionDefinition.new(node.name.to_sym, params, body)
51
+ end
52
+
53
+ def visit_global_variable_write(node)
54
+ IR::GlobalDecl.new(node.name.to_s.sub(/^\$/, "").to_sym, visit(node.value), is_static: true)
55
+ end
56
+
57
+ def visit_constant_write(node)
58
+ IR::GlobalDecl.new(node.name.to_sym, visit(node.value), is_const: true, is_static: true)
59
+ end
60
+
61
+ def visit_multi_write(node)
62
+ targets = node.lefts.map do |target|
63
+ name = target.name.to_sym
64
+ declare_variable(name)
65
+ IR::VarRef.new(name)
66
+ end
67
+
68
+ IR::MultipleAssignment.new(targets, visit(node.value))
69
+ end
70
+
71
+ def visit_local_variable_target(node)
72
+ name = node.name.to_sym
73
+ declare_variable(name)
74
+ IR::VarRef.new(name)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class ASTVisitor
6
+ module ExpressionVisiting
7
+ VISITORS = {}.tap do |visitors|
8
+ visitors[::Prism::IntegerNode] = :visit_integer if defined?(::Prism::IntegerNode)
9
+ visitors[::Prism::FloatNode] = :visit_float if defined?(::Prism::FloatNode)
10
+ visitors[::Prism::RationalNode] = :visit_rational if defined?(::Prism::RationalNode)
11
+ visitors[::Prism::TrueNode] = :visit_true if defined?(::Prism::TrueNode)
12
+ visitors[::Prism::FalseNode] = :visit_false if defined?(::Prism::FalseNode)
13
+ visitors[::Prism::ParenthesesNode] = :visit_parentheses if defined?(::Prism::ParenthesesNode)
14
+ visitors[::Prism::CallNode] = :visit_call if defined?(::Prism::CallNode)
15
+ visitors[::Prism::AndNode] = :visit_and if defined?(::Prism::AndNode)
16
+ visitors[::Prism::OrNode] = :visit_or if defined?(::Prism::OrNode)
17
+ visitors[::Prism::NotNode] = :visit_not if defined?(::Prism::NotNode)
18
+ visitors[::Prism::ArrayNode] = :visit_array if defined?(::Prism::ArrayNode)
19
+ visitors[::Prism::ConstantReadNode] = :visit_constant_read if defined?(::Prism::ConstantReadNode)
20
+ visitors[::Prism::ConstantPathNode] = :visit_constant_path if defined?(::Prism::ConstantPathNode)
21
+ visitors[::Prism::GlobalVariableReadNode] = :visit_global_variable_read if defined?(::Prism::GlobalVariableReadNode)
22
+ end.freeze
23
+
24
+ private
25
+
26
+ def visit_integer(node)
27
+ IR::Literal.new(node.value, :int)
28
+ end
29
+
30
+ def visit_float(node)
31
+ IR::Literal.new(node.value, :float)
32
+ end
33
+
34
+ def visit_rational(node)
35
+ IR::Literal.new(node.value.to_f, :float)
36
+ end
37
+
38
+ def visit_true(_node)
39
+ IR::BoolLiteral.new(true)
40
+ end
41
+
42
+ def visit_false(_node)
43
+ IR::BoolLiteral.new(false)
44
+ end
45
+
46
+ def visit_parentheses(node)
47
+ inner = normalize_expression(visit(node.body))
48
+ inner = inner.statements.first if single_statement_block?(inner)
49
+ IR::Parenthesized.new(inner)
50
+ end
51
+
52
+ def visit_call(node)
53
+ return visit_call_with_block(node) if node.block
54
+
55
+ visit_plain_call(node)
56
+ end
57
+
58
+ def visit_plain_call(node)
59
+ method_name = node.name.to_s
60
+ receiver = normalize_expression(visit(node.receiver)) if node.receiver
61
+ args = node.arguments&.arguments&.map { |arg| normalize_expression(visit(arg)) } || []
62
+
63
+ return IR::VarRef.new(method_name.to_sym, infer_param_type(method_name.to_sym)) if parameter_reference_call?(method_name, receiver, args)
64
+ return visit_receiver_call(method_name, receiver) if receiver_without_arguments?(node, receiver, args)
65
+ return IR::BinaryOp.new(method_name, receiver, args.first) if binary_operator_call?(method_name, receiver, args)
66
+ return IR::UnaryOp.new("-", receiver) if method_name == "-@" && receiver
67
+ return IR::UnaryOp.new("!", args.first) if method_name == "!" && args.length == 1
68
+ return IR::ArrayIndex.new(receiver, args.first) if method_name == "[]" && receiver && args.length == 1
69
+
70
+ IR::FuncCall.new(method_name.to_sym, args, receiver)
71
+ end
72
+
73
+ def visit_and(node)
74
+ left = normalize_expression(visit(node.left))
75
+ right = normalize_expression(visit(node.right))
76
+ IR::BinaryOp.new("&&", left, right, :bool)
77
+ end
78
+
79
+ def visit_or(node)
80
+ left = normalize_expression(visit(node.left))
81
+ right = normalize_expression(visit(node.right))
82
+ IR::BinaryOp.new("||", left, right, :bool)
83
+ end
84
+
85
+ def visit_not(node)
86
+ operand = normalize_expression(visit(node.expression))
87
+ IR::UnaryOp.new("!", operand, :bool)
88
+ end
89
+
90
+ def visit_array(node)
91
+ elements = node.elements.map { |elem| normalize_expression(visit(elem)) }
92
+ IR::ArrayLiteral.new(elements)
93
+ end
94
+
95
+ def visit_index(node)
96
+ array = normalize_expression(visit(node.receiver))
97
+ index = normalize_expression(visit(node.arguments.arguments.first))
98
+ IR::ArrayIndex.new(array, index)
99
+ end
100
+
101
+ def visit_constant_read(node)
102
+ name = node.name.to_s
103
+ return IR::Constant.new(name.to_sym, :float) if %w[PI TAU].include?(name)
104
+
105
+ IR::VarRef.new(name.to_sym)
106
+ end
107
+
108
+ def visit_constant_path(node)
109
+ path_parts = []
110
+ current = node
111
+ while current.is_a?(::Prism::ConstantPathNode)
112
+ path_parts.unshift(current.name.to_s)
113
+ current = current.parent
114
+ end
115
+ path_parts.unshift(current.name.to_s) if current.respond_to?(:name)
116
+
117
+ IR::VarRef.new(path_parts.join("_").to_sym)
118
+ end
119
+
120
+ def visit_global_variable_read(node)
121
+ IR::VarRef.new(node.name.to_s.sub(/^\$/, "").to_sym)
122
+ end
123
+
124
+ def normalize_expression(node)
125
+ return node unless node.is_a?(IR::IfStatement)
126
+
127
+ then_expr = extract_if_branch_expr(node.then_branch)
128
+ else_expr = extract_if_branch_expr(node.else_branch)
129
+ raise "Unsupported if-expression: branches must be single expressions" unless then_expr && else_expr
130
+
131
+ IR::Ternary.new(node.condition, then_expr, else_expr)
132
+ end
133
+
134
+ def extract_if_branch_expr(branch)
135
+ return nil unless branch
136
+ return branch unless branch.is_a?(IR::Block)
137
+ return nil if branch.statements.empty? || branch.statements.length != 1
138
+
139
+ branch.statements.first
140
+ end
141
+
142
+ def single_statement_block?(node)
143
+ node.is_a?(IR::Block) && node.statements.length == 1
144
+ end
145
+
146
+ def parameter_reference_call?(method_name, receiver, args)
147
+ !receiver && args.empty? && parameter_reference?(method_name.to_sym)
148
+ end
149
+
150
+ def receiver_without_arguments?(node, receiver, args)
151
+ receiver && args.empty? && !node.arguments
152
+ end
153
+
154
+ def visit_receiver_call(method_name, receiver)
155
+ return receiver if method_name == "freeze"
156
+ return IR::FieldAccess.new(receiver, method_name, :float) if Builtins.single_component_field?(method_name)
157
+ return IR::Swizzle.new(receiver, method_name, Builtins.swizzle_type(method_name)) if Builtins.swizzle?(method_name)
158
+
159
+ IR::FieldAccess.new(receiver, method_name)
160
+ end
161
+
162
+ def binary_operator_call?(method_name, receiver, args)
163
+ BINARY_OPERATORS.include?(method_name) && receiver && args.length == 1
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class ASTVisitor
6
+ class ScopeContext
7
+ Scope = Struct.new(:params, :declared_vars)
8
+
9
+ def initialize(params: [])
10
+ @scopes = [build_scope(params)]
11
+ end
12
+
13
+ def with_scope(params: [])
14
+ @scopes << build_scope(params)
15
+ yield
16
+ ensure
17
+ @scopes.pop
18
+ end
19
+
20
+ def parameter?(name)
21
+ @scopes.reverse_each.any? { |scope| scope.params.include?(name.to_sym) }
22
+ end
23
+
24
+ def declared?(name)
25
+ @scopes.reverse_each.any? { |scope| scope.declared_vars.include?(name.to_sym) }
26
+ end
27
+
28
+ def known_variable?(name)
29
+ parameter?(name) || declared?(name)
30
+ end
31
+
32
+ def declare(name)
33
+ @scopes.last.declared_vars.add(name.to_sym)
34
+ end
35
+
36
+ private
37
+
38
+ def build_scope(params)
39
+ Scope.new(Set.new(Array(params).map(&:to_sym)), Set.new)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ module Prism
5
+ class ASTVisitor
6
+ class VisitorRegistry
7
+ TRANSPARENT_NODES = [
8
+ (::Prism::ArgumentsNode if defined?(::Prism::ArgumentsNode)),
9
+ (::Prism::BlockParametersNode if defined?(::Prism::BlockParametersNode)),
10
+ (::Prism::ParametersNode if defined?(::Prism::ParametersNode))
11
+ ].compact.freeze
12
+
13
+ def self.build(*visitor_maps)
14
+ visitor_maps.each_with_object({}) do |visitor_map, merged|
15
+ merged.merge!(visitor_map)
16
+ end.freeze
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end