From 98e14af249c91fdf2d9c1783511c13f5736b7314 Mon Sep 17 00:00:00 2001 From: Teakowa Date: Fri, 28 Aug 2026 18:12:26 +0800 Subject: [PATCH] feat(compiler): integrate Workshop backend slices Refs #37 --- Cargo.lock | 1 + compatibility/support-matrix.json | 34 +- crates/opy-compiler/Cargo.toml | 1 + crates/opy-compiler/src/lib.rs | 343 ++++++++++++++++++- crates/opy-rs/src/lib.rs | 3 + crates/opy-rs/src/support.rs | 5 +- crates/opy-rs/support-matrix.json | 34 +- docs/language-support/registries.md | 6 +- docs/language-support/syntax-and-projects.md | 6 +- docs/language-support/tooling-and-backend.md | 6 +- docs/opy/compatibility-baseline.md | 2 +- docs/opy/tooling-api.md | 4 +- docs/overpy-support/syntax-and-projects.md | 6 +- docs/overpy-support/tooling-and-backend.md | 4 +- 14 files changed, 398 insertions(+), 57 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 72830c2..ce2b9bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,7 @@ dependencies = [ name = "opy-compiler" version = "0.1.2" dependencies = [ + "opy-macro-js", "opy-rs", "serde_json", "workshop-rs", diff --git a/compatibility/support-matrix.json b/compatibility/support-matrix.json index 71cbb94..27929cf 100644 --- a/compatibility/support-matrix.json +++ b/compatibility/support-matrix.json @@ -9,9 +9,9 @@ "integrity": "sha512-oX17nauJcPTaKIrRFY/rD0Rl8atqFUVv9Hg2TKH+A68/fC8+ZO344Mkd1A/Y0oOVp1hr5tktMBjzMEDDnMEYUw==" }, "snapshot": { - "date": "2026-08-24", - "note": "Readiness baseline for #7 through the #28/#33 Draft PR series plus the bounded #35, #40, #46, and #47 integration slices. Source implementation-supported rows include the pinned OPY syntax, directives, preprocessing, macro statements, rule directives/model, JavaScript macros, and runtime hooks. Semantic-supported rows include declaration resolution, for-loop binders, modules, keyword arguments, the declared alias surface, and the OPY-owned manifest overlay for builtin/member/enum semantics. Canonical Workshop builtin/member/enum breadth and emission remain separate lowering-dependent rows; no Workshop catalog data is copied into opy-rs. The differential corpus currently reports 0 unexpected divergences and 0 inconclusive results; its exact match and known-gap counts are generated evidence, not a support claim. #!postCompileHook is parsed/validated/recorded by the source implementation; execution against final Workshop text is lowering-dependent (#8).", - "asOfCommit": "c5714a4028464f19f236f54d390e7b7237e434bd" + "date": "2026-08-29", + "note": "Readiness baseline through the bounded #37 compiler slice. Settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Optimizer controls remain non-blocking presentation differences; semantic replacement directives remain explicit gaps and fail at the compiler boundary until canonical lowering exists. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", + "asOfCommit": "9f7c62b1958a3804780a2de7d1bd55e2fb298533" }, "states": { "planned": "Declared surface, not yet implemented in opy-rs; evidence/provenance recorded for the implementing issue.", @@ -369,13 +369,13 @@ "id": "semantics/settings-emission", "name": "Workshop settings-section emission: key table, enum values, map/hero list elements", "category": "semantics", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "fixtures:synthetic/settings", "fixtures:real-world/6v6-adjustments/lobby/lobby.opy", "upstream:src/tests/customGameSettings.opy" ], - "notes": "The typed settings payload is source implementation-owned; the emission table and its domains are Workshop data owned by workshop-rs. Key-existence and leaf-kind settings validation is Workshop schema content and lowering-dependent (#8) — the core validates structure only. Emitted settings are deliberately not reparseable (round-trip boundary)." + "notes": "Settings lower directly into the neutral workshop-rs carrier; workshop-rs owns key/value validation, locale spellings, and emission. Emitted settings are deliberately not reparseable (round-trip boundary)." }, { "id": "preprocessing/include", @@ -428,7 +428,7 @@ "fixtures:synthetic/issue-29-directives", "upstream:src/data/opy/preprocessing.ts" ], - "notes": "#8/workshop-rs owns optimizer execution, replacement rewrites, generated translation helpers, and final Workshop output; opy-rs does not approximate them." + "notes": "Rule-prefix metadata is already reflected in HIR rule names. Optimizer controls remain non-blocking presentation differences. The compiler rejects replacement directives at their source anchor until a canonical lowering and corpus evidence exist; it does not silently drop or approximate semantic rewrites." }, { "id": "macros/definitions", @@ -463,20 +463,21 @@ "test:opy-rs-differential", "fixtures:real-world/overpy-meipocalypse" ], - "notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (root-relative script resolution, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); the source implementation never executes the hook, so no Workshop payload is fabricated. Structured script-* diagnostics carry script provenance. Catalog constants and post-compile-hook Workshop-output execution stay lowering-dependent (see hooks/post-compile-workshop, issue #8); `#!require` does not exist in the pinned reference." + "notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (root-relative script resolution, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); compiler execution against final Workshop text is tracked by hooks/post-compile-workshop. Structured script-* diagnostics carry script provenance. Catalog constants remain lowering-dependent; `#!require` does not exist in the pinned reference." }, { "id": "hooks/post-compile-workshop", "name": "Post-compile hook execution against the final Workshop text", "category": "macros", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "upstream:src/tests/postCompileHook.opy", "upstream:src/quickjs.ts", "test:opy-macro-js-hooks", - "test:opy-rs-macro-integration" + "test:opy-rs-macro-integration", + "test:opy-compiler" ], - "notes": "Issue #6 + #8. The runtime's hook ABI (content injection, console capture, result/error semantics, 2000 ms budget) is implemented and tested on synthetic content in opy-macro-js; the source implementation parses, validates, and records #!postCompileHook but never executes it. Real hook execution receives the final Workshop text from lowering and is inventory-only until the workshop-rs integration stage (#8); the source implementation never fabricates a Workshop payload." + "notes": "The compiler runs the bounded opy-macro-js hook only after successful workshop-rs emission, passes the exact emitted Workshop text, and exposes the string completion as final output; hook failures retain both the directive anchor and structured script provenance." }, { "id": "directives/rule-annotations", @@ -537,12 +538,13 @@ "id": "translations/locale-emission", "name": "Localized Workshop output (--language flag, localizedStrings data)", "category": "translations", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "upstream:src/data/localizedStrings.ts", - "oracle:CLI --language en-US invocation recorded in compatibility/run_oracle.py" + "oracle:CLI --language en-US invocation recorded in compatibility/run_oracle.py", + "test:opy-compiler" ], - "notes": "Locale data is Workshop-owned content (workshop-rs); the oracle harness pins en-US as the evidence language." + "notes": "Compiler locale selection delegates to workshop-rs catalog coverage and rejects undeclared locales explicitly; missing per-identity mappings remain fail-explicit." }, { "id": "optimization/controls", @@ -565,7 +567,7 @@ "fixtures:synthetic/issue-29-directives", "upstream:src/data/opy/preprocessing.ts" ], - "notes": "#8/workshop-rs owns optimizer execution and any Workshop element-count or replacement consequences; output-text identity is not required." + "notes": "Optimizer transformations and replacement rewrites remain lowering-dependent. Optimizer controls remain non-blocking; replacement directives fail explicitly rather than silently changing Workshop output." }, { "id": "optimization/emission-form", @@ -749,8 +751,8 @@ "planned": 0, "source-supported": 23, "semantic-supported": 13, - "lowering-dependent": 13, - "end-to-end-supported": 7 + "lowering-dependent": 10, + "end-to-end-supported": 10 }, "byCategory": { "syntax": 14, diff --git a/crates/opy-compiler/Cargo.toml b/crates/opy-compiler/Cargo.toml index 038d674..4103ed6 100644 --- a/crates/opy-compiler/Cargo.toml +++ b/crates/opy-compiler/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] opy-rs.workspace = true +opy-macro-js = { path = "../opy-macro-js", version = "0.1.1" } workshop-rs.workspace = true [dev-dependencies] diff --git a/crates/opy-compiler/src/lib.rs b/crates/opy-compiler/src/lib.rs index 4fec89c..bf67a6a 100644 --- a/crates/opy-compiler/src/lib.rs +++ b/crates/opy-compiler/src/lib.rs @@ -25,6 +25,16 @@ pub struct IntegrationDiagnostic { pub code: String, pub message: String, pub span: Option, + pub script: Option>, +} + +/// Script-runtime provenance retained alongside the OPY directive anchor. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScriptDiagnostic { + pub source_name: Option, + pub line: Option, + pub column: Option, + pub stack: Option, } impl IntegrationDiagnostic { @@ -33,6 +43,7 @@ impl IntegrationDiagnostic { code: code.into(), message: message.into(), span, + script: None, } } } @@ -49,6 +60,28 @@ impl IntegrationError { diagnostic: IntegrationDiagnostic::new(code, message, span), } } + + fn post_compile_hook(error: opy_macro_js::MacroError, span: Option) -> Self { + let message = error.to_string(); + let script = match error { + opy_macro_js::MacroError::Script(error) => Some(Box::new(ScriptDiagnostic { + source_name: error.source_name, + line: error.line, + column: error.column, + stack: error.stack, + })), + opy_macro_js::MacroError::InvalidResult { .. } + | opy_macro_js::MacroError::Internal(_) => None, + }; + Self { + diagnostic: IntegrationDiagnostic { + code: "post-compile-hook".to_string(), + message, + span, + script, + }, + } + } } impl std::fmt::Display for IntegrationError { @@ -186,6 +219,23 @@ impl Compiler { /// Lower a resolved OPY HIR program into canonical WIR, validate it /// against the canonical catalog, and emit deterministic en-US Workshop. pub fn compile_hir(&self, hir: &hir::Program) -> Result { + self.compile_hir_with_locale(hir, &Locale::new("en-US")) + } + + /// Lower and emit using a locale declared by the canonical catalog. + pub fn compile_hir_with_locale( + &self, + hir: &hir::Program, + locale: &Locale, + ) -> Result { + if !self.catalog.supports(locale) { + return Err(IntegrationError::new( + "locale-unsupported", + format!("workshop catalog does not declare locale '{locale}'"), + None, + )); + } + reject_unlowered_directives(hir)?; let mut lowering = Lowering::new(self, hir)?; lowering.copy_files()?; lowering.lower_declarations()?; @@ -205,19 +255,96 @@ impl Compiler { }, )?; let emitted = - workshop_rs::emitter::emit(&lowering.wir, &self.catalog, &Locale::new("en-US")) - .map_err(|error| { - let span = workshop_error_span(&error) - .and_then(|span| lowering.hir_span_from_workshop(span)); - IntegrationError::new("workshop-emission", error.to_string(), span) - })?; + workshop_rs::emitter::emit(&lowering.wir, &self.catalog, locale).map_err(|error| { + let span = workshop_error_span(&error) + .and_then(|span| lowering.hir_span_from_workshop(span)); + IntegrationError::new("workshop-emission", error.to_string(), span) + })?; Ok(CompilationArtifact { wir: lowering.wir, + final_output: emitted.clone(), emitted, catalog_identity: self.catalog.identity(), + hook_console_output: Vec::new(), }) } + + /// Compile source and run a declared post-compile hook only after the + /// final Workshop emission has succeeded. + pub fn compile_source( + &self, + source: &str, + main_path: &str, + root: &std::path::Path, + locale: &Locale, + ) -> Result { + let outcome = opy_rs::compile_with_overlay_outcome( + source, + main_path, + root, + &std::collections::BTreeMap::new(), + ); + let hir = outcome.hir.ok_or_else(|| { + let error = outcome + .error + .expect("failed frontend compile has diagnostic"); + IntegrationError::new( + error.code, + error.message, + error.span.map(hir_span_from_diag), + ) + })?; + let hook = outcome.post_compile_hook; + let mut artifact = self.compile_hir_with_locale(&hir, locale)?; + if let Some(hook) = hook { + let runtime = opy_macro_js::MacroRuntime::new(opy_macro_js::Limits::default()); + let result = runtime + .run_hook(&hook.source, &artifact.emitted, &hook.script) + .map_err(|error| { + IntegrationError::post_compile_hook(error, hook.span.map(hir_span_from_diag)) + })?; + artifact.final_output = result.text; + artifact.hook_console_output = result.console_output; + } + Ok(artifact) + } +} + +fn reject_unlowered_directives(hir: &hir::Program) -> Result<(), IntegrationError> { + if let Some(replacement) = hir.preprocessing.replacements.first() { + let span = hir + .preprocessing + .directives + .iter() + .find(|directive| directive.name.starts_with("replace")) + .and_then(|directive| directive.span) + .or(replacement.span); + return Err(IntegrationError::new( + "backend-directive-unsupported", + format!( + "replacement directive '{}' has no canonical workshop-rs lowering", + replacement.value + ), + span, + )); + } + if let Some(replacement) = hir + .preprocessing + .directives + .iter() + .find(|directive| directive.name.starts_with("replace")) + { + return Err(IntegrationError::new( + "backend-directive-unsupported", + format!( + "replacement directive '{}' has no canonical workshop-rs lowering", + replacement.name + ), + replacement.span, + )); + } + Ok(()) } /// A validated WIR program and its emitted Workshop artifact. @@ -225,6 +352,8 @@ pub struct CompilationArtifact { pub wir: Program, pub emitted: String, pub catalog_identity: CatalogIdentity, + pub final_output: String, + pub hook_console_output: Vec, } fn convert_settings(settings: opy_rs::hir::Settings) -> workshop_rs::settings::Settings { @@ -2620,6 +2749,20 @@ fn modify_catalog_name_from_str(op: &str) -> Option<&'static str> { } } +fn hir_span_from_diag(span: opy_rs::diag::Span) -> HirSpan { + HirSpan { + file: span.file, + start: hir::Position { + line: span.start.line, + col: span.start.col, + }, + end: hir::Position { + line: span.end.line, + col: span.end.col, + }, + } +} + fn workshop_error_span(error: &workshop_rs::WorkshopError) -> Option { match error { workshop_rs::WorkshopError::Unknown { span, .. } @@ -2635,7 +2778,7 @@ mod tests { use super::{Compiler, WORKSHOP_RS_VERSION, cross_check_manifest}; use opy_rs::manifest::Manifest; use std::path::Path; - use workshop_rs::catalog::Catalog; + use workshop_rs::catalog::{Catalog, Locale}; #[test] fn public_contract_is_pinned_and_manifest_links_are_checked() { @@ -3431,4 +3574,190 @@ rule "main": .contains("Set Player Variable(Event Player, q,") ); } + + #[test] + fn settings_lower_through_workshop_owned_emission() { + let compiler = Compiler::new().unwrap(); + let fixture = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("../../compatibility/fixtures/synthetic/settings"); + let source = std::fs::read_to_string(fixture.join("source.opy")).unwrap(); + let hir = opy_rs::compile(&source, "source.opy", &fixture).unwrap(); + let artifact = compiler.compile_hir(&hir).unwrap(); + let oracle: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(fixture.join("oracle.json")).unwrap()) + .unwrap(); + let expected = oracle["compile"]["workshop"] + .as_str() + .unwrap() + .split("\n\nrule") + .next() + .unwrap(); + let actual = artifact.emitted.split("\n\nrule").next().unwrap(); + assert_eq!( + normalize_workshop_structural_whitespace(actual), + normalize_workshop_structural_whitespace(expected) + ); + } + + #[test] + fn unsupported_locale_has_no_fabricated_source_span() { + let compiler = Compiler::new().unwrap(); + let hir = opy_rs::compile( + "#!translations en\nrule \"r\":\n @Event global\n pass\n", + "locale.opy", + Path::new("."), + ) + .unwrap(); + let error = match compiler.compile_hir_with_locale(&hir, &Locale::new("xx-XX")) { + Ok(_) => panic!("unsupported locale unexpectedly compiled"), + Err(error) => error, + }; + assert_eq!(error.diagnostic.code, "locale-unsupported"); + assert_eq!(error.diagnostic.span, None); + } + + #[test] + fn locale_selection_emits_catalog_localized_workshop() { + let compiler = Compiler::new().unwrap(); + let hir = opy_rs::compile( + "rule \"locale\":\n @Event global\n disableInspector()\n", + "locale.opy", + Path::new("."), + ) + .unwrap(); + let artifact = compiler + .compile_hir_with_locale(&hir, &Locale::new("zh-CN")) + .unwrap(); + assert!(artifact.emitted.contains("规则 (\"locale\")")); + assert!(artifact.emitted.contains("禁用查看器录制")); + } + + #[test] + fn unsupported_backend_directives_fail_at_their_source_anchor() { + let compiler = Compiler::new().unwrap(); + let hir = opy_rs::compile( + "#!replace0ByCapturePercentage\nrule \"r\":\n @Event global\n pass\n", + "directives.opy", + Path::new("."), + ) + .unwrap(); + let error = match compiler.compile_hir(&hir) { + Ok(_) => panic!("backend directive unexpectedly compiled"), + Err(error) => error, + }; + assert_eq!(error.diagnostic.code, "backend-directive-unsupported"); + assert_eq!(error.diagnostic.span.unwrap().start.line, 1); + } + + #[test] + fn optimizer_directives_remain_non_blocking_presentation_controls() { + let compiler = Compiler::new().unwrap(); + let hir = opy_rs::compile( + "#!disableOptimizations\nrule \"r\":\n @Event global\n pass\n", + "optimization.opy", + Path::new("."), + ) + .unwrap(); + compiler.compile_hir(&hir).unwrap(); + } + + #[test] + fn replacement_directive_records_are_checked_even_if_final_state_is_restored() { + let compiler = Compiler::new().unwrap(); + let mut hir = opy_rs::compile( + "#!replace0ByCapturePercentage\nrule \"r\":\n @Event global\n pass\n", + "directives.opy", + Path::new("."), + ) + .unwrap(); + hir.preprocessing.replacements.clear(); + let error = match compiler.compile_hir(&hir) { + Ok(_) => panic!("replacement directive unexpectedly compiled"), + Err(error) => error, + }; + assert_eq!(error.diagnostic.code, "backend-directive-unsupported"); + assert_eq!(error.diagnostic.span.unwrap().start.line, 1); + } + + #[test] + fn active_replacement_state_is_checked_without_directive_history() { + let compiler = Compiler::new().unwrap(); + let mut hir = opy_rs::compile( + "#!replace0ByCapturePercentage\nrule \"r\":\n @Event global\n pass\n", + "directives.opy", + Path::new("."), + ) + .unwrap(); + hir.preprocessing.directives.clear(); + hir.preprocessing.replacements[0].span = None; + let error = match compiler.compile_hir(&hir) { + Ok(_) => panic!("active replacement state unexpectedly compiled"), + Err(error) => error, + }; + assert_eq!(error.diagnostic.code, "backend-directive-unsupported"); + assert_eq!(error.diagnostic.span, None); + } + + #[test] + fn post_compile_hook_receives_exact_emitted_workshop() { + let compiler = Compiler::new().unwrap(); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../opy-rs/tests/fixtures/macros"); + let source = "#!postCompileHook \"hook.js\"\n\nrule \"setup\":\n pass\n"; + let artifact = compiler + .compile_source(source, "hook.opy", &root, &Locale::new("en-US")) + .unwrap(); + assert!(artifact.emitted.contains("rule (\"setup\")")); + assert!(artifact.final_output.contains("rule (\"transformed\")")); + assert_ne!(artifact.final_output, artifact.emitted); + } + + #[test] + fn post_compile_hook_failure_keeps_script_provenance_and_directive_anchor() { + let compiler = Compiler::new().unwrap(); + let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../opy-rs/tests/fixtures/macros"); + let source = "#!postCompileHook \"hook-boom.js\"\n\nrule \"setup\":\n pass\n"; + let error = match compiler.compile_source(source, "hook.opy", &root, &Locale::new("en-US")) + { + Ok(_) => panic!("failing post-compile hook unexpectedly compiled"), + Err(error) => error, + }; + assert_eq!(error.diagnostic.code, "post-compile-hook"); + assert_eq!(error.diagnostic.span.unwrap().start.line, 1); + let script = error.diagnostic.script.unwrap(); + assert_eq!(script.source_name.as_deref(), Some("hook-boom.js")); + assert_eq!(script.line, Some(1)); + assert!(script.stack.unwrap().contains("hook-boom.js:1")); + } + + fn normalize_workshop_structural_whitespace(text: &str) -> String { + let mut normalized = String::with_capacity(text.len()); + let mut quote = None; + let mut escaped = false; + for character in text.chars() { + if let Some(delimiter) = quote { + normalized.push(character); + if escaped { + escaped = false; + } else if character == '\\' { + escaped = true; + } else if character == delimiter { + quote = None; + } + } else if matches!(character, '\"' | '\'') { + quote = Some(character); + normalized.push(character); + } else if !character.is_whitespace() { + normalized.push(character); + } + } + normalized + } + + #[test] + fn settings_whitespace_normalization_preserves_quoted_values() { + assert_ne!( + normalize_workshop_structural_whitespace("Description: \"a b\""), + normalize_workshop_structural_whitespace("Description: \"ab\"") + ); + } } diff --git a/crates/opy-rs/src/lib.rs b/crates/opy-rs/src/lib.rs index b295791..4428b7e 100644 --- a/crates/opy-rs/src/lib.rs +++ b/crates/opy-rs/src/lib.rs @@ -147,6 +147,8 @@ pub struct CompileOutcome { pub struct PostCompileHookRecord { /// The script path as declared (root-relative). pub script: String, + /// The resolved script source, retained for the backend hook ABI. + pub source: String, /// The directive's source span, when known. pub span: Option, } @@ -180,6 +182,7 @@ pub fn compile_with_overlay_outcome( // `PostCompileHookRecord`). let post_compile_hook = outcome.post_compile_hook.map(|hook| PostCompileHookRecord { script: hook.path, + source: hook.source, span: Some(hook.span), }); CompileOutcome { diff --git a/crates/opy-rs/src/support.rs b/crates/opy-rs/src/support.rs index 199ad09..774e288 100644 --- a/crates/opy-rs/src/support.rs +++ b/crates/opy-rs/src/support.rs @@ -217,7 +217,10 @@ mod tests { assert_eq!(syntax.len(), 14); assert!(syntax.iter().all(|feature| feature.category == "syntax")); let lowering = matrix.features_by_state("lowering-dependent"); - assert_eq!(lowering.len(), 13); + assert_eq!( + lowering.len(), + matrix.summary().by_state["lowering-dependent"] as usize + ); assert!( lowering .iter() diff --git a/crates/opy-rs/support-matrix.json b/crates/opy-rs/support-matrix.json index 71cbb94..27929cf 100644 --- a/crates/opy-rs/support-matrix.json +++ b/crates/opy-rs/support-matrix.json @@ -9,9 +9,9 @@ "integrity": "sha512-oX17nauJcPTaKIrRFY/rD0Rl8atqFUVv9Hg2TKH+A68/fC8+ZO344Mkd1A/Y0oOVp1hr5tktMBjzMEDDnMEYUw==" }, "snapshot": { - "date": "2026-08-24", - "note": "Readiness baseline for #7 through the #28/#33 Draft PR series plus the bounded #35, #40, #46, and #47 integration slices. Source implementation-supported rows include the pinned OPY syntax, directives, preprocessing, macro statements, rule directives/model, JavaScript macros, and runtime hooks. Semantic-supported rows include declaration resolution, for-loop binders, modules, keyword arguments, the declared alias surface, and the OPY-owned manifest overlay for builtin/member/enum semantics. Canonical Workshop builtin/member/enum breadth and emission remain separate lowering-dependent rows; no Workshop catalog data is copied into opy-rs. The differential corpus currently reports 0 unexpected divergences and 0 inconclusive results; its exact match and known-gap counts are generated evidence, not a support claim. #!postCompileHook is parsed/validated/recorded by the source implementation; execution against final Workshop text is lowering-dependent (#8).", - "asOfCommit": "c5714a4028464f19f236f54d390e7b7237e434bd" + "date": "2026-08-29", + "note": "Readiness baseline through the bounded #37 compiler slice. Settings, catalog-declared locale emission, and post-compile hooks have explicit OPY -> HIR -> WIR -> validated Workshop emission evidence. Optimizer controls remain non-blocking presentation differences; semantic replacement directives remain explicit gaps and fail at the compiler boundary until canonical lowering exists. Canonical Workshop catalog data, settings tables, locale spellings, WIR, validation, and emission remain owned by workshop-rs.", + "asOfCommit": "9f7c62b1958a3804780a2de7d1bd55e2fb298533" }, "states": { "planned": "Declared surface, not yet implemented in opy-rs; evidence/provenance recorded for the implementing issue.", @@ -369,13 +369,13 @@ "id": "semantics/settings-emission", "name": "Workshop settings-section emission: key table, enum values, map/hero list elements", "category": "semantics", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "fixtures:synthetic/settings", "fixtures:real-world/6v6-adjustments/lobby/lobby.opy", "upstream:src/tests/customGameSettings.opy" ], - "notes": "The typed settings payload is source implementation-owned; the emission table and its domains are Workshop data owned by workshop-rs. Key-existence and leaf-kind settings validation is Workshop schema content and lowering-dependent (#8) — the core validates structure only. Emitted settings are deliberately not reparseable (round-trip boundary)." + "notes": "Settings lower directly into the neutral workshop-rs carrier; workshop-rs owns key/value validation, locale spellings, and emission. Emitted settings are deliberately not reparseable (round-trip boundary)." }, { "id": "preprocessing/include", @@ -428,7 +428,7 @@ "fixtures:synthetic/issue-29-directives", "upstream:src/data/opy/preprocessing.ts" ], - "notes": "#8/workshop-rs owns optimizer execution, replacement rewrites, generated translation helpers, and final Workshop output; opy-rs does not approximate them." + "notes": "Rule-prefix metadata is already reflected in HIR rule names. Optimizer controls remain non-blocking presentation differences. The compiler rejects replacement directives at their source anchor until a canonical lowering and corpus evidence exist; it does not silently drop or approximate semantic rewrites." }, { "id": "macros/definitions", @@ -463,20 +463,21 @@ "test:opy-rs-differential", "fixtures:real-world/overpy-meipocalypse" ], - "notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (root-relative script resolution, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); the source implementation never executes the hook, so no Workshop payload is fabricated. Structured script-* diagnostics carry script provenance. Catalog constants and post-compile-hook Workshop-output execution stay lowering-dependent (see hooks/post-compile-workshop, issue #8); `#!require` does not exist in the pinned reference." + "notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (root-relative script resolution, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); compiler execution against final Workshop text is tracked by hooks/post-compile-workshop. Structured script-* diagnostics carry script provenance. Catalog constants remain lowering-dependent; `#!require` does not exist in the pinned reference." }, { "id": "hooks/post-compile-workshop", "name": "Post-compile hook execution against the final Workshop text", "category": "macros", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "upstream:src/tests/postCompileHook.opy", "upstream:src/quickjs.ts", "test:opy-macro-js-hooks", - "test:opy-rs-macro-integration" + "test:opy-rs-macro-integration", + "test:opy-compiler" ], - "notes": "Issue #6 + #8. The runtime's hook ABI (content injection, console capture, result/error semantics, 2000 ms budget) is implemented and tested on synthetic content in opy-macro-js; the source implementation parses, validates, and records #!postCompileHook but never executes it. Real hook execution receives the final Workshop text from lowering and is inventory-only until the workshop-rs integration stage (#8); the source implementation never fabricates a Workshop payload." + "notes": "The compiler runs the bounded opy-macro-js hook only after successful workshop-rs emission, passes the exact emitted Workshop text, and exposes the string completion as final output; hook failures retain both the directive anchor and structured script provenance." }, { "id": "directives/rule-annotations", @@ -537,12 +538,13 @@ "id": "translations/locale-emission", "name": "Localized Workshop output (--language flag, localizedStrings data)", "category": "translations", - "state": "lowering-dependent", + "state": "end-to-end-supported", "evidence": [ "upstream:src/data/localizedStrings.ts", - "oracle:CLI --language en-US invocation recorded in compatibility/run_oracle.py" + "oracle:CLI --language en-US invocation recorded in compatibility/run_oracle.py", + "test:opy-compiler" ], - "notes": "Locale data is Workshop-owned content (workshop-rs); the oracle harness pins en-US as the evidence language." + "notes": "Compiler locale selection delegates to workshop-rs catalog coverage and rejects undeclared locales explicitly; missing per-identity mappings remain fail-explicit." }, { "id": "optimization/controls", @@ -565,7 +567,7 @@ "fixtures:synthetic/issue-29-directives", "upstream:src/data/opy/preprocessing.ts" ], - "notes": "#8/workshop-rs owns optimizer execution and any Workshop element-count or replacement consequences; output-text identity is not required." + "notes": "Optimizer transformations and replacement rewrites remain lowering-dependent. Optimizer controls remain non-blocking; replacement directives fail explicitly rather than silently changing Workshop output." }, { "id": "optimization/emission-form", @@ -749,8 +751,8 @@ "planned": 0, "source-supported": 23, "semantic-supported": 13, - "lowering-dependent": 13, - "end-to-end-supported": 7 + "lowering-dependent": 10, + "end-to-end-supported": 10 }, "byCategory": { "syntax": 14, diff --git a/docs/language-support/registries.md b/docs/language-support/registries.md index 224a033..c949779 100644 --- a/docs/language-support/registries.md +++ b/docs/language-support/registries.md @@ -42,7 +42,7 @@ Reference: `889d9749d1def17f146548cbddb94ea1ab015847`. | `lambda` | ✅ Supported | | `macro` | ✅ Supported | | `self` | 🚧 Coming soon | -| `settings` | 🚧 Coming soon | +| `settings` | ✅ Bounded compiler slice | | `main` | 🚧 Coming soon | | `gamemodes` | 🚧 Coming soon | | `heroes` | 🚧 Coming soon | @@ -83,8 +83,8 @@ Reference: `889d9749d1def17f146548cbddb94ea1ab015847`. | `keepUnusedTranslations` | 🚧 Coming soon | | `disableTranslationSourceLines` | 🚧 Coming soon | | `writeToOutputFile` | 🚧 Coming soon | -| `postCompileHook` | 🚧 Coming soon | -| `rulePrefix`, `rulePrefixTemplate` | 🚧 Coming soon | +| `postCompileHook` | ✅ Bounded compiler slice | +| `rulePrefix`, `rulePrefixTemplate` | ✅ Supported | ## Annotations (`src/data/opy/annotations.ts`) diff --git a/docs/language-support/syntax-and-projects.md b/docs/language-support/syntax-and-projects.md index dc4f9b3..e866dcc 100644 --- a/docs/language-support/syntax-and-projects.md +++ b/docs/language-support/syntax-and-projects.md @@ -65,8 +65,8 @@ Source: pinned OverPy `9.7.10`, content commit | Feature | Status | Notes | | --- | --- | --- | -| `settings { ... }` custom-game-settings block | 🚧 Coming soon | Typed source representation exists; complete behavior is incomplete. | -| Schema keys, enum values and map/hero list settings | 🚧 Coming soon | Audited against upstream schema/data. | +| `settings { ... }` custom-game-settings block | ✅ Bounded compiler slice | Lowers through Workshop-owned validation and canonical emission. | +| Schema keys, enum values and map/hero list settings | ✅ Bounded compiler slice | The compiler delegates the supported slice to Workshop-owned settings tables. | | `#!translations` and `.po` translation sources | 🚧 Coming soon | Declaration and output lifecycle are separate. | | `_`, `__`, `___` translation functions | 🚧 Coming soon | One- and two-argument modes differ. | -| Localized output language selection | 🚧 Coming soon | Upstream supports all in-game languages. | +| Localized output language selection | ✅ Bounded compiler slice | The compiler accepts catalog-declared locales and rejects undeclared locales explicitly. | diff --git a/docs/language-support/tooling-and-backend.md b/docs/language-support/tooling-and-backend.md index 531f11b..3e869d6 100644 --- a/docs/language-support/tooling-and-backend.md +++ b/docs/language-support/tooling-and-backend.md @@ -14,11 +14,11 @@ upstream README, `overpy.d.ts`, `cli.js`, compiler/decompiler sources, | `#!mainFile`, `#!include`, `#!excludeVariablesInCompilation` | ✅ Supported | Selection and output filtering have separate effects. | | Optimization controls (`#!enableOptimizations`, `#!disableOptimizations`, `#!optimize*`) | 🚧 Coming soon | Recognition is not backend-effect support. | | Replacement directives (`#!replace0By*`, team/string replacements) | 🚧 Coming soon | Each replacement target has its own output contract. | -| `#!rulePrefix` and `#!rulePrefixTemplate` | 🚧 Coming soon | Prefix text and placeholders are separate. | +| `#!rulePrefix` and `#!rulePrefixTemplate` | ✅ Supported | Source preprocessing applies the resulting rule names before compiler lowering. | | `#!extension` and extension-point accounting | 🚧 Coming soon | Output metadata is part of the contract. | | `macro name(params)` function/constant macros | ✅ Supported | Defaults, keywords and member macros differ. | | `__script__` JavaScript macros | 🚧 Coming soon | QuickJS return ABI and limits are observable. | -| `#!postCompileHook` | 🚧 Coming soon | Parsing is not execution against final Workshop text. | +| `#!postCompileHook` | ✅ Bounded compiler slice | Runs only after final Workshop emission; failures keep directive and script provenance. | ## Compilation, CLI and API @@ -28,7 +28,7 @@ upstream README, `overpy.d.ts`, `cli.js`, compiler/decompiler sources, | CLI compile/check invocation and structured diagnostics | ✅ Supported | Exit behavior and source attribution are contractual. | | Upstream JS `compile(content, language, rootPath, mainFileName)` API | 🚧 Coming soon | API shape audited; Rust parity is incomplete. | | Compile metadata: variables, subroutines, warnings, translations, element count | 🚧 Coming soon | Fields have independent completeness requirements. | -| Localized Workshop text and custom settings emission | 🚧 Coming soon | Canonical Workshop semantics remain in `workshop-rs`. | +| Localized Workshop text and custom settings emission | ✅ Bounded compiler slice | The compiler delegates validation and emission to `workshop-rs`; undeclared locales fail explicitly. | | Observable optimization/replacement effects | 🚧 Coming soon | Formatting is not a target unless observable. | ## Decompilation and round trips diff --git a/docs/opy/compatibility-baseline.md b/docs/opy/compatibility-baseline.md index 5ee93ee..cca15dd 100644 --- a/docs/opy/compatibility-baseline.md +++ b/docs/opy/compatibility-baseline.md @@ -69,7 +69,7 @@ rejected/documented-absent dimension, `—` an inapplicable dimension, and | 4 | **Rule directives & annotations**: `@Event`, `@Condition`, bare `@Team`/`@Slot`, rule name, event defaults (`global`, `all` team/player) | `baseline-supported` (bare forms) | ✅ | ✅ | ✅ (integration) | ✅ | ✅ | | 4a | `@Team`/`@Slot` with arguments, `@Name`, `@Hero`, `@Disabled`, `@Delimiter`, `@NewPage`, `@SuppressWarnings` | `baseline-supported` for source implementation state; Workshop domain/UI effects remain lowering-dependent | ✅ | ✅ | partial | ✅ | ✅ oracle probes | | 5 | **Preprocessing/include/macro**: `#!include`, `#!define` (object- and function-like), `#!undef`, include cycle detection | `baseline-supported` | ✅ | ✅ | ✅ (integration) | ✅ | ✅ | -| 5a | `#!mainFile`, `#!allowMacroRedeclaration`, `#!optimize*`/`#!replace0By*` family, `#!translations`, `#!rulePrefix*`, `__script__` JS hooks | `baseline-supported` for source implementation state; optimizer/locale/hook effects remain lowering-dependent | ✅ | ✅ | partial | ✅ | ✅ oracle probes | +| 5a | `#!mainFile`, `#!allowMacroRedeclaration`, `#!optimize*`/`#!replace0By*` family, `#!translations`, `#!rulePrefix*`, `__script__` JS hooks | `baseline-supported` for source implementation state; rule-prefix metadata, catalog locale emission, and post-compile hooks have bounded compiler evidence, while optimizer/replacement effects remain explicit gaps | ✅ | ✅ | partial | ✅ | ✅ oracle probes | | 6 | **OPY builtin actions & values (generic)**: manifest identities, signatures, aliases, and call semantics | `baseline-supported` for the probe-validated manifest overlay; canonical Workshop existence/content/emission is `lowering-dependent` | ✅ | ✅ | partial (integration) | ✅ | ✅ probes | | 6a | **Canonical Workshop builtin action/value catalog**: full catalog existence, content, and emission | `lowering-dependent` (`workshop-rs`, #8) | — | — | ❌ (integration) | — | ✅ inventory/oracle evidence | | 7 | **OPY receiver/member semantics**: receiver categories, explicit-argument signatures, variable receivers | `baseline-supported` for the manifest-declared OPY overlay; canonical member existence/content/emission is `lowering-dependent` | ✅ | ✅ | partial (integration) | ✅ | ✅ probes | diff --git a/docs/opy/tooling-api.md b/docs/opy/tooling-api.md index 24e33e8..6cbfa49 100644 --- a/docs/opy/tooling-api.md +++ b/docs/opy/tooling-api.md @@ -37,8 +37,8 @@ pub struct CheckOutcome { the file registry to `(file id, path, line/col)`. * `PostCompileHook`: the declared `#!postCompileHook` script (root-relative path plus directive span), present only when the source declared one and - the project checked clean. It is a declaration record only — the source implementation - never executes the hook (execution is lowering-dependent, issue #8). + the project checked clean. It is a declaration record only; the compiler + executes it after successful final Workshop emission. `SemanticModel` wraps the resolved program and answers queries: diff --git a/docs/overpy-support/syntax-and-projects.md b/docs/overpy-support/syntax-and-projects.md index dc4f9b3..1b9f523 100644 --- a/docs/overpy-support/syntax-and-projects.md +++ b/docs/overpy-support/syntax-and-projects.md @@ -65,8 +65,8 @@ Source: pinned OverPy `9.7.10`, content commit | Feature | Status | Notes | | --- | --- | --- | -| `settings { ... }` custom-game-settings block | 🚧 Coming soon | Typed source representation exists; complete behavior is incomplete. | -| Schema keys, enum values and map/hero list settings | 🚧 Coming soon | Audited against upstream schema/data. | +| `settings { ... }` custom-game-settings block | ✅ Supported | Lowered through the canonical `workshop-rs` settings carrier and emitter. | +| Schema keys, enum values and map/hero list settings | ✅ Supported (bounded) | Validation and spellings come from the Workshop-owned catalog; unsupported keys remain explicit failures. | | `#!translations` and `.po` translation sources | 🚧 Coming soon | Declaration and output lifecycle are separate. | | `_`, `__`, `___` translation functions | 🚧 Coming soon | One- and two-argument modes differ. | -| Localized output language selection | 🚧 Coming soon | Upstream supports all in-game languages. | +| Localized output language selection | ✅ Supported (catalog-declared) | Undeclared locales and missing mappings fail explicitly; no guessed fallback. | diff --git a/docs/overpy-support/tooling-and-backend.md b/docs/overpy-support/tooling-and-backend.md index 531f11b..c2e6404 100644 --- a/docs/overpy-support/tooling-and-backend.md +++ b/docs/overpy-support/tooling-and-backend.md @@ -18,7 +18,7 @@ upstream README, `overpy.d.ts`, `cli.js`, compiler/decompiler sources, | `#!extension` and extension-point accounting | 🚧 Coming soon | Output metadata is part of the contract. | | `macro name(params)` function/constant macros | ✅ Supported | Defaults, keywords and member macros differ. | | `__script__` JavaScript macros | 🚧 Coming soon | QuickJS return ABI and limits are observable. | -| `#!postCompileHook` | 🚧 Coming soon | Parsing is not execution against final Workshop text. | +| `#!postCompileHook` | ✅ Supported (compiler) | Runs after successful canonical Workshop emission against the exact emitted text; the string result is final output. | ## Compilation, CLI and API @@ -28,7 +28,7 @@ upstream README, `overpy.d.ts`, `cli.js`, compiler/decompiler sources, | CLI compile/check invocation and structured diagnostics | ✅ Supported | Exit behavior and source attribution are contractual. | | Upstream JS `compile(content, language, rootPath, mainFileName)` API | 🚧 Coming soon | API shape audited; Rust parity is incomplete. | | Compile metadata: variables, subroutines, warnings, translations, element count | 🚧 Coming soon | Fields have independent completeness requirements. | -| Localized Workshop text and custom settings emission | 🚧 Coming soon | Canonical Workshop semantics remain in `workshop-rs`. | +| Localized Workshop text and custom settings emission | ✅ Supported (catalog-declared) | Canonical settings and locale semantics remain in `workshop-rs`. | | Observable optimization/replacement effects | 🚧 Coming soon | Formatting is not a target unless observable. | ## Decompilation and round trips