diff --git a/README.md b/README.md index 2b0dc46..80fff36 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ use workshop_rs::emitter::emit; let catalog = Catalog::builtin()?; let locale = Locale::new("en-US"); -let program = workshop_rs::parser::parse_with_context(text, &catalog, &locale, &catalog)?; +let program = workshop_rs::parser::parse(text, &catalog, &locale)?; let emitted = emit(&program, &catalog, &locale)?; let converted = convert( diff --git a/crates/workshop-rs/src/parser.rs b/crates/workshop-rs/src/parser.rs index 9435bfb..ab83ced 100644 --- a/crates/workshop-rs/src/parser.rs +++ b/crates/workshop-rs/src/parser.rs @@ -11,7 +11,7 @@ use std::sync::OnceLock; use crate::settings::table::{self, KeyKind, PathPart}; use crate::settings::{Settings, SettingsListElement, SettingsNode}; -use crate::signatures::{ExpectedDomain, NoExpectedDomain}; +use crate::signatures::ExpectedDomain; use crate::source::{Position, SourceFile, Span}; use crate::wir::{ self, Action, Event, EventTarget, EventTeam, ModifyOp, PlayerEventKind, Value, ValueNode, @@ -39,11 +39,13 @@ enum AssignmentOperator { Modify(ModifyOp), } -/// Parse localized Workshop text into Workshop IR with no signature context: -/// ambiguous bare enum members (e.g. the `None` shared by several domains) -/// stay rejected. See [`parse_with_context`] for the context-sensitive form. +/// Parse localized Workshop text into Workshop IR using the catalog's +/// canonical call-signature context. Ambiguous bare enum members resolve when +/// their enclosing call pins one matching domain; unpinned ambiguity remains a +/// structured unsupported diagnostic. See [`parse_with_context`] when a +/// consumer needs to provide additional signature context. pub fn parse(input: &str, catalog: &Catalog, locale: &Locale) -> Result { - parse_with_context(input, catalog, locale, &NoExpectedDomain) + parse_with_context(input, catalog, locale, catalog) } /// Parse localized Workshop text into Workshop IR, resolving ambiguous bare diff --git a/crates/workshop-rs/src/signatures.rs b/crates/workshop-rs/src/signatures.rs index 13c5a57..a7e1583 100644 --- a/crates/workshop-rs/src/signatures.rs +++ b/crates/workshop-rs/src/signatures.rs @@ -24,10 +24,8 @@ pub trait ExpectedDomain { fn expected_domain(&self, catalog_id: &str, arg_index: usize) -> Option<&str>; } -/// The default context: no signature pins any argument domain, so ambiguous -/// bare enum members stay rejected. Used by the plain -/// [`crate::parser::parse`] entry point and callers without signature -/// metadata. +/// A context with no signature metadata. Ambiguous bare enum members stay +/// rejected. Used by callers that intentionally need context-free parsing. #[derive(Debug, Clone, Copy, Default)] pub struct NoExpectedDomain; diff --git a/crates/workshop-rs/tests/parser.rs b/crates/workshop-rs/tests/parser.rs index a90ede1..9bd5885 100644 --- a/crates/workshop-rs/tests/parser.rs +++ b/crates/workshop-rs/tests/parser.rs @@ -591,8 +591,8 @@ fn unsupported_construct_is_distinct_from_malformed() { #[test] fn bare_chase_reevaluation_none_is_ambiguous_across_domains() { // Both reference reevaluation domains spell their NONE member "None". - // Without a signature pin the flat parser rejects the bare spelling - // with a structured Unsupported diagnostic. + // Without a signature pin the catalog-backed parser rejects the bare + // spelling with a structured Unsupported diagnostic. let text = "variables { global: 0: g }\nrule (\"x\") { event { Ongoing - Global; } actions { Set Global Variable(g, None); } }"; let error = parser::parse(text, &catalog(), &Locale::new("en-US")).unwrap_err(); assert!( @@ -656,6 +656,68 @@ fn context_pinned_ambiguous_none_resolves_for_set_invisible() { ); } +#[test] +fn released_parse_contract_resolves_pinned_oracle_members() { + let catalog = catalog(); + let locale = Locale::new("en-US"); + + let cake = parser::parse(&corpus_workshop_text("overpy-cake"), &catalog, &locale) + .expect("the pinned cake Workshop output must parse through the released contract"); + assert!(cake.values.iter().any(|node| matches!( + node.value, + wir::Value::Enum { ref value_type, ref value } + if value_type == "EffectReeval" && value == "VISIBILITY" + ))); + + let chase = r#"variables { + global: + 0: Round_Attack_Time +} + +rule ("chase and condition") { + event { + Ongoing - Global; + } + conditions { + Is Game In Progress == True; + Global.Round_Attack_Time != 0; + } + actions { + Chase Global Variable Over Time(Round_Attack_Time, 0, 30, None); + } +} +"#; + let chase = parser::parse(chase, &catalog, &locale) + .expect("the pinned chase Workshop output must parse through the released contract"); + assert!(chase.values.iter().any(|node| matches!( + node.value, + wir::Value::Enum { ref value_type, ref value } + if value_type == "ChaseTimeReeval" && value == "NONE" + ))); + + let chase_zh = r#"variables { + global: + 0: Round_Attack_Time +} + +rule ("chase and condition") { + event { + 持续 - 全局; + } + actions { + 持续追踪全局变量(Round_Attack_Time, 0, 30, 全部禁用); + } +} +"#; + let chase_zh = parser::parse(chase_zh, &catalog, &Locale::new("zh-CN")) + .expect("the pinned chase Workshop output must parse in zh-CN"); + assert!(chase_zh.values.iter().any(|node| matches!( + node.value, + wir::Value::Enum { ref value_type, ref value } + if value_type == "ChaseTimeReeval" && value == "NONE" + ))); +} + #[test] fn wrong_domain_context_keeps_the_ambiguity_rejected() { // A signature pinning a *different* domain than the ambiguous member's