Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compatibility/compiler-expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -427,9 +427,9 @@
"provenance:real-world/6v6-adjustments/fixture.json"
],
"owner": "opy-rs#88",
"note": "The full real-world project accepts included-file #!mainFile directives and reaches the next unsupported #!defineMember frontend boundary.",
"note": "The full real-world project now resolves included-file #!mainFile and #!defineMember directives; it remains outside the compiler baseline at the existing included settings-placement boundary.",
"failureClass": "frontend",
"diagnosticCode": "unsupported-directive"
"diagnosticCode": "settings-placement"
},
{
"fixture": "real-world/overpy-broken-weapons",
Expand Down Expand Up @@ -593,9 +593,9 @@
"provenance:real-world/ow1-emulator/fixture.json"
],
"owner": "opy-rs#88",
"note": "The full real-world project accepts included-file #!mainFile directives and reaches the next unsupported #!defineMember frontend boundary.",
"note": "The full real-world project now resolves included-file #!mainFile and #!defineMember directives; it remains outside the compiler baseline at the next lexer line-continuation boundary.",
"failureClass": "frontend",
"diagnosticCode": "unsupported-directive"
"diagnosticCode": "lex-error"
},
{
"fixture": "synthetic/chase-condition-agentlab",
Expand Down
4 changes: 2 additions & 2 deletions compatibility/differential-expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@
{"fixture": "real-world/overpy-parabola", "nativeStatus": "failure", "classification": "known-gap", "ruleNames": false, "evidence": ["oracle:real-world/overpy-parabola/oracle.json", "regression:real-world/overpy-parabola/fixture.json"], "note": "The numeric enum member regression now resolves; the full project reaches the next multiline parenthesized expression gap at parabola.opy:50."},
{"fixture": "real-world/overpy-meipocalypse", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/overpy-meipocalypse/oracle.json", "provenance:real-world/overpy-meipocalypse/fixture.json"], "note": "The reference rejects the missing script hook; native rejection is retained with a different earlier diagnostic."},
{"fixture": "real-world/overpy-zencopter", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/overpy-zencopter/oracle.json", "provenance:real-world/overpy-zencopter/fixture.json"], "note": "Both sides reject the pinned upstream example; native diagnostic wording is not used as semantic evidence."},
{"fixture": "real-world/ow1-emulator", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/ow1-emulator/oracle.json", "provenance:real-world/ow1-emulator/fixture.json"], "note": "The full project accepts included-file #!mainFile directives and remains preserved as a failure corpus case with the next unsupported #!defineMember boundary."},
{"fixture": "real-world/6v6-adjustments", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/6v6-adjustments/oracle.json", "provenance:real-world/6v6-adjustments/fixture.json"], "note": "The full project accepts included-file #!mainFile directives and remains preserved as a failure corpus case with the next unsupported #!defineMember boundary."},
{"fixture": "real-world/ow1-emulator", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/ow1-emulator/oracle.json", "provenance:real-world/ow1-emulator/fixture.json"], "note": "The full project now resolves included-file #!mainFile and #!defineMember directives; the failure case remains at the next lexer line-continuation boundary."},
{"fixture": "real-world/6v6-adjustments", "nativeStatus": "failure", "classification": "match", "ruleNames": false, "evidence": ["oracle:real-world/6v6-adjustments/oracle.json", "provenance:real-world/6v6-adjustments/fixture.json"], "note": "The full project now resolves included-file #!mainFile and #!defineMember directives; the failure case remains at the existing included settings-placement boundary."},
{"fixture": "synthetic/issue-35-integration", "nativeStatus": "success", "classification": "match", "ruleNames": true, "evidence": ["oracle:synthetic/issue-35-integration/oracle.json", "implementation-invariant:opy-rs::compiler-vertical-slice"], "note": "The OPY source implementation resolves the source fixture; the compiler test independently lowers it through canonical WIR validation and deterministic workshop-rs emission."},
{"fixture": "synthetic/issue-40-structural", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-40-structural/oracle.json", "implementation-invariant:opy-rs::compiler-structural-lowering"], "note": "The pinned oracle records subroutine source identity, deterministic explicit/implicit variable allocation, and player event filters; the compiler test independently asserts those structures in canonical WIR."},
{"fixture": "synthetic/issue-46-primitives", "nativeStatus": "success", "classification": "match", "ruleNames": false, "evidence": ["oracle:synthetic/issue-46-primitives/oracle.json", "implementation-invariant:opy-rs::compiler-primitive-lowering", "test:opy-rs::compiler-issue-46-native-wir-equivalence"], "note": "The pinned oracle records the evidenced assignment and modification lowering (including **= and single-level indexed forms), value expressions, array indexing (including the firstOf index-0 read normalization), not-comparison negation, implicit default variables at fixed Workshop slots, and non-null variable initializers; null-default initializer semantics and deeper indexed writes remain explicit follow-ups. The compiler test compares native lowered WIR directly with the parsed oracle WIR and asserts structural equivalence."},
Expand Down
18 changes: 11 additions & 7 deletions compatibility/support-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@
},
{
"id": "preprocessing/include",
"name": "#!include: root-relative resolution, cycle detection, missing-file diagnostics, file registry",
"name": "#!include: including-file-relative resolution, cycle detection, missing-file diagnostics, file registry",
"category": "preprocessing",
"state": "source-supported",
"evidence": [
Expand All @@ -411,34 +411,38 @@
"upstream:src/tests/include3.opy",
"upstream:src/tests/include5.opy"
],
"notes": "Root-relative include resolution, cycle detection, missing-file diagnostics, file registry; deterministic multi-file tests."
"notes": "Includes resolve from the declaring source file, including nested closures; cycle detection, missing-file diagnostics, file registry, and deterministic multi-file provenance are covered."
},
{
"id": "preprocessing/define-undef",
"name": "#!define (object- and function-like), #!undef, recursive expansion, recursion guard",
"name": "#!define/#!defineMember (object- and function-like), #!undef, recursive expansion, recursion guard",
"category": "preprocessing",
"state": "source-supported",
"evidence": [
"fixtures:synthetic/preprocessing",
"fixtures:real-world/overpy-cake",
"test:opy-rs::compiler::integration_tests::issue_142_preprocessing::included_define_member_expands_with_definition_provenance",
"test:opy-rs::compiler::integration_tests::issue_142_preprocessing::nested_includes_resolve_relative_to_the_including_file",
"upstream:src/tests/macros.opy",
"upstream:src/tests/builtinmacros.opy"
],
"notes": "Object- and function-like #!define, #!undef, recursive expansion with recursion guard; preprocessing fixtures."
"notes": "Object- and function-like #!define/#!defineMember, #!undef, recursive expansion with recursion guard; member defines share the pinned textual macro contract and retain definition-site provenance."
},
{
"id": "preprocessing/advanced-directives",
"name": "#!mainFile, #!allowMacroRedeclaration, #!optimize*/#!replace0By* family, #!rulePrefix*",
"name": "#!mainFile, #!allowMacroRedeclaration, backend-only directive state, #!optimize*/#!replace0By* family, #!rulePrefix*",
"category": "preprocessing",
"state": "semantic-supported",
"evidence": [
"fixtures:synthetic/issue-29-directives",
"fixtures:synthetic/issue-29-main-file",
"upstream:src/tests/rulePrefix.opy",
"upstream:src/tests/compression.opy",
"upstream:src/data/opy/preprocessing.ts"
"upstream:src/data/opy/preprocessing.ts",
"test:opy-rs::preprocess::tests::backend_only_directives_are_validated_and_recorded",
"test:opy-rs::preprocess::tests::extension_directive_rejects_unknown_schema_values"
],
"notes": "Issue #29: source implementation records directive state, macro-redeclaration policy, main-file provenance, and rule-prefix application. Optimizer and generated Workshop effects remain separate."
"notes": "Issue #29 plus #142: source implementation validates and records main-file provenance, macro-redeclaration policy, backend-only directive state, canonical extension names, and rule-prefix application. Optimizer, localization, extension point accounting, output-file, compression, and generated Workshop effects remain separate."
},
{
"id": "preprocessing/directive-effects",
Expand Down
2 changes: 2 additions & 0 deletions crates/opy-rs/src/compiler/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ mod issue_129_included_main_file;
mod issue_130_horizontal_facing_angle;
#[path = "tests/issue_131_spec_visibility.rs"]
mod issue_131_spec_visibility;
#[path = "tests/issue_142_preprocessing.rs"]
mod issue_142_preprocessing;
#[path = "tests/issue_42_oracle.rs"]
mod issue_42_oracle;
#[path = "tests/issue_46_oracle.rs"]
Expand Down
78 changes: 78 additions & 0 deletions crates/opy-rs/src/compiler/tests/issue_142_preprocessing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! Public compile-path coverage for issue #142's preprocessor member defines.

use std::collections::BTreeMap;

use crate::compile_with_overlay;
use crate::hir::{Expr, RuleEntry, Stmt};

#[test]
fn included_define_member_expands_with_definition_provenance() {
let mut overlay = BTreeMap::new();
overlay.insert(
"shared.opy".to_string(),
"#!defineMember VALUE 2\n".to_string(),
);
let hir = compile_with_overlay(
"#!include \"shared.opy\"\nrule \"member define\":\n @Event global\n A = VALUE\n",
"main.opy",
std::path::Path::new("."),
&overlay,
)
.expect("included member defines must expand through the public compile path");

assert!(hir.dump().contains("assign A = 2"), "{}", hir.dump());
assert_eq!(hir.defines.len(), 1);
assert_eq!(hir.defines[0].name, "VALUE");
assert!(hir.defines[0].is_member);
assert_eq!(hir.defines[0].span.expect("define span").file, 1);
assert_eq!(hir.files[1].path, "shared.opy");
}

#[test]
fn function_define_member_uses_the_same_textual_macro_contract() {
let hir = crate::compile(
"#!defineMember add(value) value + 1\nrule \"member function\":\n @Event global\n A = add(2)\n",
"main.opy",
std::path::Path::new("."),
)
.expect("function-like member defines must expand");
assert!(hir.defines[0].is_member);

let RuleEntry::Rule(rule) = &hir.rules[0] else {
panic!("expected a rule");
};
let Stmt::Assign { value, .. } = &rule.actions[0] else {
panic!("expected an assignment");
};
let Expr::Binary { left, right, .. } = &**value else {
panic!("expected the expanded expression to preserve its operands");
};
assert!(matches!(&**left, Expr::Number { value, .. } if *value == 2.0));
assert!(matches!(&**right, Expr::Number { value, .. } if *value == 1.0));
}

#[test]
fn nested_includes_resolve_relative_to_the_including_file() {
let overlay = BTreeMap::from([
(
"dir/child.opy".to_string(),
"#!include \"grandchild.opy\"\n".to_string(),
),
(
"dir/grandchild.opy".to_string(),
"#!defineMember VALUE 2\n".to_string(),
),
]);
let hir = compile_with_overlay(
"#!include \"dir/child.opy\"\nrule \"nested include\":\n @Event global\n A = VALUE\n",
"main.opy",
std::path::Path::new("."),
&overlay,
)
.expect("nested include paths must be relative to the including file");

assert!(hir.dump().contains("assign A = 2"), "{}", hir.dump());
assert_eq!(hir.defines[0].span.expect("define span").file, 2);
assert_eq!(hir.files[1].path, "dir/child.opy");
assert_eq!(hir.files[2].path, "dir/grandchild.opy");
}
3 changes: 2 additions & 1 deletion crates/opy-rs/src/hir/dump.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ pub fn dump(program: &Program) -> String {
out.push_str("defines:\n");
for define in &program.defines {
out.push_str(&format!(
" {} ({}){}\n",
" {} ({}{}){}\n",
define.name,
if define.is_function {
"function"
} else {
"constant"
},
if define.is_member { ", member" } else { "" },
span_suffix(define.span.as_ref()),
));
}
Expand Down
3 changes: 3 additions & 0 deletions crates/opy-rs/src/hir/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ pub struct Define {
pub name: String,
#[serde(default)]
pub is_function: bool,
/// Whether the definition used OverPy's `#!defineMember` surface.
#[serde(default)]
pub is_member: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub span: Option<Span>,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/opy-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//!
//! Owns the OPY source-language surface of the `opy-rs` repository: a lexer,
//! an indentation-aware CST/parser with structured diagnostics and recovery,
//! token-level preprocessing (includes and `#!define` macros), semantic
//! token-level preprocessing (includes and `#!define`/`#!defineMember` macros), semantic
//! resolution, and lowering into the opy-rs-owned Opy HIR contract
//! ([`hir::Program`]). Everything from source through the Opy HIR semantic
//! model is Workshop-independent: source analysis never depends on `workshop-rs`,
Expand Down
Loading
Loading