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() {
|
fn test_unknown_life_arc_state_error() {
|
||||||
let life_arc = LifeArc {
|
let life_arc = LifeArc {
|
||||||
name: "Growth".to_string(),
|
name: "Growth".to_string(),
|
||||||
|
required_fields: vec![],
|
||||||
states: vec![
|
states: vec![
|
||||||
ArcState {
|
ArcState {
|
||||||
name: "child".to_string(),
|
name: "child".to_string(),
|
||||||
|
|||||||
@@ -829,6 +829,7 @@ mod tests {
|
|||||||
fn test_life_arc_valid_transitions() {
|
fn test_life_arc_valid_transitions() {
|
||||||
let life_arc = LifeArc {
|
let life_arc = LifeArc {
|
||||||
name: "Test".to_string(),
|
name: "Test".to_string(),
|
||||||
|
required_fields: vec![],
|
||||||
states: vec![
|
states: vec![
|
||||||
ArcState {
|
ArcState {
|
||||||
name: "start".to_string(),
|
name: "start".to_string(),
|
||||||
@@ -859,6 +860,7 @@ mod tests {
|
|||||||
fn test_life_arc_invalid_transition() {
|
fn test_life_arc_invalid_transition() {
|
||||||
let life_arc = LifeArc {
|
let life_arc = LifeArc {
|
||||||
name: "Test".to_string(),
|
name: "Test".to_string(),
|
||||||
|
required_fields: vec![],
|
||||||
states: vec![ArcState {
|
states: vec![ArcState {
|
||||||
name: "start".to_string(),
|
name: "start".to_string(),
|
||||||
on_enter: None,
|
on_enter: None,
|
||||||
|
|||||||
@@ -158,6 +158,7 @@ proptest! {
|
|||||||
|
|
||||||
let life_arc = LifeArc {
|
let life_arc = LifeArc {
|
||||||
name: "Test".to_string(),
|
name: "Test".to_string(),
|
||||||
|
required_fields: vec![],
|
||||||
states: vec![
|
states: vec![
|
||||||
ArcState {
|
ArcState {
|
||||||
name: state1_name.clone(),
|
name: state1_name.clone(),
|
||||||
|
|||||||
@@ -217,10 +217,19 @@ pub enum OverrideOp {
|
|||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct LifeArc {
|
pub struct LifeArc {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub required_fields: Vec<FieldRequirement>,
|
||||||
pub states: Vec<ArcState>,
|
pub states: Vec<ArcState>,
|
||||||
pub span: Span,
|
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)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub struct ArcState {
|
pub struct ArcState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ pub enum Token {
|
|||||||
ConceptComparison,
|
ConceptComparison,
|
||||||
#[token("any")]
|
#[token("any")]
|
||||||
Any,
|
Any,
|
||||||
|
#[token("requires")]
|
||||||
|
Requires,
|
||||||
#[token("state")]
|
#[token("state")]
|
||||||
State,
|
State,
|
||||||
#[token("on")]
|
#[token("on")]
|
||||||
|
|||||||
@@ -338,13 +338,26 @@ OverrideOp: OverrideOp = {
|
|||||||
// ===== Life Arc =====
|
// ===== Life Arc =====
|
||||||
|
|
||||||
LifeArc: LifeArc = {
|
LifeArc: LifeArc = {
|
||||||
"life_arc" <name:Ident> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
|
"life_arc" <name:Ident> <reqs:RequiresClause?> "{" <fields:Field*> <states:ArcState*> "}" => LifeArc {
|
||||||
name,
|
name,
|
||||||
|
required_fields: reqs.unwrap_or_default(),
|
||||||
states,
|
states,
|
||||||
span: Span::new(0, 0),
|
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 = {
|
ArcState: ArcState = {
|
||||||
"state" <name:Ident> "{" <on_enter:OnEnter?> <fields:Field*> <transitions:Transition*> "}" => ArcState {
|
"state" <name:Ident> "{" <on_enter:OnEnter?> <fields:Field*> <transitions:Transition*> "}" => ArcState {
|
||||||
name,
|
name,
|
||||||
@@ -963,6 +976,7 @@ extern {
|
|||||||
"sub_concept" => Token::SubConcept,
|
"sub_concept" => Token::SubConcept,
|
||||||
"concept_comparison" => Token::ConceptComparison,
|
"concept_comparison" => Token::ConceptComparison,
|
||||||
"any" => Token::Any,
|
"any" => Token::Any,
|
||||||
|
"requires" => Token::Requires,
|
||||||
"state" => Token::State,
|
"state" => Token::State,
|
||||||
"on" => Token::On,
|
"on" => Token::On,
|
||||||
"enter" => Token::Enter,
|
"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"),
|
| _ => 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