// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn preprocess_blank_text(text : String) -> String {
let mut out = ""
let mut line_start = 0
let mut i = 0
while i < text.length() {
let width = wgsl_line_break_width(text, i)
if width > 0 {
out = out + spaces(utf8_byte_length(text[line_start:i].to_owned()))
out = out + text[i:i + width].to_owned()
i = i + width
line_start = i
} else {
i = i + 1
}
}
if line_start < text.length() {
out = out +
spaces(utf8_byte_length(text[line_start:text.length()].to_owned()))
}
out
}
///|
fn preprocess_check_scope_directive(
shader_defs : Map[String, ShaderDefValue],
directive : WgslPreprocessorDirective,
scope : Scope?,
offset : Int,
) -> (Bool, String?) raise PreprocessError {
let tail = wgsl_effective_directive_tail(directive)
let has_required_separator = directive.has_required_tail_separator()
match wgsl_effective_directive_kind(directive) {
Ifdef =>
if has_required_separator {
match parse_wgsl_directive_identifier(tail) {
Some(name) => {
let condition = shader_defs.contains(name)
if scope is Some(scope_state) {
scope_state.branch(false, condition, offset)
}
(true, Some(name))
}
None => (false, None)
}
} else {
(false, None)
}
Ifndef =>
if has_required_separator {
match parse_wgsl_directive_identifier(tail) {
Some(name) => {
let condition = !shader_defs.contains(name)
if scope is Some(scope_state) {
scope_state.branch(false, condition, offset)
}
(true, Some(name))
}
None => (false, None)
}
} else {
(false, None)
}
If =>
if has_required_separator {
check_if_operator_scope(shader_defs, tail, scope, offset, false)
} else {
(false, None)
}
Else => {
let else_tail_has_space = wgsl_text_starts_with_tokenized_space(tail)
if else_tail_has_space &&
wgsl_required_space_word_tail(tail, "ifdef") is Some(rest) &&
parse_wgsl_directive_identifier(rest) is Some(name) {
let condition = shader_defs.contains(name)
if scope is Some(scope_state) {
scope_state.branch(true, condition, offset)
}
return (true, Some(name))
}
if else_tail_has_space &&
wgsl_required_space_word_tail(tail, "ifndef") is Some(rest) &&
parse_wgsl_directive_identifier(rest) is Some(name) {
let condition = !shader_defs.contains(name)
if scope is Some(scope_state) {
scope_state.branch(true, condition, offset)
}
return (true, Some(name))
}
if else_tail_has_space &&
wgsl_required_space_word_tail(tail, "if") is Some(expr) {
let (is_scope, def) = check_if_operator_scope(
shader_defs, expr, scope, offset, true,
)
if is_scope {
return (true, def)
}
}
if scope is Some(scope_state) {
scope_state.branch(true, true, offset)
}
(true, None)
}
Endif => {
if scope is Some(scope_state) {
scope_state.pop(offset)
}
(true, None)
}
_ => (false, None)
}
}
///|
fn preprocess_source_line_text_end(text : String, line_start : Int) -> Int {
wgsl_line_text_end(text, line_start)
}
///|
fn preprocess_replace_shader_defs_in_text(
text : String,
shader_defs : Map[String, ShaderDefValue],
) -> String {
let mut out = ""
let mut line_start = 0
while line_start < text.length() {
let line_text_end = preprocess_source_line_text_end(text, line_start)
let line_end = line_text_end + wgsl_line_break_width(text, line_text_end)
let line = text[line_start:line_text_end].to_owned()
out = out + replace_shader_defs_in_line(line, shader_defs)
if line_end > line_text_end {
out = out + "\n"
}
line_start = line_end
}
out
}
///|
fn preprocess_pad_rewritten_source_item(
rewritten : String,
reference : String,
) -> String {
let mut out = ""
let mut rewritten_line_start = 0
let mut reference_line_start = 0
while rewritten_line_start < rewritten.length() {
let rewritten_line_text_end = preprocess_source_line_text_end(
rewritten, rewritten_line_start,
)
let rewritten_line_end = rewritten_line_text_end +
wgsl_line_break_width(rewritten, rewritten_line_text_end)
let reference_line_text_end = preprocess_source_line_text_end(
reference, reference_line_start,
)
let reference_line_end = reference_line_text_end +
wgsl_line_break_width(reference, reference_line_text_end)
let rewritten_line = rewritten[rewritten_line_start:rewritten_line_text_end].to_owned()
let reference_line = reference[reference_line_start:reference_line_text_end].to_owned()
out = out + rewritten_line
let diff = utf8_byte_length(reference_line) -
utf8_byte_length(rewritten_line)
if diff > 0 {
out = out + spaces(diff)
}
if rewritten_line_end > rewritten_line_text_end {
out = out + "\n"
}
rewritten_line_start = rewritten_line_end
reference_line_start = reference_line_end
}
out
}
///|
priv enum WgslPreprocessEmissionKind {
BlankTrivia
KeepTrivia
RewriteActiveSource
}
///|
fn preprocess_emit_scanned_item(
kind : WgslPreprocessEmissionKind,
item : WgslDirectiveScanItem,
shader_defs : Map[String, ShaderDefValue],
import_state : WgslImportSubstitutionState,
used_imports : Array[ImportDefWithOffset],
) -> String raise PreprocessError {
let source = item.source()
match kind {
BlankTrivia => preprocess_blank_text(source.original_text())
KeepTrivia => source.original_text()
RewriteActiveSource =>
preprocess_rewrite_source_item(
item, shader_defs, import_state, used_imports,
)
}
}
///|
fn preprocess_rewrite_source_item(
item : WgslDirectiveScanItem,
shader_defs : Map[String, ShaderDefValue],
import_state : WgslImportSubstitutionState,
used_imports : Array[ImportDefWithOffset],
) -> String raise PreprocessError {
let source = item.source()
let replaced_original = preprocess_replace_shader_defs_in_text(
source.original_text(),
shader_defs,
)
let replaced_decommented = preprocess_replace_shader_defs_in_text(
source.cleaned_text(),
shader_defs,
)
let ignored_imports : Array[ImportDefWithOffset] = []
let rewritten_original = import_state.rewrite_source(
replaced_original,
item.start(),
ignored_imports,
true,
) catch {
AmbiguousImport(position) =>
raise ImportParseError("Ambiguous import path for item", position)
InvalidWgslSyntax(message, position) =>
raise ImportParseError(message, position)
}
ignore(
import_state.rewrite_source(
replaced_decommented,
item.start(),
used_imports,
false,
) catch {
AmbiguousImport(position) =>
raise ImportParseError("Ambiguous import path for item", position)
InvalidWgslSyntax(message, position) =>
raise ImportParseError(message, position)
},
)
preprocess_pad_rewritten_source_item(rewritten_original, replaced_decommented)
}