feat: implement storybook DSL with template composition and validation
Add complete domain-specific language for authoring narrative content for agent simulations. Features: - Complete parser using LALRPOP + logos lexer - Template composition (includes + multiple inheritance) - Strict mode validation for templates - Reserved keyword protection - Semantic validators (trait ranges, schedule overlaps, life arcs, behaviors) - Name resolution and cross-reference tracking - CLI tool (validate, inspect, query commands) - Query API with filtering - 260 comprehensive tests (unit, integration, property-based) Implementation phases: - Phase 1 (Parser): Complete - Phase 2 (Resolution + Validation): Complete - Phase 3 (Public API + CLI): Complete BREAKING CHANGE: Initial implementation
This commit is contained in:
164
src/resolve/integration_tests.rs
Normal file
164
src/resolve/integration_tests.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
//! Integration tests for the resolution engine
|
||||
|
||||
use crate::{
|
||||
resolve::names::{
|
||||
DeclKind,
|
||||
NameTable,
|
||||
},
|
||||
syntax::{
|
||||
lexer::Lexer,
|
||||
FileParser,
|
||||
},
|
||||
};
|
||||
|
||||
fn parse(source: &str) -> crate::syntax::ast::File {
|
||||
let lexer = Lexer::new(source);
|
||||
let parser = FileParser::new();
|
||||
parser.parse(lexer).expect("Should parse successfully")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_resolution_example_file() {
|
||||
let source = r#"
|
||||
character Alice {
|
||||
age: 30
|
||||
}
|
||||
|
||||
character Bob {
|
||||
age: 35
|
||||
}
|
||||
|
||||
template PersonTemplate {
|
||||
age: 18..80
|
||||
}
|
||||
|
||||
enum Status {
|
||||
active,
|
||||
inactive
|
||||
}
|
||||
"#;
|
||||
|
||||
let file = parse(source);
|
||||
let table = NameTable::from_file(&file).expect("Should build name table");
|
||||
|
||||
// Verify all names are registered
|
||||
assert!(table.lookup(&["Alice".to_string()]).is_some());
|
||||
assert!(table.lookup(&["Bob".to_string()]).is_some());
|
||||
assert!(table.lookup(&["PersonTemplate".to_string()]).is_some());
|
||||
assert!(table.lookup(&["Status".to_string()]).is_some());
|
||||
|
||||
// Verify kind filtering
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Character).count(), 2);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Template).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Enum).count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_use_statements_are_parsed() {
|
||||
let source = r#"
|
||||
use characters::Martha;
|
||||
use templates::{Person, NPC};
|
||||
use locations::*;
|
||||
|
||||
character LocalChar {
|
||||
age: 25
|
||||
}
|
||||
"#;
|
||||
|
||||
let file = parse(source);
|
||||
let table = NameTable::from_file(&file).expect("Should build name table");
|
||||
|
||||
// Verify imports were collected
|
||||
assert_eq!(table.imports().len(), 3);
|
||||
|
||||
// Verify local declaration is registered
|
||||
assert!(table.lookup(&["LocalChar".to_string()]).is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duplicate_name_error() {
|
||||
let source = r#"
|
||||
character Martha {
|
||||
age: 30
|
||||
}
|
||||
|
||||
character Martha {
|
||||
age: 35
|
||||
}
|
||||
"#;
|
||||
|
||||
let file = parse(source);
|
||||
let result = NameTable::from_file(&file);
|
||||
|
||||
// Should fail with duplicate error
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuzzy_matching_suggestion() {
|
||||
let source = r#"
|
||||
character Elizabeth {
|
||||
age: 30
|
||||
}
|
||||
"#;
|
||||
|
||||
let file = parse(source);
|
||||
let table = NameTable::from_file(&file).expect("Should build name table");
|
||||
|
||||
// Typo "Elizabet" should suggest "Elizabeth"
|
||||
let suggestion = table.find_suggestion("Elizabet");
|
||||
assert_eq!(suggestion, Some("Elizabeth".to_string()));
|
||||
|
||||
// Typo "Elizabth" should also suggest "Elizabeth"
|
||||
let suggestion = table.find_suggestion("Elizabth");
|
||||
assert_eq!(suggestion, Some("Elizabeth".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_declaration_kinds() {
|
||||
let source = r#"
|
||||
character C { age: 1 }
|
||||
template T { age: 1..2 }
|
||||
life_arc L {
|
||||
state s {}
|
||||
}
|
||||
schedule S {
|
||||
10:00 -> 11:00: activity
|
||||
}
|
||||
behavior B {
|
||||
action
|
||||
}
|
||||
institution I {
|
||||
name: "Test"
|
||||
}
|
||||
relationship R {
|
||||
C
|
||||
C
|
||||
}
|
||||
location Loc {
|
||||
name: "Place"
|
||||
}
|
||||
species Sp {
|
||||
lifespan: 100
|
||||
}
|
||||
enum E {
|
||||
a,
|
||||
b
|
||||
}
|
||||
"#;
|
||||
|
||||
let file = parse(source);
|
||||
let table = NameTable::from_file(&file).expect("Should build name table");
|
||||
|
||||
// All 10 declaration kinds should be represented
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Character).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Template).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::LifeArc).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Schedule).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Behavior).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Institution).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Relationship).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Location).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Species).count(), 1);
|
||||
assert_eq!(table.entries_of_kind(DeclKind::Enum).count(), 1);
|
||||
}
|
||||
Reference in New Issue
Block a user