owned this note
owned this note
Published
Linked with GitHub
# WESL imports and :: vs /.
We're debating what syntax WESL should use for `import` and inline identifiers.
<table>
<tr>
<td> :: </td> <td> /. </td>
</tr>
<tr>
<td>
```wgsl
import super::util::zip;
import rand_pkg as rand;
fn main() {
let color = rand::pcg(1.0);
return zip.zap(color);
}
```
</td>
<td>
```wgsl
import ./util/zip;
import rand_pkg as rand;
fn main() {
let color = rand.pcg(1.0);
return zip.zap(color);
}
```
</td>
</tr>
</table>
One approach uses `::` for both import statements and inline identifiers,
another uses `/` for import statements and `.` for inline identifiers.
(Also see below for a [combination approach](https://hackmd.io/ljkByEcnQa2NdNLWed2M6Q?both=&stext=3042%3A20%3A0%3A1732054639%3AhUj1wy)).
## WESL Module System Background
### Imports
WESL will support `import` statements so programmers can mix in code from other files or from external libraries.
### Shaders in Files (or Strings)
WESL programmers may store shader source code in files or strings, but shader code in files will be much more mainstream.
### Module Hierarchy Starts From File Hierarchy
Code in files and libraries are addressable in a 'module hierarchy'. File and directory names names in applications map automatically into the module hierarchy. Future features like `namespace` would also be placed into the module hierarchy.
<img src="https://docs.google.com/drawings/d/e/2PACX-1vQ-zjogxndAvpcqu5NXVY-fXgxQHylIvkV7o2PC6YYgrDm_JE7UDhmmdfjGsE7DXxJvbwRQhXGkqbfS/pub?w=1300&h=374">
Programmers storing shader code in strings can specify the module address with the string.
### Identifiers with Separators
WESL will support inline identifiers with separators to address the module hierarchy. Programmers will be able to write something like `utils::random()` or `utils.random()`.
### Visibility Control
WESL will support marking WGSL elements as private. Private elements will not be visible for import from other modules.
### Notable Future Development Outside the Module System
WESL is likely to eventually support some form of `namespace` scoping declaration within files. Namespaces should be addressable from import statements and inline identifiers.
WESL is likely to eventually support some form of web served shader code like other web native languages. Import statements will need to be translated to relative or absolute URLs.
## Feature Details
Import `zip.wgsl`.
- `import super::util::zip;`
- `import ./util/zip;`
Inline use after importing `zip`.
- `zip::bar();`
- `zip.bar();`
Import `fn bar` from `zip.wgsl`.
- `import super::util::zip::bar;`
- `import ./util/zip/bar;`
Inline use after importing `bar`.
- `bar();`
- `bar();`
Import a library `rand_pkg`.
- `import rand_pkg;`
- `import rand_pkg;`
Inline use after importing `rand_pkg`.
- `rand_pkg::pcg();`
- `rand_pkg.pcg();`
Densely nested imports for concision.
- ```wgsl
import bevy_pbr::{
forward_io::VertexOutput,
pbr_types::{PbrInput, pbr_input_new},
};
```
- ```wgsl
import bevy_pbr/{
forward_io/VertexOutput,
pbr_types/{PbrInput, pbr_input_new},
};
```
Combination approach, with `/` for imports and `::` inline.
- ```wgsl
import ./util/zip;
import rand_pkg as rand;
fn main() {
let color = rand::pcg(1.0);
return zip::zap(color);
}
```
### Possible Future Features in the Module System
#### root shortcut for import statements
Import from the root of the current package.
- `import package::util::zip::bar;`
- `import /util/zip/bar;`
#### root shortcut for inline separators
Inline separator addresses the root of the current package.
- `package::util::zip::bar();`
- `package.util.zip.bar();`
#### namespaces
If `zip.wgsl` has a future namespace construct like `namespace dig { fn bar() {} }`, the namespace would map into the same addressable hierarchy.
- `import super::util::zip::dig::bar;`
- `import ./util/zip/dig/bar;`
- If a namespace name matches a file (or directory) name at the same place in the module hierarchy, both can be imported. Names conflicts can happen.
#### like named files
Allow `util.wgsl` and `util/` both exist in the filesystem.
- Allow for both to be imported, similar to above
#### inline relative imports
Inline references to `super` are also possible.
- `super::zip::bar()`
- `super.zip.bar()`
#### hierarchy remapping (aka re-exporting)
Allow grafting subtrees into the module hierarchy from WESL.
For example,
let's say the file system contains `other/bar/zig.wesl` and `other/bar/zag.wesl`. We've discussed `namespace` variations that place a namespace in the module hierarchy. We might put a statement like this in `util/stuff.wesl`:
- `namespace foo = package::other::bar;`
- `namespace foo = /other/bar;`
Then you could write:
- `import /util/stuff/foo/zig`
- `import package::util::stuff::foo::zig`
#### library view
Libraries may want to remap the module hierarchy for library consumers.
For example, a library may wish to publish a flat view of public library functions
that are developed internally in a file/module hierarchy.
We've discussed a variety of approaches to creating library views, some based on hierarchy remapping.
#### filesystem vs module system character sets
The character set for filesystem identifiers isn't quite the same as the character set
for WGSL identifiers we're planning to use in import statements and inline paths.
- We could specify a smaller set of characters for use in import specifiers, but the current thinking is to simply use WGSL identifiers,
and expect users to avoid using odd characters in their shader files and directories.
#### Mapping imports to files
Language servers and linkers will refer to the `wesl.toml` file to find the the root directory containing `.wesl` source files.
Import statements map to files with minimal searching.
- Each segment in an `import` path can either be a file, a directory, a namespace within a file, or wgsl element (for trailing segments).
- Proceding left to right through the path segments, consider the segments `prev` and `seg`.
1. if `prev.wesl` exists and includes WESL elements, check if `seg` is one of those elements, e.g.`fn seg` or `namespace seg`.
1. else if the directory `prev/` exists, check to see if the file `seg.wesl` or the directory `seg/` is in the `prev/` directory;
1. error if a `seg` is not found.
If `seg` is an element in the module, and an ambiguously named file/directory, emit an extra warning.
#### Are const_assert statements included?
Generally, WGSL elements are included if they are recursively referenced from the root module (use analysis). But `const_assert` statements are also included if they are in the same module or namespace as a referenced element.
- ```wgsl
// main.wgsl:
import foo/bar;
fn main() { bar(); }
// foo.wgsl:
import ./zig;
const_assert(1 > 0); // included in link because bar is used
fn bar() { }
fn miz() { zig.zag() }
// zig.wgsl:
const_assert(2 < 0); // not included in link
fn zag() { }
```
## Arguments for :: vs /.
- (`::`) Consistency with imports vs. identifier separators.
- Having `/` for imports and `.` for identifiers means programmers need to mentally
map from one to the other when using imports and inline references with separators.
- (`::`) Suporting `.` syntax in identifiers is not ambiguous, but it's a bigger
WGSL grammar change because . is used in the syntax already.
(I gather it's trivial for lookahead parsers,
but requires a bigger grammar change for LR(1) parsers.)
- e.g. parse `var x = foo.bar.baz++;` as struct dereferncing vs parse `var x = foo.bar.baz;` as a module path.
- e.g. parsing `foo.bar.baz()`. (`ident` branch in the `statement` grammar.)
And parse `foo.bar.baz++` as an increment. (`variable_updating_statement` in the `statement` grammar.) Requires bigger grammar change, or arbitrary lookahead.
- (`/.`) Consistency with the file system and urls.
- Having `::` for imports means that users need to mentally map
`super::foo` to `../foo` for local file paths and urls.
- (`/.`) The `::` syntax is a bit scary looking to web programmers, which hurts new user adoption slightly.
- (`/.`) The `/.` syntax is more concise.
- (`/.`) The `.` syntax conflates struct membership with module membership.
- Combining two relatable membership concepts into the same syntax is simpler for the user. Less to learn and remember how to distinguish.
- Variables may shadow modules with `.` syntax.
- Ben Clayton [writes](https://github.com/gpuweb/gpuweb/issues/2426#issuecomment-1032391500) that combining may simplify user understanding of variable shadowing:
```wgsl
import pkg/blah; // or import pkg::blah;
fn f() {
var blah : B;
blah.x = 1; // Obvious (to me) that this is dealing with the var
blah::x = 1; // Where as I'm not sure whether this would be allowed?
}
```
- Mathis had a different interpretation of a similar case of shadowing:
```wgsl
// shadowing can impact your code remotely.
// we have a foo/bar/x that we could import
// access var x from foo/bar.wgsl. foo.bar.x is an identifier.
fn main() {
foo.bar.x = 10;
}
// now say I introduce this statement
// suddenly the semantics will change
var foo = bird(vec3f(0.0));
struct bird { bar: vec3f }
// access component x of member bar of struct bird in foo instance
fn main() {
foo.bar.x = 10;
}
```
## Still to Consider
### Runtime linking unbundled shader modules
Mapping import paths to files is a little more difficult for a future runtime linker fetching shader files individually over the network,
because the [algorithm](#Mapping-imports-to-files) requires checking for files that may not exist.
- Probably we'd expect users to supply a manifest file listing the source files (or an Import Map) to relieve the linker of making unnecessary network requests.
### Separate Syntax for Importing WGSL Elements
Both the `::` and `/.` syntaxes address WGSL language elements with the same syntax as files, directories, and namespaces. Some other module syntaxes intentionally provide a separate syntax for language elements for clarity. e.g. Gleam requires brackets `import gleam/option.{Some, None}` to intentionally clarify the difference between langauge elements and paths. JavaScript also has a similar distinction with `import foo from "bar"`.
- Should WESL imports should address WGSL elements differently from modules?
### root module to wesl.toml?
Should `wesl.toml` need to mention the root module file or files for the sake of the language server?