rlsl 0.1.1 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +13 -2
- data/README.md +101 -26
- data/Rakefile +7 -0
- data/lib/rlsl/base_translator/call_parser.rb +79 -0
- data/lib/rlsl/base_translator/code_rewriter.rb +107 -0
- data/lib/rlsl/base_translator/code_scanner.rb +140 -0
- data/lib/rlsl/base_translator.rb +182 -64
- data/lib/rlsl/code_generator/math_prelude.rb +172 -0
- data/lib/rlsl/code_generator/ruby_wrapper_generator.rb +137 -0
- data/lib/rlsl/code_generator/shader_function_generator.rb +19 -0
- data/lib/rlsl/code_generator/template_context.rb +44 -0
- data/lib/rlsl/code_generator/uniform_struct_generator.rb +29 -0
- data/lib/rlsl/code_generator.rb +30 -202
- data/lib/rlsl/compiled_shader.rb +5 -13
- data/lib/rlsl/errors.rb +29 -0
- data/lib/rlsl/function_context.rb +31 -14
- data/lib/rlsl/glsl/translator.rb +35 -41
- data/lib/rlsl/msl/shader.rb +34 -46
- data/lib/rlsl/msl/translator.rb +38 -34
- data/lib/rlsl/msl/uniform_buffer_packer.rb +72 -0
- data/lib/rlsl/prism/ast_visitor/control_flow_visiting.rb +137 -0
- data/lib/rlsl/prism/ast_visitor/definition_visiting.rb +92 -0
- data/lib/rlsl/prism/ast_visitor/expression_visiting.rb +172 -0
- data/lib/rlsl/prism/ast_visitor/scope_context.rb +48 -0
- data/lib/rlsl/prism/ast_visitor/visitor_registry.rb +21 -0
- data/lib/rlsl/prism/ast_visitor.rb +100 -286
- data/lib/rlsl/prism/builtins/function_registry.rb +131 -0
- data/lib/rlsl/prism/builtins/operator_rules.rb +123 -0
- data/lib/rlsl/prism/builtins/swizzle_rules.rb +47 -0
- data/lib/rlsl/prism/builtins.rb +39 -148
- data/lib/rlsl/prism/compilation_unit.rb +7 -0
- data/lib/rlsl/prism/emitters/base_emitter/control_flow_emission.rb +126 -0
- data/lib/rlsl/prism/emitters/base_emitter/definition_emission.rb +134 -0
- data/lib/rlsl/prism/emitters/base_emitter/expression_emission.rb +108 -0
- data/lib/rlsl/prism/emitters/base_emitter/statement_emission.rb +95 -0
- data/lib/rlsl/prism/emitters/base_emitter.rb +120 -414
- data/lib/rlsl/prism/emitters/c_emitter.rb +165 -112
- data/lib/rlsl/prism/emitters/glsl_emitter.rb +63 -50
- data/lib/rlsl/prism/emitters/msl_emitter.rb +67 -52
- data/lib/rlsl/prism/emitters/target_emitter.rb +124 -0
- data/lib/rlsl/prism/emitters/target_profile.rb +34 -0
- data/lib/rlsl/prism/emitters/wgsl_emitter.rb +217 -58
- data/lib/rlsl/prism/errors.rb +9 -0
- data/lib/rlsl/prism/ir/control_flow.rb +85 -0
- data/lib/rlsl/prism/ir/definitions.rb +82 -0
- data/lib/rlsl/prism/ir/expressions.rb +201 -0
- data/lib/rlsl/prism/ir/node.rb +21 -0
- data/lib/rlsl/prism/ir/nodes.rb +4 -371
- data/lib/rlsl/prism/ir/traversal.rb +66 -0
- data/lib/rlsl/prism/mutation_analyzer.rb +30 -0
- data/lib/rlsl/prism/node_traversal.rb +41 -0
- data/lib/rlsl/prism/parameter_list.rb +46 -0
- data/lib/rlsl/prism/return_flow_validator.rb +73 -0
- data/lib/rlsl/prism/source_extractor/block_locator.rb +50 -0
- data/lib/rlsl/prism/source_extractor.rb +19 -137
- data/lib/rlsl/prism/source_unit/parser.rb +88 -0
- data/lib/rlsl/prism/source_unit.rb +42 -0
- data/lib/rlsl/prism/target_capability_validator.rb +110 -0
- data/lib/rlsl/prism/transpiler.rb +99 -59
- data/lib/rlsl/prism/type_inference/call_type_resolver.rb +59 -0
- data/lib/rlsl/prism/type_inference/call_validator.rb +75 -0
- data/lib/rlsl/prism/type_inference/collection_type_resolver.rb +106 -0
- data/lib/rlsl/prism/type_inference/control_flow_inferer.rb +147 -0
- data/lib/rlsl/prism/type_inference/definition_inferer.rb +56 -0
- data/lib/rlsl/prism/type_inference/expression_inferer.rb +96 -0
- data/lib/rlsl/prism/type_inference/field_type_resolver.rb +38 -0
- data/lib/rlsl/prism/type_inference/inferer_registry.rb +38 -0
- data/lib/rlsl/prism/type_inference/scope_stack.rb +47 -0
- data/lib/rlsl/prism/type_inference/type_environment.rb +112 -0
- data/lib/rlsl/prism/type_inference/type_shapes.rb +33 -0
- data/lib/rlsl/prism/type_inference.rb +120 -249
- data/lib/rlsl/runtime_shader.rb +47 -0
- data/lib/rlsl/shader_builder/build_service.rb +104 -0
- data/lib/rlsl/shader_builder/native_extension_compiler.rb +97 -0
- data/lib/rlsl/shader_builder/shader_definition.rb +68 -0
- data/lib/rlsl/shader_builder/source_resolver.rb +109 -0
- data/lib/rlsl/shader_builder.rb +60 -111
- data/lib/rlsl/shader_name.rb +18 -0
- data/lib/rlsl/types/catalog.rb +47 -0
- data/lib/rlsl/types/target_resolver.rb +15 -0
- data/lib/rlsl/types/type_spec.rb +167 -0
- data/lib/rlsl/types/value_normalizer.rb +86 -0
- data/lib/rlsl/types.rb +9 -31
- data/lib/rlsl/uniform_context.rb +22 -11
- data/lib/rlsl/version.rb +1 -1
- data/lib/rlsl/wgsl/translator.rb +46 -39
- data/lib/rlsl/wgsl/uniform_layout.rb +25 -0
- data/lib/rlsl.rb +38 -15
- metadata +76 -11
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RLSL
|
|
4
|
+
module Prism
|
|
5
|
+
module NodeTraversal
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def each(node)
|
|
9
|
+
return enum_for(__method__, node) unless block_given?
|
|
10
|
+
return unless node
|
|
11
|
+
|
|
12
|
+
stack = [node]
|
|
13
|
+
until stack.empty?
|
|
14
|
+
current = stack.pop
|
|
15
|
+
yield current
|
|
16
|
+
stack.concat(child_nodes(current).reverse)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def depth_exceeds?(node, maximum)
|
|
21
|
+
return false unless node
|
|
22
|
+
|
|
23
|
+
stack = [[node, 1]]
|
|
24
|
+
until stack.empty?
|
|
25
|
+
current, depth = stack.pop
|
|
26
|
+
return true if depth > maximum
|
|
27
|
+
|
|
28
|
+
child_nodes(current).reverse_each { |child| stack << [child, depth + 1] }
|
|
29
|
+
end
|
|
30
|
+
false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def child_nodes(node)
|
|
34
|
+
return node.compact_child_nodes if node.respond_to?(:compact_child_nodes)
|
|
35
|
+
|
|
36
|
+
Array(node.child_nodes).compact
|
|
37
|
+
end
|
|
38
|
+
private_class_method :child_nodes
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "errors"
|
|
4
|
+
|
|
5
|
+
module RLSL
|
|
6
|
+
module Prism
|
|
7
|
+
module ParameterList
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def required_names(parameter_container)
|
|
11
|
+
parameters = unwrap(parameter_container)
|
|
12
|
+
return [] unless parameters
|
|
13
|
+
|
|
14
|
+
unsupported = children(parameters).reject { |parameter| parameter.is_a?(::Prism::RequiredParameterNode) }
|
|
15
|
+
unless unsupported.empty?
|
|
16
|
+
raise UnsupportedSyntaxError, "Only required positional parameters are supported"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
parameters.requireds.map(&:name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def names(parameter_container)
|
|
23
|
+
parameters = unwrap(parameter_container)
|
|
24
|
+
return [] unless parameters
|
|
25
|
+
|
|
26
|
+
children(parameters).filter_map { |parameter| parameter.name if parameter.respond_to?(:name) }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def unwrap(container)
|
|
30
|
+
return container.parameters if container.respond_to?(:parameters)
|
|
31
|
+
|
|
32
|
+
container
|
|
33
|
+
end
|
|
34
|
+
private_class_method :unwrap
|
|
35
|
+
|
|
36
|
+
def children(parameters)
|
|
37
|
+
if parameters.respond_to?(:compact_child_nodes)
|
|
38
|
+
parameters.compact_child_nodes
|
|
39
|
+
else
|
|
40
|
+
Array(parameters.child_nodes).compact
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
private_class_method :children
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "ir/traversal"
|
|
4
|
+
|
|
5
|
+
module RLSL
|
|
6
|
+
module Prism
|
|
7
|
+
class ReturnFlowError < RLSL::Error; end
|
|
8
|
+
|
|
9
|
+
class ReturnFlowValidator
|
|
10
|
+
VALUE_NODES = [
|
|
11
|
+
IR::VarDecl,
|
|
12
|
+
IR::Assignment,
|
|
13
|
+
IR::VarRef,
|
|
14
|
+
IR::Literal,
|
|
15
|
+
IR::BoolLiteral,
|
|
16
|
+
IR::BinaryOp,
|
|
17
|
+
IR::UnaryOp,
|
|
18
|
+
IR::FuncCall,
|
|
19
|
+
IR::FieldAccess,
|
|
20
|
+
IR::Swizzle,
|
|
21
|
+
IR::Ternary,
|
|
22
|
+
IR::Constant,
|
|
23
|
+
IR::Parenthesized,
|
|
24
|
+
IR::ArrayIndex
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
def validate!(node, needs_return:)
|
|
28
|
+
validate_functions!(node)
|
|
29
|
+
validate_returning_block!(node, "shader fragment") if needs_return
|
|
30
|
+
node
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def validate_functions!(node)
|
|
36
|
+
IR::Traversal.each(node) do |current|
|
|
37
|
+
next unless current.is_a?(IR::FunctionDefinition)
|
|
38
|
+
|
|
39
|
+
validate_returning_block!(
|
|
40
|
+
current.body,
|
|
41
|
+
"function #{current.name}",
|
|
42
|
+
tuple_return: current.return_type.is_a?(Array)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate_returning_block!(node, context, tuple_return: false)
|
|
48
|
+
return if returns_value_on_all_paths?(node, tuple_return: tuple_return)
|
|
49
|
+
|
|
50
|
+
raise ReturnFlowError.new(
|
|
51
|
+
"#{context} does not return a value on every path"
|
|
52
|
+
).with_source_location(node.location)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def returns_value_on_all_paths?(node, tuple_return: false)
|
|
56
|
+
case node
|
|
57
|
+
when IR::Block
|
|
58
|
+
returns_value_on_all_paths?(node.statements.last, tuple_return: tuple_return)
|
|
59
|
+
when IR::Return
|
|
60
|
+
!node.expression.nil? && (tuple_return || !node.expression.is_a?(IR::ArrayLiteral))
|
|
61
|
+
when IR::IfStatement
|
|
62
|
+
node.else_branch &&
|
|
63
|
+
returns_value_on_all_paths?(node.then_branch, tuple_return: tuple_return) &&
|
|
64
|
+
returns_value_on_all_paths?(node.else_branch, tuple_return: tuple_return)
|
|
65
|
+
when IR::ArrayLiteral
|
|
66
|
+
tuple_return
|
|
67
|
+
else
|
|
68
|
+
VALUE_NODES.any? { |klass| node.is_a?(klass) }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../node_traversal"
|
|
4
|
+
require_relative "../parameter_list"
|
|
5
|
+
|
|
6
|
+
module RLSL
|
|
7
|
+
module Prism
|
|
8
|
+
class SourceExtractor
|
|
9
|
+
class BlockLocator
|
|
10
|
+
def extract(source, start_line)
|
|
11
|
+
extract_unit(source, start_line).to_source
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def extract_unit(source, start_line, parameters: nil, source_name: "(shader block)")
|
|
15
|
+
parsed = ::Prism.parse(source)
|
|
16
|
+
raise SourceNotAvailable, "Unable to parse block source" unless parsed.success?
|
|
17
|
+
|
|
18
|
+
block = block_at_line(parsed.value, start_line, parameters)
|
|
19
|
+
raise SourceNotAvailable, "Unable to locate block source" unless block
|
|
20
|
+
|
|
21
|
+
SourceUnit.from_block(block, source_name: source_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def block_at_line(node, start_line, parameters)
|
|
27
|
+
candidates = NodeTraversal.each(node).select do |current|
|
|
28
|
+
current.is_a?(::Prism::BlockNode) && current.location.start_line == start_line
|
|
29
|
+
end
|
|
30
|
+
candidates.select! { |candidate| parameter_names(candidate) == required_parameter_names(parameters) } if parameters
|
|
31
|
+
|
|
32
|
+
if candidates.length > 1
|
|
33
|
+
raise SourceNotAvailable,
|
|
34
|
+
"Multiple shader blocks start on line #{start_line}; put each block on its own line"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
candidates.first
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def parameter_names(block)
|
|
41
|
+
ParameterList.names(block.parameters)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def required_parameter_names(parameters)
|
|
45
|
+
Array(parameters).filter_map { |_kind, name| name }
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -1,151 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "source_unit"
|
|
6
|
+
require_relative "source_extractor/block_locator"
|
|
7
|
+
|
|
3
8
|
module RLSL
|
|
4
9
|
module Prism
|
|
5
10
|
class SourceExtractor
|
|
6
|
-
class SourceNotAvailable <
|
|
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
|
|
11
|
+
class SourceNotAvailable < RLSL::Error; end
|
|
56
12
|
|
|
57
|
-
|
|
13
|
+
def initialize(block_locator = BlockLocator.new)
|
|
14
|
+
@block_locator = block_locator
|
|
58
15
|
end
|
|
59
16
|
|
|
60
|
-
def
|
|
61
|
-
|
|
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
|
|
17
|
+
def extract(block)
|
|
18
|
+
extract_unit(block).to_source
|
|
133
19
|
end
|
|
134
20
|
|
|
135
|
-
def
|
|
136
|
-
|
|
137
|
-
|
|
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?
|
|
21
|
+
def extract_unit(block)
|
|
22
|
+
file, line_num = block.source_location
|
|
23
|
+
raise SourceNotAvailable, "Block source location not available" unless file && File.exist?(file)
|
|
147
24
|
|
|
148
|
-
|
|
25
|
+
@block_locator.extract_unit(
|
|
26
|
+
File.read(file),
|
|
27
|
+
line_num,
|
|
28
|
+
parameters: block.parameters,
|
|
29
|
+
source_name: file
|
|
30
|
+
)
|
|
149
31
|
end
|
|
150
32
|
end
|
|
151
33
|
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "prism"
|
|
4
|
+
|
|
5
|
+
require_relative "../node_traversal"
|
|
6
|
+
require_relative "../parameter_list"
|
|
7
|
+
|
|
8
|
+
module RLSL
|
|
9
|
+
module Prism
|
|
10
|
+
class SourceUnitParser
|
|
11
|
+
def initialize(source, source_name: "(shader source)")
|
|
12
|
+
@source = source.to_s
|
|
13
|
+
@source_name = source_name
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse
|
|
17
|
+
normalized, leading_line_offset = strip_with_line_offset(@source)
|
|
18
|
+
if normalized.empty?
|
|
19
|
+
return SourceUnit.new(params: [], body: "", source_name: @source_name, line_offset: leading_line_offset)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
params_source, body_source, parameter_line_offset = split_sections(normalized)
|
|
23
|
+
stripped_body, body_line_offset = strip_with_line_offset(body_source)
|
|
24
|
+
line_offset = leading_line_offset + parameter_line_offset + body_line_offset
|
|
25
|
+
validate_body!(stripped_body, line_offset)
|
|
26
|
+
|
|
27
|
+
SourceUnit.new(
|
|
28
|
+
params: parse_params(params_source),
|
|
29
|
+
body: stripped_body,
|
|
30
|
+
source_name: @source_name,
|
|
31
|
+
line_offset: line_offset
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def split_sections(source)
|
|
38
|
+
lines = source.lines
|
|
39
|
+
first_line = lines.first&.strip
|
|
40
|
+
return [nil, source, 0] unless parameter_line?(first_line)
|
|
41
|
+
|
|
42
|
+
[first_line, lines[1..].to_a.join, 1]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def parameter_line?(line)
|
|
46
|
+
line&.start_with?("|") && line.end_with?("|")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def validate_body!(body_source, line_offset)
|
|
50
|
+
return if body_source.to_s.strip.empty?
|
|
51
|
+
|
|
52
|
+
parsed = ::Prism.parse(body_source)
|
|
53
|
+
return if parsed.success?
|
|
54
|
+
|
|
55
|
+
error = RLSL::ParseError.new("Unable to parse source unit body")
|
|
56
|
+
location = parsed.errors.first&.location
|
|
57
|
+
if location
|
|
58
|
+
error.with_source_location(
|
|
59
|
+
RLSL::SourceLocation.new(
|
|
60
|
+
source_name: @source_name,
|
|
61
|
+
line: line_offset + location.start_line,
|
|
62
|
+
column: location.start_column + 1
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
raise error
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def parse_params(params_source)
|
|
70
|
+
return [] unless params_source
|
|
71
|
+
|
|
72
|
+
parsed = ::Prism.parse("proc do #{params_source}\nend\n")
|
|
73
|
+
raise RLSL::ParseError, "Unable to parse source unit params" unless parsed.success?
|
|
74
|
+
|
|
75
|
+
block = NodeTraversal.each(parsed.value).find { |node| node.is_a?(::Prism::BlockNode) }
|
|
76
|
+
return [] unless block
|
|
77
|
+
|
|
78
|
+
ParameterList.required_names(block.parameters)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def strip_with_line_offset(source)
|
|
82
|
+
text = source.to_s
|
|
83
|
+
leading_whitespace = text[/\A\s*/].to_s
|
|
84
|
+
[text.strip, leading_whitespace.count("\n")]
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "source_unit/parser"
|
|
4
|
+
require_relative "parameter_list"
|
|
5
|
+
|
|
6
|
+
module RLSL
|
|
7
|
+
module Prism
|
|
8
|
+
SourceUnit = Struct.new(:params, :body, :source_name, :line_offset, keyword_init: true) do
|
|
9
|
+
class << self
|
|
10
|
+
def from_source(source, source_name: "(shader source)")
|
|
11
|
+
SourceUnitParser.new(source, source_name: source_name).parse
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def from_block(block, source_name: "(shader block)")
|
|
15
|
+
new(
|
|
16
|
+
params: extract_params(block),
|
|
17
|
+
body: block.body&.slice.to_s.strip,
|
|
18
|
+
source_name: source_name,
|
|
19
|
+
line_offset: block.body ? block.body.location.start_line - 1 : block.location.start_line - 1
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def extract_params(block)
|
|
26
|
+
ParameterList.required_names(block.parameters)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def without_params
|
|
31
|
+
self.class.new(params: [], body: body, source_name: source_name, line_offset: line_offset)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_source
|
|
35
|
+
segments = []
|
|
36
|
+
segments << "|#{params.join(', ')}|" unless params.empty?
|
|
37
|
+
segments << body unless body.empty?
|
|
38
|
+
segments.join("\n")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../types"
|
|
4
|
+
require_relative "ir/traversal"
|
|
5
|
+
require_relative "type_inference/type_shapes"
|
|
6
|
+
|
|
7
|
+
module RLSL
|
|
8
|
+
module Prism
|
|
9
|
+
class TargetCapabilityError < RLSL::Error; end
|
|
10
|
+
|
|
11
|
+
class TargetCapabilityValidator
|
|
12
|
+
include TypeShapes
|
|
13
|
+
|
|
14
|
+
def validate!(node, target)
|
|
15
|
+
@target = target.to_sym
|
|
16
|
+
IR::Traversal.each(node) do |current|
|
|
17
|
+
validate_node!(current)
|
|
18
|
+
rescue RLSL::Error => error
|
|
19
|
+
error.with_source_location(current.location)
|
|
20
|
+
raise
|
|
21
|
+
end
|
|
22
|
+
node
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :target
|
|
28
|
+
|
|
29
|
+
def validate_node!(node)
|
|
30
|
+
return if node.nil?
|
|
31
|
+
|
|
32
|
+
validate_type!(node.type, context: node.class.name.split("::").last)
|
|
33
|
+
|
|
34
|
+
case node
|
|
35
|
+
when IR::FuncCall
|
|
36
|
+
validate_builtin!(node)
|
|
37
|
+
when IR::FunctionDefinition
|
|
38
|
+
validate_type!(node.return_type, context: "function #{node.name} return")
|
|
39
|
+
node.param_types.each_value do |type|
|
|
40
|
+
validate_type!(type, context: "function #{node.name} parameter")
|
|
41
|
+
end
|
|
42
|
+
when IR::GlobalDecl
|
|
43
|
+
validate_type!(node.element_type, context: "global #{node.name} element")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate_builtin!(node)
|
|
48
|
+
return unless Builtins.function?(node.name)
|
|
49
|
+
|
|
50
|
+
unless Builtins.supported_on_target?(node.name, target)
|
|
51
|
+
raise TargetCapabilityError, "Builtin #{node.name} is not supported on #{target.to_s.upcase}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Builtins.explicit_types(node.name).each do |type|
|
|
55
|
+
validate_type!(type, context: "builtin #{node.name}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
validate_c_builtin_overload!(node) if target == :c
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def validate_c_builtin_overload!(node)
|
|
62
|
+
vector_types = node.args.map(&:type).select { |type| Builtins.vector_type?(type) }
|
|
63
|
+
scalar_only = %i[sqrt abs sign floor ceil fract mod min max clamp step smoothstep]
|
|
64
|
+
if scalar_only.include?(node.name.to_sym) && !vector_types.empty?
|
|
65
|
+
raise TargetCapabilityError,
|
|
66
|
+
"Builtin #{node.name} does not support vector arguments on C"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if %i[length normalize dot distance].include?(node.name.to_sym) && vector_types.empty?
|
|
70
|
+
raise TargetCapabilityError, "Builtin #{node.name} requires vector arguments on C"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return unless %i[reflect refract].include?(node.name.to_sym)
|
|
74
|
+
return if node.args.first&.type == :vec3 && node.args[1]&.type == :vec3
|
|
75
|
+
|
|
76
|
+
raise TargetCapabilityError, "Builtin #{node.name} currently requires vec3 arguments on C"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def validate_type!(type, context:)
|
|
80
|
+
return if type.nil?
|
|
81
|
+
|
|
82
|
+
if TypeShapes.array?(type)
|
|
83
|
+
validate_type!(TypeShapes.element_type(type), context: "#{context} array element")
|
|
84
|
+
return
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
if type.is_a?(Array)
|
|
88
|
+
type.each { |element_type| validate_type!(element_type, context: "#{context} tuple element") }
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if type.is_a?(IR::TupleType)
|
|
93
|
+
type.types.each { |element_type| validate_type!(element_type, context: "#{context} tuple element") }
|
|
94
|
+
return
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
return unless uniform_type_symbol?(type)
|
|
98
|
+
|
|
99
|
+
return if RLSL::UniformTypes.fetch(type).target_supported?(target)
|
|
100
|
+
|
|
101
|
+
raise TargetCapabilityError,
|
|
102
|
+
"Unsupported #{target.to_s.upcase} type #{type.inspect} in #{context}"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def uniform_type_symbol?(type)
|
|
106
|
+
type.is_a?(Symbol) && RLSL::UniformTypes.supported?(type)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|