// 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.
///|
pub(all) enum Feature {
Enable
Disable
Keep
Error
} derive(Eq, Debug)
///|
pub fn Feature::default() -> Feature {
Disable
}
///|
pub fn Feature::from_bool(value : Bool) -> Feature {
if value {
Enable
} else {
Disable
}
}
///|
priv enum CondValue {
Known(Bool)
Unknown
}
///|
fn CondValue::not(self : CondValue) -> CondValue {
match self {
Known(value) => Known(!value)
Unknown => Unknown
}
}
///|
fn CondValue::and_value(self : CondValue, other : CondValue) -> CondValue {
match (self, other) {
(Known(false), _) => Known(false)
(_, Known(false)) => Known(false)
(Known(true), value) => value
(value, Known(true)) => value
(Unknown, Unknown) => Unknown
}
}
///|
fn CondValue::or_value(self : CondValue, other : CondValue) -> CondValue {
match (self, other) {
(Known(true), _) => Known(true)
(_, Known(true)) => Known(true)
(Known(false), value) => value
(value, Known(false)) => value
(Unknown, Unknown) => Unknown
}
}
///|
pub(all) struct Features {
default : Feature
flags : @hashmap.HashMap[String, Feature]
}
///|
pub fn Features::default() -> Features {
{ default: Feature::default(), flags: HashMap([]) }
}
///|
pub fn Features::set_feature(
self : Features,
name : String,
value : Feature,
) -> Unit {
self.flags.set(name, value)
}
///|
fn Features::eval_cond(
self : Features,
name : String,
) -> CondValue raise WeslCompileError {
match self.flags.get(name).unwrap_or(self.default) {
Enable => Known(true)
Disable => Known(false)
Keep => Unknown
Error => raise InvalidExpression("unexpected feature flag: `\{name}`")
}
}
///|
pub(all) struct CompileOptions {
imports : Bool
condcomp : Bool
generics : Bool
strip : Bool
lower : Bool
validate : Bool
lazy_resolution : Bool
mangle_root : Bool
keep : Array[String]?
keep_root : Bool
features : Features
}
///|
pub fn CompileOptions::default() -> CompileOptions {
{
imports: true,
condcomp: true,
generics: false,
strip: true,
lower: false,
validate: true,
lazy_resolution: true,
mangle_root: false,
keep: None,
keep_root: false,
features: Features::default(),
}
}
///|
pub struct BasicSourceMap {
root : ModulePath
mappings : @hashmap.HashMap[String, (ModulePath, String)]
sources : @hashmap.HashMap[ModulePath, (String?, String)]
mut default_source : String?
} derive(Debug)
///|
pub(open) trait SourceMap {
fn get_decl(Self, String) -> (ModulePath, String)?
fn get_source(Self, ModulePath) -> String?
fn get_display_name(Self, ModulePath) -> String?
fn get_default_source(Self) -> String? = _
fn unmangle_text(Self, String) -> String = _
}
///|
impl SourceMap with fn get_default_source(_self) {
None
}
///|
impl SourceMap with fn unmangle_text(_self, text) {
text
}
///|
pub fn BasicSourceMap::default() -> BasicSourceMap {
{
root: ModulePath::new_root(),
mappings: HashMap([]),
sources: HashMap([]),
default_source: None,
}
}
///|
pub fn BasicSourceMap::new() -> BasicSourceMap {
BasicSourceMap::default()
}
///|
pub fn BasicSourceMap::add_decl(
self : BasicSourceMap,
decl : String,
path : ModulePath,
item : String,
) -> Unit {
self.mappings.set(decl, (path, item))
}
///|
pub fn BasicSourceMap::add_source(
self : BasicSourceMap,
file : ModulePath,
name : String?,
source : String,
) -> Unit {
self.sources.set(file, (name, source))
}
///|
pub fn BasicSourceMap::set_default_source(
self : BasicSourceMap,
source : String,
) -> Unit {
self.default_source = Some(source)
}
///|
pub fn BasicSourceMap::get_decl(
self : BasicSourceMap,
decl : String,
) -> (ModulePath, String)? {
self.mappings.get(decl)
}
///|
pub fn BasicSourceMap::get_source(
self : BasicSourceMap,
path : ModulePath,
) -> String? {
match self.sources.get(path) {
Some((_, source)) => Some(source)
None => None
}
}
///|
pub fn BasicSourceMap::get_display_name(
self : BasicSourceMap,
path : ModulePath,
) -> String? {
match self.sources.get(path) {
Some((name, _)) => name
None => None
}
}
///|
pub fn BasicSourceMap::get_default_source(self : BasicSourceMap) -> String? {
self.default_source
}
///|
pub fn BasicSourceMap::unmangle_identifier(
self : BasicSourceMap,
mangled : String,
) -> String? {
match self.get_decl(mangled) {
Some((path, item)) => Some("\{path.to_string()}::\{item}")
None => None
}
}
///|
pub fn BasicSourceMap::unmangle_text(
self : BasicSourceMap,
text : String,
) -> String {
let parts : Array[String] = []
let mut start = 0
let mut index = 0
while index < text.length() {
let code = text.code_unit_at(index).to_int()
if wesl_is_identifier_char(code) {
if start < index {
parts.push(text[start:index].to_owned())
}
let ident_start = index
index = index + 1
while index < text.length() &&
wesl_is_identifier_char(text.code_unit_at(index).to_int()) {
index = index + 1
}
let ident = text[ident_start:index].to_owned()
match self.unmangle_identifier(ident) {
Some(unmangled) => parts.push(unmangled)
None => parts.push(ident)
}
start = index
} else {
index = index + 1
}
}
if start < text.length() {
parts.push(text[start:text.length()].to_owned())
}
parts.join("")
}
///|
pub impl SourceMap for BasicSourceMap with fn get_decl(self, decl) {
self.mappings.get(decl)
}
///|
pub impl SourceMap for BasicSourceMap with fn get_source(self, path) {
match self.sources.get(path) {
Some((_, source)) => Some(source)
None => None
}
}
///|
pub impl SourceMap for BasicSourceMap with fn get_display_name(self, path) {
match self.sources.get(path) {
Some((name, _)) => name
None => None
}
}
///|
pub impl SourceMap for BasicSourceMap with fn get_default_source(self) {
self.default_source
}
///|
pub impl SourceMap for BasicSourceMap with fn unmangle_text(self, text) {
self.unmangle_text(text)
}
///|
pub struct NoSourceMap {}
///|
#warnings("-unnecessary_annotation")
pub fn NoSourceMap::new() -> NoSourceMap {
NoSourceMap::{ }
}
///|
pub fn NoSourceMap::default() -> NoSourceMap {
NoSourceMap::new()
}
///|
pub impl SourceMap for NoSourceMap with fn get_decl(_self, _decl) {
None
}
///|
pub impl SourceMap for NoSourceMap with fn get_source(_self, _path) {
None
}
///|
pub impl SourceMap for NoSourceMap with fn get_display_name(_self, _path) {
None
}
///|
pub impl SourceMap for NoSourceMap with fn get_default_source(_self) {
None
}
///|
pub struct SourceMapper[R] {
root : ModulePath
resolver : R
mangler : ManglerKind
sourcemap : BasicSourceMap
}
///|
pub fn[R] SourceMapper::new(
root : ModulePath,
resolver : R,
mangler : ManglerKind,
) -> SourceMapper[R] {
{ root, resolver, mangler, sourcemap: { ..BasicSourceMap::default(), root, } }
}
///|
pub fn[R] SourceMapper::sourcemap(self : SourceMapper[R]) -> BasicSourceMap {
self.sourcemap
}
///|
pub fn[R] SourceMapper::mangler(self : SourceMapper[R]) -> ManglerKind {
self.mangler
}
///|
pub fn[R] SourceMapper::mangle(
self : SourceMapper[R],
path : ModulePath,
item : String,
) -> String {
let mangled = ManglerKind::mangle(self.mangler, path, item)
self.sourcemap.add_decl(mangled, path, item)
mangled
}
///|
pub fn[R] SourceMapper::unmangle(
self : SourceMapper[R],
mangled : String,
) -> (ModulePath, String)? {
match self.sourcemap.get_decl(mangled) {
Some((path, item)) => Some((path, item))
None => ManglerKind::unmangle(self.mangler, mangled)
}
}
///|
pub fn[R] SourceMapper::finish(self : SourceMapper[R]) -> BasicSourceMap {
match self.sourcemap.get_source(self.root) {
Some(source) => self.sourcemap.set_default_source(source)
None => ()
}
self.sourcemap
}
///|
pub impl[R : Resolver] Resolver for SourceMapper[R] with fn resolve_source(
self,
path,
) {
let source = R::resolve_source(self.resolver, path)
self.sourcemap.add_source(path, R::display_name(self.resolver, path), source)
source
}
///|
pub impl[R : Resolver] Resolver for SourceMapper[R] with fn resolve_module(
self,
path,
) {
let source_result = try R::resolve_source(self.resolver, path) catch {
err => Err(err)
} noraise {
source => Ok(source)
}
match source_result {
Ok(source) =>
self.sourcemap.add_source(
path,
R::display_name(self.resolver, path),
source,
)
Err(_) => ()
}
R::resolve_module(self.resolver, path)
}
///|
pub impl[R : Resolver] Resolver for SourceMapper[R] with fn display_name(
self,
path,
) {
R::display_name(self.resolver, path)
}
///|
pub struct CompileResult {
syntax : TranslationUnit
sourcemap : BasicSourceMap?
modules : Array[ModulePath]
} derive(Debug)
///|
pub fn CompileResult::default() -> CompileResult {
{ syntax: TranslationUnit::default(), sourcemap: None, modules: [] }
}
///|
pub fn CompileResult::to_string(self : CompileResult) -> String {
self.syntax.to_string()
}
///|
pub fn CompileResult::has_sourcemap(self : CompileResult) -> Bool {
self.sourcemap is Some(_)
}
///|
pub fn CompileResult::unmangle_text(
self : CompileResult,
text : String,
) -> String {
match self.sourcemap {
Some(sourcemap) => sourcemap.unmangle_text(text)
None => text
}
}
///|
priv struct ImportItem {
export_module_path : ModulePath?
export_name : String?
namespace_path : ModulePath
local_name : String
public : Bool
start_line : Int
end_line : Int
}
///|
priv struct ModuleItem {
name : String?
kind : ModuleItemKind
header : GlobalDeclarationHeader
attributes : Array[Attribute]
source : String
span : SyntaxSpan
entrypoint : Bool
is_const_assert : Bool
references : Array[ModuleItemRef]
}
///|
priv enum ModuleItemKind {
Alias
Const
Var
Other
}
///|
enum ModuleItemRef {
Local(String)
Import(String, Int)
Qualified(String, ModulePath, String)
} derive(Eq, Debug)
///|
priv struct ParsedModule {
path : ModulePath
imports : Array[ImportItem]
import_by_local : @hashmap.HashMap[String, Int]
items : Array[ModuleItem]
named_items : @hashmap.HashMap[String, Int]
}
///|
priv enum ExportTarget {
Local(Int)
ReExport(ImportItem)
Private
Missing
}
///|
fn wesl_has_conditional_compile_attr(attributes : Array[Attribute]) -> Bool {
for attr in attributes {
if attr.name == "if" || attr.name == "elif" || attr.name == "else" {
return true
}
}
false
}
///|
fn wesl_duplicate_error(name : String, context : String) -> WeslCompileError {
if context == name {
Validate(Duplicate(name))
} else {
DuplicateSymbol(context)
}
}
///|
fn wesl_parse_loaded_translation_unit(
path : ModulePath,
translation_unit : TranslationUnit,
skip_conditional_duplicates : Bool,
) -> ParsedModule raise WeslCompileError {
let imports : Array[ImportItem] = []
let import_conditional : Array[Bool] = []
let parsed_items : Array[ModuleItem] = []
let named_items : @hashmap.HashMap[String, Int] = HashMap([])
let duplicate_names : @hashmap.HashMap[String, Int] = HashMap([])
for statement in translation_unit.imports {
let conditional_import = wesl_has_conditional_compile_attr(
statement.attributes,
)
for imported_name in statement.flatten_items(path) {
imports.push({
export_module_path: imported_name.export_module_path,
export_name: imported_name.export_name,
namespace_path: imported_name.namespace_path,
local_name: imported_name.local_name,
public: statement.is_public(),
start_line: statement.span.start_line,
end_line: statement.span.end_line,
})
import_conditional.push(conditional_import)
}
}
for decl in translation_unit.global_declarations {
let skip_duplicate = skip_conditional_duplicates &&
wesl_has_conditional_compile_attr(decl.attributes)
match decl.ident() {
Some(name) => {
if !skip_duplicate {
if duplicate_names.contains(name) {
let context = if skip_conditional_duplicates {
name
} else {
decl.span.symbol_context(path, name)
}
raise wesl_duplicate_error(name, context)
} else {
duplicate_names.set(name, parsed_items.length())
}
}
if !named_items.contains(name) {
named_items.set(name, parsed_items.length())
}
}
None => ()
}
parsed_items.push({
name: decl.ident(),
kind: wesl_item_kind(decl.header),
header: decl.header,
attributes: decl.attributes,
source: decl.source,
span: decl.span,
entrypoint: decl.is_entrypoint(),
is_const_assert: decl.is_const_assert(),
references: [],
})
}
let import_by_local : @hashmap.HashMap[String, Int] = HashMap([])
for i in 0.. Bool {
(code >= 48 && code <= 57) ||
(code >= 65 && code <= 90) ||
(code >= 97 && code <= 122) ||
code == 95
}
///|
fn wesl_is_whitespace(code : Int) -> Bool {
code == 32 || code == 9 || code == 10 || code == 13
}
///|
fn wesl_skip_whitespace(source : String, start : Int) -> Int {
let mut i = start
while i < source.length() &&
wesl_is_whitespace(source.code_unit_at(i).to_int()) {
i = i + 1
}
i
}
///|
fn wesl_skip_layout(source : String, start : Int) -> Int {
let mut i = start
while i < source.length() {
i = wesl_skip_whitespace(source, i)
if i + 1 < source.length() &&
source.code_unit_at(i).to_int() == 47 &&
source.code_unit_at(i + 1).to_int() == 47 {
i += 2
while i < source.length() && source.code_unit_at(i).to_int() != 10 {
i += 1
}
continue
}
break
}
i
}
///|
fn wesl_starts_with_at(source : String, index : Int, needle : String) -> Bool {
if index < 0 || index + needle.length() > source.length() {
return false
}
source[index:index + needle.length()].to_owned() == needle
}
///|
fn wesl_find_matching_delim(
source : String,
start : Int,
open_code : Int,
close_code : Int,
) -> Int raise WeslCompileError {
let mut depth = 0
let mut i = start
while i < source.length() {
let code = source.code_unit_at(i).to_int()
if code == open_code {
depth = depth + 1
} else if code == close_code {
depth = depth - 1
if depth == 0 {
return i
}
}
i = i + 1
}
raise Parse("unclosed delimiter in WESL source")
}
///|
fn wesl_eval_cond_expr_node(
node : @cond_expr_parse.CondExprNode,
features : Features,
) -> CondValue raise WeslCompileError {
match node {
Literal(value) => Known(value)
Feature(name) => features.eval_cond(name)
Not(inner) => wesl_eval_cond_expr_node(inner, features).not()
And(left, right) =>
wesl_eval_cond_expr_node(left, features).and_value(
wesl_eval_cond_expr_node(right, features),
)
Or(left, right) =>
wesl_eval_cond_expr_node(left, features).or_value(
wesl_eval_cond_expr_node(right, features),
)
}
}
///|
fn wesl_eval_cond_expression(
node : CondExpression,
features : Features,
) -> CondValue raise WeslCompileError {
match node {
Literal(value) => Known(value)
Feature(name) => features.eval_cond(name)
Not(inner) => wesl_eval_cond_expression(inner, features).not()
And(left, right) =>
wesl_eval_cond_expression(left, features).and_value(
wesl_eval_cond_expression(right, features),
)
Or(left, right) =>
wesl_eval_cond_expression(left, features).or_value(
wesl_eval_cond_expression(right, features),
)
}
}
///|
fn wesl_parse_if_value(
expr : String,
features : Features,
) -> CondValue raise WeslCompileError {
let source = expr.trim().to_owned()
match @cond_expr_parse.parse_cond_expr_source(source) {
Parsed(node) => wesl_eval_cond_expr_node(node, features)
Failed(_) => raise InvalidExpression(expr)
}
}
///|
fn wesl_find_condcomp_block(
source : String,
start : Int,
) -> (Int, Int) raise WeslCompileError {
let mut paren_depth = 0
let mut bracket_depth = 0
let mut index = start
while index < source.length() {
if index + 1 < source.length() &&
source.code_unit_at(index).to_int() == 47 &&
source.code_unit_at(index + 1).to_int() == 47 {
index += 2
while index < source.length() && source.code_unit_at(index).to_int() != 10 {
index += 1
}
continue
}
let code = source.code_unit_at(index).to_int()
if code == 40 {
paren_depth += 1
} else if code == 41 {
paren_depth -= 1
} else if code == 91 {
bracket_depth += 1
} else if code == 93 {
bracket_depth -= 1
} else if code == 123 && paren_depth == 0 && bracket_depth == 0 {
let block_end = wesl_find_matching_delim(source, index, 123, 125)
return (index, block_end + 1)
}
index += 1
}
raise Parse("missing block after conditional attribute")
}
///|
fn wesl_find_condcomp_region(
source : String,
start : Int,
) -> (Int, Int) raise WeslCompileError {
let region_start = wesl_skip_layout(source, start)
if region_start >= source.length() {
raise Parse("missing declaration after conditional attribute")
}
let first_code = source.code_unit_at(region_start).to_int()
if first_code == 123 {
let block_end = wesl_find_matching_delim(source, region_start, 123, 125)
return (region_start, block_end + 1)
}
let tokens = parser_lex(source[region_start:source.length()].to_owned())
let block_like = match tokens.get(0) {
Some(Ident(keyword, _, _, _)) =>
keyword == "fn" ||
keyword == "struct" ||
keyword == "if" ||
keyword == "switch" ||
keyword == "loop" ||
keyword == "for" ||
keyword == "while" ||
keyword == "continuing"
_ => false
}
if block_like {
let (_, region_end) = wesl_find_condcomp_block(source, region_start)
return (region_start, region_end)
}
let mut paren_depth = 0
let mut bracket_depth = 0
let mut brace_depth = 0
let mut index = region_start
while index < source.length() {
if index + 1 < source.length() &&
source.code_unit_at(index).to_int() == 47 &&
source.code_unit_at(index + 1).to_int() == 47 {
index += 2
while index < source.length() && source.code_unit_at(index).to_int() != 10 {
index += 1
}
continue
}
let code = source.code_unit_at(index).to_int()
if code == 40 {
paren_depth += 1
} else if code == 41 {
if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 {
return (region_start, index)
}
paren_depth -= 1
} else if code == 91 {
bracket_depth += 1
} else if code == 93 {
bracket_depth -= 1
} else if code == 123 {
brace_depth += 1
} else if code == 125 {
if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0 {
return (region_start, index)
}
brace_depth -= 1
} else if code == 44 &&
paren_depth == 0 &&
bracket_depth == 0 &&
brace_depth == 0 {
return (region_start, index + 1)
} else if code == 59 &&
paren_depth == 0 &&
bracket_depth == 0 &&
brace_depth == 0 {
return (region_start, index + 1)
}
index += 1
}
(region_start, source.length())
}
///|
fn wesl_apply_condcomp(
source : String,
features : Features,
) -> String raise WeslCompileError {
let mut out = ""
let mut index = 0
while index < source.length() {
if wesl_starts_with_at(source, index, "@if") {
let expr_start = wesl_skip_layout(source, index + 3)
if expr_start >= source.length() ||
source.code_unit_at(expr_start).to_int() != 40 {
raise InvalidExpression(source[index:source.length()].to_owned())
}
let expr_end = wesl_find_matching_delim(source, expr_start, 40, 41)
let expr = source[expr_start + 1:expr_end].to_owned()
let (region_start, region_end) = wesl_find_condcomp_region(
source,
expr_end + 1,
)
let region = wesl_apply_condcomp(
source[region_start:region_end].to_owned(),
features,
)
let mut prev_true = false
let mut prev_removed = false
match wesl_parse_if_value(expr, features) {
Known(true) => {
out = out + region
prev_true = true
prev_removed = false
}
Known(false) => prev_removed = true
Unknown => {
out = out + source[index:expr_end + 1].to_owned() + "\n" + region
prev_removed = false
}
}
let mut next_index = region_end
while true {
let attr_start = wesl_skip_layout(source, next_index)
if wesl_starts_with_at(source, attr_start, "@elif") {
let elif_expr_start = wesl_skip_layout(source, attr_start + 5)
if elif_expr_start >= source.length() ||
source.code_unit_at(elif_expr_start).to_int() != 40 {
raise InvalidExpression(
source[attr_start:source.length()].to_owned(),
)
}
let elif_expr_end = wesl_find_matching_delim(
source, elif_expr_start, 40, 41,
)
let elif_expr = source[elif_expr_start + 1:elif_expr_end].to_owned()
let (elif_region_start, elif_region_end) = wesl_find_condcomp_region(
source,
elif_expr_end + 1,
)
let elif_region = wesl_apply_condcomp(
source[elif_region_start:elif_region_end].to_owned(),
features,
)
if prev_true {
prev_removed = true
} else {
match wesl_parse_if_value(elif_expr, features) {
Known(true) => {
if prev_removed {
out = out + elif_region
} else {
out = out + "@else\n" + elif_region
}
prev_true = true
prev_removed = false
}
Known(false) => prev_removed = true
Unknown => {
if prev_removed {
out = out + "@if(" + elif_expr + ")\n" + elif_region
} else {
out = out +
source[attr_start:elif_expr_end + 1].to_owned() +
"\n" +
elif_region
}
prev_removed = false
}
}
}
next_index = elif_region_end
continue
}
if !wesl_starts_with_at(source, attr_start, "@else") {
break
}
let tail_start = wesl_skip_layout(source, attr_start + 5)
if wesl_starts_with_at(source, tail_start, "if") {
let elif_expr_start = wesl_skip_layout(source, tail_start + 2)
if elif_expr_start >= source.length() ||
source.code_unit_at(elif_expr_start).to_int() != 40 {
raise InvalidExpression(
source[attr_start:source.length()].to_owned(),
)
}
let elif_expr_end = wesl_find_matching_delim(
source, elif_expr_start, 40, 41,
)
let elif_expr = source[elif_expr_start + 1:elif_expr_end].to_owned()
let (elif_region_start, elif_region_end) = wesl_find_condcomp_region(
source,
elif_expr_end + 1,
)
let elif_region = wesl_apply_condcomp(
source[elif_region_start:elif_region_end].to_owned(),
features,
)
if prev_true {
prev_removed = true
} else {
match wesl_parse_if_value(elif_expr, features) {
Known(true) => {
if prev_removed {
out = out + elif_region
} else {
out = out + "@else\n" + elif_region
}
prev_true = true
prev_removed = false
}
Known(false) => prev_removed = true
Unknown => {
if prev_removed {
out = out + "@if(" + elif_expr + ")\n" + elif_region
} else {
out = out + "@elif(" + elif_expr + ")\n" + elif_region
}
prev_removed = false
}
}
}
next_index = elif_region_end
continue
}
let (else_region_start, else_region_end) = wesl_find_condcomp_region(
source, tail_start,
)
let else_region = wesl_apply_condcomp(
source[else_region_start:else_region_end].to_owned(),
features,
)
if !prev_true {
if prev_removed {
out = out + else_region
} else {
out = out + source[attr_start:tail_start].to_owned() + else_region
}
}
next_index = else_region_end
break
}
index = next_index
continue
}
out = out + source[index:index + 1].to_owned()
index = index + 1
}
out
}
///|
priv struct CondCompEvalState {
mut has_if : Bool
mut is_true : Bool
mut removed : Bool
}
///|
fn CondCompEvalState::new() -> CondCompEvalState {
{ has_if: false, is_true: false, removed: false }
}
///|
fn wesl_is_condcomp_attribute(attribute : Attribute) -> Bool {
attribute.name == "if" || attribute.name == "elif" || attribute.name == "else"
}
///|
fn wesl_single_condcomp_attribute(
attributes : Array[Attribute],
) -> Attribute? raise WeslCompileError {
let mut found : Attribute? = None
for attribute in attributes {
if wesl_is_condcomp_attribute(attribute) {
match found {
Some(_) =>
raise InvalidExpression(
"cannot have multiple @if/@elif/@else attributes on the same node",
)
None => found = Some(attribute)
}
}
}
found
}
///|
fn wesl_filter_condcomp_attributes(
attributes : Array[Attribute],
replacement : Attribute?,
) -> Array[Attribute] {
let filtered : Array[Attribute] = []
let mut inserted = false
for attribute in attributes {
if wesl_is_condcomp_attribute(attribute) {
if !inserted {
match replacement {
Some(next) => filtered.push(next)
None => ()
}
inserted = true
}
} else {
filtered.push(attribute)
}
}
filtered
}
///|
fn wesl_condcomp_attr_end(
source : String,
at_index : Int,
) -> Int raise WeslCompileError {
let name_start = at_index + 1
let mut name_end = name_start
while name_end < source.length() &&
wesl_is_identifier_char(source.code_unit_at(name_end).to_int()) {
name_end += 1
}
let name = source[name_start:name_end].to_owned()
if name == "if" || name == "elif" {
let expr_start = wesl_skip_layout(source, name_end)
if expr_start >= source.length() ||
source.code_unit_at(expr_start).to_int() != 40 {
raise InvalidExpression(source[at_index:source.length()].to_owned())
}
let expr_end = wesl_find_matching_delim(source, expr_start, 40, 41)
wesl_skip_layout(source, expr_end + 1)
} else if name == "else" {
wesl_skip_layout(source, name_end)
} else {
name_end
}
}
///|
fn wesl_remove_leading_condcomp_attr(
source : String,
) -> String raise WeslCompileError {
let attr_start = wesl_skip_layout(source, 0)
if attr_start < source.length() &&
source.code_unit_at(attr_start).to_int() == 64 {
let attr_end = wesl_condcomp_attr_end(source, attr_start)
source[0:attr_start].to_owned() +
source[attr_end:source.length()].to_owned()
} else {
source
}
}
///|
fn wesl_replace_leading_condcomp_attr(
source : String,
replacement : String,
) -> String raise WeslCompileError {
let attr_start = wesl_skip_layout(source, 0)
if attr_start < source.length() &&
source.code_unit_at(attr_start).to_int() == 64 {
let attr_end = wesl_condcomp_attr_end(source, attr_start)
source[0:attr_start].to_owned() +
replacement +
"\n" +
source[attr_end:source.length()].to_owned()
} else {
source
}
}
///|
fn wesl_condcomp_attr_value(
attr : Attribute,
features : Features,
) -> CondValue raise WeslCompileError {
match attr.condition_expr {
Some(expr) => wesl_eval_cond_expression(expr, features)
None =>
match attr.arguments {
Some(expr) => wesl_parse_if_value(expr, features)
None => raise InvalidExpression("missing @\{attr.name} expression")
}
}
}
///|
fn wesl_transform_condcomp_node(
attr : Attribute?,
attributes : Array[Attribute],
source : String,
features : Features,
state : CondCompEvalState,
) -> (Bool, Array[Attribute]?, String?) raise WeslCompileError {
match attr {
None => {
state.has_if = false
let transformed = wesl_apply_condcomp(source, features)
(true, None, Some(transformed))
}
Some(cond_attr) => {
let mut keep_node = true
let mut remove_attr = false
let mut replacement : Attribute? = None
let mut replacement_source : String? = None
let mut is_true = false
match cond_attr.name {
"if" => {
state.has_if = true
state.is_true = false
match wesl_condcomp_attr_value(cond_attr, features) {
Known(true) => {
remove_attr = true
is_true = true
}
Known(false) => keep_node = false
Unknown => replacement = Some(cond_attr)
}
}
"elif" => {
if !state.has_if {
raise InvalidExpression(
"an @elif or @else attribute must be preceded by a @if or @elif on the previous node",
)
}
state.has_if = true
match wesl_condcomp_attr_value(cond_attr, features) {
Known(true) =>
if state.is_true {
keep_node = false
} else {
is_true = true
if state.removed {
remove_attr = true
} else {
replacement = Some({
..cond_attr,
name: "else",
arguments: None,
argument_exprs: [],
condition_expr: None,
})
replacement_source = Some("@else")
}
}
Known(false) => keep_node = false
Unknown =>
if state.removed {
replacement = Some({ ..cond_attr, name: "if" })
let expr = cond_attr.arguments.unwrap_or("")
replacement_source = Some("@if(\{expr})")
} else {
replacement = Some(cond_attr)
}
}
}
"else" => {
if !state.has_if {
raise InvalidExpression(
"an @elif or @else attribute must be preceded by a @if or @elif on the previous node",
)
}
state.has_if = false
if state.is_true {
keep_node = false
} else if state.removed {
remove_attr = true
} else {
replacement = Some(cond_attr)
}
}
_ => ()
}
state.is_true = is_true || state.is_true
state.removed = !keep_node
if !keep_node {
(false, None, None)
} else {
let next_source = match replacement_source {
Some(text) => wesl_replace_leading_condcomp_attr(source, text)
None =>
if remove_attr {
wesl_remove_leading_condcomp_attr(source)
} else {
source
}
}
let transformed = wesl_apply_condcomp(next_source, features)
let next_attrs = if remove_attr {
Some(wesl_filter_condcomp_attributes(attributes, None))
} else {
Some(wesl_filter_condcomp_attributes(attributes, replacement))
}
(true, next_attrs, Some(transformed))
}
}
}
}
///|
fn wesl_condcomp_imports(
imports : Array[ImportStatement],
features : Features,
) -> Array[ImportStatement] raise WeslCompileError {
let out : Array[ImportStatement] = []
let state = CondCompEvalState::new()
for statement in imports {
let attr = wesl_single_condcomp_attribute(statement.attributes)
let (keep_node, next_attrs, next_source) = wesl_transform_condcomp_node(
attr,
statement.attributes,
statement.source,
features,
state,
)
if keep_node {
out.push({
..statement,
attributes: next_attrs.unwrap_or(statement.attributes),
source: next_source.unwrap_or(statement.source),
})
}
}
out
}
///|
fn wesl_condcomp_declarations(
declarations : Array[GlobalDeclaration],
features : Features,
) -> Array[GlobalDeclaration] raise WeslCompileError {
let out : Array[GlobalDeclaration] = []
let state = CondCompEvalState::new()
for declaration in declarations {
let attr = wesl_single_condcomp_attribute(declaration.attributes)
let (keep_node, next_attrs, next_source) = wesl_transform_condcomp_node(
attr,
declaration.attributes,
declaration.source,
features,
state,
)
if keep_node {
let transformed_source = next_source.unwrap_or(declaration.source)
let transformed_declarations = parse_global_declarations(
transformed_source,
)
guard transformed_declarations.length() == 1 else {
raise Parse(
"conditional compilation produced an invalid declaration: \{transformed_source}",
)
}
out.push({
..transformed_declarations[0],
attributes: next_attrs.unwrap_or(transformed_declarations[0].attributes),
span: declaration.span,
})
}
}
out
}
///|
pub struct CondCompPreprocessor {
features : Features
}
///|
pub fn CondCompPreprocessor::new(features : Features) -> CondCompPreprocessor {
{ features, }
}
///|
pub impl ModulePreprocessor for CondCompPreprocessor with fn preprocess_module(
self,
_path,
unit,
) {
{
imports: wesl_condcomp_imports(unit.imports, self.features) catch {
err => raise Error(err.message())
},
global_declarations: wesl_condcomp_declarations(
unit.global_declarations,
self.features,
) catch {
err => raise Error(err.message())
},
}
}
///|
fn wesl_push_unique(items : Array[String], value : String) -> Unit {
if value == "" {
return
}
for item in items {
if item == value {
return
}
}
items.push(value)
}
///|
fn wesl_scopes_contain(
scopes : Array[Array[String]],
scope_depth : Int,
name : String,
) -> Bool {
let mut depth = scope_depth
while depth >= 0 {
for item in scopes[depth] {
if item == name {
return true
}
}
depth -= 1
}
false
}
///|
fn wesl_bind_scope(
scopes : Array[Array[String]],
scope_depth : Int,
name : String,
) -> Unit {
wesl_push_unique(scopes[scope_depth], name)
}
///|
fn wesl_push_unique_item_ref(
items : Array[ModuleItemRef],
value : ModuleItemRef,
) -> Unit {
for item in items {
if item == value {
return
}
}
items.push(value)
}
///|
fn wesl_resolve_module_path(
current_path : ModulePath,
module_path_text : String,
) -> ModulePath raise WeslCompileError {
let raw_module_path = parse_module_path(module_path_text.trim().to_owned()) catch {
err => raise Parse(err.message())
}
match raw_module_path.origin {
Package(name) if raw_module_path.components.is_empty() =>
return ModulePath::new(Absolute, [name])
_ => ()
}
current_path.join_path(raw_module_path)
}
///|
fn wesl_slice_segments(
segments : Array[String],
start : Int,
end_ : Int,
) -> Array[String] {
let out : Array[String] = []
for i = start; i < end_; i = i + 1 {
out.push(segments[i])
}
out
}
///|
fn wesl_extend_module_path(
base_path : ModulePath,
extra_segments : Array[String],
) -> ModulePath raise WeslCompileError {
if extra_segments.is_empty() {
return base_path
}
let suffix = extra_segments.join("::")
let full_path = "\{base_path.to_string()}::\{suffix}"
parse_module_path(full_path) catch {
err => raise Parse(err.message())
}
}
///|
fn wesl_token_text(source : String, token : ParserToken) -> String {
source[token.start():token.end()].to_owned()
}
///|
fn wesl_is_declaration_keyword(name : String) -> Bool {
name == "fn" ||
name == "struct" ||
name == "alias" ||
name == "const" ||
name == "override" ||
name == "let" ||
name == "var"
}
///|
fn wesl_binding_keyword_before(
tokens : Array[ParserToken],
index : Int,
) -> String? {
if index <= 0 {
return None
}
match tokens[index - 1] {
Ident(previous, _, _, _) if wesl_is_declaration_keyword(previous) =>
return Some(previous)
Gt(_, _, _) => {
let mut depth = 1
let mut i = index - 2
while i >= 0 {
match tokens[i] {
Gt(_, _, _) => depth += 1
Lt(_, _, _) => {
depth -= 1
if depth == 0 {
break
}
}
_ => ()
}
i -= 1
}
if i > 0 {
match tokens[i - 1] {
Ident(previous, _, _, _) if wesl_is_declaration_keyword(previous) =>
return Some(previous)
_ => ()
}
}
}
_ => ()
}
None
}
///|
fn wesl_is_bound_identifier(
source : String,
tokens : Array[ParserToken],
index : Int,
) -> Bool {
if wesl_binding_keyword_before(tokens, index) is Some(_) {
return true
}
if index + 1 < tokens.length() {
match tokens[index + 1] {
Other(_, _, _) => wesl_token_text(source, tokens[index + 1]) == ":"
_ => false
}
} else {
false
}
}
///|
fn wesl_collect_qualified_segments(
source : String,
tokens : Array[ParserToken],
start_index : Int,
) -> (Array[String], String, Int) {
let segments : Array[String] = []
let start_offset = tokens[start_index].start()
let mut end_offset = tokens[start_index].end()
let mut index = start_index
while index < tokens.length() {
match tokens[index] {
Ident(segment, _, end_, _) => {
segments.push(segment)
end_offset = end_
index += 1
}
_ => break
}
if index + 1 < tokens.length() &&
tokens[index] is DoubleColon(_, _, _) &&
tokens[index + 1] is Ident(_, _, _, _) {
index += 1
continue
}
break
}
(segments, source[start_offset:end_offset].to_owned(), index)
}
///|
fn wesl_resolve_qualified_import_target(
import_item : ImportItem,
segments : Array[String],
) -> (ModulePath, String) raise WeslCompileError {
let item_name = segments[segments.length() - 1]
let module_path = wesl_extend_module_path(
import_item.namespace_path,
wesl_slice_segments(segments, 1, segments.length() - 1),
)
(module_path, item_name)
}
///|
fn wesl_resolve_direct_qualified_target(
current_path : ModulePath,
segments : Array[String],
) -> (ModulePath, String) raise WeslCompileError {
let item_name = segments[segments.length() - 1]
let module_path = wesl_resolve_module_path(
current_path,
wesl_slice_segments(segments, 0, segments.length() - 1).join("::"),
)
(module_path, item_name)
}
///|
fn wesl_import_named_target(import_item : ImportItem) -> (ModulePath, String)? {
match import_item.export_module_path {
Some(export_module_path) =>
match import_item.export_name {
Some(export_name) => Some((export_module_path, export_name))
None => None
}
None => None
}
}
///|
fn wesl_collect_legacy_item_references(
source : String,
item_name : String?,
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Array[ModuleItemRef] {
let references : Array[ModuleItemRef] = []
let tokens = parser_lex(source)
let scopes : Array[Array[String]] = [[]]
let mut scope_depth = 0
let mut consumed_item_decl = item_name is None
let mut index = 0
while index < tokens.length() {
match tokens[index] {
LBrace(_, _, _) => {
scope_depth += 1
if scope_depth < scopes.length() {
scopes[scope_depth] = []
} else {
scopes.push([])
}
}
RBrace(_, _, _) => if scope_depth > 0 { scope_depth -= 1 }
Ident(name, _, _, _) => {
let is_binding = wesl_is_bound_identifier(source, tokens, index)
if is_binding {
let is_item_decl = match item_name {
Some(current_item_name) =>
!consumed_item_decl && name == current_item_name
None => false
}
if is_item_decl {
consumed_item_decl = true
} else {
wesl_bind_scope(scopes, scope_depth, name)
}
index += 1
continue
}
if index + 1 < tokens.length() &&
tokens[index + 1] is DoubleColon(_, _, _) {
let (segments, raw_text, next_index) = wesl_collect_qualified_segments(
source, tokens, index,
)
if segments.length() >= 2 &&
!wesl_scopes_contain(scopes, scope_depth, segments[0]) {
match import_by_local.get(segments[0]) {
Some(import_index) => {
let (target_path, target_name) = wesl_resolve_qualified_import_target(
imports[import_index],
segments,
) catch {
_ =>
(
imports[import_index].namespace_path,
segments[segments.length() - 1],
)
}
wesl_push_unique_item_ref(
references,
Qualified(raw_text, target_path, target_name),
)
}
None =>
if segments[0] == "package" ||
segments[0] == "self" ||
segments[0] == "super" {
let (target_path, target_name) = wesl_resolve_direct_qualified_target(
current_path, segments,
) catch {
_ => (current_path, segments[segments.length() - 1])
}
wesl_push_unique_item_ref(
references,
Qualified(raw_text, target_path, target_name),
)
}
}
}
index = next_index
continue
}
if wesl_scopes_contain(scopes, scope_depth, name) {
index += 1
continue
}
if named_items.contains(name) {
wesl_push_unique_item_ref(references, Local(name))
} else {
match import_by_local.get(name) {
Some(import_index) =>
if wesl_import_named_target(imports[import_index]) is Some(_) {
wesl_push_unique_item_ref(
references,
Import(name, import_index),
)
}
None => ()
}
}
}
_ => ()
}
index += 1
}
references
}
///|
fn wesl_ast_raw_path(ty : TypeExpression) -> String {
let mut raw = ""
for segment in ty.path {
if raw == "" {
raw = segment
} else {
raw = raw + "::" + segment
}
}
if raw == "" {
ty.ident
} else {
raw + "::" + ty.ident
}
}
///|
fn wesl_ast_path_segments(ty : TypeExpression) -> Array[String] {
let segments : Array[String] = []
for segment in ty.path {
segments.push(segment)
}
segments.push(ty.ident)
segments
}
///|
fn wesl_ast_bind_name(scopes : Array[Array[String]], name : String) -> Unit {
if scopes.length() == 0 {
return
}
wesl_push_unique(scopes[scopes.length() - 1], name)
}
///|
fn wesl_ast_name_is_bound(scopes : Array[Array[String]], name : String) -> Bool {
if scopes.length() == 0 {
return false
}
wesl_scopes_contain(scopes, scopes.length() - 1, name)
}
///|
fn wesl_ast_collect_type_reference(
ty : TypeExpression,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
if ty.path.length() == 0 {
if !wesl_ast_name_is_bound(scopes, ty.ident) {
if named_items.contains(ty.ident) {
wesl_push_unique_item_ref(references, Local(ty.ident))
} else {
match import_by_local.get(ty.ident) {
Some(import_index) =>
if wesl_import_named_target(imports[import_index]) is Some(_) {
wesl_push_unique_item_ref(
references,
Import(ty.ident, import_index),
)
}
None => ()
}
}
}
} else if !wesl_ast_name_is_bound(scopes, ty.path[0]) {
let segments = wesl_ast_path_segments(ty)
match import_by_local.get(segments[0]) {
Some(import_index) => {
let (target_path, target_name) = wesl_resolve_qualified_import_target(
imports[import_index],
segments,
) catch {
_ => (imports[import_index].namespace_path, ty.ident)
}
wesl_push_unique_item_ref(
references,
Qualified(wesl_ast_raw_path(ty), target_path, target_name),
)
}
None =>
if segments[0] == "package" ||
segments[0] == "self" ||
segments[0] == "super" {
let (target_path, target_name) = wesl_resolve_direct_qualified_target(
current_path, segments,
) catch {
_ => (current_path, ty.ident)
}
wesl_push_unique_item_ref(
references,
Qualified(wesl_ast_raw_path(ty), target_path, target_name),
)
}
}
}
for arg in ty.template_args {
match arg {
Type(template_type) =>
wesl_ast_collect_type_reference(
template_type, references, scopes, named_items, import_by_local, imports,
current_path,
)
Literal(_) => ()
}
}
}
///|
fn wesl_ast_collect_expression_references(
expr : Expression,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
match expr {
Literal(_) | Bool(_) => ()
TypeOrIdentifier(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
Parenthesized(inner) =>
wesl_ast_collect_expression_references(
inner, references, scopes, named_items, import_by_local, imports, current_path,
)
NamedComponent(base, _) =>
wesl_ast_collect_expression_references(
base, references, scopes, named_items, import_by_local, imports, current_path,
)
Indexing(base, index) => {
wesl_ast_collect_expression_references(
base, references, scopes, named_items, import_by_local, imports, current_path,
)
wesl_ast_collect_expression_references(
index, references, scopes, named_items, import_by_local, imports, current_path,
)
}
Unary(_, operand) =>
wesl_ast_collect_expression_references(
operand, references, scopes, named_items, import_by_local, imports, current_path,
)
Binary(_, lhs, rhs) => {
wesl_ast_collect_expression_references(
lhs, references, scopes, named_items, import_by_local, imports, current_path,
)
wesl_ast_collect_expression_references(
rhs, references, scopes, named_items, import_by_local, imports, current_path,
)
}
FunctionCall(call) => {
wesl_ast_collect_type_reference(
call.callee,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
for arg in call.arguments {
wesl_ast_collect_expression_references(
arg, references, scopes, named_items, import_by_local, imports, current_path,
)
}
}
}
}
///|
fn wesl_ast_collect_attribute_references(
attribute : Attribute,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
for expr in attribute.argument_exprs {
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
}
}
///|
fn wesl_ast_collect_attributes_references(
attributes : Array[Attribute],
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
for attribute in attributes {
wesl_ast_collect_attribute_references(
attribute, references, scopes, named_items, import_by_local, imports, current_path,
)
}
}
///|
fn wesl_ast_collect_declaration_references(
declaration : StatementDeclaration,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
match declaration.type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match declaration.initializer_expr {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match declaration.name {
Some(name) => wesl_ast_bind_name(scopes, name)
None => ()
}
}
///|
fn wesl_ast_collect_statement_references(
statement : Statement,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
wesl_ast_collect_attributes_references(
statement.attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
match statement.expression {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match statement.declaration {
Some(declaration) =>
wesl_ast_collect_declaration_references(
declaration, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match statement.assignment {
Some(assignment) => {
wesl_ast_collect_expression_references(
assignment.lhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_expression_references(
assignment.rhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
None => ()
}
match statement.update_expression {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match statement.control {
Some(control) =>
wesl_ast_collect_control_references(
control, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
///|
fn wesl_ast_collect_body_references(
body : FunctionBody,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
scopes.push([])
for statement in body.statements {
wesl_ast_collect_statement_references(
statement, references, scopes, named_items, import_by_local, imports, current_path,
)
}
ignore(scopes.pop())
}
///|
fn wesl_ast_collect_control_references(
control : ControlStatement,
references : Array[ModuleItemRef],
scopes : Array[Array[String]],
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Unit {
match control {
Block(block) =>
wesl_ast_collect_body_references(
block.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
If(if_) => {
wesl_ast_collect_expression_references(
if_.condition,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_body_references(
if_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
match if_.else_body {
Some(body) =>
wesl_ast_collect_body_references(
body, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
Switch(switch_) => {
wesl_ast_collect_expression_references(
switch_.selector,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
for case_ in switch_.cases {
wesl_ast_collect_attributes_references(
case_.attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
for selector in case_.selectors {
wesl_ast_collect_expression_references(
selector, references, scopes, named_items, import_by_local, imports,
current_path,
)
}
wesl_ast_collect_body_references(
case_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
}
Loop(loop_) =>
wesl_ast_collect_body_references(
loop_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
For(for_) => {
scopes.push([])
match for_.initializer {
Some(Declaration(declaration)) =>
wesl_ast_collect_declaration_references(
declaration, references, scopes, named_items, import_by_local, imports,
current_path,
)
Some(Assignment(assignment)) => {
wesl_ast_collect_expression_references(
assignment.lhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_expression_references(
assignment.rhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
Some(Expression(expr)) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match for_.condition {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match for_.update {
Some(Assignment(assignment)) => {
wesl_ast_collect_expression_references(
assignment.lhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_expression_references(
assignment.rhs,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
Some(Increment(expr))
| Some(Decrement(expr))
| Some(Expression(expr)) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
wesl_ast_collect_body_references(
for_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
ignore(scopes.pop())
}
While(while_) => {
wesl_ast_collect_expression_references(
while_.condition,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_body_references(
while_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
Continuing(continuing) =>
wesl_ast_collect_body_references(
continuing.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
}
///|
fn wesl_collect_ast_item_references(
header : GlobalDeclarationHeader,
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Array[ModuleItemRef] {
let references : Array[ModuleItemRef] = []
let scopes : Array[Array[String]] = [[]]
match header {
Alias(alias_decl) =>
match alias_decl.target_type {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
Const(const_) => {
match const_.type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match const_.initializer_expr {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
Override(override_) => {
match override_.type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match override_.initializer_expr {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
Let(let_) => {
match let_.type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match let_.initializer_expr {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
Var(var_) => {
match var_.type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
match var_.initializer_expr {
Some(expr) =>
wesl_ast_collect_expression_references(
expr, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
}
Function(function_) => {
for parameter in function_.parameters {
wesl_ast_collect_attributes_references(
parameter.attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_type_reference(
parameter.type_expr,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
match function_.return_type_expr {
Some(ty) =>
wesl_ast_collect_type_reference(
ty, references, scopes, named_items, import_by_local, imports, current_path,
)
None => ()
}
wesl_ast_collect_attributes_references(
function_.return_attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
for parameter in function_.parameters {
wesl_ast_bind_name(scopes, parameter.name)
}
wesl_ast_collect_body_references(
function_.body,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
Struct(struct_) =>
for field in struct_.members {
wesl_ast_collect_attributes_references(
field.attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
wesl_ast_collect_type_reference(
field.type_expr,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
}
ConstAssert(assertion) =>
wesl_ast_collect_expression_references(
assertion.assertion_expr,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
EnableDirective(_)
| RequiresDirective(_)
| DiagnosticDirective(_)
| Other => ()
}
references
}
///|
fn wesl_collect_module_item_references(
item : ModuleItem,
named_items : @hashmap.HashMap[String, Int],
import_by_local : @hashmap.HashMap[String, Int],
imports : Array[ImportItem],
current_path : ModulePath,
) -> Array[ModuleItemRef] {
let references : Array[ModuleItemRef] = []
let scopes : Array[Array[String]] = [[]]
wesl_ast_collect_attributes_references(
item.attributes,
references,
scopes,
named_items,
import_by_local,
imports,
current_path,
)
for
reference in wesl_collect_ast_item_references(
item.header,
named_items,
import_by_local,
imports,
current_path,
) {
wesl_push_unique_item_ref(references, reference)
}
for
reference in wesl_collect_legacy_item_references(
item.source,
item.name,
named_items,
import_by_local,
imports,
current_path,
) {
wesl_push_unique_item_ref(references, reference)
}
references
}
///|
fn wesl_replace_identifier(
source : String,
old_name : String,
new_name : String,
) -> String {
if old_name == new_name || old_name == "" {
return source
}
let mut out = ""
let mut index = 0
while index < source.length() {
let can_match = index + old_name.length() <= source.length() &&
source[index:index + old_name.length()].to_owned() == old_name
if can_match {
let prev_code = if index == 0 {
-1
} else {
source.code_unit_at(index - 1).to_int()
}
let next_code = if index + old_name.length() >= source.length() {
-1
} else {
source.code_unit_at(index + old_name.length()).to_int()
}
let prev_ok = prev_code < 0 || !wesl_is_identifier_char(prev_code)
let next_ok = next_code < 0 || !wesl_is_identifier_char(next_code)
if prev_ok && next_ok {
out = out + new_name
index = index + old_name.length()
continue
}
}
out = out + source[index:index + 1].to_owned()
index = index + 1
}
out
}
///|
fn wesl_replace_reference_identifier(
source : String,
old_name : String,
new_name : String,
item_name : String?,
) -> String {
if old_name == new_name || old_name == "" {
return source
}
let tokens = parser_lex(source)
let scopes : Array[Array[String]] = [[]]
let mut scope_depth = 0
let mut consumed_item_decl = item_name is None
let mut out = ""
let mut previous_end = 0
for i in 0.. {
scope_depth += 1
if scope_depth < scopes.length() {
scopes[scope_depth] = []
} else {
scopes.push([])
}
out = out + source[start:end_].to_owned()
}
RBrace(_, _, _) => {
if scope_depth > 0 {
scope_depth -= 1
}
out = out + source[start:end_].to_owned()
}
Ident(name, _, _, _) => {
let original = source[start:end_].to_owned()
if wesl_is_bound_identifier(source, tokens, i) {
let is_item_decl = match item_name {
Some(current_item_name) =>
!consumed_item_decl && name == current_item_name
None => false
}
if is_item_decl {
consumed_item_decl = true
} else {
wesl_bind_scope(scopes, scope_depth, name)
}
out = out + original
} else if name == old_name &&
!wesl_scopes_contain(scopes, scope_depth, name) {
out = out + new_name
} else {
out = out + original
}
}
_ => out = out + source[start:end_].to_owned()
}
previous_end = end_
}
if previous_end < source.length() {
out = out + source[previous_end:source.length()].to_owned()
}
out
}
///|
fn wesl_replace_global_identifier(
source : String,
old_name : String,
new_name : String,
item_name : String?,
) -> String {
if old_name == new_name || old_name == "" {
return source
}
let tokens = parser_lex(source)
let scopes : Array[Array[String]] = [[]]
let mut scope_depth = 0
let mut consumed_item_decl = item_name is None
let mut out = ""
let mut previous_end = 0
for i in 0.. {
scope_depth += 1
if scope_depth < scopes.length() {
scopes[scope_depth] = []
} else {
scopes.push([])
}
out = out + source[start:end_].to_owned()
}
RBrace(_, _, _) => {
if scope_depth > 0 {
scope_depth -= 1
}
out = out + source[start:end_].to_owned()
}
Ident(name, _, _, _) => {
let original = source[start:end_].to_owned()
if wesl_is_bound_identifier(source, tokens, i) {
let is_item_decl = match item_name {
Some(current_item_name) =>
!consumed_item_decl && name == current_item_name
None => false
}
if is_item_decl {
consumed_item_decl = true
if name == old_name {
out = out + new_name
} else {
out = out + original
}
} else {
wesl_bind_scope(scopes, scope_depth, name)
out = out + original
}
} else if name == old_name &&
!wesl_scopes_contain(scopes, scope_depth, name) {
out = out + new_name
} else {
out = out + original
}
}
_ => out = out + source[start:end_].to_owned()
}
previous_end = end_
}
if previous_end < source.length() {
out = out + source[previous_end:source.length()].to_owned()
}
out
}
///|
fn wesl_decl_key(path : ModulePath, name : String) -> String {
"\{path.to_string()}::\{name}"
}
///|
fn wesl_is_used_decl(
used_decls : @hashmap.HashMap[String, Bool],
path : ModulePath,
name : String,
) -> Bool {
used_decls.contains(wesl_decl_key(path, name))
}
///|
fn wesl_item_kind(header : GlobalDeclarationHeader) -> ModuleItemKind {
match header {
Alias(_) => Alias
Const(_) => Const
Var(_) => Var
_ => Other
}
}
///|
fn wesl_item_has_cycle_semantics(item : ModuleItem) -> Bool {
match item.kind {
Alias => true
Const => true
Var => true
Other => false
}
}
///|
fn wesl_module_export_target(
parsed_module : ParsedModule,
name : String,
) -> ExportTarget {
match parsed_module.named_items.get(name) {
Some(index) => Local(index)
None =>
match parsed_module.import_by_local.get(name) {
Some(index) =>
if parsed_module.imports[index].public &&
wesl_import_named_target(parsed_module.imports[index]) is Some(_) {
ReExport(parsed_module.imports[index])
} else {
Private
}
None => Missing
}
}
}
///|
fn wesl_resolve_export_target(
path : ModulePath,
name : String,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
) -> (ModulePath, String) raise WeslCompileError {
guard modules.get(path) is Some(parsed_module) else {
raise MissingDecl(path, name)
}
match wesl_module_export_target(parsed_module, name) {
Local(_) => (path, name)
ReExport(import_item) =>
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
wesl_resolve_export_target(import_path, import_name, modules)
None => raise MissingDecl(path, name)
}
Private => raise Private(name, path)
Missing => raise MissingDecl(path, name)
}
}
///|
fn[R : Resolver] wesl_preload_import_item(
import_item : ImportItem,
resolver : R,
options : CompileOptions,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
order : Array[ModulePath],
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
root : ModulePath,
mangler : ManglerKind,
) -> Unit raise WeslCompileError {
let mut missing_named_target : (ModulePath, String)? = None
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
try
wesl_load_module(
import_path, resolver, options, modules, order, sourcemap, record_sourcemap,
root, mangler,
)
catch {
_ => ()
} noraise {
loaded =>
match wesl_module_export_target(loaded, import_name) {
Local(_) => return
ReExport(_) => return
Private => raise Private(import_name, loaded.path)
Missing => missing_named_target = Some((loaded.path, import_name))
}
}
None => ()
}
ignore(
wesl_load_module(
import_item.namespace_path,
resolver,
options,
modules,
order,
sourcemap,
record_sourcemap,
root,
mangler,
),
) catch {
err =>
match missing_named_target {
Some((missing_path, missing_name)) =>
raise MissingDecl(missing_path, missing_name)
None => raise err
}
}
}
///|
fn[R : Resolver] wesl_record_sourcemap_source(
path : ModulePath,
resolver : R,
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
) -> Unit {
if !record_sourcemap || sourcemap.get_source(path) is Some(_) {
return
}
let source_result = try R::resolve_source(resolver, path) catch {
err => Err(err)
} noraise {
source => Ok(source)
}
match source_result {
Ok(source) =>
sourcemap.add_source(path, R::display_name(resolver, path), source)
Err(_) => ()
}
}
///|
fn wesl_record_sourcemap_decls(
path : ModulePath,
root : ModulePath,
items : Array[ModuleItem],
options : CompileOptions,
mangler : ManglerKind,
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
) -> Unit {
if !record_sourcemap {
return
}
for item in items {
match item.name {
Some(name) =>
if path != root || options.mangle_root {
sourcemap.add_decl(
ManglerKind::mangle(mangler, path, name),
path,
name,
)
}
None => ()
}
}
}
///|
fn[R : Resolver] wesl_load_module(
path : ModulePath,
resolver : R,
options : CompileOptions,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
order : Array[ModulePath],
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
root : ModulePath,
mangler : ManglerKind,
) -> ParsedModule raise WeslCompileError {
match modules.get(path) {
Some(existing) => existing
None => {
wesl_record_sourcemap_source(path, resolver, sourcemap, record_sourcemap)
let translation_unit = if options.condcomp && options.imports {
let unit = R::resolve_module(resolver, path) catch {
Error(message) => raise Parse(message)
err => raise Resolve(err)
}
ModulePreprocessor::preprocess_module(
CondCompPreprocessor::new(options.features),
path,
unit,
) catch {
Error(message) => raise InvalidExpression(message)
err => raise Resolve(err)
}
} else if options.condcomp {
let raw_source = R::resolve_source(resolver, path) catch {
err => raise Resolve(err)
}
let unit = parse_translation_unit(path, raw_source, options.imports)
ModulePreprocessor::preprocess_module(
CondCompPreprocessor::new(options.features),
path,
unit,
) catch {
Error(message) => raise InvalidExpression(message)
err => raise Resolve(err)
}
} else if !options.imports {
let raw_source = R::resolve_source(resolver, path) catch {
err => raise Resolve(err)
}
parse_translation_unit(path, raw_source, options.imports)
} else {
R::resolve_module(resolver, path) catch {
Error(message) => raise Parse(message)
err => raise Resolve(err)
}
}
let parsed_module = wesl_parse_loaded_translation_unit(
path, translation_unit, false,
)
wesl_record_sourcemap_decls(
path,
root,
parsed_module.items,
options,
mangler,
sourcemap,
record_sourcemap,
)
modules.set(path, parsed_module)
order.push(path)
if options.imports && (!options.strip || !options.lazy_resolution) {
for import_item in parsed_module.imports {
ignore(
wesl_preload_import_item(
import_item, resolver, options, modules, order, sourcemap, record_sourcemap,
root, mangler,
),
)
}
}
parsed_module
}
}
}
///|
fn[R : Resolver] wesl_mark_module_used(
path : ModulePath,
resolver : R,
options : CompileOptions,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
order : Array[ModulePath],
used_modules : @hashmap.HashMap[ModulePath, Bool],
used_decls : @hashmap.HashMap[String, Bool],
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
root : ModulePath,
mangler : ManglerKind,
) -> ParsedModule raise WeslCompileError {
let parsed_module = wesl_load_module(
path, resolver, options, modules, order, sourcemap, record_sourcemap, root, mangler,
)
if used_modules.contains(path) {
return parsed_module
}
used_modules.set(path, true)
for item in parsed_module.items {
if !item.is_const_assert {
continue
}
ignore(
wesl_mark_item_refs(
path, parsed_module, item, resolver, options, modules, order, used_modules,
used_decls, sourcemap, record_sourcemap, root, mangler,
),
)
}
parsed_module
}
///|
fn[R : Resolver] wesl_mark_named_decl(
path : ModulePath,
name : String,
resolver : R,
options : CompileOptions,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
order : Array[ModulePath],
used_modules : @hashmap.HashMap[ModulePath, Bool],
used_decls : @hashmap.HashMap[String, Bool],
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
root : ModulePath,
mangler : ManglerKind,
) -> Unit raise WeslCompileError {
let parsed_module = wesl_mark_module_used(
path, resolver, options, modules, order, used_modules, used_decls, sourcemap,
record_sourcemap, root, mangler,
)
let key = wesl_decl_key(path, name)
if used_decls.contains(key) {
return
}
match wesl_module_export_target(parsed_module, name) {
Local(index) => {
used_decls.set(key, true)
let item = parsed_module.items[index]
ignore(
wesl_mark_item_refs(
path, parsed_module, item, resolver, options, modules, order, used_modules,
used_decls, sourcemap, record_sourcemap, root, mangler,
),
)
}
ReExport(import_item) => {
used_decls.set(key, true)
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
ignore(
wesl_mark_named_decl(
import_path, import_name, resolver, options, modules, order, used_modules,
used_decls, sourcemap, record_sourcemap, root, mangler,
),
)
None => ()
}
}
Private => raise Private(name, path)
Missing => raise MissingDecl(path, name)
}
}
///|
fn wesl_root_keep_names(
root : ParsedModule,
options : CompileOptions,
) -> Array[String] {
let keep_names : Array[String] = []
if !options.strip || options.keep_root {
for entry in root.named_items.iter() {
let (name, _) = entry
keep_names.push(name)
}
return keep_names
}
match options.keep {
Some(keep) => {
for name in keep {
if root.named_items.contains(name) {
keep_names.push(name)
}
}
keep_names
}
None => {
for item in root.items {
if item.entrypoint && item.name is Some(name) {
keep_names.push(name)
}
}
keep_names
}
}
}
///|
fn[R : Resolver] wesl_mark_item_refs(
path : ModulePath,
parsed_module : ParsedModule,
item : ModuleItem,
resolver : R,
options : CompileOptions,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
order : Array[ModulePath],
used_modules : @hashmap.HashMap[ModulePath, Bool],
used_decls : @hashmap.HashMap[String, Bool],
sourcemap : BasicSourceMap,
record_sourcemap : Bool,
root : ModulePath,
mangler : ManglerKind,
) -> Unit raise WeslCompileError {
for reference in item.references {
match reference {
Local(name) =>
ignore(
wesl_mark_named_decl(
path, name, resolver, options, modules, order, used_modules, used_decls,
sourcemap, record_sourcemap, root, mangler,
),
)
Import(_, import_index) => {
let import_item = parsed_module.imports[import_index]
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
ignore(
wesl_mark_named_decl(
import_path, import_name, resolver, options, modules, order, used_modules,
used_decls, sourcemap, record_sourcemap, root, mangler,
),
)
None => ()
}
}
Qualified(_, target_path, target_name) =>
ignore(
wesl_mark_named_decl(
target_path, target_name, resolver, options, modules, order, used_modules,
used_decls, sourcemap, record_sourcemap, root, mangler,
),
)
}
}
}
///|
fn wesl_apply_replacements(
source : String,
replacements : Array[(String, String)],
) -> String {
let mut out = source
for replacement in replacements {
out = wesl_replace_identifier(out, replacement.0, replacement.1)
}
out
}
///|
fn wesl_close_replacements(
replacements : Array[(String, String)],
) -> Array[(String, String)] {
let mut current = replacements
for _ in 0.. Bool {
for reference in item.references {
match reference {
Local(reference_name) if reference_name == name => return true
_ => ()
}
}
false
}
///|
fn wesl_apply_referenced_replacements(
item : ModuleItem,
replacements : Array[(String, String)],
) -> String {
let filtered : Array[(String, String)] = []
for replacement in replacements {
if wesl_item_references_local(item, replacement.0) {
filtered.push(replacement)
}
}
wesl_apply_replacements(item.source, filtered)
}
///|
fn wesl_lower_source(source : String) -> String raise WeslCompileError {
let path = ModulePath::from_path("/__wesl_lower")
let parsed_module = wesl_parse_loaded_translation_unit(
path,
parse_translation_unit(path, source, true),
false,
)
let alias_replacements : Array[(String, String)] = []
let const_replacements : Array[(String, String)] = []
let kept_items : Array[ModuleItem] = []
for parsed in parsed_module.items {
match parsed.header {
Alias(alias_decl) =>
match alias_decl.target {
Some(target) => {
alias_replacements.push(
(
alias_decl.name,
wesl_apply_replacements(
wesl_apply_replacements(target, alias_replacements),
const_replacements,
),
),
)
continue
}
None => kept_items.push(parsed)
}
Const(const_decl) =>
match const_decl.initializer {
Some(initializer) => {
const_replacements.push(
(
const_decl.name,
"(\{wesl_apply_replacements(wesl_apply_replacements(initializer, alias_replacements), const_replacements)})",
),
)
continue
}
None => kept_items.push(parsed)
}
_ => kept_items.push(parsed)
}
}
let replacements : Array[(String, String)] = []
for replacement in alias_replacements {
replacements.push(replacement)
}
for replacement in const_replacements {
replacements.push(replacement)
}
let closed_replacements = wesl_close_replacements(replacements)
let lowered : Array[String] = []
for item in kept_items {
let lowered_item = wesl_apply_referenced_replacements(
item, closed_replacements,
)
.trim()
.to_owned()
if lowered_item != "" {
lowered.push(lowered_item)
}
}
lowered.join("\n\n")
}
///|
fn wesl_cycle_reference_target(
current_path : ModulePath,
parsed_module : ParsedModule,
reference : ModuleItemRef,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
) -> (ModulePath, String)? {
match reference {
Local(name) => Some((current_path, name))
Import(_, import_index) =>
match wesl_import_named_target(parsed_module.imports[import_index]) {
Some((import_path, import_name)) => {
let target = wesl_resolve_export_target(
import_path, import_name, modules,
) catch {
_ => return None
}
Some(target)
}
None => None
}
Qualified(_, target_path, target_name) => {
let target = wesl_resolve_export_target(target_path, target_name, modules) catch {
_ => return None
}
Some(target)
}
}
}
///|
fn wesl_validate_decl_cycle(
path : ModulePath,
name : String,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
states : @hashmap.HashMap[String, Int],
) -> Unit raise WeslCompileError {
let key = wesl_decl_key(path, name)
match states.get(key) {
Some(state) =>
if state == 1 {
guard modules.get(path) is Some(parsed_module) else { return }
guard parsed_module.named_items.get(name) is Some(index) else { return }
let item = parsed_module.items[index]
raise CircularDecl(item.span.symbol_context(path, name))
} else {
return
}
None => ()
}
guard modules.get(path) is Some(parsed_module) else { return }
guard parsed_module.named_items.get(name) is Some(index) else { return }
let item = parsed_module.items[index]
if !wesl_item_has_cycle_semantics(item) {
states.set(key, 2)
return
}
states.set(key, 1)
for reference in item.references {
match wesl_cycle_reference_target(path, parsed_module, reference, modules) {
Some((target_path, target_name)) =>
ignore(
wesl_validate_decl_cycle(target_path, target_name, modules, states),
)
None => ()
}
}
states.set(key, 2)
}
///|
fn wesl_validate_loaded_cycles(
order : Array[ModulePath],
modules : @hashmap.HashMap[ModulePath, ParsedModule],
) -> Unit raise WeslCompileError {
let states : @hashmap.HashMap[String, Int] = HashMap([])
for path in order {
guard modules.get(path) is Some(parsed_module) else { continue }
for item in parsed_module.items {
guard item.name is Some(name) else { continue }
if !wesl_item_has_cycle_semantics(item) {
continue
}
ignore(wesl_validate_decl_cycle(path, name, modules, states))
}
}
}
///|
pub fn validate_wesl(unit : TranslationUnit) -> Unit raise WeslCompileError {
let path = ModulePath::from_path("/__validate__")
let parsed_module = wesl_parse_loaded_translation_unit(path, unit, true)
let modules : @hashmap.HashMap[ModulePath, ParsedModule] = HashMap([])
let order : Array[ModulePath] = [path]
modules.set(path, parsed_module)
wesl_validate_loaded_cycles(order, modules) catch {
CircularDecl(symbol) => raise Validate(Cycle(symbol, symbol))
error => raise error
}
}
///|
fn wesl_validate_wgsl_source(source : String) -> Unit raise WeslCompileError {
let module_ = @ir.parse_wgsl_module_to_ir(source) catch {
error => raise Validation(error.message())
}
@ir.validate_wgsl_ir_module(module_) catch {
error => raise Validation(error.message())
}
}
///|
fn wesl_emit_item_source(
parsed_module : ParsedModule,
item : ModuleItem,
root : ModulePath,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
used_decls : @hashmap.HashMap[String, Bool],
mangler : ManglerKind,
options : CompileOptions,
) -> String {
let mut current = item.source
for reference in item.references {
match reference {
Import(import_ref, import_index) => {
let import_item = parsed_module.imports[import_index]
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) => {
let (target_path, target_name) = wesl_resolve_export_target(
import_path, import_name, modules,
) catch {
_ => (import_path, import_name)
}
current = wesl_replace_reference_identifier(
current,
import_ref,
ManglerKind::mangle(mangler, target_path, target_name),
item.name,
)
}
None => ()
}
}
Qualified(qualified_ref, target_path, target_name) =>
current = wesl_replace_identifier(
current,
qualified_ref,
ManglerKind::mangle(mangler, target_path, target_name),
)
Local(_) => ()
}
}
if parsed_module.path != root || options.mangle_root {
for entry in parsed_module.named_items.iter() {
let (name, _) = entry
if !options.strip ||
wesl_is_used_decl(used_decls, parsed_module.path, name) {
current = wesl_replace_global_identifier(
current,
name,
ManglerKind::mangle(mangler, parsed_module.path, name),
item.name,
)
}
}
}
current.trim().to_owned()
}
///|
fn wesl_emit_module(
path : ModulePath,
root : ModulePath,
modules : @hashmap.HashMap[ModulePath, ParsedModule],
used_modules : @hashmap.HashMap[ModulePath, Bool],
used_decls : @hashmap.HashMap[String, Bool],
emitted : @hashmap.HashMap[ModulePath, Bool],
parts : Array[String],
mangler : ManglerKind,
options : CompileOptions,
) -> Unit {
if emitted.contains(path) || !used_modules.contains(path) {
return
}
emitted.set(path, true)
guard modules.get(path) is Some(parsed_module) else { return }
if !options.strip || !options.lazy_resolution {
for import_item in parsed_module.imports {
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
try
wesl_resolve_export_target(import_path, import_name, modules)
catch {
_ => ()
} noraise {
(target_path, _) =>
wesl_emit_module(
target_path, root, modules, used_modules, used_decls, emitted, parts,
mangler, options,
)
}
None => ()
}
wesl_emit_module(
import_item.namespace_path,
root,
modules,
used_modules,
used_decls,
emitted,
parts,
mangler,
options,
)
}
} else {
for import_item in parsed_module.imports {
if import_item.public &&
wesl_is_used_decl(
used_decls,
parsed_module.path,
import_item.local_name,
) {
match wesl_import_named_target(import_item) {
Some((import_path, import_name)) =>
try
wesl_resolve_export_target(import_path, import_name, modules)
catch {
_ => ()
} noraise {
(target_path, _) =>
wesl_emit_module(
target_path, root, modules, used_modules, used_decls, emitted,
parts, mangler, options,
)
}
None => ()
}
}
}
for item in parsed_module.items {
let should_emit = match item.name {
Some(name) => wesl_is_used_decl(used_decls, parsed_module.path, name)
None => true
}
if !should_emit {
continue
}
for reference in item.references {
match reference {
Import(_, import_index) =>
match
wesl_import_named_target(parsed_module.imports[import_index]) {
Some((import_path, import_name)) =>
try
wesl_resolve_export_target(import_path, import_name, modules)
catch {
_ => ()
} noraise {
(target_path, _) =>
wesl_emit_module(
target_path, root, modules, used_modules, used_decls, emitted,
parts, mangler, options,
)
}
None => ()
}
Qualified(_, target_path, _) =>
wesl_emit_module(
target_path, root, modules, used_modules, used_decls, emitted, parts,
mangler, options,
)
Local(_) => ()
}
}
}
}
for item in parsed_module.items {
let should_emit = match item.name {
Some(name) =>
if options.strip {
wesl_is_used_decl(used_decls, parsed_module.path, name)
} else {
true
}
None => true
}
if !should_emit {
continue
}
let emitted_item = wesl_emit_item_source(
parsed_module, item, root, modules, used_decls, mangler, options,
)
if emitted_item != "" {
parts.push(emitted_item)
}
}
}
///|
fn[R : Resolver] compile_with_mangler_kind(
root : ModulePath,
resolver : R,
mangler : ManglerKind,
options : CompileOptions,
use_sourcemap : Bool,
) -> CompileResult raise WeslCompileError {
let sourcemap = BasicSourceMap::{ ..BasicSourceMap::default(), root, }
compile_with_mangler_kind_and_sourcemap(
root, resolver, mangler, options, use_sourcemap, sourcemap,
)
}
///|
fn[R : Resolver] compile_with_mangler_kind_and_sourcemap(
root : ModulePath,
resolver : R,
mangler : ManglerKind,
options : CompileOptions,
use_sourcemap : Bool,
sourcemap : BasicSourceMap,
) -> CompileResult raise WeslCompileError {
let _ = options.lower
let _ = options.generics
let modules : @hashmap.HashMap[ModulePath, ParsedModule] = HashMap([])
let order : Array[ModulePath] = []
let used_modules : @hashmap.HashMap[ModulePath, Bool] = HashMap([])
let used_decls : @hashmap.HashMap[String, Bool] = HashMap([])
let root_module = wesl_mark_module_used(
root, resolver, options, modules, order, used_modules, used_decls, sourcemap,
use_sourcemap, root, mangler,
)
if options.validate {
wesl_validate_loaded_cycles(order, modules)
}
for keep_name in wesl_root_keep_names(root_module, options) {
ignore(
wesl_mark_named_decl(
root, keep_name, resolver, options, modules, order, used_modules, used_decls,
sourcemap, use_sourcemap, root, mangler,
),
)
}
if !options.strip {
for module_path in order {
used_modules.set(module_path, true)
}
}
let emitted : @hashmap.HashMap[ModulePath, Bool] = HashMap([])
let parts : Array[String] = []
wesl_emit_module(
root, root, modules, used_modules, used_decls, emitted, parts, mangler, options,
)
let assembled = parts.filter(fn(part) { part != "" }).join("\n\n")
let source = if options.lower {
wesl_lower_source(assembled)
} else {
assembled
}
if options.validate {
wesl_validate_wgsl_source(source)
}
let syntax = parse_translation_unit(root, source, false)
let output_sourcemap = if use_sourcemap {
match sourcemap.get_source(root) {
Some(root_source) => sourcemap.set_default_source(root_source)
None => ()
}
Some(sourcemap)
} else {
None
}
{ syntax, sourcemap: output_sourcemap, modules: order }
}
///|
pub fn[R : Resolver] compile(
root : ModulePath,
resolver : R,
_mangler : EscapeMangler,
options : CompileOptions,
) -> CompileResult raise WeslCompileError {
compile_with_mangler_kind(root, resolver, Escape, options, false)
}
///|
pub fn[R : Resolver] compile_sourcemap(
root : ModulePath,
resolver : R,
_mangler : EscapeMangler,
options : CompileOptions,
) -> CompileResult raise WeslCompileError {
compile_with_mangler_kind(root, resolver, Escape, options, true)
}
///|
fn[R : Resolver] wesl_diagnostic_from_compile_error(
root : ModulePath,
resolver : R,
mangler : ManglerKind,
error : WeslCompileError,
use_sourcemap : Bool,
sourcemap : BasicSourceMap,
) -> Diagnostic {
let mut diagnostic = error
.diagnostic()
.with_module_path(root, R::display_name(resolver, root))
.unmangle_with_mangler(mangler)
if use_sourcemap {
match sourcemap.get_source(root) {
Some(root_source) => sourcemap.set_default_source(root_source)
None => {
let source_result = try R::resolve_source(resolver, root) catch {
err => Err(err)
} noraise {
source => Ok(source)
}
match source_result {
Ok(source) => {
sourcemap.add_source(root, R::display_name(resolver, root), source)
sourcemap.set_default_source(source)
}
Err(_) => ()
}
}
}
diagnostic = diagnostic.with_sourcemap(sourcemap).infer_span_from_message()
}
diagnostic
}
///|
fn[R : Resolver] compile_diagnostic_with_mangler_kind(
root : ModulePath,
resolver : R,
mangler : ManglerKind,
options : CompileOptions,
use_sourcemap : Bool,
) -> CompileResult raise WeslDiagnosticError {
let sourcemap = BasicSourceMap::{ ..BasicSourceMap::default(), root, }
compile_with_mangler_kind_and_sourcemap(
root, resolver, mangler, options, use_sourcemap, sourcemap,
) catch {
err =>
raise Diagnostic(
wesl_diagnostic_from_compile_error(
root, resolver, mangler, err, use_sourcemap, sourcemap,
),
)
}
}
///|
pub fn[R : Resolver] compile_diagnostic(
root : ModulePath,
resolver : R,
_mangler : EscapeMangler,
options : CompileOptions,
) -> CompileResult raise WeslDiagnosticError {
compile_diagnostic_with_mangler_kind(root, resolver, Escape, options, false)
}
///|
pub fn[R : Resolver] compile_sourcemap_diagnostic(
root : ModulePath,
resolver : R,
_mangler : EscapeMangler,
options : CompileOptions,
) -> CompileResult raise WeslDiagnosticError {
compile_diagnostic_with_mangler_kind(root, resolver, Escape, options, true)
}