diff --git a/compatibility/compiler-expectations.json b/compatibility/compiler-expectations.json index 63259a9..7b74cfe 100644 --- a/compatibility/compiler-expectations.json +++ b/compatibility/compiler-expectations.json @@ -593,9 +593,9 @@ "provenance:real-world/ow1-emulator/fixture.json" ], "owner": "opy-rs#88", - "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.", + "note": "The full real-world project now resolves included-file #!mainFile, #!defineMember directives, and continued directive lines; it remains outside the compiler baseline at the duplicate-include macro-redeclaration boundary.", "failureClass": "frontend", - "diagnosticCode": "lex-error" + "diagnosticCode": "macro-redeclaration" }, { "fixture": "synthetic/chase-condition-agentlab", diff --git a/compatibility/differential-expectations.json b/compatibility/differential-expectations.json index 99ddaac..26f9067 100644 --- a/compatibility/differential-expectations.json +++ b/compatibility/differential-expectations.json @@ -46,7 +46,7 @@ {"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 now resolves included-file #!mainFile and #!defineMember directives; the failure case remains at the next lexer line-continuation 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, #!defineMember directives, and continued directive lines; the failure case remains at the duplicate-include macro-redeclaration 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."}, diff --git a/crates/opy-rs/src/lexer.rs b/crates/opy-rs/src/lexer.rs index 99a6c9b..b32e267 100644 --- a/crates/opy-rs/src/lexer.rs +++ b/crates/opy-rs/src/lexer.rs @@ -223,7 +223,13 @@ impl Lexer { self.advance(); self.advance(); let mut text = String::new(); - while self.pos < self.chars.len() && self.chars[self.pos] != '\n' { + while self.pos < self.chars.len() { + if self.chars[self.pos] == '\\' && self.skip_line_continuation() { + continue; + } + if self.chars[self.pos] == '\n' { + break; + } text.push(self.chars[self.pos]); self.advance(); } @@ -574,6 +580,18 @@ mod tests { assert!(!tokens.iter().any(|t| t.text == "comment")); } + #[test] + fn directive_line_continuation_is_part_of_one_directive() { + let tokens = lex_ok("#!define X first + \\\n second\n"); + let directive = tokens + .iter() + .find(|token| token.kind == TokenKind::Directive) + .unwrap(); + assert_eq!(directive.text, "define X first + second"); + assert_eq!(directive.span.start, Position::new(1, 1)); + assert_eq!(directive.span.end, Position::new(2, 9)); + } + #[test] fn operators() { let tokens = lex_ok("a += b == c <= d != e / f");