feat(life-arc): add field requirements for life arcs
Added optional requires clause to life_arc declarations:
life_arc Baker requires { skill_level: Number } { ... }
Includes new FieldRequirement AST type, requires keyword token,
and parser support for the requires clause.
This commit is contained in:
@@ -210,6 +210,7 @@ fn test_validation_error_generic() {
|
||||
fn test_unknown_life_arc_state_error() {
|
||||
let life_arc = LifeArc {
|
||||
name: "Growth".to_string(),
|
||||
required_fields: vec![],
|
||||
states: vec![
|
||||
ArcState {
|
||||
name: "child".to_string(),
|
||||
|
||||
@@ -829,6 +829,7 @@ mod tests {
|
||||
fn test_life_arc_valid_transitions() {
|
||||
let life_arc = LifeArc {
|
||||
name: "Test".to_string(),
|
||||
required_fields: vec![],
|
||||
states: vec![
|
||||
ArcState {
|
||||
name: "start".to_string(),
|
||||
@@ -859,6 +860,7 @@ mod tests {
|
||||
fn test_life_arc_invalid_transition() {
|
||||
let life_arc = LifeArc {
|
||||
name: "Test".to_string(),
|
||||
required_fields: vec![],
|
||||
states: vec![ArcState {
|
||||
name: "start".to_string(),
|
||||
on_enter: None,
|
||||
|
||||
@@ -158,6 +158,7 @@ proptest! {
|
||||
|
||||
let life_arc = LifeArc {
|
||||
name: "Test".to_string(),
|
||||
required_fields: vec![],
|
||||
states: vec![
|
||||
ArcState {
|
||||
name: state1_name.clone(),
|
||||
|
||||
@@ -217,10 +217,19 @@ pub enum OverrideOp {
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct LifeArc {
|
||||
pub name: String,
|
||||
pub required_fields: Vec<FieldRequirement>,
|
||||
pub states: Vec<ArcState>,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
/// A required field declaration in a life arc
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FieldRequirement {
|
||||
pub name: String,
|
||||
pub type_name: String,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ArcState {
|
||||
pub name: String,
|
||||
|
||||
@@ -37,6 +37,8 @@ pub enum Token {
|
||||
ConceptComparison,
|
||||
#[token("any")]
|
||||
Any,
|
||||
#[token("requires")]
|
||||
Requires,
|
||||
#[token("state")]
|
||||
State,
|
||||
#[token("on")]
|
||||
|
||||
@@ -338,13 +338,26 @@ OverrideOp: OverrideOp = {
|
||||
// ===== Life Arc =====
|
||||
|
||||
LifeArc: LifeArc = {
|
||||
"life_arc" <name:Ident> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
|
||||
"life_arc" <name:Ident> <reqs:RequiresClause?> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
|
||||
name,
|
||||
required_fields: reqs.unwrap_or_default(),
|
||||
states,
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
};
|
||||
|
||||
RequiresClause: Vec<FieldRequirement> = {
|
||||
"requires" "{" <reqs:Comma<FieldReq>> "}" => reqs,
|
||||
};
|
||||
|
||||
FieldReq: FieldRequirement = {
|
||||
<name:Ident> ":" <type_name:Ident> => FieldRequirement {
|
||||
name,
|
||||
type_name,
|
||||
span: Span::new(0, 0),
|
||||
}
|
||||
};
|
||||
|
||||
ArcState: ArcState = {
|
||||
"state" <name:Ident> "{" <on_enter:OnEnter?> <fields:Field*> <transitions:Transition*> "}" => ArcState {
|
||||
name,
|
||||
@@ -963,6 +976,7 @@ extern {
|
||||
"sub_concept" => Token::SubConcept,
|
||||
"concept_comparison" => Token::ConceptComparison,
|
||||
"any" => Token::Any,
|
||||
"requires" => Token::Requires,
|
||||
"state" => Token::State,
|
||||
"on" => Token::On,
|
||||
"enter" => Token::Enter,
|
||||
|
||||
12795
src/syntax/parser.rs
12795
src/syntax/parser.rs
File diff suppressed because it is too large
Load Diff
@@ -270,3 +270,70 @@ template Knight: Human {
|
||||
| _ => panic!("Expected Template declaration"),
|
||||
}
|
||||
}
|
||||
|
||||
// ===== Life arc requires clause tests =====
|
||||
|
||||
#[test]
|
||||
fn test_life_arc_with_requires() {
|
||||
let input = r#"
|
||||
life_arc Baker requires { skill_level: Number } {
|
||||
state Apprentice {
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let file = parse(input);
|
||||
|
||||
assert_eq!(file.declarations.len(), 1);
|
||||
match &file.declarations[0] {
|
||||
| Declaration::LifeArc(la) => {
|
||||
assert_eq!(la.name, "Baker");
|
||||
assert_eq!(la.required_fields.len(), 1);
|
||||
assert_eq!(la.required_fields[0].name, "skill_level");
|
||||
assert_eq!(la.required_fields[0].type_name, "Number");
|
||||
assert_eq!(la.states.len(), 1);
|
||||
},
|
||||
| _ => panic!("Expected LifeArc declaration"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_life_arc_without_requires() {
|
||||
let input = r#"
|
||||
life_arc Growth {
|
||||
state Child {
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let file = parse(input);
|
||||
|
||||
assert_eq!(file.declarations.len(), 1);
|
||||
match &file.declarations[0] {
|
||||
| Declaration::LifeArc(la) => {
|
||||
assert_eq!(la.name, "Growth");
|
||||
assert!(la.required_fields.is_empty());
|
||||
},
|
||||
| _ => panic!("Expected LifeArc declaration"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_life_arc_multiple_requirements() {
|
||||
let input = r#"
|
||||
life_arc Career requires { skill_level: Number, experience: Number, title: Text } {
|
||||
state Junior {
|
||||
}
|
||||
}
|
||||
"#;
|
||||
let file = parse(input);
|
||||
|
||||
assert_eq!(file.declarations.len(), 1);
|
||||
match &file.declarations[0] {
|
||||
| Declaration::LifeArc(la) => {
|
||||
assert_eq!(la.required_fields.len(), 3);
|
||||
assert_eq!(la.required_fields[0].name, "skill_level");
|
||||
assert_eq!(la.required_fields[1].name, "experience");
|
||||
assert_eq!(la.required_fields[2].name, "title");
|
||||
},
|
||||
| _ => panic!("Expected LifeArc declaration"),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user