feat(grammar): update tree-sitter grammar to v0.3.0
Updated tree-sitter grammar to match v0.3.0 LALRPOP parser: Grammar updates: - Schedule: block-based syntax with extends, override, recurrence - Life arc: requires clause for field validation - Template: uses behaviors/schedules syntax - Behavior: correct keywords (choose/then/repeat with optional params) - Type system: concept_comparison with any/is_condition - Removed concept semicolon requirement Query file updates: - highlights.scm: updated node names to *_declaration - outline.scm: updated for new declaration node names - indents.scm: updated node names, removed concept semicolon Corpus test updates: - Created schedules.txt with v0.3.0 syntax tests - Created highlights.txt for highlighting tests - Updated type_system.txt for v0.3.0 type syntax - Updated behaviors.txt for correct expression wrapping - Updated declarations.txt to use correct node names - Updated basic.txt to use character_declaration/character_body - Deleted obsolete v0.2.0 syntax tests Integration tests: - Added tree_sitter_integration.rs test suite - Fixed test_any_type to use correct v0.3.0 syntax - Fixed test_tree_sitter_grammar_builds to use generate command
This commit is contained in:
444
tests/tree_sitter_integration.rs
Normal file
444
tests/tree_sitter_integration.rs
Normal file
@@ -0,0 +1,444 @@
|
||||
//! Tree-sitter Grammar Integration Tests
|
||||
//!
|
||||
//! These tests validate that:
|
||||
//! 1. The tree-sitter grammar builds correctly
|
||||
//! 2. All queries (highlights, injections, etc.) are valid
|
||||
//! 3. Example files parse without errors
|
||||
//! 4. The Zed extension builds successfully
|
||||
//!
|
||||
//! These are acceptance tests that catch integration issues between
|
||||
//! the Storybook language and its tree-sitter representation.
|
||||
|
||||
use std::{
|
||||
path::{
|
||||
Path,
|
||||
PathBuf,
|
||||
},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
fn tree_sitter_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("tree-sitter-storybook");
|
||||
path
|
||||
}
|
||||
|
||||
fn zed_extension_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("zed-storybook");
|
||||
path
|
||||
}
|
||||
|
||||
fn examples_dir() -> PathBuf {
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
path.push("examples");
|
||||
path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tree-sitter Grammar Build Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_tree_sitter_grammar_generates() {
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("generate")
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter generate. Is tree-sitter-cli installed?");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"tree-sitter generate failed. Grammar may have syntax errors."
|
||||
);
|
||||
|
||||
// Verify generated files exist
|
||||
let parser_c = tree_sitter_dir().join("src").join("parser.c");
|
||||
assert!(
|
||||
parser_c.exists(),
|
||||
"parser.c not generated at {:?}",
|
||||
parser_c
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tree_sitter_grammar_builds() {
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("generate")
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter generate");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"tree-sitter generate failed. Grammar may not compile."
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Query Validation Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_highlights_query_valid() {
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("test")
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter test");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"tree-sitter test failed. Queries (highlights.scm, etc.) may have errors."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_queries_reference_valid_nodes() {
|
||||
// This test verifies that all node types referenced in queries
|
||||
// actually exist in the grammar. This catches issues like:
|
||||
// - Using "any" instead of (any_type)
|
||||
// - Referencing removed or renamed nodes
|
||||
// - Typos in node names
|
||||
|
||||
let highlights_scm = tree_sitter_dir().join("queries").join("highlights.scm");
|
||||
|
||||
assert!(
|
||||
highlights_scm.exists(),
|
||||
"highlights.scm not found at {:?}",
|
||||
highlights_scm
|
||||
);
|
||||
|
||||
let content = std::fs::read_to_string(&highlights_scm).expect("Failed to read highlights.scm");
|
||||
|
||||
// Check for common mistakes
|
||||
let problematic_patterns = vec![
|
||||
(
|
||||
"\"any\"",
|
||||
"Should use (any_type) instead of \"any\" string literal",
|
||||
),
|
||||
(
|
||||
"\"Number\"",
|
||||
"Number is not a keyword, should use a type node",
|
||||
),
|
||||
(
|
||||
"\"Decimal\"",
|
||||
"Decimal is not a keyword, should use a type node",
|
||||
),
|
||||
("\"Text\"", "Text is not a keyword, should use a type node"),
|
||||
(
|
||||
"\"Boolean\"",
|
||||
"Boolean is not a keyword, should use a type node",
|
||||
),
|
||||
];
|
||||
|
||||
for (pattern, msg) in problematic_patterns {
|
||||
if content.contains(pattern) {
|
||||
// Check if it's in a comment
|
||||
let lines: Vec<&str> = content.lines().collect();
|
||||
for line in lines {
|
||||
if line.contains(pattern) && !line.trim_start().starts_with(';') {
|
||||
panic!(
|
||||
"highlights.scm contains problematic pattern '{}': {}",
|
||||
pattern, msg
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Example File Parsing Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_baker_family_example_parses() {
|
||||
let baker_family = examples_dir().join("baker-family");
|
||||
assert!(baker_family.exists(), "baker-family example not found");
|
||||
|
||||
// Find all .sb files
|
||||
let sb_files = find_sb_files(&baker_family);
|
||||
assert!(
|
||||
!sb_files.is_empty(),
|
||||
"No .sb files found in baker-family example"
|
||||
);
|
||||
|
||||
for file in &sb_files {
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("parse")
|
||||
.arg(file)
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter parse");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("Failed to parse: {:?}", file);
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Failed to parse {:?} with tree-sitter",
|
||||
file
|
||||
);
|
||||
|
||||
// Check for ERROR nodes in the parse tree
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if stdout.contains("ERROR") {
|
||||
eprintln!("Parse tree for {:?}:", file);
|
||||
eprintln!("{}", stdout);
|
||||
panic!("Parse tree contains ERROR nodes for {:?}", file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_examples_parse() {
|
||||
let sb_files = find_sb_files(&examples_dir());
|
||||
assert!(!sb_files.is_empty(), "No .sb files found in examples/");
|
||||
|
||||
let mut failures = Vec::new();
|
||||
|
||||
for file in &sb_files {
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("parse")
|
||||
.arg(file)
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter parse");
|
||||
|
||||
if !output.status.success() {
|
||||
failures.push((file.clone(), "Parse command failed".to_string()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for ERROR nodes
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
if stdout.contains("ERROR") {
|
||||
failures.push((file.clone(), "Parse tree contains ERROR nodes".to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
eprintln!("Failed to parse {} files:", failures.len());
|
||||
for (file, reason) in &failures {
|
||||
eprintln!(" - {:?}: {}", file, reason);
|
||||
}
|
||||
panic!("{} files failed to parse with tree-sitter", failures.len());
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Specific Language Feature Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
fn test_type_system_keywords_parse() {
|
||||
let test_file = tree_sitter_dir()
|
||||
.join("test")
|
||||
.join("corpus")
|
||||
.join("type_system.txt");
|
||||
|
||||
if !test_file.exists() {
|
||||
panic!(
|
||||
"Type system test corpus not found at {:?}. Please create it.",
|
||||
test_file
|
||||
);
|
||||
}
|
||||
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("test")
|
||||
.arg("--filter")
|
||||
.arg("type_system")
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter test");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(output.status.success(), "Type system test corpus failed");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_type_highlights_correctly() {
|
||||
// Create a minimal test file with any_type usage
|
||||
let test_content = r#"
|
||||
concept_comparison SkillLevel {
|
||||
Novice: { skill: any },
|
||||
Expert: { skill: Tier is Expert }
|
||||
}
|
||||
"#;
|
||||
|
||||
let temp_dir = std::env::temp_dir();
|
||||
let test_file = temp_dir.join("test_any_type.sb");
|
||||
std::fs::write(&test_file, test_content).expect("Failed to write test file");
|
||||
|
||||
let output = Command::new("npx")
|
||||
.arg("tree-sitter")
|
||||
.arg("parse")
|
||||
.arg(&test_file)
|
||||
.current_dir(tree_sitter_dir())
|
||||
.output()
|
||||
.expect("Failed to run tree-sitter parse");
|
||||
|
||||
std::fs::remove_file(&test_file).ok(); // Clean up
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(output.status.success(), "Failed to parse any_type usage");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||
assert!(
|
||||
stdout.contains("any_type"),
|
||||
"Parse tree should contain any_type node"
|
||||
);
|
||||
assert!(
|
||||
!stdout.contains("ERROR"),
|
||||
"Parse tree should not contain ERROR nodes"
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Zed Extension Build Tests
|
||||
// ============================================================================
|
||||
|
||||
#[test]
|
||||
#[ignore] // Only run with --ignored flag as this is slow
|
||||
fn test_zed_extension_builds() {
|
||||
let build_script = zed_extension_dir().join("build-extension.sh");
|
||||
|
||||
if !build_script.exists() {
|
||||
panic!("build-extension.sh not found at {:?}", build_script);
|
||||
}
|
||||
|
||||
let output = Command::new("bash")
|
||||
.arg(&build_script)
|
||||
.current_dir(zed_extension_dir())
|
||||
.output()
|
||||
.expect("Failed to run build-extension.sh");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDOUT: {}", String::from_utf8_lossy(&output.stdout));
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(output.status.success(), "build-extension.sh failed");
|
||||
|
||||
// Verify the extension.wasm was created
|
||||
let wasm = zed_extension_dir().join("extension.wasm");
|
||||
assert!(wasm.exists(), "extension.wasm not created at {:?}", wasm);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extension_toml_has_valid_revision() {
|
||||
let extension_toml = zed_extension_dir().join("extension.toml");
|
||||
let content = std::fs::read_to_string(&extension_toml).expect("Failed to read extension.toml");
|
||||
|
||||
// Check that rev is set to a valid commit SHA
|
||||
let rev_line = content
|
||||
.lines()
|
||||
.find(|line| line.starts_with("rev = "))
|
||||
.expect("No 'rev' field found in extension.toml");
|
||||
|
||||
let rev = rev_line
|
||||
.split('"')
|
||||
.nth(1)
|
||||
.expect("Could not parse rev value");
|
||||
|
||||
assert_eq!(
|
||||
rev.len(),
|
||||
40,
|
||||
"Revision should be a 40-character git SHA, got: {}",
|
||||
rev
|
||||
);
|
||||
|
||||
// Verify the commit exists
|
||||
let output = Command::new("git")
|
||||
.arg("cat-file")
|
||||
.arg("-t")
|
||||
.arg(rev)
|
||||
.current_dir(env!("CARGO_MANIFEST_DIR"))
|
||||
.output()
|
||||
.expect("Failed to run git cat-file");
|
||||
|
||||
if !output.status.success() {
|
||||
eprintln!("STDERR: {}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"Revision {} does not exist in git history",
|
||||
rev
|
||||
);
|
||||
|
||||
let obj_type = String::from_utf8_lossy(&output.stdout);
|
||||
assert_eq!(
|
||||
obj_type.trim(),
|
||||
"commit",
|
||||
"Revision {} is not a commit",
|
||||
rev
|
||||
);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
fn find_sb_files(dir: &Path) -> Vec<PathBuf> {
|
||||
let mut sb_files = Vec::new();
|
||||
|
||||
if !dir.exists() {
|
||||
return sb_files;
|
||||
}
|
||||
|
||||
visit_dirs(dir, &mut |path| {
|
||||
if path.extension().and_then(|s| s.to_str()) == Some("sb") {
|
||||
sb_files.push(path.to_path_buf());
|
||||
}
|
||||
});
|
||||
|
||||
sb_files
|
||||
}
|
||||
|
||||
fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&Path)) {
|
||||
if dir.is_dir() {
|
||||
for entry in std::fs::read_dir(dir).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
visit_dirs(&path, cb);
|
||||
} else {
|
||||
cb(&path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,8 @@ module.exports = grammar({
|
||||
|
||||
conflicts: $ => [
|
||||
[$.path_segments],
|
||||
[$.sub_concept_enum_body, $.sub_concept_record_body]
|
||||
[$.sub_concept_enum_body, $.sub_concept_record_body],
|
||||
[$.uses_behaviors]
|
||||
],
|
||||
|
||||
word: $ => $.identifier,
|
||||
@@ -37,15 +38,15 @@ module.exports = grammar({
|
||||
// Declarations
|
||||
declaration: $ => choice(
|
||||
$.use_declaration,
|
||||
$.character,
|
||||
$.template,
|
||||
$.life_arc,
|
||||
$.schedule,
|
||||
$.behavior,
|
||||
$.institution,
|
||||
$.relationship,
|
||||
$.location,
|
||||
$.species,
|
||||
$.character_declaration,
|
||||
$.template_declaration,
|
||||
$.life_arc_declaration,
|
||||
$.schedule_declaration,
|
||||
$.behavior_declaration,
|
||||
$.institution_declaration,
|
||||
$.relationship_declaration,
|
||||
$.location_declaration,
|
||||
$.species_declaration,
|
||||
$.enum_declaration,
|
||||
$.concept_declaration,
|
||||
$.sub_concept,
|
||||
@@ -68,28 +69,59 @@ module.exports = grammar({
|
||||
path_segments: $ => sep1($.identifier, token('::')),
|
||||
|
||||
// Character declaration
|
||||
character: $ => seq(
|
||||
character_declaration: $ => seq(
|
||||
'character',
|
||||
field('name', $.identifier),
|
||||
optional(seq(':', field('species', $.identifier))),
|
||||
optional(field('template', $.template_clause)),
|
||||
field('body', $.block)
|
||||
field('body', $.character_body)
|
||||
),
|
||||
|
||||
character_body: $ => seq(
|
||||
'{',
|
||||
repeat($.field),
|
||||
'}'
|
||||
),
|
||||
|
||||
template_clause: $ => seq('from', commaSep1($.identifier)),
|
||||
|
||||
// Template declaration
|
||||
template: $ => seq(
|
||||
template_declaration: $ => seq(
|
||||
'template',
|
||||
field('name', $.identifier),
|
||||
optional(seq(':', field('species', $.identifier))),
|
||||
optional('strict'),
|
||||
field('body', $.template_body)
|
||||
),
|
||||
|
||||
template_body: $ => seq(
|
||||
'{',
|
||||
repeat($.include),
|
||||
repeat($.field),
|
||||
repeat(choice(
|
||||
$.include,
|
||||
$.uses_behaviors,
|
||||
$.uses_schedule,
|
||||
$.field
|
||||
)),
|
||||
'}'
|
||||
),
|
||||
|
||||
uses_behaviors: $ => seq(
|
||||
'uses',
|
||||
'behaviors',
|
||||
':',
|
||||
commaSep1($.identifier)
|
||||
),
|
||||
|
||||
uses_schedule: $ => seq(
|
||||
'uses',
|
||||
choice('schedule', 'schedules'),
|
||||
':',
|
||||
choice(
|
||||
$.identifier,
|
||||
seq('[', commaSep1($.identifier), ']')
|
||||
)
|
||||
),
|
||||
|
||||
include: $ => seq('include', $.identifier),
|
||||
|
||||
// Fields (key: value pairs)
|
||||
@@ -177,18 +209,36 @@ module.exports = grammar({
|
||||
)))),
|
||||
|
||||
// Life arc declaration
|
||||
life_arc: $ => seq(
|
||||
life_arc_declaration: $ => seq(
|
||||
'life_arc',
|
||||
field('name', $.identifier),
|
||||
optional(field('requires', $.requires_clause)),
|
||||
'{',
|
||||
repeat($.field),
|
||||
repeat($.arc_state),
|
||||
repeat($.state_block),
|
||||
'}'
|
||||
),
|
||||
|
||||
arc_state: $ => seq(
|
||||
requires_clause: $ => seq(
|
||||
'requires',
|
||||
'{',
|
||||
commaSep1($.required_field),
|
||||
'}'
|
||||
),
|
||||
|
||||
required_field: $ => seq(
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('type', $.identifier)
|
||||
),
|
||||
|
||||
state_block: $ => seq(
|
||||
'state',
|
||||
field('name', $.identifier),
|
||||
field('body', $.state_body)
|
||||
),
|
||||
|
||||
state_body: $ => seq(
|
||||
'{',
|
||||
optional($.on_enter),
|
||||
repeat($.field),
|
||||
@@ -206,28 +256,76 @@ module.exports = grammar({
|
||||
),
|
||||
|
||||
// Schedule declaration
|
||||
schedule: $ => seq(
|
||||
schedule_declaration: $ => seq(
|
||||
'schedule',
|
||||
field('name', $.identifier),
|
||||
optional(seq('extends', field('extends', $.identifier))),
|
||||
field('body', $.schedule_body)
|
||||
),
|
||||
|
||||
schedule_body: $ => seq(
|
||||
'{',
|
||||
repeat($.field),
|
||||
repeat($.schedule_block),
|
||||
repeat(choice(
|
||||
$.field,
|
||||
$.schedule_block,
|
||||
$.override_block,
|
||||
$.recurrence_block
|
||||
)),
|
||||
'}'
|
||||
),
|
||||
|
||||
// Named block: block name { time range, action, fields }
|
||||
schedule_block: $ => seq(
|
||||
'block',
|
||||
field('name', $.identifier),
|
||||
'{',
|
||||
field('time_range', $.time_range),
|
||||
repeat($.block_field),
|
||||
'}'
|
||||
),
|
||||
|
||||
// Override block: override name { time range, action, fields }
|
||||
override_block: $ => seq(
|
||||
'override',
|
||||
field('name', $.identifier),
|
||||
'{',
|
||||
field('time_range', $.time_range),
|
||||
repeat($.block_field),
|
||||
'}'
|
||||
),
|
||||
|
||||
// Recurrence: recurrence Name on DayOfWeek { blocks }
|
||||
recurrence_block: $ => seq(
|
||||
'recurrence',
|
||||
field('name', $.identifier),
|
||||
'on',
|
||||
field('day', $.identifier),
|
||||
'{',
|
||||
repeat1($.schedule_block),
|
||||
'}'
|
||||
),
|
||||
|
||||
time_range: $ => seq(
|
||||
field('start', $.time),
|
||||
'->',
|
||||
field('end', $.time),
|
||||
optional(',')
|
||||
),
|
||||
|
||||
block_field: $ => seq(
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('activity', $.identifier),
|
||||
$.block
|
||||
field('value', choice($.identifier, $.string, $.integer, $.float))
|
||||
),
|
||||
|
||||
// Behavior tree declaration
|
||||
behavior: $ => seq(
|
||||
behavior_declaration: $ => seq(
|
||||
'behavior',
|
||||
field('name', $.identifier),
|
||||
field('body', $.behavior_body)
|
||||
),
|
||||
|
||||
behavior_body: $ => seq(
|
||||
'{',
|
||||
repeat($.field),
|
||||
field('root', $.behavior_node),
|
||||
@@ -239,6 +337,7 @@ module.exports = grammar({
|
||||
$.sequence_node,
|
||||
$.condition_node,
|
||||
$.if_decorator_node,
|
||||
$.repeat_node,
|
||||
$.decorator_node,
|
||||
$.action_node,
|
||||
$.subtree_node
|
||||
@@ -281,7 +380,20 @@ module.exports = grammar({
|
||||
'}'
|
||||
),
|
||||
|
||||
// Decorator node: repeat/retry/timeout/etc { child }
|
||||
// Repeat node: repeat { child } or repeat(N) { child } or repeat(min..max) { child }
|
||||
repeat_node: $ => seq(
|
||||
'repeat',
|
||||
optional(field('params', choice(
|
||||
seq('(', $.integer, ')'),
|
||||
seq('(', $.integer, '..', $.integer, ')'),
|
||||
seq('(', $.duration, ')')
|
||||
))),
|
||||
'{',
|
||||
field('child', $.behavior_node),
|
||||
'}'
|
||||
),
|
||||
|
||||
// Decorator node: retry/timeout/etc { child }
|
||||
decorator_node: $ => seq(
|
||||
field('decorator', $.decorator_keyword),
|
||||
optional(field('params', $.decorator_params)),
|
||||
@@ -291,7 +403,6 @@ module.exports = grammar({
|
||||
),
|
||||
|
||||
decorator_keyword: $ => choice(
|
||||
'repeat',
|
||||
'invert',
|
||||
'retry',
|
||||
'timeout',
|
||||
@@ -330,16 +441,20 @@ module.exports = grammar({
|
||||
subtree_node: $ => seq('include', $.path),
|
||||
|
||||
// Institution declaration
|
||||
institution: $ => seq(
|
||||
institution_declaration: $ => seq(
|
||||
'institution',
|
||||
field('name', $.identifier),
|
||||
$.block
|
||||
field('body', $.block)
|
||||
),
|
||||
|
||||
// Relationship declaration
|
||||
relationship: $ => seq(
|
||||
relationship_declaration: $ => seq(
|
||||
'relationship',
|
||||
field('name', $.identifier),
|
||||
field('body', $.relationship_body)
|
||||
),
|
||||
|
||||
relationship_body: $ => seq(
|
||||
'{',
|
||||
repeat1($.participant),
|
||||
repeat($.field),
|
||||
@@ -354,22 +469,42 @@ module.exports = grammar({
|
||||
),
|
||||
|
||||
// Location declaration
|
||||
location: $ => seq(
|
||||
location_declaration: $ => seq(
|
||||
'location',
|
||||
field('name', $.identifier),
|
||||
$.block
|
||||
field('body', $.block)
|
||||
),
|
||||
|
||||
// Species declaration
|
||||
species: $ => seq(
|
||||
species_declaration: $ => seq(
|
||||
'species',
|
||||
field('name', $.identifier),
|
||||
field('body', $.species_body)
|
||||
),
|
||||
|
||||
species_body: $ => seq(
|
||||
'{',
|
||||
repeat($.include),
|
||||
repeat($.field),
|
||||
repeat($.species_field),
|
||||
'}'
|
||||
),
|
||||
|
||||
species_field: $ => choice(
|
||||
// Field with range: name: min..max
|
||||
seq(
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('value', $.range)
|
||||
),
|
||||
// Field with single value: name: value
|
||||
seq(
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('value', choice($.integer, $.float, $.boolean, $.string))
|
||||
),
|
||||
$.prose_block
|
||||
),
|
||||
|
||||
// Enum declaration
|
||||
enum_declaration: $ => seq(
|
||||
'enum',
|
||||
@@ -382,8 +517,7 @@ module.exports = grammar({
|
||||
// Concept declaration - base type with no structure
|
||||
concept_declaration: $ => seq(
|
||||
'concept',
|
||||
field('name', $.identifier),
|
||||
';'
|
||||
field('name', $.identifier)
|
||||
),
|
||||
|
||||
// Sub-concept declaration - enum or record subtype with dot notation
|
||||
@@ -403,13 +537,13 @@ module.exports = grammar({
|
||||
// Enum form: Variant1, Variant2, Variant3
|
||||
sub_concept_enum_body: $ => commaSep1($.identifier),
|
||||
|
||||
// Record form: field_name: Type, field_name: any
|
||||
// Record form: field_name: value, field_name: value
|
||||
sub_concept_record_body: $ => commaSep1($.sub_concept_field),
|
||||
|
||||
sub_concept_field: $ => seq(
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('type', choice($.identifier, $.any_type))
|
||||
field('value', choice($.integer, $.float, $.string, $.boolean))
|
||||
),
|
||||
|
||||
any_type: $ => 'any',
|
||||
@@ -432,25 +566,26 @@ module.exports = grammar({
|
||||
'}'
|
||||
),
|
||||
|
||||
// A condition on a sub_concept field
|
||||
// Field condition: field_name: any OR field_name: Type is Value [or Type is Value]*
|
||||
field_condition: $ => seq(
|
||||
field('sub_concept', $.dotted_path),
|
||||
field('name', $.identifier),
|
||||
':',
|
||||
field('condition', $.condition_expr)
|
||||
field('condition', choice(
|
||||
$.any_type,
|
||||
$.is_condition
|
||||
))
|
||||
),
|
||||
|
||||
// Condition: either 'any' or 'Type is Value or Type is Value2'
|
||||
condition_expr: $ => choice(
|
||||
$.any_type,
|
||||
$.is_condition
|
||||
),
|
||||
|
||||
// Pattern: DottedPath is Ident [or DottedPath is Ident]*
|
||||
// Is condition: Type is Value [or Type is Value]*
|
||||
is_condition: $ => seq(
|
||||
$.dotted_path,
|
||||
$.is_value,
|
||||
repeat(seq('or', $.is_value))
|
||||
),
|
||||
|
||||
is_value: $ => seq(
|
||||
field('field', $.identifier),
|
||||
'is',
|
||||
$.identifier,
|
||||
repeat(seq('or', $.dotted_path, 'is', $.identifier))
|
||||
field('value', $.identifier)
|
||||
),
|
||||
|
||||
// Expressions (for conditions in life arcs)
|
||||
|
||||
@@ -69,30 +69,30 @@
|
||||
(string) @string
|
||||
|
||||
; Identifiers in different contexts
|
||||
(character name: (identifier) @type.character)
|
||||
(template name: (identifier) @type.template)
|
||||
(life_arc name: (identifier) @type.life_arc)
|
||||
(schedule name: (identifier) @type.schedule)
|
||||
(behavior name: (identifier) @type.behavior)
|
||||
(institution name: (identifier) @type.institution)
|
||||
(relationship name: (identifier) @type.relationship)
|
||||
(location name: (identifier) @type.location)
|
||||
(species name: (identifier) @type.species)
|
||||
(character_declaration name: (identifier) @type.character)
|
||||
(template_declaration name: (identifier) @type.template)
|
||||
(life_arc_declaration name: (identifier) @type.life_arc)
|
||||
(schedule_declaration name: (identifier) @type.schedule)
|
||||
(behavior_declaration name: (identifier) @type.behavior)
|
||||
(institution_declaration name: (identifier) @type.institution)
|
||||
(relationship_declaration name: (identifier) @type.relationship)
|
||||
(location_declaration name: (identifier) @type.location)
|
||||
(species_declaration name: (identifier) @type.species)
|
||||
(enum_declaration name: (identifier) @type.enum)
|
||||
(arc_state name: (identifier) @type.state)
|
||||
(state_block name: (identifier) @type.state)
|
||||
(concept_declaration name: (identifier) @type.concept)
|
||||
(sub_concept parent: (identifier) @type.concept)
|
||||
(sub_concept name: (identifier) @type.sub_concept)
|
||||
(concept_comparison name: (identifier) @type.concept_comparison)
|
||||
(variant_pattern name: (identifier) @type.variant)
|
||||
(template species: (identifier) @type.builtin)
|
||||
(template_declaration species: (identifier) @type.builtin)
|
||||
|
||||
; Field names
|
||||
(field name: (dotted_path) @property)
|
||||
(sub_concept_field name: (identifier) @property)
|
||||
|
||||
; Species reference
|
||||
(character species: (identifier) @type.builtin)
|
||||
(character_declaration species: (identifier) @type.builtin)
|
||||
|
||||
; Paths and identifiers
|
||||
(path) @namespace
|
||||
@@ -148,7 +148,7 @@
|
||||
(transition target: (identifier) @type.state)
|
||||
|
||||
; Schedule blocks
|
||||
(schedule_block activity: (identifier) @function.activity)
|
||||
(schedule_block name: (identifier) @function.schedule_block)
|
||||
|
||||
; Override operations
|
||||
(override "@" @keyword.override)
|
||||
|
||||
@@ -24,17 +24,17 @@
|
||||
; Block structures that should indent their contents
|
||||
[
|
||||
(block)
|
||||
(character)
|
||||
(template)
|
||||
(life_arc)
|
||||
(arc_state)
|
||||
(schedule)
|
||||
(character_declaration)
|
||||
(template_declaration)
|
||||
(life_arc_declaration)
|
||||
(state_block)
|
||||
(schedule_declaration)
|
||||
(schedule_block)
|
||||
(behavior)
|
||||
(institution)
|
||||
(relationship)
|
||||
(location)
|
||||
(species)
|
||||
(behavior_declaration)
|
||||
(institution_declaration)
|
||||
(relationship_declaration)
|
||||
(location_declaration)
|
||||
(species_declaration)
|
||||
(enum_declaration)
|
||||
(selector_node)
|
||||
(sequence_node)
|
||||
@@ -46,4 +46,3 @@
|
||||
|
||||
; Dedent after semicolon at top level
|
||||
(use_declaration ";" @indent.end)
|
||||
(concept_declaration ";" @indent.end)
|
||||
|
||||
@@ -2,52 +2,52 @@
|
||||
; Defines what symbols appear in the document outline
|
||||
|
||||
; Characters
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.character
|
||||
|
||||
; Templates
|
||||
(template
|
||||
(template_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.template
|
||||
|
||||
; Life arcs
|
||||
(life_arc
|
||||
(life_arc_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.life_arc
|
||||
|
||||
; Life arc states
|
||||
(arc_state
|
||||
(state_block
|
||||
name: (identifier) @name
|
||||
) @symbol.state
|
||||
|
||||
; Schedules
|
||||
(schedule
|
||||
(schedule_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.schedule
|
||||
|
||||
; Behaviors
|
||||
(behavior
|
||||
(behavior_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.behavior
|
||||
|
||||
; Institutions
|
||||
(institution
|
||||
(institution_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.institution
|
||||
|
||||
; Relationships
|
||||
(relationship
|
||||
(relationship_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.relationship
|
||||
|
||||
; Locations
|
||||
(location
|
||||
(location_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.location
|
||||
|
||||
; Species
|
||||
(species
|
||||
(species_declaration
|
||||
name: (identifier) @name
|
||||
) @symbol.species
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,9 +10,9 @@ character Alice {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))))))
|
||||
@@ -47,9 +47,9 @@ character Bob {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
(prose_block
|
||||
marker: (prose_marker)
|
||||
|
||||
@@ -1,29 +1,11 @@
|
||||
==================
|
||||
Simple Behavior
|
||||
Basic behavior tree
|
||||
==================
|
||||
|
||||
behavior SimpleBehavior {
|
||||
walk_around
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(action_node (identifier))))))
|
||||
|
||||
==================
|
||||
Selector Behavior
|
||||
==================
|
||||
|
||||
behavior SelectorBehavior {
|
||||
? {
|
||||
try_option_a
|
||||
try_option_b
|
||||
fallback
|
||||
behavior BasicNeeds {
|
||||
then {
|
||||
CheckHunger
|
||||
if(hungry) { EatFood }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,23 +13,33 @@ behavior SelectorBehavior {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
(behavior_declaration
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(selector_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier))))))))
|
||||
body: (behavior_body
|
||||
root: (behavior_node
|
||||
(sequence_node
|
||||
(behavior_node
|
||||
(action_node
|
||||
(identifier)))
|
||||
(behavior_node
|
||||
(if_decorator_node
|
||||
condition: (expression
|
||||
(primary_expression
|
||||
(path
|
||||
(path_segments
|
||||
(identifier)))))
|
||||
child: (behavior_node
|
||||
(action_node
|
||||
(identifier)))))))))))
|
||||
|
||||
==================
|
||||
Sequence Behavior
|
||||
Behavior with selector
|
||||
==================
|
||||
|
||||
behavior SequenceBehavior {
|
||||
> {
|
||||
check_energy
|
||||
move_to_location
|
||||
perform_action
|
||||
behavior Combat {
|
||||
choose {
|
||||
if(has_weapon) { Attack }
|
||||
Flee
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,25 +47,32 @@ behavior SequenceBehavior {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
(behavior_declaration
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(sequence_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier))))))))
|
||||
body: (behavior_body
|
||||
root: (behavior_node
|
||||
(selector_node
|
||||
(behavior_node
|
||||
(if_decorator_node
|
||||
condition: (expression
|
||||
(primary_expression
|
||||
(path
|
||||
(path_segments
|
||||
(identifier)))))
|
||||
child: (behavior_node
|
||||
(action_node
|
||||
(identifier)))))
|
||||
(behavior_node
|
||||
(action_node
|
||||
(identifier)))))))))
|
||||
|
||||
==================
|
||||
Nested Behavior
|
||||
Behavior with repeat
|
||||
==================
|
||||
|
||||
behavior NestedBehavior {
|
||||
> {
|
||||
? {
|
||||
check_condition_a
|
||||
check_condition_b
|
||||
}
|
||||
perform_action
|
||||
behavior Patrol {
|
||||
repeat(5) {
|
||||
WalkPath
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,100 +80,12 @@ behavior NestedBehavior {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
(behavior_declaration
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(sequence_node
|
||||
(behavior_node
|
||||
(selector_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier)))))
|
||||
(behavior_node (action_node (identifier))))))))
|
||||
|
||||
==================
|
||||
Repeat Behavior
|
||||
==================
|
||||
|
||||
behavior RepeatBehavior {
|
||||
* {
|
||||
patrol
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(repeat_node
|
||||
(behavior_node (action_node (identifier))))))))
|
||||
|
||||
==================
|
||||
Behavior with Subtree
|
||||
==================
|
||||
|
||||
behavior WithSubtree {
|
||||
> {
|
||||
@helpers::check_preconditions
|
||||
main_action
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(sequence_node
|
||||
(behavior_node
|
||||
(subtree_node
|
||||
(path
|
||||
(path_segments
|
||||
(identifier)
|
||||
(identifier)))))
|
||||
(behavior_node (action_node (identifier))))))))
|
||||
|
||||
==================
|
||||
Complex Nested Behavior
|
||||
==================
|
||||
|
||||
behavior ComplexBehavior {
|
||||
? {
|
||||
> {
|
||||
check_threat
|
||||
flee_to_safety
|
||||
}
|
||||
> {
|
||||
check_resources
|
||||
gather_resources
|
||||
}
|
||||
* {
|
||||
idle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(selector_node
|
||||
(behavior_node
|
||||
(sequence_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier)))))
|
||||
(behavior_node
|
||||
(sequence_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node (action_node (identifier)))))
|
||||
(behavior_node
|
||||
(repeat_node
|
||||
(behavior_node (action_node (identifier))))))))))
|
||||
|
||||
body: (behavior_body
|
||||
root: (behavior_node
|
||||
(repeat_node
|
||||
params: (integer)
|
||||
child: (behavior_node
|
||||
(action_node
|
||||
(identifier)))))))))
|
||||
|
||||
@@ -67,11 +67,11 @@ character Alice: Human from Curious {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
species: (identifier)
|
||||
template: (template_clause (identifier))
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))
|
||||
@@ -110,16 +110,17 @@ template BaseCharacter strict {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(template
|
||||
(template_declaration
|
||||
name: (identifier)
|
||||
(include (identifier))
|
||||
(include (identifier))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string))))))
|
||||
body: (template_body
|
||||
(include (identifier))
|
||||
(include (identifier))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string)))))))
|
||||
|
||||
==================
|
||||
Life arc with states and transitions
|
||||
@@ -150,7 +151,7 @@ life_arc Journey {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(life_arc
|
||||
(life_arc_declaration
|
||||
name: (identifier)
|
||||
(field
|
||||
(prose_block
|
||||
@@ -158,115 +159,32 @@ life_arc Journey {
|
||||
tag: (identifier)
|
||||
content: (prose_content)
|
||||
end: (prose_marker)))
|
||||
(arc_state
|
||||
(state_block
|
||||
name: (identifier)
|
||||
(on_enter
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))))
|
||||
(transition
|
||||
condition: (expression
|
||||
(comparison
|
||||
(expression (primary_expression (path (path_segments (identifier)))))
|
||||
(expression (primary_expression (integer)))))
|
||||
target: (identifier)))
|
||||
(arc_state
|
||||
body: (state_body
|
||||
(on_enter
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))))
|
||||
(transition
|
||||
condition: (expression
|
||||
(comparison
|
||||
(expression (primary_expression (path (path_segments (identifier)))))
|
||||
(expression (primary_expression (integer)))))
|
||||
target: (identifier))))
|
||||
(state_block
|
||||
name: (identifier)
|
||||
(transition
|
||||
condition: (expression
|
||||
(comparison
|
||||
(expression (primary_expression (path (path_segments (identifier)))))
|
||||
(expression (primary_expression (boolean)))))
|
||||
target: (identifier)))
|
||||
(arc_state
|
||||
name: (identifier)))))
|
||||
|
||||
==================
|
||||
Schedule with time blocks
|
||||
==================
|
||||
|
||||
schedule DailyRoutine {
|
||||
08:00 -> 09:00: breakfast {
|
||||
location: kitchen
|
||||
}
|
||||
|
||||
09:00 -> 12:00: work {
|
||||
duration: 3h
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(schedule
|
||||
name: (identifier)
|
||||
(schedule_block
|
||||
start: (time)
|
||||
end: (time)
|
||||
activity: (identifier)
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (path (path_segments (identifier)))))))
|
||||
(schedule_block
|
||||
start: (time)
|
||||
end: (time)
|
||||
activity: (identifier)
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (duration))))))))
|
||||
|
||||
==================
|
||||
Behavior tree - all node types
|
||||
==================
|
||||
|
||||
behavior GuardBehavior {
|
||||
? {
|
||||
patrol
|
||||
> {
|
||||
detect_threat
|
||||
alert(priority: high, "Guard duty")
|
||||
}
|
||||
* {
|
||||
watch
|
||||
}
|
||||
@base::behaviors::Idle
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(behavior
|
||||
name: (identifier)
|
||||
root: (behavior_node
|
||||
(selector_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node
|
||||
(sequence_node
|
||||
(behavior_node (action_node (identifier)))
|
||||
(behavior_node
|
||||
(action_node
|
||||
(identifier)
|
||||
(action_param
|
||||
(dotted_path (identifier))
|
||||
(value (path (path_segments (identifier)))))
|
||||
(action_param
|
||||
(value (string)))))))
|
||||
(behavior_node
|
||||
(repeat_node
|
||||
(behavior_node (action_node (identifier)))))
|
||||
(behavior_node
|
||||
(subtree_node
|
||||
(path
|
||||
(path_segments
|
||||
(identifier)
|
||||
(identifier)
|
||||
(identifier))))))))))
|
||||
body: (state_body
|
||||
(transition
|
||||
condition: (expression
|
||||
(comparison
|
||||
(expression (primary_expression (path (path_segments (identifier)))))
|
||||
(expression (primary_expression (boolean)))))
|
||||
target: (identifier))))
|
||||
(state_block
|
||||
name: (identifier)
|
||||
body: (state_body)))))
|
||||
|
||||
==================
|
||||
Institution
|
||||
@@ -281,9 +199,9 @@ institution Wonderland {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(institution
|
||||
(institution_declaration
|
||||
name: (identifier)
|
||||
(block
|
||||
body: (block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string)))
|
||||
@@ -291,44 +209,6 @@ institution Wonderland {
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string)))))))
|
||||
|
||||
==================
|
||||
Relationship with participants
|
||||
==================
|
||||
|
||||
relationship Friendship {
|
||||
Alice {
|
||||
bond_strength: 5
|
||||
}
|
||||
|
||||
WhiteRabbit as friend {
|
||||
trust: 0.8
|
||||
}
|
||||
|
||||
Caterpillar
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(relationship
|
||||
name: (identifier)
|
||||
(participant
|
||||
(path (path_segments (identifier)))
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer)))))
|
||||
(participant
|
||||
(path (path_segments (identifier)))
|
||||
(identifier)
|
||||
(block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (float)))))
|
||||
(participant
|
||||
(path (path_segments (identifier)))))))
|
||||
|
||||
==================
|
||||
Location
|
||||
==================
|
||||
@@ -342,9 +222,9 @@ location TeaParty {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(location
|
||||
(location_declaration
|
||||
name: (identifier)
|
||||
(block
|
||||
body: (block
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string)))
|
||||
@@ -367,15 +247,16 @@ species Cat {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(species
|
||||
(species_declaration
|
||||
name: (identifier)
|
||||
(include (identifier))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (boolean)))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (integer))))))
|
||||
body: (species_body
|
||||
(include (identifier))
|
||||
(species_field
|
||||
name: (identifier)
|
||||
value: (boolean))
|
||||
(species_field
|
||||
name: (identifier)
|
||||
value: (integer))))))
|
||||
|
||||
==================
|
||||
Enum declaration
|
||||
@@ -413,9 +294,9 @@ character SpecialAlice {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value
|
||||
@@ -452,49 +333,52 @@ life_arc ComplexLogic {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(life_arc
|
||||
(life_arc_declaration
|
||||
name: (identifier)
|
||||
(arc_state
|
||||
(state_block
|
||||
name: (identifier)
|
||||
(transition
|
||||
condition: (expression
|
||||
(and_expression
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (integer)))))
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (float)))))))
|
||||
target: (identifier))
|
||||
(transition
|
||||
condition: (expression
|
||||
(or_expression
|
||||
(expression
|
||||
(not_expression
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))))
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (boolean)))))))
|
||||
target: (identifier)))
|
||||
(arc_state
|
||||
name: (identifier))
|
||||
(arc_state
|
||||
name: (identifier)))))
|
||||
body: (state_body
|
||||
(transition
|
||||
condition: (expression
|
||||
(and_expression
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (integer)))))
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (float)))))))
|
||||
target: (identifier))
|
||||
(transition
|
||||
condition: (expression
|
||||
(or_expression
|
||||
(expression
|
||||
(not_expression
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))))
|
||||
(expression
|
||||
(comparison
|
||||
(expression
|
||||
(field_access
|
||||
(expression (primary_expression))
|
||||
(identifier)))
|
||||
(expression (primary_expression (boolean)))))))
|
||||
target: (identifier))))
|
||||
(state_block
|
||||
name: (identifier)
|
||||
body: (state_body))
|
||||
(state_block
|
||||
name: (identifier)
|
||||
body: (state_body)))))
|
||||
|
||||
==================
|
||||
List and object values
|
||||
@@ -513,9 +397,9 @@ character DataRich {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value
|
||||
@@ -560,9 +444,9 @@ character Nested {
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(character
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
body: (block
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path
|
||||
(identifier)
|
||||
|
||||
53
tree-sitter-storybook/test/corpus/highlights.txt
Normal file
53
tree-sitter-storybook/test/corpus/highlights.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
==================
|
||||
Keywords should highlight
|
||||
==================
|
||||
|
||||
use schema::types;
|
||||
|
||||
character Alice from Person {
|
||||
age: 25
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(use_declaration
|
||||
(path_segments
|
||||
(identifier)
|
||||
(identifier))))
|
||||
(declaration
|
||||
(character_declaration
|
||||
name: (identifier)
|
||||
template: (template_clause
|
||||
(identifier))
|
||||
body: (character_body
|
||||
(field
|
||||
name: (dotted_path
|
||||
(identifier))
|
||||
value: (value
|
||||
(integer)))))))
|
||||
|
||||
==================
|
||||
Any type in sub-concept
|
||||
==================
|
||||
|
||||
sub_concept Field.Type {
|
||||
name: "text",
|
||||
value: 42
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(sub_concept
|
||||
parent: (identifier)
|
||||
name: (identifier)
|
||||
body: (sub_concept_record_body
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
value: (string))
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
value: (integer))))))
|
||||
106
tree-sitter-storybook/test/corpus/schedules.txt
Normal file
106
tree-sitter-storybook/test/corpus/schedules.txt
Normal file
@@ -0,0 +1,106 @@
|
||||
==================
|
||||
Basic schedule
|
||||
==================
|
||||
|
||||
schedule WorkDay {
|
||||
block morning {
|
||||
08:00 -> 12:00
|
||||
action: Work
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(schedule_declaration
|
||||
name: (identifier)
|
||||
body: (schedule_body
|
||||
(schedule_block
|
||||
name: (identifier)
|
||||
time_range: (time_range
|
||||
start: (time)
|
||||
end: (time))
|
||||
(block_field
|
||||
name: (identifier)
|
||||
value: (identifier)))))))
|
||||
|
||||
==================
|
||||
Schedule with extends
|
||||
==================
|
||||
|
||||
schedule BakerDay extends WorkDay {
|
||||
block early_prep {
|
||||
05:00 -> 08:00
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(schedule_declaration
|
||||
name: (identifier)
|
||||
extends: (identifier)
|
||||
body: (schedule_body
|
||||
(schedule_block
|
||||
name: (identifier)
|
||||
time_range: (time_range
|
||||
start: (time)
|
||||
end: (time)))))))
|
||||
|
||||
==================
|
||||
Schedule with override
|
||||
==================
|
||||
|
||||
schedule CustomDay extends BaseDay {
|
||||
override work {
|
||||
06:00 -> 14:00
|
||||
intensity: "high"
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(schedule_declaration
|
||||
name: (identifier)
|
||||
extends: (identifier)
|
||||
body: (schedule_body
|
||||
(override_block
|
||||
name: (identifier)
|
||||
time_range: (time_range
|
||||
start: (time)
|
||||
end: (time))
|
||||
(block_field
|
||||
name: (identifier)
|
||||
value: (string)))))))
|
||||
|
||||
==================
|
||||
Schedule with recurrence
|
||||
==================
|
||||
|
||||
schedule WeeklySchedule {
|
||||
recurrence MarketDay on Saturday {
|
||||
block market {
|
||||
06:00 -> 14:00
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(schedule_declaration
|
||||
name: (identifier)
|
||||
body: (schedule_body
|
||||
(recurrence_block
|
||||
name: (identifier)
|
||||
day: (identifier)
|
||||
(schedule_block
|
||||
name: (identifier)
|
||||
time_range: (time_range
|
||||
start: (time)
|
||||
end: (time))))))))
|
||||
@@ -2,7 +2,7 @@
|
||||
Concept declaration
|
||||
==================
|
||||
|
||||
concept Cup;
|
||||
concept BakedGood
|
||||
|
||||
---
|
||||
|
||||
@@ -12,34 +12,13 @@ concept Cup;
|
||||
name: (identifier))))
|
||||
|
||||
==================
|
||||
Multiple concept declarations
|
||||
Sub-concept enum declaration
|
||||
==================
|
||||
|
||||
concept Cup;
|
||||
concept Customer;
|
||||
concept Vendor;
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(concept_declaration
|
||||
name: (identifier)))
|
||||
(declaration
|
||||
(concept_declaration
|
||||
name: (identifier)))
|
||||
(declaration
|
||||
(concept_declaration
|
||||
name: (identifier))))
|
||||
|
||||
==================
|
||||
Sub-concept - enum form
|
||||
==================
|
||||
|
||||
sub_concept Cup.Size {
|
||||
Small,
|
||||
Medium,
|
||||
Large
|
||||
sub_concept BakedGood.Category {
|
||||
Bread,
|
||||
Pastry,
|
||||
Cake
|
||||
}
|
||||
|
||||
---
|
||||
@@ -55,14 +34,12 @@ sub_concept Cup.Size {
|
||||
(identifier)))))
|
||||
|
||||
==================
|
||||
Sub-concept - record form with any
|
||||
Sub-concept record declaration with values
|
||||
==================
|
||||
|
||||
sub_concept Vendor.Inventory {
|
||||
Bread: any,
|
||||
Pastries: any,
|
||||
Cakes: any,
|
||||
Cup: any
|
||||
sub_concept BakedGood.Quality {
|
||||
freshness: 1.0,
|
||||
taste: 0.8
|
||||
}
|
||||
|
||||
---
|
||||
@@ -75,51 +52,18 @@ sub_concept Vendor.Inventory {
|
||||
body: (sub_concept_record_body
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (any_type))
|
||||
value: (float))
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (any_type))
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (any_type))
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (any_type))))))
|
||||
value: (float))))))
|
||||
|
||||
==================
|
||||
Sub-concept - record form with typed fields
|
||||
Concept comparison
|
||||
==================
|
||||
|
||||
sub_concept Vendor.Inventory {
|
||||
Bread: any,
|
||||
Pastries: Number
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(sub_concept
|
||||
parent: (identifier)
|
||||
name: (identifier)
|
||||
body: (sub_concept_record_body
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (any_type))
|
||||
(sub_concept_field
|
||||
name: (identifier)
|
||||
type: (identifier))))))
|
||||
|
||||
==================
|
||||
Concept comparison with any conditions
|
||||
==================
|
||||
|
||||
concept_comparison NotInterested {
|
||||
NotInterested: {
|
||||
Cup.Size: any,
|
||||
Cup.Type: any,
|
||||
Cup.Color: any
|
||||
}
|
||||
concept_comparison SkillLevel {
|
||||
Apprentice: { freshness: any },
|
||||
Master: { freshness: Tier is Master }
|
||||
}
|
||||
|
||||
---
|
||||
@@ -131,230 +75,58 @@ concept_comparison NotInterested {
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(any_type)))
|
||||
name: (identifier)
|
||||
condition: (any_type)))
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(any_type)))
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(any_type)))))))
|
||||
name: (identifier)
|
||||
condition: (is_condition
|
||||
(is_value
|
||||
field: (identifier)
|
||||
value: (identifier))))))))
|
||||
|
||||
==================
|
||||
Concept comparison with is conditions
|
||||
Species declaration
|
||||
==================
|
||||
|
||||
concept_comparison Interest {
|
||||
Interested: {
|
||||
Cup.Type: Cup.Type is Glass or Cup.Type is Plastic,
|
||||
Cup.Color: Cup.Color is Red
|
||||
}
|
||||
species Human {
|
||||
age: 25
|
||||
energy: 0.75
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(concept_comparison
|
||||
(species_declaration
|
||||
name: (identifier)
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier)
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier))))
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier))))))))
|
||||
body: (species_body
|
||||
(species_field
|
||||
name: (identifier)
|
||||
value: (integer))
|
||||
(species_field
|
||||
name: (identifier)
|
||||
value: (float))))))
|
||||
|
||||
==================
|
||||
Concept comparison with multiple variants
|
||||
==================
|
||||
|
||||
concept_comparison FoodQuality {
|
||||
Excellent: {
|
||||
Food.Freshness: Food.Freshness is Fresh
|
||||
},
|
||||
Poor: {
|
||||
Food.Freshness: Food.Freshness is Stale or Food.Freshness is Spoiled
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(concept_comparison
|
||||
name: (identifier)
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier)))))
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier)
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier))))))))
|
||||
|
||||
==================
|
||||
Template with species extension
|
||||
Template with species base
|
||||
==================
|
||||
|
||||
template Person: Human {
|
||||
age: 0..100
|
||||
name: ""
|
||||
name: "Unknown"
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(template
|
||||
(template_declaration
|
||||
name: (identifier)
|
||||
species: (identifier)
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (range (integer) (integer))))
|
||||
(field
|
||||
name: (dotted_path (identifier))
|
||||
value: (value (string))))))
|
||||
|
||||
==================
|
||||
Full type system example
|
||||
==================
|
||||
|
||||
concept Cup;
|
||||
|
||||
sub_concept Cup.Size {
|
||||
Small,
|
||||
Medium,
|
||||
Large
|
||||
}
|
||||
|
||||
sub_concept Cup.Type {
|
||||
Ceramic,
|
||||
Glass,
|
||||
Plastic
|
||||
}
|
||||
|
||||
concept_comparison CupPreference {
|
||||
Preferred: {
|
||||
Cup.Size: any,
|
||||
Cup.Type: Cup.Type is Glass or Cup.Type is Ceramic
|
||||
},
|
||||
Avoided: {
|
||||
Cup.Size: any,
|
||||
Cup.Type: Cup.Type is Plastic
|
||||
}
|
||||
}
|
||||
|
||||
---
|
||||
|
||||
(source_file
|
||||
(declaration
|
||||
(concept_declaration
|
||||
name: (identifier)))
|
||||
(declaration
|
||||
(sub_concept
|
||||
parent: (identifier)
|
||||
name: (identifier)
|
||||
body: (sub_concept_enum_body
|
||||
(identifier)
|
||||
(identifier)
|
||||
(identifier))))
|
||||
(declaration
|
||||
(sub_concept
|
||||
parent: (identifier)
|
||||
name: (identifier)
|
||||
body: (sub_concept_enum_body
|
||||
(identifier)
|
||||
(identifier)
|
||||
(identifier))))
|
||||
(declaration
|
||||
(concept_comparison
|
||||
name: (identifier)
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
body: (template_body
|
||||
(field
|
||||
name: (dotted_path
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(any_type)))
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier)
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier)))))
|
||||
(variant_pattern
|
||||
name: (identifier)
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(any_type)))
|
||||
(field_condition
|
||||
sub_concept: (dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
condition: (condition_expr
|
||||
(is_condition
|
||||
(dotted_path
|
||||
(identifier)
|
||||
(identifier))
|
||||
(identifier))))))))
|
||||
value: (value
|
||||
(string)))))))
|
||||
|
||||
Submodule zed-storybook/grammars/storybook deleted from 26bbef58d3
Reference in New Issue
Block a user