Skip to content

Commit bad5f0a

Browse files
committed
refactor: enforce conceptual module ownership
1 parent ac90042 commit bad5f0a

234 files changed

Lines changed: 6649 additions & 3832 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.mbt.md

Lines changed: 31 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -1,207 +1,37 @@
1-
# moon_wgsl
2-
3-
`Milky2018/moon_wgsl` is a MoonBit library for composing WGSL shader modules
4-
with `naga_oil`-style preprocessing and imports.
5-
6-
Use it when your shaders contain directives such as `#define_import_path`,
7-
`#ifdef`, `#define`, and `#import`, and you want to resolve them from MoonBit
8-
without adding a separate shader build step.
9-
10-
## Install
11-
12-
Add the package from Mooncakes, then import the subpackages you need:
13-
14-
```mbt check
15-
///|
16-
test "README: package is available" {
17-
let value_defines = @common.default_wgsl_value_defines()
18-
debug_inspect(value_defines.length() > 0, content="true")
19-
}
1+
# moon_wgsl workspace
2+
3+
This repository contains four separately owned MoonBit modules:
4+
5+
- `Milky2018/wgsl` — official WGSL lexer, AST, parser, semantic IR,
6+
validation, and runtime writer
7+
- `Milky2018/moon_wgsl_naga` — Naga-compatible ordering, naming, writer,
8+
and trace behavior
9+
- `Milky2018/moon_wgsl_naga_oil` — naga-oil directives, imports,
10+
preprocessing, resolution, composition, export, profiles, and diagnostics
11+
- `Milky2018/moon_wgsl` — the small user-facing workflow facade
12+
13+
Most applications should import `Milky2018/moon_wgsl`. Its opaque `Composer`
14+
supports exactly source/module registration, `prepare`, `compose`, and
15+
`export_wgsl`. Lower-level parser, IR, compatibility, graph, rewrite, and
16+
diagnostic stages are not facade methods.
17+
18+
```mbt
19+
let composer = @moon_wgsl.Composer::default()
20+
composer.register_source("main.wgsl", "fn answer() -> u32 { return 42u; }")
21+
let source = composer.compose(
22+
"main.wgsl",
23+
@moon_wgsl.WgslComposeOptions::default(),
24+
)
2025
```
2126

22-
Most users only need these packages:
23-
24-
- `@common` for shared option and result types
25-
- `@metadata` for inspecting directives and imports
26-
- `@preprocess` for evaluating one shader source
27-
- `@resolver` for source registries and source-tree scanning
28-
- `@compose` for module composition
29-
- `@export` for single-file export
30-
31-
## Features
32-
33-
- Conditional preprocessing: `#ifdef`, `#ifndef`, `#if`, `#else if`, `#else`,
34-
and `#endif`
35-
- Shader definition values: bools, signed integers, unsigned integers, and raw
36-
WGSL text values
37-
- Grouped, aliased, and quoted-path imports
38-
- Composer-owned source registries for hermetic composition
39-
- Optional source-tree scanning through `moonbitlang/x/fs`
40-
- Single-file WGSL export with source catalog, source map, provenance, and
41-
diagnostics
42-
43-
## Quick Start
44-
45-
### Compose Modules
46-
47-
Register WGSL source strings on a `Composer`, then compose the root shader.
48-
49-
```mbt check
50-
///|
51-
test "README: compose registered modules" {
52-
let composer : @compose.Composer = @compose.Composer::default()
53-
composer.clear_sources()
54-
55-
composer.register_source(
56-
"maths.wgsl", "#define_import_path demo::maths\nconst TWO: f32 = 2.0;\n",
57-
)
58-
composer.register_source(
59-
"main.wgsl", "#import demo::maths::TWO\nfn scale(x: f32) -> f32 {\n return x * TWO;\n}\n",
60-
)
61-
62-
let options : @common.WgslComposeOptions = @common.WgslComposeOptions::default()
63-
let composed = composer.compose_wgsl("main.wgsl", options) catch {
64-
err => abort(err.message())
65-
}
66-
67-
debug_inspect(composed.contains("fn scale"), content="true")
68-
debug_inspect(composed.contains("#import"), content="false")
69-
}
70-
```
71-
72-
### Preprocess One Shader
73-
74-
Use `Preprocessor::preprocess` when you only need conditional compilation and
75-
shader-definition substitution for a single source string.
76-
77-
```mbt check
78-
///|
79-
test "README: preprocess one shader" {
80-
let defs : Map[String, @common.ShaderDefValue] = Map([])
81-
defs.set("TEXTURE", Bool(true))
82-
83-
let source = "#ifdef TEXTURE\nvar sprite_texture: texture_2d<f32>;\n#else\nvar sprite_texture: texture_2d_array<f32>;\n#endif\n"
84-
let output = @preprocess.Preprocessor::default().preprocess(source, defs) catch {
85-
_ => abort("preprocess failed")
86-
}
87-
88-
debug_inspect(
89-
output.preprocessed_source.contains("texture_2d<f32>"),
90-
content="true",
91-
)
92-
}
93-
```
94-
95-
### Read Metadata
96-
97-
Use metadata extraction when you want to inspect a shader before composing it.
98-
99-
```mbt check
100-
///|
101-
test "README: inspect metadata" {
102-
let source = "#define_import_path demo::main\n#define HDR\n#import demo::maths::TWO\nfn scale(x: f32) -> f32 {\n return x * TWO;\n}\n"
103-
let metadata = @metadata.get_preprocessor_metadata(source) catch {
104-
_ => abort("metadata extraction failed")
105-
}
27+
The synchronized ownership change is intentionally breaking. Legacy package
28+
paths, compatibility records in WGSL Core, direct directive/import parsers,
29+
and the re-exported lower-level `Composer` are not retained as aliases. See
30+
[`docs/ownership-migration.md`](docs/ownership-migration.md) for the complete
31+
old-to-new mapping and [`docs/adr/0020-enforce-conceptual-ownership-and-deep-interfaces.md`](docs/adr/0020-enforce-conceptual-ownership-and-deep-interfaces.md)
32+
for the final architecture.
10633

107-
debug_inspect(metadata.name, content="Some(\"demo::main\")")
108-
debug_inspect(metadata.imports.length(), content="1")
109-
}
110-
```
111-
112-
### Export One File
113-
114-
Use `export_wgsl_with_options` to compose and tree-shake a root shader into one
115-
WGSL file.
116-
117-
```mbt check
118-
///|
119-
test "README: export single file" {
120-
let composer : @compose.Composer = @compose.Composer::default()
121-
composer.clear_sources()
122-
composer.register_source(
123-
"shared.wgsl", "#define_import_path demo::shared\nstruct Value {\n x: f32,\n}\nfn read(value: Value) -> f32 {\n return value.x;\n}\n",
124-
)
125-
composer.register_source(
126-
"main.wgsl", "#import demo::shared::{Value, read}\nfn shade(value: Value) -> f32 {\n return read(value);\n}\n",
127-
)
128-
129-
let compose_options : @common.WgslComposeOptions = @common.WgslComposeOptions::default()
130-
let export_options : @common.WgslExportOptions = { root_items: ["shade"] }
131-
let output = @export.export_wgsl_with_options(
132-
composer, "main.wgsl", compose_options, export_options,
133-
) catch {
134-
err => abort(err.message())
135-
}
136-
137-
debug_inspect(output.source.contains("#import"), content="false")
138-
debug_inspect(output.source.contains("fn shade"), content="true")
139-
debug_inspect(output.diagnostics.length(), content="0")
140-
}
141-
```
142-
143-
## Import Syntax
144-
145-
The supported import forms match common `naga_oil` usage:
146-
147-
```wgsl
148-
#import bevy_render::view::View
149-
#import bevy_render::maths as maths
150-
#import bevy_render::{view::View, globals::Globals}
151-
#import bevy_render::{maths::{PI_2, powsafe}}
152-
#import "shaders/skills/shared.wgsl" Vertex, VertexOutput
153-
#import "../shared/common.wgsl" SharedVertex, build_color
154-
```
155-
156-
Relative quoted imports are resolved against the registered path of the
157-
importing shader.
158-
159-
## Recommended APIs
160-
161-
- `@compose.Composer::default`
162-
- `Composer::register_source`
163-
- `Composer::register_source_files`
164-
- `Composer::register_source_tree`
165-
- `Composer::compose_wgsl`
166-
- `@preprocess.Preprocessor::default`
167-
- `Preprocessor::preprocess`
168-
- `@metadata.get_preprocessor_metadata`
169-
- `@resolver.scan_wgsl_source_files`
170-
- `@resolver.scan_wgsl_source_files_checked`
171-
- `@export.export_wgsl_with_options`
172-
173-
Important option/result types live in `@common`, including:
174-
175-
- `WgslComposeOptions`
176-
- `WgslExportOptions`
177-
- `WgslSourceFile`
178-
- `WgslSourceScanOptions`
179-
- `PreprocessOutput`
180-
- `PreprocessorMetaData`
181-
- `PreparedWgslSource`
182-
- `WgslExportOutput`
183-
- `WgslDiagnostic`
184-
- `ShaderDefValue`
185-
186-
## Compatibility
187-
188-
The library aims to preserve the practical `naga_oil` programming model used by
189-
real shader pipelines. Internally, composition uses structured parsing,
190-
symbol-identity-aware binding, and an IR-backed validation pipeline before
191-
returning runtime-oriented WGSL.
192-
193-
For implementation details and parity status, see:
194-
195-
- [`docs/naga_oil-parity.md`](docs/naga_oil-parity.md)
196-
- [`docs/moon_wgsl-issue-tracker.md`](docs/moon_wgsl-issue-tracker.md)
197-
198-
## Development
199-
200-
Run the test suite from the module root:
201-
202-
```bash
203-
moon test
204-
```
34+
Development and release commands are documented in [`README.md`](README.md).
20535

20636
## License
20737

README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,20 @@ by ownership instead of exposing every internal package through one module.
99
and runtime WGSL emission.
1010
- `Milky2018/moon_wgsl_naga`: Naga-compatible writer and trace entry points.
1111
- `Milky2018/moon_wgsl_naga_oil`: naga-oil-compatible preprocessing,
12-
import resolution, composition, and export.
12+
import resolution, composition contracts, explicit project profiles, and
13+
export.
1314
- `Milky2018/moon_wgsl`: thin user-facing facade for ordinary preprocessing and
1415
composition workflows.
1516

1617
Most users should install `Milky2018/moon_wgsl`. Use the lower-level modules
1718
only when you need their specific parser, IR, Naga, or naga-oil boundary.
1819

20+
The facade owns an opaque `Composer` with only registration, `prepare`,
21+
`compose`, and `export_wgsl` workflows. Repository diagnostics use the explicit
22+
`Milky2018/moon_wgsl_naga_oil/diagnostics` adapter. Naga-oil directive parsing,
23+
import parsing, substitution, transformation, and source editing are
24+
compiler-enforced `internal/` packages.
25+
1926
## Migration
2027

2128
Legacy internal paths such as `Milky2018/moon_wgsl/parser`,
@@ -27,10 +34,20 @@ Use these imports instead:
2734
```text
2835
Milky2018/wgsl/parser
2936
Milky2018/wgsl/ir
37+
Milky2018/moon_wgsl_naga_oil/contract
38+
Milky2018/moon_wgsl_naga_oil/profile
3039
Milky2018/moon_wgsl_naga_oil/compose
3140
Milky2018/moon_wgsl_naga_oil/preprocess
3241
```
3342

43+
The former `Milky2018/wgsl/common` contracts now belong to
44+
`Milky2018/moon_wgsl_naga_oil/contract`. The former
45+
`default_wgsl_value_defines()` policy is now the explicit
46+
`bevy_wgsl_value_defines()` profile.
47+
48+
See [the ownership migration guide](docs/ownership-migration.md) for the full
49+
package, type, method, result-accessor, and diagnostics mapping.
50+
3451
## Development
3552

3653
Run workspace checks from the repository root:
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Enforce conceptual ownership and deep interfaces
2+
3+
The physical workspace split is necessary but not sufficient. A module owns a concept only when its interface and implementation can change for that concept without forcing an unrelated lower-level module to change. The workspace therefore adopts the following final seams and treats deviations as migration debt tracked by ISS-034.
4+
5+
## WGSL Core
6+
7+
`Milky2018/wgsl` owns the Official WGSL Frontend:
8+
9+
- official WGSL lexer, AST, parser, validation, and semantic IR
10+
- official `enable`, `requires`, and `diagnostic` directives
11+
- generic semantic analysis and valid-WGSL writing
12+
- neutral writer mechanics only when their interface contains no Naga or naga-oil policy
13+
14+
WGSL Core does not own preprocessing conditionals, shader definitions, imports, composition descriptors, source registries, symbol redirects, project profiles, generated-import provenance, final names, link graphs, import arena events, or compatibility writer modes.
15+
16+
`parse_wgsl_module_to_ir(source)` is the sole public semantic-lowering entry point. The resulting semantic IR records program meaning and source facts required by official WGSL diagnostics. It contains no compatibility flags, provenance caches, compose graph records, or writer naming policy.
17+
18+
## naga-oil frontend and composer
19+
20+
`Milky2018/moon_wgsl_naga_oil` owns all naga-oil dialect syntax and composition contracts:
21+
22+
- `ifdef`, `ifndef`, conditional expressions, defines, template constants, `define_import_path`, and imports
23+
- shader-definition values and explicitly selected project profiles
24+
- preprocess, metadata, source registry, compose, export, source catalog, and compatibility diagnostic contracts
25+
- Compose Graph, Symbol Graph, Final Name Table, import events, source provenance, emission plans, and source editing
26+
27+
The naga-oil frontend preserves extension-source spans and diagnostics, then produces official WGSL before invoking WGSL Core. It may reuse the neutral lexer, but it must not add dialect variants to the official AST.
28+
29+
Generic defaults are empty and language-neutral. Bevy-compatible defaults live in an explicit profile selected by a caller; they are not implicit WGSL or generic composer facts.
30+
31+
Directive scanning, import syntax parsing, transform, import substitution, and source rewrite are in-process implementation packages under `Milky2018/moon_wgsl_naga_oil/internal/`. MoonBit's `internal` visibility prevents packages outside the owning module from importing them while preserving local package boundaries and white-box invariant tests. Their seams are not external interfaces.
32+
33+
## Naga compatibility
34+
35+
`Milky2018/moon_wgsl_naga` owns Naga-shaped declaration provenance, import ordering events, arena scheduling, final temporary naming, and writer behavior.
36+
37+
Its central seam is conceptually:
38+
39+
```text
40+
write_naga_compatible_wgsl(
41+
module: WgslSemanticIr,
42+
context: NagaCompatibilityContext,
43+
options: NagaWriterOptions,
44+
) -> String
45+
```
46+
47+
`NagaCompatibilityContext` is opaque. Its construction types are Naga-owned compatibility facts, not WGSL IR records and not naga-oil graph implementation types. naga-oil maps its graph into this context. The Naga module derives its compatibility view internally; callers never mutate semantic IR to request compatibility behavior.
48+
49+
Trace and parity inspection belong to a diagnostics package used by repository tools. They are not methods on the normal writer or facade object.
50+
51+
## Moon WGSL Facade
52+
53+
`Milky2018/moon_wgsl` owns its user-facing `Composer` type instead of re-exporting the naga-oil implementation type. The target method interface is:
54+
55+
- `Composer::default`
56+
- `Composer::register_source`
57+
- `Composer::register_source_files`
58+
- `Composer::clear_sources`
59+
- `Composer::add_module`
60+
- `Composer::remove_module`
61+
- `Composer::compose`
62+
- `Composer::prepare`
63+
- `Composer::export`
64+
65+
`compose` is the single normal composition entry point. Runtime-valid versus strict compatibility output is selected through `ComposeOptions`, not through additional pipeline-stage methods. Filesystem scanning remains a separate adapter because it introduces I/O and is not required by the in-memory composition seam.
66+
67+
The facade owns its errors and maps lower-level failures. It exposes no before-IR, trace, parity, writer-plan, symbol-graph, source-edit, or internal-stage interface.
68+
69+
## Writer source ownership
70+
71+
Source symlinks are not an ownership mechanism. WGSL Core is the sole source owner for three neutral deep services: `WgslIrReachability` computes semantic root reachability, `WgslIrTypeInference` answers expression type queries, and `WgslIrTypeSpelling` formats types from caller-supplied final-name and global-expression lookups plus two explicit byte spelling choices. These interfaces are much smaller than their implementations and contain no Naga ordering, provenance, temporary naming, or compatibility view.
72+
73+
The Naga adapter consumes those services and owns all compatibility policy. It supplies names and byte choices but cannot mutate semantic IR through the service interfaces. The architecture manifest records each source owner, rejects retired Naga copies even under a new exact-copy path, and forbids all cross-module source symlinks.
74+
75+
## Migration and enforcement
76+
77+
This change is a synchronized breaking release. Compatibility aliases must not remain in WGSL Core merely to preserve old imports.
78+
79+
The architecture manifest records:
80+
81+
- exact package inventories
82+
- forbidden concept families by owning path
83+
- complete facade type method inventories, following re-exported types
84+
- source symlink ownership
85+
- issue-linked migration exceptions
86+
87+
An exception is valid only while its issue is unresolved. Once the violating code disappears, the exception becomes stale and must be removed. A closed issue with a remaining exception or violation fails the architecture gate.

0 commit comments

Comments
 (0)