rlsl 1.0.0 → 1.0.1

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/CHANGELOG.md +3 -1
  4. data/README.md +101 -28
  5. data/Rakefile +7 -0
  6. data/lib/rlsl/base_translator/call_parser.rb +13 -2
  7. data/lib/rlsl/base_translator/code_scanner.rb +39 -1
  8. data/lib/rlsl/base_translator.rb +24 -2
  9. data/lib/rlsl/code_generator/math_prelude.rb +103 -10
  10. data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +52 -12
  11. data/lib/rlsl/code_generator/template_context.rb +6 -3
  12. data/lib/rlsl/code_generator/uniform_struct_generator.rb +1 -1
  13. data/lib/rlsl/code_generator.rb +7 -4
  14. data/lib/rlsl/compiled_shader.rb +1 -1
  15. data/lib/rlsl/errors.rb +29 -0
  16. data/lib/rlsl/glsl/translator.rb +19 -6
  17. data/lib/rlsl/msl/shader.rb +27 -9
  18. data/lib/rlsl/msl/translator.rb +25 -4
  19. data/lib/rlsl/msl/uniform_buffer_packer.rb +11 -7
  20. data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +59 -18
  21. data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +22 -9
  22. data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +18 -14
  23. data/lib/rlsl/prism/ast_visitor/scope_context.rb +4 -0
  24. data/lib/rlsl/prism/ast_visitor.rb +78 -20
  25. data/lib/rlsl/prism/builtins/function_registry.rb +41 -24
  26. data/lib/rlsl/prism/builtins/operator_rules.rb +24 -0
  27. data/lib/rlsl/prism/builtins/swizzle_rules.rb +10 -1
  28. data/lib/rlsl/prism/builtins.rb +8 -0
  29. data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +51 -8
  30. data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +42 -12
  31. data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +20 -4
  32. data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +7 -1
  33. data/lib/rlsl/prism/emitters/base_emitter.rb +66 -5
  34. data/lib/rlsl/prism/emitters/c_emitter.rb +103 -7
  35. data/lib/rlsl/prism/emitters/glsl_emitter.rb +48 -0
  36. data/lib/rlsl/prism/emitters/msl_emitter.rb +43 -1
  37. data/lib/rlsl/prism/emitters/target_emitter.rb +56 -9
  38. data/lib/rlsl/prism/emitters/wgsl_emitter.rb +173 -18
  39. data/lib/rlsl/prism/errors.rb +9 -0
  40. data/lib/rlsl/prism/ir/control_flow.rb +6 -4
  41. data/lib/rlsl/prism/ir/definitions.rb +17 -2
  42. data/lib/rlsl/prism/ir/expressions.rb +5 -1
  43. data/lib/rlsl/prism/ir/node.rb +1 -1
  44. data/lib/rlsl/prism/ir/traversal.rb +6 -2
  45. data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
  46. data/lib/rlsl/prism/node_traversal.rb +41 -0
  47. data/lib/rlsl/prism/parameter_list.rb +46 -0
  48. data/lib/rlsl/prism/return_flow_validator.rb +73 -0
  49. data/lib/rlsl/prism/source_extractor/block_locator.rb +23 -25
  50. data/lib/rlsl/prism/source_extractor.rb +7 -6
  51. data/lib/rlsl/prism/source_unit/parser.rb +42 -32
  52. data/lib/rlsl/prism/source_unit.rb +10 -10
  53. data/lib/rlsl/prism/target_capability_validator.rb +27 -2
  54. data/lib/rlsl/prism/transpiler.rb +40 -3
  55. data/lib/rlsl/prism/type_inference/call_type_resolver.rb +28 -4
  56. data/lib/rlsl/prism/type_inference/call_validator.rb +5 -1
  57. data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +45 -19
  58. data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +88 -7
  59. data/lib/rlsl/prism/type_inference/definition_inferer.rb +16 -1
  60. data/lib/rlsl/prism/type_inference/expression_inferer.rb +4 -0
  61. data/lib/rlsl/prism/type_inference/field_type_resolver.rb +23 -2
  62. data/lib/rlsl/prism/type_inference/scope_stack.rb +1 -1
  63. data/lib/rlsl/prism/type_inference/type_shapes.rb +3 -3
  64. data/lib/rlsl/prism/type_inference.rb +10 -5
  65. data/lib/rlsl/shader_builder/build_service.rb +34 -7
  66. data/lib/rlsl/shader_builder/native_extension_compiler.rb +50 -24
  67. data/lib/rlsl/shader_builder/shader_definition.rb +3 -3
  68. data/lib/rlsl/shader_builder/source_resolver.rb +19 -1
  69. data/lib/rlsl/shader_builder.rb +50 -10
  70. data/lib/rlsl/shader_name.rb +18 -0
  71. data/lib/rlsl/types/type_spec.rb +3 -3
  72. data/lib/rlsl/types/value_normalizer.rb +16 -11
  73. data/lib/rlsl/types.rb +0 -4
  74. data/lib/rlsl/uniform_context.rb +18 -1
  75. data/lib/rlsl/version.rb +1 -1
  76. data/lib/rlsl/wgsl/translator.rb +29 -6
  77. data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
  78. data/lib/rlsl.rb +24 -3
  79. metadata +23 -11
@@ -8,7 +8,7 @@ module RLSL
8
8
  end
9
9
 
10
10
  def generate
11
- return "typedef struct {} Uniforms;\n" if @context.uniform_entries.empty?
11
+ return "typedef struct { unsigned char _unused; } Uniforms;\n" if @context.uniform_entries.empty?
12
12
 
13
13
  <<~C
14
14
  typedef struct {
@@ -8,18 +8,21 @@ require_relative "code_generator/ruby_wrapper_generator"
8
8
 
9
9
  module RLSL
10
10
  class CodeGenerator
11
- def initialize(name, uniforms, helpers_block, fragment_block)
11
+ def initialize(name, uniforms, helpers_block, fragment_block, extension_name: name)
12
12
  @context = TemplateContext.new(
13
13
  name: name,
14
14
  uniforms: uniforms,
15
15
  helpers_block: helpers_block,
16
- fragment_block: fragment_block
16
+ fragment_block: fragment_block,
17
+ extension_name: extension_name
17
18
  )
18
19
  end
19
20
 
20
21
  def generate
21
22
  <<~C
22
23
  #include <ruby.h>
24
+ #include <ruby/thread.h>
25
+ #include <limits.h>
23
26
  #include <math.h>
24
27
  #include <stdint.h>
25
28
  #ifdef __APPLE__
@@ -45,10 +48,10 @@ module RLSL
45
48
 
46
49
  def init_function
47
50
  <<~C
48
- void Init_#{@context.name}(void) {
51
+ void Init_#{@context.extension_name}(void) {
49
52
  VALUE mRLSL = rb_define_module("RLSL");
50
53
  VALUE mShaders = rb_define_module_under(mRLSL, "CompiledShaders");
51
- rb_define_module_function(mShaders, "#{@context.name}_render", shader_#{@context.name}_render, #{@context.render_arity});
54
+ rb_define_module_function(mShaders, "#{@context.extension_name}_render", shader_#{@context.name}_render, #{@context.render_arity});
52
55
  }
53
56
  C
54
57
  end
@@ -5,7 +5,7 @@ module RLSL
5
5
  def initialize(name, ext_name, uniforms)
6
6
  super(name, uniforms)
7
7
  @ext_name = ext_name
8
- @render_method = RLSL::CompiledShaders.method("#{name}_render")
8
+ @render_method = RLSL::CompiledShaders.method("#{ext_name}_render")
9
9
  end
10
10
 
11
11
  def render(buffer, width, height, uniforms = {})
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RLSL
4
+ SourceLocation = Struct.new(:source_name, :line, :column, keyword_init: true) do
5
+ def to_s
6
+ "#{source_name || '(shader source)'}:#{line}:#{column}"
7
+ end
8
+ end
9
+
10
+ class Error < StandardError
11
+ attr_reader :source_location
12
+
13
+ def with_source_location(location)
14
+ @source_location ||= location
15
+ self
16
+ end
17
+
18
+ def message
19
+ return super unless source_location
20
+
21
+ "#{super} at #{source_location}"
22
+ end
23
+ end
24
+ class InternalError < Error; end
25
+ class ParseError < Error; end
26
+ class TranslationError < Error; end
27
+ class CompilationError < Error; end
28
+ class UniformValueError < ArgumentError; end
29
+ end
@@ -10,12 +10,15 @@ module RLSL
10
10
  target_vec2: "vec2",
11
11
  target_vec3: "vec3",
12
12
  target_vec4: "vec4"
13
+ ).merge(
14
+ "fmodf" => BaseTranslator.rename_call("mod"),
15
+ "atan2f" => BaseTranslator.rename_call("atan")
13
16
  )
14
17
  )
15
18
 
16
- def initialize(uniforms, helpers_code, fragment_code, version: "450")
17
- super(uniforms, helpers_code, fragment_code)
18
- @version = version
19
+ def initialize(uniforms, helpers_code, fragment_code, version: "450", name: nil)
20
+ super(uniforms, helpers_code, fragment_code, name: name)
21
+ @version = validate_version!(version)
19
22
  end
20
23
 
21
24
  protected
@@ -23,6 +26,7 @@ module RLSL
23
26
  def generate_shader(helpers, fragment)
24
27
  <<~GLSL
25
28
  #version #{@version}
29
+ #{generated_by_comment("GLSL")}
26
30
 
27
31
  // Uniforms
28
32
  #{generate_uniform_declarations}
@@ -33,8 +37,7 @@ module RLSL
33
37
  #{helpers}
34
38
 
35
39
  vec3 shader_fragment(vec2 frag_coord, vec2 resolution) {
36
- vec2 uv = frag_coord / resolution.y;
37
- #{fragment}
40
+ #{indent_source(fragment, 4)}
38
41
  }
39
42
 
40
43
  layout(local_size_x = 8, local_size_y = 8) in;
@@ -64,15 +67,25 @@ module RLSL
64
67
  end
65
68
 
66
69
  def generate_uniform_declarations
67
- declarations = ["layout(binding = 1) uniform ShaderUniforms {"]
70
+ declarations = ["layout(std140, binding = 1) uniform ShaderUniforms {"]
68
71
  declarations.concat(
69
72
  uniform_lines(resolution_line: " #{target_vec2_type} resolution;") do |name, glsl_type|
70
73
  " #{glsl_type} #{name};"
71
74
  end
72
75
  )
73
76
  declarations << "} u;"
77
+ texture_uniforms.each_with_index do |(name, _type), index|
78
+ declarations << "layout(binding = #{index + 2}) uniform sampler2D #{name};"
79
+ end
74
80
  declarations.join("\n")
75
81
  end
82
+
83
+ def validate_version!(version)
84
+ normalized = version.to_s
85
+ return normalized if normalized.match?(/\A[1-9]\d{2}(?: (?:core|compatibility|es))?\z/)
86
+
87
+ raise ArgumentError, "Invalid GLSL version: #{version.inspect}"
88
+ end
76
89
  end
77
90
  end
78
91
  end
@@ -1,22 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- begin
4
- require "metaco"
5
- METACO_AVAILABLE = true
6
- rescue LoadError
7
- METACO_AVAILABLE = false
8
- end
9
-
10
3
  module RLSL
11
4
  module MSL
5
+ begin
6
+ require "metaco"
7
+ METACO_AVAILABLE = true
8
+ rescue LoadError
9
+ METACO_AVAILABLE = false
10
+ end
11
+
12
12
  class Shader < RuntimeShader
13
+ COMPILED_HANDLE_CACHE_LIMIT = 64
13
14
  attr_reader :name, :msl_source
14
15
 
15
16
  def initialize(name, uniforms, msl_source)
16
17
  super(name, uniforms)
17
18
  @msl_source = msl_source
18
19
  @compiled_handles = {}
19
- @uniform_buffer_packer = UniformBufferPacker.new(@name, @uniform_types, @uniform_names)
20
+ value_uniform_types = @uniform_types.reject { |_name, type| type == :sampler2D }
21
+ @uniform_buffer_packer = UniformBufferPacker.new(@name, value_uniform_types, value_uniform_types.keys)
20
22
  end
21
23
 
22
24
  def metal?
@@ -27,7 +29,7 @@ module RLSL
27
29
  render_metal(handle, width, height, uniforms)
28
30
  end
29
31
 
30
- def render_metal(handle, width, height, uniforms = {})
32
+ def prepare(handle)
31
33
  unless METACO_AVAILABLE
32
34
  raise LoadError, "metaco gem is required for Metal rendering. Install it with: gem install metaco"
33
35
  end
@@ -35,10 +37,26 @@ module RLSL
35
37
  unless @compiled_handles[handle]
36
38
  Metaco.compile_compute_shader(handle, @msl_source)
37
39
  @compiled_handles[handle] = true
40
+ @compiled_handles.shift while @compiled_handles.length > COMPILED_HANDLE_CACHE_LIMIT
38
41
  end
42
+ self
43
+ end
44
+
45
+ def render_metal(handle, width, height, uniforms = {}, textures: {})
46
+ prepare(handle)
39
47
 
40
48
  uniform_data = pack_uniforms(uniforms, width, height)
41
49
 
50
+ sampler_names = @uniform_types.filter_map { |name, type| name if type == :sampler2D }
51
+ sampler_names.each do |name|
52
+ texture = textures[name] || textures[name.to_s]
53
+ raise ArgumentError, "missing texture uniform: #{name}" unless texture
54
+ unless Metaco.respond_to?(:bind_compute_texture)
55
+ raise LoadError, "metaco texture support is required for sampler2D uniforms"
56
+ end
57
+ Metaco.bind_compute_texture(handle, sampler_names.index(name), texture)
58
+ end
59
+
42
60
  Metaco.dispatch_compute(handle, uniform_data)
43
61
  Metaco.present_compute(handle)
44
62
  end
@@ -23,6 +23,13 @@ module RLSL
23
23
  <<~MSL
24
24
  #include <metal_stdlib>
25
25
  using namespace metal;
26
+ #{generated_by_comment("MSL")}
27
+
28
+ constexpr sampler rlsl_texture_sampler(coord::normalized, address::clamp_to_edge, filter::linear);
29
+
30
+ float rlsl_mod(float x, float y) {
31
+ return x - y * floor(x / y);
32
+ }
26
33
 
27
34
  // Uniform buffer structure
28
35
  struct Uniforms {
@@ -33,22 +40,22 @@ module RLSL
33
40
  #{helpers}
34
41
 
35
42
  // Fragment shader function
36
- float3 shader_fragment(float2 frag_coord, float2 resolution, constant Uniforms& u) {
37
- float2 uv = frag_coord / resolution.y;
38
- #{fragment}
43
+ float3 shader_fragment(float2 frag_coord, float2 resolution, constant Uniforms& u#{fragment_texture_parameters}) {
44
+ #{indent_source(fragment, 4)}
39
45
  }
40
46
 
41
47
  // Compute kernel entry point
42
48
  kernel void compute_shader(
43
49
  texture2d<float, access::write> output [[texture(0)]],
44
50
  constant Uniforms& u [[buffer(0)]],
51
+ #{kernel_texture_parameters}
45
52
  uint2 gid [[thread_position_in_grid]]
46
53
  ) {
47
54
  float2 resolution = float2(output.get_width(), output.get_height());
48
55
  if (gid.x >= uint(resolution.x) || gid.y >= uint(resolution.y)) return;
49
56
 
50
57
  float2 frag_coord = float2(gid.x, resolution.y - 1.0 - float(gid.y));
51
- float3 color = shader_fragment(frag_coord, resolution, u);
58
+ float3 color = shader_fragment(frag_coord, resolution, u#{fragment_texture_arguments});
52
59
 
53
60
  output.write(float4(clamp(color, 0.0, 1.0), 1.0), gid);
54
61
  }
@@ -67,6 +74,20 @@ module RLSL
67
74
  end
68
75
  fields.join("\n ")
69
76
  end
77
+
78
+ def fragment_texture_parameters
79
+ texture_uniforms.keys.map { |name| ", texture2d<float> #{name}" }.join
80
+ end
81
+
82
+ def fragment_texture_arguments
83
+ texture_uniforms.keys.map { |name| ", #{name}" }.join
84
+ end
85
+
86
+ def kernel_texture_parameters
87
+ texture_uniforms.keys.each_with_index.map do |name, index|
88
+ " texture2d<float, access::sample> #{name} [[texture(#{index + 1})]],"
89
+ end.join("\n")
90
+ end
70
91
  end
71
92
  end
72
93
  end
@@ -11,7 +11,7 @@ module RLSL
11
11
 
12
12
  def pack(width, height, uniforms)
13
13
  normalized_uniforms = UniformTypes.normalize_values(@uniform_types, uniforms, shader_name: @shader_name)
14
- data = [width.to_f, height.to_f].pack("ff")
14
+ data = [width.to_f, height.to_f].pack("e2")
15
15
  current_offset = 8
16
16
 
17
17
  @uniform_names.each do |name|
@@ -22,6 +22,10 @@ module RLSL
22
22
  current_offset += spec.metal_size
23
23
  end
24
24
 
25
+ if data.bytesize > 256
26
+ raise ArgumentError, "Metal uniform buffer exceeds 256 bytes (#{data.bytesize} bytes)"
27
+ end
28
+
25
29
  data.ljust(256, "\x00")
26
30
  end
27
31
 
@@ -37,11 +41,11 @@ module RLSL
37
41
  def pack_uniform_value(spec, value)
38
42
  case spec.wrapper_kind
39
43
  when :float
40
- [value.to_f].pack("f")
44
+ [value.to_f].pack("e")
41
45
  when :int
42
- [value.to_i].pack("l")
46
+ [value.to_i].pack("l<")
43
47
  when :bool
44
- [value ? 1 : 0].pack("l")
48
+ [value ? 1 : 0].pack("l<")
45
49
  when :vector
46
50
  pack_vector_uniform(spec.vector_size, value)
47
51
  else
@@ -54,11 +58,11 @@ module RLSL
54
58
 
55
59
  case vector_size
56
60
  when 2
57
- components.pack("ff")
61
+ components.pack("e2")
58
62
  when 3
59
- (components + [0.0]).pack("ffff")
63
+ (components + [0.0]).pack("e4")
60
64
  when 4
61
- components.pack("ffff")
65
+ components.pack("e4")
62
66
  else
63
67
  raise ArgumentError, "Unsupported vector size: #{vector_size}"
64
68
  end
@@ -29,58 +29,63 @@ module RLSL
29
29
 
30
30
  def visit_if(node)
31
31
  condition = visit(node.predicate)
32
+ hoisted_variables = hoist_branch_variables(node)
32
33
  then_branch = visit_with_scoped_vars(node.statements)
33
34
  else_branch = node.subsequent ? visit_with_scoped_vars(node.subsequent) : nil
34
35
 
35
- IR::IfStatement.new(condition, then_branch, else_branch)
36
+ IR::IfStatement.new(condition, then_branch, else_branch, hoisted_variables: hoisted_variables)
36
37
  end
37
38
 
38
39
  def visit_else(node)
39
40
  visit(node.statements)
40
41
  end
41
42
 
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
43
  def visit_unless(node)
51
44
  condition = IR::UnaryOp.new("!", visit(node.predicate))
45
+ hoisted_variables = hoist_branch_variables(node)
52
46
  then_branch = visit_with_scoped_vars(node.statements)
53
47
  else_branch = node.else_clause ? visit_with_scoped_vars(node.else_clause) : nil
54
48
 
55
- IR::IfStatement.new(condition, then_branch, else_branch)
49
+ IR::IfStatement.new(condition, then_branch, else_branch, hoisted_variables: hoisted_variables)
56
50
  end
57
51
 
58
52
  def visit_return(node)
59
- expr = node.arguments ? normalize_expression(visit(node.arguments.arguments.first)) : nil
53
+ arguments = node.arguments&.arguments || []
54
+ if arguments.length > 1
55
+ raise UnsupportedSyntaxError, "Returning multiple values requires an explicitly declared tuple helper"
56
+ end
57
+
58
+ expr = arguments.empty? ? nil : normalize_expression(visit(arguments.first))
60
59
  IR::Return.new(expr)
61
60
  end
62
61
 
63
62
  def visit_range(node)
64
- [visit(node.left), visit(node.right)]
63
+ [visit(node.left), visit(node.right), node.exclude_end?]
65
64
  end
66
65
 
67
66
  def visit_for(node)
68
67
  range = visit(node.collection)
69
- body = visit(node.statements)
70
- IR::ForLoop.new(node.index.name.to_sym, range[0], range[1], body)
68
+ variable = node.index.name.to_sym
69
+ body = visit_with_scoped_vars(node.statements, params: [variable]) || IR::Block.new
70
+ IR::ForLoop.new(variable, range[0], range[1], body, exclude_end: range[2])
71
71
  end
72
72
 
73
73
  def visit_call_with_block(node)
74
- return visit_plain_call(node) unless times_loop?(node)
74
+ unless times_loop?(node)
75
+ raise UnsupportedSyntaxError, "Blocks are only supported for Integer#times loops"
76
+ end
75
77
 
76
78
  count = visit(node.receiver)
77
- block = visit(node.block)
78
- var_name = extract_block_params(node.block).first || :i
79
+ block_params = extract_block_params(node.block)
80
+ var_name = block_params.first || next_implicit_loop_variable
81
+ block = visit_with_scoped_vars(node.block.body, params: [var_name]) || IR::Block.new
79
82
  IR::ForLoop.new(var_name, IR::Literal.new(0, :int), count, block)
80
83
  end
81
84
 
82
85
  def visit_while(node)
83
- IR::WhileLoop.new(visit(node.predicate), visit(node.statements))
86
+ condition = visit(node.predicate)
87
+ body = visit_with_scoped_vars(node.statements) || IR::Block.new
88
+ IR::WhileLoop.new(condition, body)
84
89
  end
85
90
 
86
91
  def visit_break(_node)
@@ -90,6 +95,42 @@ module RLSL
90
95
  def times_loop?(node)
91
96
  node.name.to_s == "times" && node.receiver
92
97
  end
98
+
99
+ def hoist_branch_variables(node)
100
+ then_names = definitely_assigned_names(node.statements)
101
+ else_node = node.respond_to?(:subsequent) ? node.subsequent : node.else_clause
102
+ else_names = definitely_assigned_names(else_node)
103
+
104
+ (then_names & else_names).each_with_object({}) do |name, variables|
105
+ next if known_variable?(name)
106
+
107
+ declare_variable(name)
108
+ variables[name] = nil
109
+ end
110
+ end
111
+
112
+ def definitely_assigned_names(node)
113
+ return Set.new unless node
114
+
115
+ case node
116
+ when ::Prism::StatementsNode
117
+ node.body.each_with_object(Set.new) do |statement, names|
118
+ names.merge(definitely_assigned_names(statement))
119
+ end
120
+ when ::Prism::LocalVariableWriteNode, ::Prism::LocalVariableOperatorWriteNode
121
+ Set[node.name.to_sym]
122
+ when ::Prism::MultiWriteNode
123
+ Set.new(node.lefts.map { |target| target.name.to_sym })
124
+ when ::Prism::IfNode
125
+ definitely_assigned_names(node.statements) & definitely_assigned_names(node.subsequent)
126
+ when ::Prism::UnlessNode
127
+ definitely_assigned_names(node.statements) & definitely_assigned_names(node.else_clause)
128
+ when ::Prism::ElseNode
129
+ definitely_assigned_names(node.statements)
130
+ else
131
+ Set.new
132
+ end
133
+ end
93
134
  end
94
135
  end
95
136
  end
@@ -20,9 +20,10 @@ module RLSL
20
20
  def visit_local_variable_write(node)
21
21
  name = node.name.to_sym
22
22
  value = normalize_expression(visit(node.value))
23
+ emitted_name = emitted_assignment_name(name)
23
24
 
24
25
  if known_variable?(name)
25
- IR::Assignment.new(IR::VarRef.new(name), value)
26
+ IR::Assignment.new(IR::VarRef.new(emitted_name), value)
26
27
  else
27
28
  declare_variable(name)
28
29
  IR::VarDecl.new(name, value)
@@ -31,17 +32,23 @@ module RLSL
31
32
 
32
33
  def visit_local_variable_operator_write(node)
33
34
  name = node.name.to_sym
34
- operator = node.operator.to_s.delete_suffix("=")
35
+ operator = node.binary_operator.to_s
35
36
  value = normalize_expression(visit(node.value))
36
37
 
37
- declare_variable(name)
38
- target = IR::VarRef.new(name)
39
- expr = IR::BinaryOp.new(operator, IR::VarRef.new(name), value)
38
+ unless known_variable?(name)
39
+ raise UnsupportedSyntaxError, "Operator assignment requires an initialized variable: #{name}"
40
+ end
41
+ emitted_name = emitted_assignment_name(name)
42
+ target = IR::VarRef.new(emitted_name)
43
+ expr = IR::BinaryOp.new(operator, IR::VarRef.new(emitted_name), IR::Parenthesized.new(value))
40
44
  IR::Assignment.new(target, expr)
41
45
  end
42
46
 
43
47
  def visit_local_variable_read(node)
44
- IR::VarRef.new(node.name.to_sym, infer_param_type(node.name.to_sym))
48
+ name = node.name.to_sym
49
+ emitted_name = fragment_parameter_reference?(name) ? emitted_parameter_name(name) : name
50
+ type = infer_param_type(name) if fragment_parameter_reference?(name)
51
+ IR::VarRef.new(emitted_name, type)
45
52
  end
46
53
 
47
54
  def visit_def(node)
@@ -59,13 +66,19 @@ module RLSL
59
66
  end
60
67
 
61
68
  def visit_multi_write(node)
69
+ if node.rest || !node.rights.empty?
70
+ raise UnsupportedSyntaxError, "Splat multiple assignment is not supported"
71
+ end
72
+
73
+ declarations = []
62
74
  targets = node.lefts.map do |target|
63
75
  name = target.name.to_sym
64
- declare_variable(name)
65
- IR::VarRef.new(name)
76
+ declarations << !known_variable?(name)
77
+ declare_variable(name) if declarations.last
78
+ IR::VarRef.new(emitted_assignment_name(name))
66
79
  end
67
80
 
68
- IR::MultipleAssignment.new(targets, visit(node.value))
81
+ IR::MultipleAssignment.new(targets, visit(node.value), declarations: declarations)
69
82
  end
70
83
 
71
84
  def visit_local_variable_target(node)
@@ -60,11 +60,13 @@ module RLSL
60
60
  receiver = normalize_expression(visit(node.receiver)) if node.receiver
61
61
  args = node.arguments&.arguments&.map { |arg| normalize_expression(visit(arg)) } || []
62
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)
63
+ if parameter_reference_call?(method_name, receiver, args)
64
+ return IR::VarRef.new(emitted_parameter_name(method_name), infer_param_type(method_name))
65
+ end
66
+ return IR::UnaryOp.new("-", receiver) if method_name == "-@" && receiver
67
+ return IR::UnaryOp.new("!", receiver) if method_name == "!" && receiver && args.empty?
64
68
  return visit_receiver_call(method_name, receiver) if receiver_without_arguments?(node, receiver, args)
65
69
  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
70
  return IR::ArrayIndex.new(receiver, args.first) if method_name == "[]" && receiver && args.length == 1
69
71
 
70
72
  IR::FuncCall.new(method_name.to_sym, args, receiver)
@@ -92,12 +94,6 @@ module RLSL
92
94
  IR::ArrayLiteral.new(elements)
93
95
  end
94
96
 
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
97
  def visit_constant_read(node)
102
98
  name = node.name.to_s
103
99
  return IR::Constant.new(name.to_sym, :float) if %w[PI TAU].include?(name)
@@ -114,6 +110,10 @@ module RLSL
114
110
  end
115
111
  path_parts.unshift(current.name.to_s) if current.respond_to?(:name)
116
112
 
113
+ if path_parts.first == "Math" && %w[PI TAU].include?(path_parts.last) && path_parts.length == 2
114
+ return IR::Constant.new(path_parts.last.to_sym, :float)
115
+ end
116
+
117
117
  IR::VarRef.new(path_parts.join("_").to_sym)
118
118
  end
119
119
 
@@ -126,9 +126,13 @@ module RLSL
126
126
 
127
127
  then_expr = extract_if_branch_expr(node.then_branch)
128
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
129
+ unless then_expr && else_expr
130
+ raise UnsupportedSyntaxError, "Unsupported if-expression: branches must be single expressions"
131
+ end
130
132
 
131
- IR::Ternary.new(node.condition, then_expr, else_expr)
133
+ IR::Ternary.new(node.condition, then_expr, else_expr).tap do |ternary|
134
+ ternary.location = node.location
135
+ end
132
136
  end
133
137
 
134
138
  def extract_if_branch_expr(branch)
@@ -144,7 +148,8 @@ module RLSL
144
148
  end
145
149
 
146
150
  def parameter_reference_call?(method_name, receiver, args)
147
- !receiver && args.empty? && parameter_reference?(method_name.to_sym)
151
+ !receiver && args.empty? &&
152
+ (parameter_reference?(method_name.to_sym) || infer_param_type(method_name.to_sym))
148
153
  end
149
154
 
150
155
  def receiver_without_arguments?(node, receiver, args)
@@ -152,9 +157,8 @@ module RLSL
152
157
  end
153
158
 
154
159
  def visit_receiver_call(method_name, receiver)
160
+ return IR::FieldAccess.new(receiver, method_name) if receiver.type == :uniforms
155
161
  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
162
 
159
163
  IR::FieldAccess.new(receiver, method_name)
160
164
  end
@@ -21,6 +21,10 @@ module RLSL
21
21
  @scopes.reverse_each.any? { |scope| scope.params.include?(name.to_sym) }
22
22
  end
23
23
 
24
+ def root_parameter?(name)
25
+ @scopes.rindex { |scope| scope.params.include?(name.to_sym) } == 0
26
+ end
27
+
24
28
  def declared?(name)
25
29
  @scopes.reverse_each.any? { |scope| scope.declared_vars.include?(name.to_sym) }
26
30
  end