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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
12 changes: 7 additions & 5 deletions crates/workshop-rs/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<wir::Program> {
parse_with_context(input, catalog, locale, &NoExpectedDomain)
parse_with_context(input, catalog, locale, catalog)
}

/// Parse localized Workshop text into Workshop IR, resolving ambiguous bare
Expand Down
6 changes: 2 additions & 4 deletions crates/workshop-rs/src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
66 changes: 64 additions & 2 deletions crates/workshop-rs/tests/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down Expand Up @@ -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
Expand Down
Loading