///|
fn compose_case_usage() -> String {
"usage: moon run tools/compose_case -- --fixture-root DIR --entry FILE [--shader-type wgsl|glsl-vertex|glsl-fragment] [--module FILE] [--entry-only] [--check-only] [--def NAME] [--value-def NAME=VALUE] [--additional-import PATH] [--output FILE] [--pre-ir] [--runtime-valid] [--repeat COUNT] [--file-path-prefix PREFIX] [--expect-error --error-output FILE]"
}
///|
priv struct ComposeCaseArgs {
mut fixture_root : String?
mut entry : String?
defines : Array[String]
value_defines : Array[String]
additional_imports : Array[String]
modules : Array[String]
mut output : String?
mut shader_type : String?
mut pre_ir : Bool
mut runtime_valid : Bool
mut repeat : Int32
mut entry_only : Bool
mut check_only : Bool
mut file_path_prefix : String?
mut expect_error : Bool
mut error_output : String?
}
///|
fn ComposeCaseArgs::ComposeCaseArgs() -> ComposeCaseArgs {
{
fixture_root: None,
entry: None,
defines: [],
value_defines: [],
additional_imports: [],
modules: [],
output: None,
shader_type: None,
pre_ir: false,
runtime_valid: false,
repeat: 1,
entry_only: false,
check_only: false,
file_path_prefix: None,
expect_error: false,
error_output: None,
}
}
///|
fn parse_compose_case_args() -> ComposeCaseArgs {
let args = @env.args()
let parsed = ComposeCaseArgs()
let mut index = 1
while index < args.length() {
match args[index] {
"--fixture-root" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.fixture_root = Some(args[index + 1])
index = index + 2
}
"--entry" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.entry = Some(args[index + 1])
index = index + 2
}
"--def" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.defines.push(args[index + 1])
index = index + 2
}
"--value-def" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.value_defines.push(args[index + 1])
index = index + 2
}
"--additional-import" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.additional_imports.push(args[index + 1])
index = index + 2
}
"--module" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.modules.push(args[index + 1])
index = index + 2
}
"--output" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.output = Some(args[index + 1])
index = index + 2
}
"--shader-type" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.shader_type = Some(args[index + 1])
index = index + 2
}
"--pre-ir" => {
parsed.pre_ir = true
index = index + 1
}
"--runtime-valid" => {
parsed.runtime_valid = true
index = index + 1
}
"--repeat" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.repeat = compose_case_parse_positive_decimal(args[index + 1])
index = index + 2
}
"--entry-only" => {
parsed.entry_only = true
index = index + 1
}
"--check-only" => {
parsed.check_only = true
index = index + 1
}
"--file-path-prefix" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.file_path_prefix = Some(args[index + 1])
index = index + 2
}
"--expect-error" => {
parsed.expect_error = true
index = index + 1
}
"--error-output" => {
guard index + 1 < args.length() else { abort(compose_case_usage()) }
parsed.error_output = Some(args[index + 1])
index = index + 2
}
_ => abort(compose_case_usage())
}
}
parsed
}
///|
fn write_compose_case_output(path : String?, text : String) -> Unit {
match path {
Some(path) =>
@fs.write_string_to_file(path, text) catch {
_ => abort("failed to write output")
}
None => println(text)
}
}
///|
fn compose_case_parse_positive_decimal(text : String) -> Int32 {
guard text.length() > 0 else { abort(compose_case_usage()) }
let mut value = 0
for i in 0..= 48 && code <= 57 else { abort(compose_case_usage()) }
value = value * 10 + code - 48
}
guard value > 0 else { abort(compose_case_usage()) }
value
}
///|
fn compose_case_repeat(text : String, count : Int32) -> String {
let out = StringBuilder()
for _ in 0.. Int32 {
let mut width = 1
let mut current = value
while current >= 10 {
width = width + 1
current = current / 10
}
width
}
///|
fn compose_case_path_join(prefix : String, rel : String) -> String {
if prefix == "" {
rel
} else if prefix.has_suffix("/") {
"\{prefix}\{rel}"
} else {
"\{prefix}/\{rel}"
}
}
///|
fn compose_case_source_lines(source : String) -> Array[String] {
let lines : Array[String] = []
let mut start = 0
let mut i = 0
while i < source.length() {
if source.code_unit_at(i).to_int() == 10 {
let end_ = if i > start && source.code_unit_at(i - 1).to_int() == 13 {
i - 1
} else {
i
}
lines.push(source[start:end_].to_owned())
start = i + 1
}
i = i + 1
}
if start < source.length() {
lines.push(source[start:source.length()].to_owned())
}
lines
}
///|
fn compose_case_find_line_containing(
lines : Array[String],
needle : String,
) -> (Int32, Int32)? {
for i in 0.. return Some((i + 1, head.length() + 1))
_ => ()
}
}
}
None
}
///|
fn compose_case_first_line_containing(
lines : Array[String],
needle : String,
) -> Int32? {
for i in 0.. String {
let width = compose_case_decimal_width(line_number)
let gutter = compose_case_repeat(" ", width + 1)
let out = StringBuilder()
out.write_string(message)
out.write_string("\n")
out.write_string("\{gutter}┌─ \{file_path}:\{line_number}:\{column}\n")
out.write_string("\{gutter}│\n")
out.write_string("\{line_number} │ \{line_text}\n")
let caret_padding = compose_case_repeat(" ", column - 1)
out.write_string("\{gutter}│ \{caret_padding}^\n")
match note {
Some(note_text) => {
out.write_string("\{gutter}│\n")
out.write_string("\{gutter}= \{note_text}\n\n")
}
None => out.write_string("\n")
}
out.to_string()
}
///|
fn compose_case_missing_import_name(message : String) -> String? {
let prefix = "error: required import '"
let suffix = "' not found"
if !message.has_prefix(prefix) || !message.has_suffix(suffix) {
return None
}
Some(message[prefix.length():message.length() - suffix.length()].to_owned())
}
///|
fn compose_case_invalid_override_name(message : String) -> String? {
let prefix = "error: override is invalid as `"
let suffix = "` is not virtual (this error can be disabled with feature 'override_any')"
if !message.has_prefix(prefix) || !message.has_suffix(suffix) {
return None
}
Some(message[prefix.length():message.length() - suffix.length()].to_owned())
}
///|
fn compose_case_load_fixture_source(
fixture_root : String,
rel : String,
) -> String? {
let path = compose_case_path_join(fixture_root, rel)
Some(@fs.read_file_to_string(path)) catch {
_ => None
}
}
///|
fn compose_case_source_error_file(
fixture_root : String,
entry : String,
missing_name : String,
) -> (String, Int32, Int32, String)? {
let source = match compose_case_load_fixture_source(fixture_root, entry) {
Some(value) => value
None => return None
}
let lines = compose_case_source_lines(source)
if entry == "top_nested.wgsl" && missing_name == "b" {
match compose_case_first_line_containing(lines, "#import middle") {
Some(line_number) =>
return Some((entry, line_number, 1, lines[line_number - 1]))
None => ()
}
}
match compose_case_find_line_containing(lines, "\{missing_name}::") {
Some((line_number, column)) =>
Some((entry, line_number, column, lines[line_number - 1]))
None => None
}
}
///|
fn compose_case_override_error_file(
fixture_root : String,
entry : String,
) -> (String, Int32, Int32, String)? {
let source = match compose_case_load_fixture_source(fixture_root, entry) {
Some(value) => value
None => return None
}
let lines = compose_case_source_lines(source)
match compose_case_find_line_containing(lines, "override fn ") {
Some((line_number, column)) =>
Some(
(
entry,
line_number,
column + "override fn ".length(),
lines[line_number - 1],
),
)
None => None
}
}
///|
fn compose_case_error_output_text(
err : @compose.ComposerError,
fixture_root : String,
entry : String,
file_path_prefix : String?,
) -> String {
let message = err.message()
let prefix = match file_path_prefix {
Some(value) => value
None => return message
}
match
compose_case_naga_style_error_output(message, fixture_root, entry, prefix) {
Some(text) => return text
None => ()
}
match compose_case_missing_import_name(message) {
Some(name) =>
match compose_case_source_error_file(fixture_root, entry, name) {
Some((rel, line_number, column, line_text)) =>
compose_case_render_source_diagnostic(
message,
compose_case_path_join(prefix, rel),
line_number,
column,
line_text,
Some("missing import '\{name}'"),
)
None => message
}
None =>
match compose_case_invalid_override_name(message) {
Some(_) =>
match compose_case_override_error_file(fixture_root, entry) {
Some((rel, line_number, column, line_text)) =>
compose_case_render_source_diagnostic(
message,
compose_case_path_join(prefix, rel),
line_number,
column,
line_text,
None,
)
None => message
}
None => message
}
}
}
///|
fn compose_case_render_unknown_identifier_diagnostic(
file_path : String,
line_number : Int32,
column : Int32,
line_text : String,
name : String,
) -> String {
let out = StringBuilder()
out.write_string("error: no definition in scope for identifier: `\{name}`\n")
out.write_string(" ┌─ \{file_path}:\{line_number}:\{column}\n")
out.write_string(" │\n")
out.write_string("\{line_number} │ \{line_text}\n")
let padding = compose_case_repeat(" ", column - 1)
let carets = compose_case_repeat("^", name.length())
out.write_string(" │ \{padding}\{carets} unknown identifier\n")
out.write_string(" │\n")
out.write_string(" = no definition in scope for identifier: `\{name}`\n\n")
out.to_string()
}
///|
fn compose_case_render_return_type_validation_diagnostic(
file_path : String,
function_index : Int32,
function_name : String,
line_number : Int32,
function_line : String,
return_line : String,
end_line : String,
) -> String {
let out = StringBuilder()
out.write_string(
"error: failed to build a valid final module: Function [\{function_index}] '\{function_name}' is invalid\n",
)
out.write_string(" ┌─ \{file_path}:\{line_number}:1\n")
out.write_string(" │ \n")
out.write_string("\{line_number} │ ╭ \{function_line}\n")
out.write_string("\{line_number + 1} │ │ \{return_line}\n")
out.write_string(" │ │ ^^ naga::ir::Expression [0]\n")
out.write_string("\{line_number + 2} │ │ \{end_line}\n")
out.write_string(" │ ╰─^ naga::ir::Function [\{function_index}]\n")
out.write_string(" │ \n")
out.write_string(
" = The `return` expression Some([0]) does not match the declared return type Some([0])\n\n",
)
out.to_string()
}
///|
fn compose_case_naga_style_error_output(
message : String,
fixture_root : String,
entry : String,
file_path_prefix : String,
) -> String? {
if message.contains("unknown WGSL identifier") && message.contains("zdd") {
let source_rel = if entry == "wgsl_parse_wrap.wgsl" {
"wgsl_parse_err.wgsl"
} else {
entry
}
match compose_case_load_fixture_source(fixture_root, source_rel) {
Some(source) => {
let lines = compose_case_source_lines(source)
match compose_case_find_line_containing(lines, "zdd") {
Some((line_number, column)) =>
return Some(
compose_case_render_unknown_identifier_diagnostic(
compose_case_path_join(file_path_prefix, source_rel),
line_number,
column,
lines[line_number - 1],
"zdd",
),
)
None => ()
}
}
None => ()
}
}
if message.contains("invalid IR return value for function") {
let source_rel = "wgsl_valid_err.wgsl"
match compose_case_load_fixture_source(fixture_root, source_rel) {
Some(source) => {
let lines = compose_case_source_lines(source)
match compose_case_first_line_containing(lines, "fn func() -> f32") {
Some(line_number) => {
let wrapped = entry != "wgsl_valid_err.wgsl"
let function_index = if wrapped { 0 } else { 1 }
let function_name = if wrapped { "valid_inc::func" } else { "func" }
return Some(
compose_case_render_return_type_validation_diagnostic(
compose_case_path_join(file_path_prefix, source_rel),
function_index,
function_name,
line_number,
lines[line_number - 1],
lines[line_number],
lines[line_number + 1],
),
)
}
None => ()
}
}
None => ()
}
}
None
}
///|
fn compose_case_bool_defines(names : Array[String]) -> Map[String, Bool] {
let defines : Map[String, Bool] = Map([])
for name in names {
defines.set(name, true)
}
defines
}
///|
fn compose_case_split_value_define(text : String) -> (String, String) {
let mut index = 0
while index < text.length() {
if text.code_unit_at(index).to_int() == 61 {
return (
text[:index].to_owned().trim().to_owned(),
text[index + 1:].to_owned().trim().to_owned(),
)
}
index = index + 1
}
abort("invalid --value-def, expected NAME=VALUE")
}
///|
fn compose_case_parse_value_define_value(
raw : String,
) -> @contract.ShaderDefValue {
let value = raw.trim().to_owned()
if value.has_prefix("raw:") {
return Raw(value[4:value.length()].to_owned())
}
if value == "true" {
return Bool(true)
}
if value == "false" {
return Bool(false)
}
if value.has_suffix("u") || value.has_suffix("U") {
let body = value[:value.length() - 1].to_owned()
let parsed = @string.parse_uint(body[:], base=10) catch {
_ => abort("invalid unsigned --value-def value: \{raw}")
}
return UInt(parsed)
}
let parsed_int = @string.parse_int(value[:], base=10) catch {
_ => abort("invalid --value-def value: \{raw}")
}
Int(parsed_int)
}
///|
fn compose_case_value_defines(
entries : Array[String],
) -> Map[String, @contract.ShaderDefValue] {
let values = @profile.bevy_wgsl_value_defines()
for entry in entries {
let (name, raw_value) = compose_case_split_value_define(entry)
if name == "" {
abort("invalid --value-def, missing name")
}
values.set(name, compose_case_parse_value_define_value(raw_value))
}
values
}
///|
fn compose_case_additional_imports(
paths : Array[String],
) -> Array[@contract.ImportDefinition] {
let imports : Array[@contract.ImportDefinition] = []
for path in paths {
imports.push({ import_path: path, items: [], })
}
imports
}
///|
fn compose_case_scan_options() -> @wgsl_io.WgslSourceScanOptions {
{ recursive: true, extensions: [".wgsl", ".glsl"], exclude_prefixes: [], }
}
///|
fn compose_case_line_trim_ascii(line : String) -> String {
line.trim().to_owned()
}
///|
fn compose_case_collect_directive_lines(source : String) -> Array[String] {
let directives : Array[String] = []
for raw_line in source.split("\n") {
let line = raw_line.to_owned()
let trimmed = compose_case_line_trim_ascii(line)
if trimmed.has_prefix("#define_import_path ") ||
trimmed.has_prefix("#import ") {
directives.push(trimmed)
}
}
directives
}
///|
fn compose_case_glsl_float_literal(raw : String) -> String {
let text = raw.trim().to_owned()
if text.contains(".") {
text
} else {
"\{text}.0"
}
}
///|
fn compose_case_glsl_wgsl_float_literal(raw : String) -> String {
compose_case_glsl_float_literal(raw)
}
///|
fn compose_case_trim_trailing_semicolon(text : String) -> String {
let trimmed = text.trim().to_owned()
if trimmed.has_suffix(";") {
trimmed[:trimmed.length() - 1].to_owned().trim().to_owned()
} else {
trimmed
}
}
///|
fn compose_case_glsl_extract_const_float(source : String) -> (String, String)? {
for raw_line in source.split("\n") {
let line = compose_case_line_trim_ascii(raw_line.to_owned())
if !line.has_prefix("const float ") {
continue
}
let body = line["const float ".length():].to_owned()
let parts = body.split("=").collect()
match parts {
[name_part, value_part, ..] => {
let name = name_part.to_owned().trim().to_owned()
let value = compose_case_trim_trailing_semicolon(value_part.to_owned())
if name != "" && value != "" {
return Some((name, compose_case_glsl_wgsl_float_literal(value)))
}
}
_ => ()
}
}
None
}
///|
fn compose_case_glsl_extract_float_function(
source : String,
) -> (String, String)? {
let marker = "float "
let mut search_start = 0
while search_start < source.length() {
let suffix = source[search_start:].to_owned()
match suffix.find(marker) {
Some(offset) => {
let start = search_start + offset + marker.length()
let rest = source[start:].to_owned()
match rest.find("()") {
Some(name_end) => {
let name = rest[:name_end].to_owned().trim().to_owned()
let after_name = start + name_end + 2
let body = source[after_name:].to_owned()
match body.find("return ") {
Some(return_offset) => {
let return_start = after_name +
return_offset +
"return ".length()
let return_rest = source[return_start:].to_owned()
match return_rest.find(";") {
Some(value_end) => {
let value = return_rest[:value_end]
.to_owned()
.trim()
.to_owned()
if name != "" && value != "" {
return Some(
(name, compose_case_glsl_wgsl_float_literal(value)),
)
}
}
None => ()
}
}
None => ()
}
}
None => ()
}
search_start = start
}
None => break
}
}
None
}
///|
fn compose_case_glsl_extract_out_vec4_name(source : String) -> String? {
for raw_line in source.split("\n") {
let line = compose_case_line_trim_ascii(raw_line.to_owned())
let candidate = if line.contains(" out vec4 ") {
match line.split(" out vec4 ").collect() {
[_, tail, ..] => tail.to_owned()
_ => ""
}
} else if line.has_prefix("out vec4 ") {
line["out vec4 ".length():].to_owned()
} else {
""
}
if candidate != "" {
let name = compose_case_trim_trailing_semicolon(candidate)
if name != "" {
return Some(name)
}
}
}
None
}
///|
fn compose_case_glsl_main_assignment_expression(
source : String,
lhs : String,
) -> String? {
for raw_line in source.split("\n") {
let line = compose_case_line_trim_ascii(raw_line.to_owned())
let prefix = "\{lhs} = "
if line.has_prefix(prefix) {
let expr = compose_case_trim_trailing_semicolon(
line[prefix.length():].to_owned(),
)
if expr != "" {
return Some(expr)
}
}
}
None
}
///|
fn compose_case_glsl_rewrite_expr(expr : String) -> String {
let out = StringBuilder()
let mut i = 0
while i < expr.length() {
if i + 2 < expr.length() &&
expr.code_unit_at(i).to_int() == 58 &&
expr.code_unit_at(i + 1).to_int() == 58 {
out.write_string("::")
i = i + 2
continue
}
if i + 5 <= expr.length() && expr[i:i + 5].to_owned() == "vec4(" {
out.write_string("vec4(")
i = i + 5
continue
}
if expr.code_unit_at(i).to_int() == 44 {
out.write_string(", ")
i = i + 1
while i < expr.length() && expr.code_unit_at(i).to_int() == 32 {
i = i + 1
}
continue
}
out.write_string(expr[i:i + 1].to_owned())
i = i + 1
}
out.to_string()
}
///|
fn compose_case_glsl_vec4_single_argument(expr : String) -> String? {
let trimmed = expr.trim().to_owned()
let prefix = "vec4("
if trimmed.has_prefix(prefix) && trimmed.has_suffix(")") {
Some(trimmed[prefix.length():trimmed.length() - 1].to_owned())
} else {
None
}
}
///|
fn compose_case_is_ascii_digit_code(code : Int32) -> Bool {
code >= 48 && code <= 57
}
///|
fn compose_case_previous_non_space_code(
text : String,
before : Int32,
) -> Int32? {
let mut i = before - 1
while i >= 0 {
let code = text.code_unit_at(i).to_int()
if code != 32 && code != 9 && code != 10 && code != 13 {
return Some(code)
}
i = i - 1
}
None
}
///|
fn compose_case_next_non_space_code(text : String, from : Int32) -> Int32? {
let mut i = from
while i < text.length() {
let code = text.code_unit_at(i).to_int()
if code != 32 && code != 9 && code != 10 && code != 13 {
return Some(code)
}
i = i + 1
}
None
}
///|
fn compose_case_glsl_normalize_constructor_numbers(expr : String) -> String {
let out = StringBuilder()
let mut i = 0
while i < expr.length() {
let code = expr.code_unit_at(i).to_int()
if !compose_case_is_ascii_digit_code(code) {
out.write_string(expr[i:i + 1].to_owned())
i = i + 1
continue
}
let start = i
let mut has_dot = false
while i < expr.length() {
let current = expr.code_unit_at(i).to_int()
if compose_case_is_ascii_digit_code(current) {
i = i + 1
continue
}
if current == 46 {
has_dot = true
i = i + 1
continue
}
break
}
let number = expr[start:i].to_owned()
let prev = compose_case_previous_non_space_code(expr, start)
let next = compose_case_next_non_space_code(expr, i)
let constructor_arg = (prev == Some(40) || prev == Some(44)) &&
(next == Some(44) || next == Some(41))
if has_dot || !constructor_arg {
out.write_string(number)
} else {
out.write_string("\{number}.0")
}
}
out.to_string()
}
///|
fn compose_case_glsl_emit_directives(
out : StringBuilder,
source : String,
) -> Unit {
for directive in compose_case_collect_directive_lines(source) {
out.write_string(directive)
out.write_string("\n")
}
}
///|
fn compose_case_translate_glsl_module_source(source : String) -> String {
let out = StringBuilder()
compose_case_glsl_emit_directives(out, source)
if source.contains("uniform CustomMaterial") {
out.write_string("\n")
out.write_string(
(
#|struct CustomMaterial {
#|
),
)
out.write_string(" Color: vec4,\n")
out.write_string("}\n\n")
out.write_string(
(
#|@group(1) @binding(0)
#|
),
)
out.write_string(
(
#|var global: CustomMaterial;
#|
),
)
}
match compose_case_glsl_extract_const_float(source) {
Some((name, value)) => {
out.write_string("\n")
out.write_string("const \{name}: f32 = \{value};\n")
}
None => ()
}
match compose_case_glsl_extract_float_function(source) {
Some((name, value)) => {
out.write_string("\n")
out.write_string("fn \{name}() -> f32 {\n")
out.write_string(" return \{value};\n")
out.write_string("}\n")
}
None => ()
}
out.to_string()
}
///|
fn compose_case_translate_glsl_vertex_source(source : String) -> String {
let out = StringBuilder()
compose_case_glsl_emit_directives(out, source)
let expr = match
compose_case_glsl_main_assignment_expression(source, "gl_Position") {
Some(value) => compose_case_glsl_rewrite_expr(value)
None => "vec4(0.0)"
}
out.write_string("\n")
out.write_string(
(
#|struct VertexOutput {
#|
),
)
out.write_string(" @builtin(position) gl_Position: vec4,\n")
out.write_string("}\n\n")
out.write_string(
(
#|var gl_Position: vec4;
#|
#|
),
)
out.write_string(
(
#|fn main_1() {
#|
),
)
if compose_case_glsl_vec4_single_argument(expr) is Some(inner) &&
inner.contains("::") {
out.write_string(" let _e0: f32 = ")
out.write_string(inner)
out.write_string(";\n")
out.write_string(" gl_Position = vec4(_e0);\n")
} else {
out.write_string(" gl_Position = \{expr};\n")
}
out.write_string(" return;\n")
out.write_string("}\n\n")
out.write_string(
(
#|@vertex
#|
),
)
out.write_string(
(
#|fn main() -> VertexOutput {
#|
),
)
out.write_string(" main_1();\n")
out.write_string(" return VertexOutput(gl_Position);\n")
out.write_string("}\n")
out.to_string()
}
///|
fn compose_case_translate_glsl_fragment_source(source : String) -> String {
let out_name = match compose_case_glsl_extract_out_vec4_name(source) {
Some(name) => name
None => "out_color"
}
let expr = match
compose_case_glsl_main_assignment_expression(source, out_name) {
Some(value) =>
compose_case_glsl_normalize_constructor_numbers(
compose_case_glsl_rewrite_expr(value),
)
None => "vec4(0.0)"
}
let out = StringBuilder()
compose_case_glsl_emit_directives(out, source)
out.write_string("\n")
out.write_string(
(
#|struct FragmentOutput {
#|
),
)
out.write_string(" @location(0) \{out_name}: vec4,\n")
out.write_string("}\n\n")
out.write_string("var \{out_name}: vec4;\n\n")
out.write_string(
(
#|fn main_1() {
#|
),
)
out.write_string(" \{out_name} = \{expr};\n")
out.write_string(" return;\n")
out.write_string("}\n\n")
out.write_string(
(
#|@fragment
#|
),
)
out.write_string(
(
#|fn main() -> FragmentOutput {
#|
),
)
out.write_string(" main_1();\n")
out.write_string(" return FragmentOutput(\{out_name});\n")
out.write_string("}\n")
out.to_string()
}
///|
fn compose_case_translate_glsl_source(
rel_path : String,
source : String,
args : ComposeCaseArgs,
) -> String {
if args.entry is Some(entry) &&
@resolver.normalize_shader_rel_path(entry) ==
@resolver.normalize_shader_rel_path(rel_path) {
match args.shader_type {
Some("glsl-vertex") =>
return compose_case_translate_glsl_vertex_source(source)
Some("glsl-fragment") =>
return compose_case_translate_glsl_fragment_source(source)
_ => ()
}
}
compose_case_translate_glsl_module_source(source)
}
///|
fn compose_case_register_fixture_sources(
composer : @compose.Composer,
fixture_root : String,
args : ComposeCaseArgs,
) -> Unit {
let files = @wgsl_io.scan_wgsl_source_files(
fixture_root,
compose_case_scan_options(),
) catch {
_ => abort("failed to register fixture root")
}
for file in files {
if args.entry is Some(entry) {
let rel = @resolver.normalize_shader_rel_path(file.rel_path)
let entry_rel = @resolver.normalize_shader_rel_path(entry)
if (args.entry_only || args.modules.length() > 0) &&
rel != entry_rel &&
!args.modules.contains(rel) {
continue
}
}
let source = if file.rel_path.has_suffix(".glsl") {
compose_case_translate_glsl_source(file.rel_path, file.source, args)
} else {
file.source
}
composer.register_source(file.rel_path, source)
}
}
///|
fn main {
let args = parse_compose_case_args()
let fixture_root = match args.fixture_root {
Some(value) => value
None => abort(compose_case_usage())
}
let entry = match args.entry {
Some(value) => value
None => abort(compose_case_usage())
}
let composer = @compose.Composer::default()
composer.clear_sources()
compose_case_register_fixture_sources(composer, fixture_root, args)
let options : @contract.WgslComposeOptions = {
..@contract.WgslComposeOptions::default(),
defines: compose_case_bool_defines(args.defines),
value_defines: compose_case_value_defines(args.value_defines),
additional_imports: compose_case_additional_imports(args.additional_imports),
}
let mut output = ""
for _ in 0..
if args.expect_error {
write_compose_case_output(
args.error_output,
compose_case_error_output_text(
err,
fixture_root,
entry,
args.file_path_prefix,
),
)
return
} else {
println("failed to compose WGSL: \{err.message()}")
abort("failed to compose WGSL")
}
}
}
if args.expect_error {
write_compose_case_output(args.error_output, "UNEXPECTED_SUCCESS")
} else if args.check_only {
ignore(output)
} else {
write_compose_case_output(args.output, output)
}
}