feat(tree-sitter): add Tree-sitter grammar for Storybook DSL

This commit is contained in:
2026-02-09 22:06:25 +00:00
parent 734eb93f12
commit b100ded3d3
27 changed files with 13383 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
[package]
name = "tree-sitter-storybook"
version = "0.1.0"
description = "Tree-sitter grammar for Storybook narrative DSL"
authors = ["Storybook Contributors"]
license = "MIT"
readme = "README.md"
keywords = ["tree-sitter", "parser", "storybook"]
repository = "https://github.com/yourusername/storybook"
edition = "2021"
include = [
"bindings/rust/*",
"grammar.js",
"queries/*",
"src/*"
]
[lib]
path = "bindings/rust/lib.rs"
[dependencies]
tree-sitter = "~0.20"
[build-dependencies]
cc = "1.0"

View File

@@ -0,0 +1,88 @@
# tree-sitter-storybook
Tree-sitter grammar for the Storybook narrative DSL.
## Overview
This is a Tree-sitter grammar for Storybook, a domain-specific language for narrative design and character development. The grammar provides syntax highlighting, code navigation, and other editor features through Tree-sitter.
## Features
- **Syntax highlighting** for all Storybook constructs
- **Symbol outline** for navigating characters, templates, relationships, etc.
- **Bracket matching** including special handling for prose blocks
- **Auto-indentation** support
## Installation
### For development
```bash
# Install dependencies
npm install
# Generate parser
npm run build
# Run tests
npm run test
```
### For Zed editor
This grammar is integrated into the Zed Storybook extension. See the `zed-storybook` directory for the extension.
## Grammar Structure
The grammar supports the following top-level declarations:
- `use` - Import statements
- `character` - Character definitions
- `template` - Reusable field templates
- `life_arc` - State machines for character development
- `schedule` - Daily schedules and routines
- `behavior` - Behavior trees for AI
- `institution` - Organizations and groups
- `relationship` - Multi-party relationships
- `location` - Places and settings
- `species` - Species definitions with templates
- `enum` - Enumeration types
## Special Features
### Prose Blocks
The grammar includes an external scanner (written in C) to handle prose blocks:
```storybook
---backstory
This is a prose block that can contain
multiple lines of narrative text.
---
```
### Expression Support
Full support for expressions in life arc transitions:
```storybook
on self.age >= 18 and self.location is wonderland -> adult_state
```
## Testing
The grammar includes comprehensive tests. Run them with:
```bash
tree-sitter test
```
To test parsing a specific file:
```bash
tree-sitter parse path/to/file.sb
```
## License
MIT

View File

@@ -0,0 +1,19 @@
{
"targets": [
{
"target_name": "tree_sitter_storybook_binding",
"include_dirs": [
"<!(node -e \"require('nan')\")",
"src"
],
"sources": [
"bindings/node/binding.cc",
"src/parser.c",
"src/scanner.c"
],
"cflags_c": [
"-std=c99",
]
}
]
}

View File

@@ -0,0 +1,28 @@
#include "tree_sitter/parser.h"
#include <node.h>
#include "nan.h"
using namespace v8;
extern "C" TSLanguage * tree_sitter_storybook();
namespace {
NAN_METHOD(New) {}
void Init(Local<Object> exports, Local<Object> module) {
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
tpl->SetClassName(Nan::New("Language").ToLocalChecked());
tpl->InstanceTemplate()->SetInternalFieldCount(1);
Local<Function> constructor = Nan::GetFunction(tpl).ToLocalChecked();
Local<Object> instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked();
Nan::SetInternalFieldPointer(instance, 0, tree_sitter_storybook());
Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("storybook").ToLocalChecked());
Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance);
}
NODE_MODULE(tree_sitter_storybook_binding, Init)
} // namespace

View File

@@ -0,0 +1,19 @@
try {
module.exports = require("../../build/Release/tree_sitter_storybook_binding");
} catch (error1) {
if (error1.code !== 'MODULE_NOT_FOUND') {
throw error1;
}
try {
module.exports = require("../../build/Debug/tree_sitter_storybook_binding");
} catch (error2) {
if (error2.code !== 'MODULE_NOT_FOUND') {
throw error2;
}
throw error1
}
}
try {
module.exports.nodeTypeInfo = require("../../src/node-types.json");
} catch (_) {}

View File

@@ -0,0 +1,40 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(&src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
// If your language uses an external scanner written in C,
// then include this block of code:
/*
let scanner_path = src_dir.join("scanner.c");
c_config.file(&scanner_path);
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
c_config.compile("parser");
println!("cargo:rerun-if-changed={}", parser_path.to_str().unwrap());
// If your language uses an external scanner written in C++,
// then include this block of code:
/*
let mut cpp_config = cc::Build::new();
cpp_config.cpp(true);
cpp_config.include(&src_dir);
cpp_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable");
let scanner_path = src_dir.join("scanner.cc");
cpp_config.file(&scanner_path);
cpp_config.compile("scanner");
println!("cargo:rerun-if-changed={}", scanner_path.to_str().unwrap());
*/
}

View File

@@ -0,0 +1,52 @@
//! This crate provides storybook language support for the [tree-sitter][] parsing library.
//!
//! Typically, you will use the [language][language func] function to add this language to a
//! tree-sitter [Parser][], and then use the parser to parse some code:
//!
//! ```
//! let code = "";
//! let mut parser = tree_sitter::Parser::new();
//! parser.set_language(tree_sitter_storybook::language()).expect("Error loading storybook grammar");
//! let tree = parser.parse(code, None).unwrap();
//! ```
//!
//! [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
//! [language func]: fn.language.html
//! [Parser]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Parser.html
//! [tree-sitter]: https://tree-sitter.github.io/
use tree_sitter::Language;
extern "C" {
fn tree_sitter_storybook() -> Language;
}
/// Get the tree-sitter [Language][] for this grammar.
///
/// [Language]: https://docs.rs/tree-sitter/*/tree_sitter/struct.Language.html
pub fn language() -> Language {
unsafe { tree_sitter_storybook() }
}
/// The content of the [`node-types.json`][] file for this grammar.
///
/// [`node-types.json`]: https://tree-sitter.github.io/tree-sitter/using-parsers#static-node-types
pub const NODE_TYPES: &'static str = include_str!("../../src/node-types.json");
// Uncomment these to include any queries that this grammar contains
// pub const HIGHLIGHTS_QUERY: &'static str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &'static str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &'static str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &'static str = include_str!("../../queries/tags.scm");
#[cfg(test)]
mod tests {
#[test]
fn test_can_load_grammar() {
let mut parser = tree_sitter::Parser::new();
parser
.set_language(super::language())
.expect("Error loading storybook language");
}
}

View File

@@ -0,0 +1,20 @@
fn main() {
let src_dir = std::path::Path::new("src");
let mut c_config = cc::Build::new();
c_config.include(src_dir);
c_config
.flag_if_supported("-Wno-unused-parameter")
.flag_if_supported("-Wno-unused-but-set-variable")
.flag_if_supported("-Wno-trigraphs");
let parser_path = src_dir.join("parser.c");
c_config.file(&parser_path);
// If we have a scanner
let scanner_path = src_dir.join("scanner.c");
if scanner_path.exists() {
c_config.file(&scanner_path);
}
c_config.compile("tree-sitter-storybook");
}

View File

@@ -0,0 +1,379 @@
/**
* Tree-sitter grammar for Storybook DSL
*
* This grammar defines the syntax for the Storybook narrative DSL,
* including characters, templates, relationships, life arcs, behaviors, etc.
*/
module.exports = grammar({
name: 'storybook',
// externals: $ => [
// $._prose_block_content,
// $._prose_block_end
// ],
extras: $ => [
/\s/, // Whitespace
$.line_comment,
$.block_comment
],
conflicts: $ => [
[$.path_segments]
],
word: $ => $.identifier,
rules: {
// Top-level structure
source_file: $ => repeat($.declaration),
// Comments
line_comment: $ => token(seq('//', /.*/)),
block_comment: $ => token(seq('/*', /[^*]*\*+([^/*][^*]*\*+)*/, '/')),
// Declarations
declaration: $ => choice(
$.use_declaration,
$.character,
$.template,
$.life_arc,
$.schedule,
$.behavior,
$.institution,
$.relationship,
$.location,
$.species,
$.enum_declaration
),
// Use declarations
use_declaration: $ => seq(
'use',
$.path_segments,
optional(choice(
seq('::', '{', commaSep1($.identifier), '}'),
seq('::', '*')
)),
';'
),
path: $ => $.path_segments,
path_segments: $ => sep1($.identifier, token('::')),
// Character declaration
character: $ => seq(
'character',
field('name', $.identifier),
optional(seq(':', field('species', $.identifier))),
optional(field('template', $.template_clause)),
field('body', $.block)
),
template_clause: $ => seq('from', commaSep1($.identifier)),
// Template declaration
template: $ => seq(
'template',
field('name', $.identifier),
optional('strict'),
'{',
repeat($.include),
repeat($.field),
'}'
),
include: $ => seq('include', $.identifier),
// Fields (key: value pairs)
field: $ => choice(
seq(
field('name', $.dotted_path),
':',
field('value', $.value)
),
$.prose_block
),
dotted_path: $ => sep1($.identifier, '.'),
// Values
value: $ => choice(
$.integer,
$.float,
$.string,
$.boolean,
$.range,
$.time,
$.duration,
$.path,
$.prose_block,
$.list,
$.object,
$.override
),
integer: $ => /-?[0-9]+/,
float: $ => /-?[0-9]+\.[0-9]+/,
string: $ => /"([^"\\]|\\.)*"/,
boolean: $ => choice('true', 'false'),
range: $ => choice(
seq($.integer, '..', $.integer),
seq($.float, '..', $.float)
),
time: $ => /[0-9]{2}:[0-9]{2}(:[0-9]{2})?/,
duration: $ => /[0-9]+[hms]([0-9]+[hms])*/,
list: $ => seq('[', commaSep($.value), ']'),
object: $ => $.block,
block: $ => seq('{', repeat($.field), '}'),
// Override (@base { remove field, field: value })
override: $ => seq(
'@',
$.path,
'{',
repeat($.override_op),
'}'
),
override_op: $ => choice(
seq('remove', $.identifier),
seq('append', $.field),
$.field
),
// Prose blocks (---tag content ---)
prose_block: $ => seq(
field('marker', $.prose_marker),
field('tag', $.identifier),
optional(/[^\n]*/), // Rest of opening line
field('content', $.prose_content),
field('end', $.prose_marker)
),
prose_marker: $ => '---',
// Capture prose content as a single token for markdown injection
prose_content: $ => token(prec(-1, repeat1(choice(
/[^\-]+/, // Any non-dash characters
/-[^\-]/, // Single dash not followed by another dash
/-\-[^\-]/, // Two dashes not followed by another dash
)))),
// Life arc declaration
life_arc: $ => seq(
'life_arc',
field('name', $.identifier),
'{',
repeat($.field),
repeat($.arc_state),
'}'
),
arc_state: $ => seq(
'state',
field('name', $.identifier),
'{',
optional($.on_enter),
repeat($.field),
repeat($.transition),
'}'
),
on_enter: $ => seq('on', 'enter', $.block),
transition: $ => seq(
'on',
field('condition', $.expression),
'->',
field('target', $.identifier)
),
// Schedule declaration
schedule: $ => seq(
'schedule',
field('name', $.identifier),
'{',
repeat($.field),
repeat($.schedule_block),
'}'
),
schedule_block: $ => seq(
field('start', $.time),
'->',
field('end', $.time),
':',
field('activity', $.identifier),
$.block
),
// Behavior tree declaration
behavior: $ => seq(
'behavior',
field('name', $.identifier),
'{',
repeat($.field),
field('root', $.behavior_node),
'}'
),
behavior_node: $ => choice(
$.selector_node,
$.sequence_node,
$.repeat_node,
$.action_node,
$.subtree_node
),
selector_node: $ => seq('?', '{', repeat1($.behavior_node), '}'),
sequence_node: $ => seq('>', '{', repeat1($.behavior_node), '}'),
repeat_node: $ => seq('*', '{', $.behavior_node, '}'),
action_node: $ => choice(
seq($.identifier, '(', commaSep($.action_param), ')'),
$.identifier
),
action_param: $ => choice(
// Named parameter: name: value
seq($.dotted_path, ':', $.value),
// Positional parameter: just value
$.value
),
subtree_node: $ => seq('@', $.path),
// Institution declaration
institution: $ => seq(
'institution',
field('name', $.identifier),
$.block
),
// Relationship declaration
relationship: $ => seq(
'relationship',
field('name', $.identifier),
'{',
repeat1($.participant),
repeat($.field),
'}'
),
participant: $ => choice(
// name { fields }
seq($.path, $.block),
// name as role { fields }
seq($.path, 'as', $.identifier, $.block),
// bare name
$.path
),
// Location declaration
location: $ => seq(
'location',
field('name', $.identifier),
$.block
),
// Species declaration
species: $ => seq(
'species',
field('name', $.identifier),
'{',
repeat($.include),
repeat($.field),
'}'
),
// Enum declaration
enum_declaration: $ => seq(
'enum',
field('name', $.identifier),
'{',
commaSep1($.identifier),
'}'
),
// Expressions (for conditions in life arcs)
expression: $ => choice(
$.or_expression,
$.and_expression,
$.not_expression,
$.comparison,
$.field_access,
$.primary_expression
),
or_expression: $ => prec.left(1, seq(
$.expression,
'or',
$.expression
)),
and_expression: $ => prec.left(2, seq(
$.expression,
'and',
$.expression
)),
not_expression: $ => prec(3, seq('not', $.expression)),
comparison: $ => prec.left(4, seq(
$.expression,
field('operator', choice('is', '>', '>=', '<', '<=')),
$.expression
)),
field_access: $ => prec.left(5, seq(
$.expression,
'.',
$.identifier
)),
primary_expression: $ => choice(
'self',
'other',
$.integer,
$.float,
$.string,
$.boolean,
$.path
),
// Identifiers
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
}
});
/**
* Helper to create comma-separated lists with optional trailing comma
*/
function commaSep(rule) {
return optional(commaSep1(rule));
}
function commaSep1(rule) {
return seq(rule, repeat(seq(',', rule)), optional(','));
}
/**
* Helper to create separator-based lists (e.g., :: or .)
*/
function sep1(rule, separator) {
return seq(rule, repeat(seq(separator, rule)));
}

554
tree-sitter-storybook/package-lock.json generated Normal file
View File

@@ -0,0 +1,554 @@
{
"name": "tree-sitter-storybook",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tree-sitter-storybook",
"version": "0.1.0",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-addon-api": "^7.1.0",
"node-gyp-build": "^4.8.0"
},
"devDependencies": {
"prebuildify": "^6.0.0",
"tree-sitter-cli": "^0.20.8"
},
"peerDependencies": {
"tree-sitter": "^0.20.4"
},
"peerDependenciesMeta": {
"tree_sitter": {
"optional": true
}
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT",
"peer": true
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC",
"peer": true
},
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/nan": {
"version": "2.25.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.25.0.tgz",
"integrity": "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g==",
"license": "MIT",
"peer": true
},
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT",
"peer": true
},
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-addon-api": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"license": "MIT"
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/npm-run-path": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz",
"integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"peer": true,
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prebuildify": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/prebuildify/-/prebuildify-6.0.1.tgz",
"integrity": "sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"minimist": "^1.2.5",
"mkdirp-classic": "^0.5.3",
"node-abi": "^3.3.0",
"npm-run-path": "^3.1.0",
"pump": "^3.0.0",
"tar-fs": "^2.1.0"
},
"bin": {
"prebuildify": "bin.js"
}
},
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"peer": true,
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tree-sitter": {
"version": "0.20.6",
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.20.6.tgz",
"integrity": "sha512-GxJodajVpfgb3UREzzIbtA1hyRnTxVbWVXrbC6sk4xTMH5ERMBJk9HJNq4c8jOJeUaIOmLcwg+t6mez/PDvGqg==",
"hasInstallScript": true,
"license": "MIT",
"peer": true,
"dependencies": {
"nan": "^2.18.0",
"prebuild-install": "^7.1.1"
}
},
"node_modules/tree-sitter-cli": {
"version": "0.20.8",
"resolved": "https://registry.npmjs.org/tree-sitter-cli/-/tree-sitter-cli-0.20.8.tgz",
"integrity": "sha512-XjTcS3wdTy/2cc/ptMLc/WRyOLECRYcMTrSWyhZnj1oGSOWbHLTklgsgRICU3cPfb0vy+oZCC33M43u6R1HSCA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"tree-sitter": "cli.js"
}
},
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
}
}
}

View File

@@ -0,0 +1,55 @@
{
"name": "tree-sitter-storybook",
"version": "0.1.0",
"description": "Tree-sitter grammar for Storybook narrative DSL",
"main": "bindings/node",
"types": "bindings/node",
"keywords": [
"tree-sitter",
"parser",
"storybook"
],
"files": [
"grammar.js",
"binding.gyp",
"prebuilds/**",
"bindings/node/*",
"queries/*",
"src/**"
],
"dependencies": {
"node-addon-api": "^7.1.0",
"node-gyp-build": "^4.8.0"
},
"devDependencies": {
"tree-sitter-cli": "^0.20.8",
"prebuildify": "^6.0.0"
},
"peerDependencies": {
"tree-sitter": "^0.20.4"
},
"peerDependenciesMeta": {
"tree_sitter": {
"optional": true
}
},
"scripts": {
"install": "node-gyp-build",
"prebuildify": "prebuildify --napi --strip",
"build": "tree-sitter generate && node-gyp-build",
"build-wasm": "tree-sitter build-wasm",
"test": "tree-sitter test"
},
"tree-sitter": [
{
"scope": "source.storybook",
"file-types": ["sb"],
"highlights": "queries/highlights.scm"
}
],
"repository": {
"type": "git",
"url": "https://github.com/yourusername/storybook"
},
"license": "MIT"
}

View File

@@ -0,0 +1,16 @@
; Bracket matching for Storybook DSL
; Curly braces
("{" @open "}" @close) @bracket
; Parentheses
("(" @open ")" @close) @bracket
; Square brackets
("[" @open "]" @close) @bracket
; Prose blocks (special bracket matching for --- markers)
; Note: Both markers are the same node type
(prose_block
(prose_marker) @open
(prose_marker) @close) @bracket

View File

@@ -0,0 +1,153 @@
; Highlights query for Storybook DSL
; Maps grammar nodes to standard highlight groups
; Comments
(line_comment) @comment.line
(block_comment) @comment.block
; Keywords - Declaration keywords
[
"character"
"template"
"life_arc"
"schedule"
"behavior"
"institution"
"relationship"
"location"
"species"
"enum"
"state"
] @keyword.declaration
; Keywords - Control flow and modifiers
[
"and"
"or"
"not"
"on"
"enter"
"strict"
] @keyword.control
; Keywords - Import/module
[
"use"
"include"
"from"
] @keyword.import
; Keywords - Special
[
"as"
"self"
"other"
"remove"
"append"
"is"
] @keyword.special
; Boolean literals
[
"true"
"false"
] @constant.builtin.boolean
; Numbers
(integer) @constant.numeric.integer
(float) @constant.numeric.float
(time) @constant.numeric.time
(duration) @constant.numeric.duration
; Strings
(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)
(enum_declaration name: (identifier) @type.enum)
(arc_state name: (identifier) @type.state)
; Field names
(field name: (dotted_path) @property)
; Species reference
(character species: (identifier) @type.builtin)
; Paths and identifiers
(path) @namespace
(identifier) @variable
; Prose blocks - tag and content
(prose_block tag: (identifier) @tag)
(prose_block marker: (prose_marker) @punctuation.delimiter)
(prose_content) @markup.raw
; Operators
[
">"
">="
"<"
"<="
"->"
"is"
] @operator
; Punctuation
[
"{"
"}"
] @punctuation.bracket
[
"("
")"
] @punctuation.bracket
[
"["
"]"
] @punctuation.bracket
[
":"
"::"
";"
","
"."
".."
"*"
"?"
"@"
] @punctuation.delimiter
; Behavior tree nodes
(selector_node "?" @keyword.behavior.selector)
(sequence_node ">" @keyword.behavior.sequence)
(repeat_node "*" @keyword.behavior.repeat)
(action_node (identifier) @function.action)
; Transitions
(transition "->" @operator.transition)
(transition target: (identifier) @type.state)
; Schedule blocks
(schedule_block activity: (identifier) @function.activity)
; Override operations
(override "@" @keyword.override)
(override_op "remove" @keyword.override)
(override_op "append" @keyword.override)
; Template clause
(template_clause "from" @keyword.import)
; Error handling
(ERROR) @error

View File

@@ -0,0 +1,45 @@
; Indentation query for Storybook DSL
; Increase indent after opening braces
[
"{"
"("
"["
] @indent.begin
; Decrease indent before closing braces
[
"}"
")"
"]"
] @indent.end
; Special handling for prose blocks
(prose_block
marker: (prose_marker) @indent.begin
content: (_)
end: (_) @indent.end
)
; Block structures that should indent their contents
[
(block)
(character)
(template)
(life_arc)
(arc_state)
(schedule)
(schedule_block)
(behavior)
(institution)
(relationship)
(location)
(species)
(enum_declaration)
(selector_node)
(sequence_node)
(repeat_node)
] @indent.begin
; Dedent after semicolon at top level
(use_declaration ";" @indent.end)

View File

@@ -0,0 +1,12 @@
; Injections for embedded languages in Storybook
; Treat prose block content as Markdown
((prose_content) @injection.content
(#set! injection.language "markdown"))
; Alternative: If you want to be more specific and only inject for certain tags:
; ((prose_block
; tag: (identifier) @tag
; content: (_) @injection.content)
; (#match? @tag "^(description|backstory|narrative|details|notes)$")
; (#set! injection.language "markdown"))

View File

@@ -0,0 +1,57 @@
; Outline/symbols query for Storybook DSL
; Defines what symbols appear in the document outline
; Characters
(character
name: (identifier) @name
) @symbol.character
; Templates
(template
name: (identifier) @name
) @symbol.template
; Life arcs
(life_arc
name: (identifier) @name
) @symbol.life_arc
; Life arc states
(arc_state
name: (identifier) @name
) @symbol.state
; Schedules
(schedule
name: (identifier) @name
) @symbol.schedule
; Behaviors
(behavior
name: (identifier) @name
) @symbol.behavior
; Institutions
(institution
name: (identifier) @name
) @symbol.institution
; Relationships
(relationship
name: (identifier) @name
) @symbol.relationship
; Locations
(location
name: (identifier) @name
) @symbol.location
; Species
(species
name: (identifier) @name
) @symbol.species
; Enums
(enum_declaration
name: (identifier) @name
) @symbol.enum

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

View File

@@ -0,0 +1,157 @@
/**
* External scanner for Storybook Tree-sitter grammar
*
* This scanner handles prose blocks (---tag content ---) which require
* stateful scanning to properly match opening and closing delimiters.
*/
#include <tree_sitter/parser.h>
#include <wctype.h>
#include <string.h>
enum TokenType {
PROSE_BLOCK_CONTENT,
PROSE_BLOCK_END,
};
typedef struct {
bool in_prose_block;
} Scanner;
void *tree_sitter_storybook_external_scanner_create() {
Scanner *scanner = (Scanner *)malloc(sizeof(Scanner));
scanner->in_prose_block = false;
return scanner;
}
void tree_sitter_storybook_external_scanner_destroy(void *payload) {
Scanner *scanner = (Scanner *)payload;
free(scanner);
}
unsigned tree_sitter_storybook_external_scanner_serialize(
void *payload,
char *buffer
) {
Scanner *scanner = (Scanner *)payload;
buffer[0] = scanner->in_prose_block ? 1 : 0;
return 1;
}
void tree_sitter_storybook_external_scanner_deserialize(
void *payload,
const char *buffer,
unsigned length
) {
Scanner *scanner = (Scanner *)payload;
scanner->in_prose_block = (length > 0 && buffer[0] == 1);
}
/**
* Check if we're at the start of a line (only whitespace before)
*/
static bool at_line_start(TSLexer *lexer) {
// Look back to see if there's only whitespace since last newline
// For simplicity, we'll assume --- at start of content is line-start
return true; // Tree-sitter will handle this check via grammar
}
/**
* Scan prose block content
*
* Returns true if we successfully scanned content, false otherwise
*/
static bool scan_prose_content(Scanner *scanner, TSLexer *lexer) {
bool has_content = false;
// Consume characters until we see --- at line start
for (;;) {
// Check for end-of-file
if (lexer->eof(lexer)) {
// If we have content, return it; otherwise error
if (has_content) {
lexer->result_symbol = PROSE_BLOCK_CONTENT;
scanner->in_prose_block = false;
return true;
}
return false;
}
// Check for closing ---
if (lexer->lookahead == '-') {
// Look ahead to see if it's ---
lexer->mark_end(lexer);
lexer->advance(lexer, false);
if (lexer->lookahead == '-') {
lexer->advance(lexer, false);
if (lexer->lookahead == '-') {
// Found closing ---, stop here
// Mark end before the ---
scanner->in_prose_block = false;
if (has_content) {
lexer->result_symbol = PROSE_BLOCK_CONTENT;
return true;
}
// No content, scan the closing marker
lexer->result_symbol = PROSE_BLOCK_END;
lexer->advance(lexer, false); // Consume final -
return true;
}
}
// Not a closing marker, continue with content
has_content = true;
continue;
}
// Regular content character
lexer->advance(lexer, false);
has_content = true;
}
}
/**
* Scan for closing prose block marker (---)
*/
static bool scan_prose_end(Scanner *scanner, TSLexer *lexer) {
// We should be at ---
if (lexer->lookahead == '-') {
lexer->advance(lexer, false);
if (lexer->lookahead == '-') {
lexer->advance(lexer, false);
if (lexer->lookahead == '-') {
lexer->advance(lexer, false);
lexer->result_symbol = PROSE_BLOCK_END;
scanner->in_prose_block = false;
return true;
}
}
}
return false;
}
bool tree_sitter_storybook_external_scanner_scan(
void *payload,
TSLexer *lexer,
const bool *valid_symbols
) {
Scanner *scanner = (Scanner *)payload;
// Skip whitespace at the start
while (iswspace(lexer->lookahead)) {
lexer->advance(lexer, true);
}
// If we're looking for prose content
if (valid_symbols[PROSE_BLOCK_CONTENT]) {
scanner->in_prose_block = true;
return scan_prose_content(scanner, lexer);
}
// If we're looking for prose end
if (valid_symbols[PROSE_BLOCK_END]) {
return scan_prose_end(scanner, lexer);
}
return false;
}

View File

@@ -0,0 +1,54 @@
#ifndef TREE_SITTER_ALLOC_H_
#define TREE_SITTER_ALLOC_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
// Allow clients to override allocation functions
#ifdef TREE_SITTER_REUSE_ALLOCATOR
extern void *(*ts_current_malloc)(size_t size);
extern void *(*ts_current_calloc)(size_t count, size_t size);
extern void *(*ts_current_realloc)(void *ptr, size_t size);
extern void (*ts_current_free)(void *ptr);
#ifndef ts_malloc
#define ts_malloc ts_current_malloc
#endif
#ifndef ts_calloc
#define ts_calloc ts_current_calloc
#endif
#ifndef ts_realloc
#define ts_realloc ts_current_realloc
#endif
#ifndef ts_free
#define ts_free ts_current_free
#endif
#else
#ifndef ts_malloc
#define ts_malloc malloc
#endif
#ifndef ts_calloc
#define ts_calloc calloc
#endif
#ifndef ts_realloc
#define ts_realloc realloc
#endif
#ifndef ts_free
#define ts_free free
#endif
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ALLOC_H_

View File

@@ -0,0 +1,347 @@
#ifndef TREE_SITTER_ARRAY_H_
#define TREE_SITTER_ARRAY_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "./alloc.h"
#include <assert.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4101)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#endif
#define Array(T) \
struct { \
T *contents; \
uint32_t size; \
uint32_t capacity; \
}
/// Initialize an array.
#define array_init(self) \
((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
/// Create an empty array.
#define array_new() \
{ NULL, 0, 0 }
/// Get a pointer to the element at a given `index` in the array.
#define array_get(self, _index) \
(assert((uint32_t)(_index) < (self)->size), &(self)->contents[_index])
/// Get a pointer to the first element in the array.
#define array_front(self) array_get(self, 0)
/// Get a pointer to the last element in the array.
#define array_back(self) array_get(self, (self)->size - 1)
/// Clear the array, setting its size to zero. Note that this does not free any
/// memory allocated for the array's contents.
#define array_clear(self) ((self)->size = 0)
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
/// less than the array's current capacity, this function has no effect.
#define array_reserve(self, new_capacity) \
((self)->contents = _array__reserve( \
(void *)(self)->contents, &(self)->capacity, \
array_elem_size(self), new_capacity) \
)
/// Free any memory allocated for this array. Note that this does not free any
/// memory allocated for the array's contents.
#define array_delete(self) _array__delete((self), (void *)(self)->contents, sizeof(*self))
/// Push a new `element` onto the end of the array.
#define array_push(self, element) \
do { \
(self)->contents = _array__grow( \
(void *)(self)->contents, (self)->size, &(self)->capacity, \
1, array_elem_size(self) \
); \
(self)->contents[(self)->size++] = (element); \
} while(0)
/// Increase the array's size by `count` elements.
/// New elements are zero-initialized.
#define array_grow_by(self, count) \
do { \
if ((count) == 0) break; \
(self)->contents = _array__grow( \
(self)->contents, (self)->size, &(self)->capacity, \
count, array_elem_size(self) \
); \
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
(self)->size += (count); \
} while (0)
/// Append all elements from one array to the end of another.
#define array_push_all(self, other) \
array_extend((self), (other)->size, (other)->contents)
/// Append `count` elements to the end of the array, reading their values from the
/// `contents` pointer.
#define array_extend(self, count, other_contents) \
(self)->contents = _array__splice( \
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), (self)->size, 0, count, other_contents \
)
/// Remove `old_count` elements from the array starting at the given `index`. At
/// the same index, insert `new_count` new elements, reading their values from the
/// `new_contents` pointer.
#define array_splice(self, _index, old_count, new_count, new_contents) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, old_count, new_count, new_contents \
)
/// Insert one `element` into the array at the given `index`.
#define array_insert(self, _index, element) \
(self)->contents = _array__splice( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
array_elem_size(self), _index, 0, 1, &(element) \
)
/// Remove one element from the array at the given `index`.
#define array_erase(self, _index) \
_array__erase((void *)(self)->contents, &(self)->size, array_elem_size(self), _index)
/// Pop the last element off the array, returning the element by value.
#define array_pop(self) ((self)->contents[--(self)->size])
/// Assign the contents of one array to another, reallocating if necessary.
#define array_assign(self, other) \
(self)->contents = _array__assign( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
)
/// Swap one array with another
#define array_swap(self, other) \
do { \
struct Swap swapped_contents = _array__swap( \
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
(void *)(other)->contents, &(other)->size, &(other)->capacity \
); \
(self)->contents = swapped_contents.self_contents; \
(other)->contents = swapped_contents.other_contents; \
} while (0)
/// Get the size of the array contents
#define array_elem_size(self) (sizeof *(self)->contents)
/// Search a sorted array for a given `needle` value, using the given `compare`
/// callback to determine the order.
///
/// If an existing element is found to be equal to `needle`, then the `index`
/// out-parameter is set to the existing value's index, and the `exists`
/// out-parameter is set to true. Otherwise, `index` is set to an index where
/// `needle` should be inserted in order to preserve the sorting, and `exists`
/// is set to false.
#define array_search_sorted_with(self, compare, needle, _index, _exists) \
_array__search_sorted(self, 0, compare, , needle, _index, _exists)
/// Search a sorted array for a given `needle` value, using integer comparisons
/// of a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_with`.
#define array_search_sorted_by(self, field, needle, _index, _exists) \
_array__search_sorted(self, 0, _compare_int, field, needle, _index, _exists)
/// Insert a given `value` into a sorted array, using the given `compare`
/// callback to determine the order.
#define array_insert_sorted_with(self, compare, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_with(self, compare, &(value), &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
/// Insert a given `value` into a sorted array, using integer comparisons of
/// a given struct field (specified with a leading dot) to determine the order.
///
/// See also `array_search_sorted_by`.
#define array_insert_sorted_by(self, field, value) \
do { \
unsigned _index, _exists; \
array_search_sorted_by(self, field, (value) field, &_index, &_exists); \
if (!_exists) array_insert(self, _index, value); \
} while (0)
// Private
// Pointers to individual `Array` fields (rather than the entire `Array` itself)
// are passed to the various `_array__*` functions below to address strict aliasing
// violations that arises when the _entire_ `Array` struct is passed as `Array(void)*`.
//
// The `Array` type itself was not altered as a solution in order to avoid breakage
// with existing consumers (in particular, parsers with external scanners).
/// This is not what you're looking for, see `array_delete`.
static inline void _array__delete(void *self, void *contents, size_t self_size) {
if (contents) ts_free(contents);
if (self) memset(self, 0, self_size);
}
/// This is not what you're looking for, see `array_erase`.
static inline void _array__erase(void* self_contents, uint32_t *size,
size_t element_size, uint32_t index) {
assert(index < *size);
char *contents = (char *)self_contents;
memmove(contents + index * element_size, contents + (index + 1) * element_size,
(*size - index - 1) * element_size);
(*size)--;
}
/// This is not what you're looking for, see `array_reserve`.
static inline void *_array__reserve(void *contents, uint32_t *capacity,
size_t element_size, uint32_t new_capacity) {
void *new_contents = contents;
if (new_capacity > *capacity) {
if (contents) {
new_contents = ts_realloc(contents, new_capacity * element_size);
} else {
new_contents = ts_malloc(new_capacity * element_size);
}
*capacity = new_capacity;
}
return new_contents;
}
/// This is not what you're looking for, see `array_assign`.
static inline void *_array__assign(void* self_contents, uint32_t *self_size, uint32_t *self_capacity,
const void *other_contents, uint32_t other_size, size_t element_size) {
void *new_contents = _array__reserve(self_contents, self_capacity, element_size, other_size);
*self_size = other_size;
memcpy(new_contents, other_contents, *self_size * element_size);
return new_contents;
}
struct Swap {
void *self_contents;
void *other_contents;
};
/// This is not what you're looking for, see `array_swap`.
// static inline void _array__swap(Array *self, Array *other) {
static inline struct Swap _array__swap(void *self_contents, uint32_t *self_size, uint32_t *self_capacity,
void *other_contents, uint32_t *other_size, uint32_t *other_capacity) {
void *new_self_contents = other_contents;
uint32_t new_self_size = *other_size;
uint32_t new_self_capacity = *other_capacity;
void *new_other_contents = self_contents;
*other_size = *self_size;
*other_capacity = *self_capacity;
*self_size = new_self_size;
*self_capacity = new_self_capacity;
struct Swap out = {
.self_contents = new_self_contents,
.other_contents = new_other_contents,
};
return out;
}
/// This is not what you're looking for, see `array_push` or `array_grow_by`.
static inline void *_array__grow(void *contents, uint32_t size, uint32_t *capacity,
uint32_t count, size_t element_size) {
void *new_contents = contents;
uint32_t new_size = size + count;
if (new_size > *capacity) {
uint32_t new_capacity = *capacity * 2;
if (new_capacity < 8) new_capacity = 8;
if (new_capacity < new_size) new_capacity = new_size;
new_contents = _array__reserve(contents, capacity, element_size, new_capacity);
}
return new_contents;
}
/// This is not what you're looking for, see `array_splice`.
static inline void *_array__splice(void *self_contents, uint32_t *size, uint32_t *capacity,
size_t element_size,
uint32_t index, uint32_t old_count,
uint32_t new_count, const void *elements) {
uint32_t new_size = *size + new_count - old_count;
uint32_t old_end = index + old_count;
uint32_t new_end = index + new_count;
assert(old_end <= *size);
void *new_contents = _array__reserve(self_contents, capacity, element_size, new_size);
char *contents = (char *)new_contents;
if (*size > old_end) {
memmove(
contents + new_end * element_size,
contents + old_end * element_size,
(*size - old_end) * element_size
);
}
if (new_count > 0) {
if (elements) {
memcpy(
(contents + index * element_size),
elements,
new_count * element_size
);
} else {
memset(
(contents + index * element_size),
0,
new_count * element_size
);
}
}
*size += new_count - old_count;
return new_contents;
}
/// A binary search routine, based on Rust's `std::slice::binary_search_by`.
/// This is not what you're looking for, see `array_search_sorted_with` or `array_search_sorted_by`.
#define _array__search_sorted(self, start, compare, suffix, needle, _index, _exists) \
do { \
*(_index) = start; \
*(_exists) = false; \
uint32_t size = (self)->size - *(_index); \
if (size == 0) break; \
int comparison; \
while (size > 1) { \
uint32_t half_size = size / 2; \
uint32_t mid_index = *(_index) + half_size; \
comparison = compare(&((self)->contents[mid_index] suffix), (needle)); \
if (comparison <= 0) *(_index) = mid_index; \
size -= half_size; \
} \
comparison = compare(&((self)->contents[*(_index)] suffix), (needle)); \
if (comparison == 0) *(_exists) = true; \
else if (comparison < 0) *(_index) += 1; \
} while (0)
/// Helper macro for the `_sorted_by` routines below. This takes the left (existing)
/// parameter by reference in order to work with the generic sorting function above.
#define _compare_int(a, b) ((int)*(a) - (int)(b))
#ifdef _MSC_VER
#pragma warning(pop)
#elif defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_ARRAY_H_

View File

@@ -0,0 +1,224 @@
#ifndef TREE_SITTER_PARSER_H_
#define TREE_SITTER_PARSER_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#define ts_builtin_sym_error ((TSSymbol)-1)
#define ts_builtin_sym_end 0
#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
typedef uint16_t TSStateId;
#ifndef TREE_SITTER_API_H_
typedef uint16_t TSSymbol;
typedef uint16_t TSFieldId;
typedef struct TSLanguage TSLanguage;
#endif
typedef struct {
TSFieldId field_id;
uint8_t child_index;
bool inherited;
} TSFieldMapEntry;
typedef struct {
uint16_t index;
uint16_t length;
} TSFieldMapSlice;
typedef struct {
bool visible;
bool named;
bool supertype;
} TSSymbolMetadata;
typedef struct TSLexer TSLexer;
struct TSLexer {
int32_t lookahead;
TSSymbol result_symbol;
void (*advance)(TSLexer *, bool);
void (*mark_end)(TSLexer *);
uint32_t (*get_column)(TSLexer *);
bool (*is_at_included_range_start)(const TSLexer *);
bool (*eof)(const TSLexer *);
};
typedef enum {
TSParseActionTypeShift,
TSParseActionTypeReduce,
TSParseActionTypeAccept,
TSParseActionTypeRecover,
} TSParseActionType;
typedef union {
struct {
uint8_t type;
TSStateId state;
bool extra;
bool repetition;
} shift;
struct {
uint8_t type;
uint8_t child_count;
TSSymbol symbol;
int16_t dynamic_precedence;
uint16_t production_id;
} reduce;
uint8_t type;
} TSParseAction;
typedef struct {
uint16_t lex_state;
uint16_t external_lex_state;
} TSLexMode;
typedef union {
TSParseAction action;
struct {
uint8_t count;
bool reusable;
} entry;
} TSParseActionEntry;
struct TSLanguage {
uint32_t version;
uint32_t symbol_count;
uint32_t alias_count;
uint32_t token_count;
uint32_t external_token_count;
uint32_t state_count;
uint32_t large_state_count;
uint32_t production_id_count;
uint32_t field_count;
uint16_t max_alias_sequence_length;
const uint16_t *parse_table;
const uint16_t *small_parse_table;
const uint32_t *small_parse_table_map;
const TSParseActionEntry *parse_actions;
const char * const *symbol_names;
const char * const *field_names;
const TSFieldMapSlice *field_map_slices;
const TSFieldMapEntry *field_map_entries;
const TSSymbolMetadata *symbol_metadata;
const TSSymbol *public_symbol_map;
const uint16_t *alias_map;
const TSSymbol *alias_sequences;
const TSLexMode *lex_modes;
bool (*lex_fn)(TSLexer *, TSStateId);
bool (*keyword_lex_fn)(TSLexer *, TSStateId);
TSSymbol keyword_capture_token;
struct {
const bool *states;
const TSSymbol *symbol_map;
void *(*create)(void);
void (*destroy)(void *);
bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
unsigned (*serialize)(void *, char *);
void (*deserialize)(void *, const char *, unsigned);
} external_scanner;
const TSStateId *primary_state_ids;
};
/*
* Lexer Macros
*/
#define START_LEXER() \
bool result = false; \
bool skip = false; \
bool eof = false; \
int32_t lookahead; \
goto start; \
next_state: \
lexer->advance(lexer, skip); \
start: \
skip = false; \
lookahead = lexer->lookahead;
#define ADVANCE(state_value) \
{ \
state = state_value; \
goto next_state; \
}
#define SKIP(state_value) \
{ \
skip = true; \
state = state_value; \
goto next_state; \
}
#define ACCEPT_TOKEN(symbol_value) \
result = true; \
lexer->result_symbol = symbol_value; \
lexer->mark_end(lexer);
#define END_STATE() return result;
/*
* Parse Table Macros
*/
#define SMALL_STATE(id) id - LARGE_STATE_COUNT
#define STATE(id) id
#define ACTIONS(id) id
#define SHIFT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value \
} \
}}
#define SHIFT_REPEAT(state_value) \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.state = state_value, \
.repetition = true \
} \
}}
#define SHIFT_EXTRA() \
{{ \
.shift = { \
.type = TSParseActionTypeShift, \
.extra = true \
} \
}}
#define REDUCE(symbol_val, child_count_val, ...) \
{{ \
.reduce = { \
.type = TSParseActionTypeReduce, \
.symbol = symbol_val, \
.child_count = child_count_val, \
__VA_ARGS__ \
}, \
}}
#define RECOVER() \
{{ \
.type = TSParseActionTypeRecover \
}}
#define ACCEPT_INPUT() \
{{ \
.type = TSParseActionTypeAccept \
}}
#ifdef __cplusplus
}
#endif
#endif // TREE_SITTER_PARSER_H_

View File

@@ -0,0 +1,58 @@
==================
Simple character
==================
character Alice {
age: 7
}
---
(source_file
(declaration
(character
name: (identifier)
body: (block
(field
name: (dotted_path (identifier))
value: (value (integer)))))))
==================
Use declaration
==================
use schema::core::Item;
---
(source_file
(declaration
(use_declaration
(path_segments
(identifier)
(identifier)
(identifier)))))
==================
Prose block
==================
character Bob {
---backstory
This is Bob's story.
---
}
---
(source_file
(declaration
(character
name: (identifier)
body: (block
(field
(prose_block
marker: (prose_marker)
tag: (identifier)
content: (prose_content)
end: (prose_marker)))))))

View File

@@ -0,0 +1,180 @@
==================
Simple Behavior
==================
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
}
}
---
(source_file
(declaration
(behavior
name: (identifier)
root: (behavior_node
(selector_node
(behavior_node (action_node (identifier)))
(behavior_node (action_node (identifier)))
(behavior_node (action_node (identifier))))))))
==================
Sequence Behavior
==================
behavior SequenceBehavior {
> {
check_energy
move_to_location
perform_action
}
}
---
(source_file
(declaration
(behavior
name: (identifier)
root: (behavior_node
(sequence_node
(behavior_node (action_node (identifier)))
(behavior_node (action_node (identifier)))
(behavior_node (action_node (identifier))))))))
==================
Nested Behavior
==================
behavior NestedBehavior {
> {
? {
check_condition_a
check_condition_b
}
perform_action
}
}
---
(source_file
(declaration
(behavior
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))))))))))

View File

@@ -0,0 +1,577 @@
==================
Use declaration - simple
==================
use schema::core::Item;
---
(source_file
(declaration
(use_declaration
(path_segments
(identifier)
(identifier)
(identifier)))))
==================
Use declaration - grouped import
==================
use schema::core::{Item1, Item2, Item3};
---
(source_file
(declaration
(use_declaration
(path_segments
(identifier)
(identifier))
(identifier)
(identifier)
(identifier))))
==================
Use declaration - wildcard
==================
use schema::beings::*;
---
(source_file
(declaration
(use_declaration
(path_segments
(identifier)
(identifier)))))
==================
Character - full featured
==================
character Alice: Human from Curious {
age: 7
height: 4.2
name: "Alice Liddell"
curious: true
age_range: 5..10
---backstory
A curious young girl.
---
}
---
(source_file
(declaration
(character
name: (identifier)
species: (identifier)
template: (template_clause (identifier))
body: (block
(field
name: (dotted_path (identifier))
value: (value (integer)))
(field
name: (dotted_path (identifier))
value: (value (float)))
(field
name: (dotted_path (identifier))
value: (value (string)))
(field
name: (dotted_path (identifier))
value: (value (boolean)))
(field
name: (dotted_path (identifier))
value: (value (range (integer) (integer))))
(field
(prose_block
marker: (prose_marker)
tag: (identifier)
content: (prose_content)
end: (prose_marker)))))))
==================
Template with includes
==================
template BaseCharacter strict {
include Physical
include Mental
age: 0
name: ""
}
---
(source_file
(declaration
(template
name: (identifier)
(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
==================
life_arc Journey {
---description
The character's journey.
---
state young {
on enter {
age: 5
}
on age >= 18 -> adult
}
state adult {
on retire is true -> retired
}
state retired {
}
}
---
(source_file
(declaration
(life_arc
name: (identifier)
(field
(prose_block
marker: (prose_marker)
tag: (identifier)
content: (prose_content)
end: (prose_marker)))
(arc_state
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
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))))))))))
==================
Institution
==================
institution Wonderland {
type: "fantasy_realm"
ruler: "Queen of Hearts"
}
---
(source_file
(declaration
(institution
name: (identifier)
(block
(field
name: (dotted_path (identifier))
value: (value (string)))
(field
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
==================
location TeaParty {
description: "A mad tea party"
capacity: 10
}
---
(source_file
(declaration
(location
name: (identifier)
(block
(field
name: (dotted_path (identifier))
value: (value (string)))
(field
name: (dotted_path (identifier))
value: (value (integer)))))))
==================
Species with includes
==================
species Cat {
include Mammal
purrs: true
lives: 9
}
---
(source_file
(declaration
(species
name: (identifier)
(include (identifier))
(field
name: (dotted_path (identifier))
value: (value (boolean)))
(field
name: (dotted_path (identifier))
value: (value (integer))))))
==================
Enum declaration
==================
enum EmotionalState {
happy, sad, angry, confused, curious
}
---
(source_file
(declaration
(enum_declaration
name: (identifier)
(identifier)
(identifier)
(identifier)
(identifier)
(identifier))))
==================
Override syntax
==================
character SpecialAlice {
base: @Alice {
remove backstory
append age_category: "child"
curious: false
}
}
---
(source_file
(declaration
(character
name: (identifier)
body: (block
(field
name: (dotted_path (identifier))
value: (value
(override
(path (path_segments (identifier)))
(override_op (identifier))
(override_op
(field
name: (dotted_path (identifier))
value: (value (string))))
(override_op
(field
name: (dotted_path (identifier))
value: (value (boolean)))))))))))
==================
Complex expressions in conditions
==================
life_arc ComplexLogic {
state test {
on self.age > 18 and other.trust >= 0.5 -> trusted
on not self.valid or self.expired is true -> invalid
}
state trusted {
}
state invalid {
}
}
---
(source_file
(declaration
(life_arc
name: (identifier)
(arc_state
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)))))
==================
List and object values
==================
character DataRich {
tags: ["smart", "brave", "curious"]
stats: {
strength: 10
intelligence: 15
}
matrix: [[1, 2], [3, 4]]
}
---
(source_file
(declaration
(character
name: (identifier)
body: (block
(field
name: (dotted_path (identifier))
value: (value
(list
(value (string))
(value (string))
(value (string)))))
(field
name: (dotted_path (identifier))
value: (value
(object
(block
(field
name: (dotted_path (identifier))
value: (value (integer)))
(field
name: (dotted_path (identifier))
value: (value (integer)))))))
(field
name: (dotted_path (identifier))
value: (value
(list
(value
(list
(value (integer))
(value (integer))))
(value
(list
(value (integer))
(value (integer)))))))))))
==================
Dotted field paths
==================
character Nested {
appearance.hair.color: "blonde"
stats.mental.intelligence: 15
}
---
(source_file
(declaration
(character
name: (identifier)
body: (block
(field
name: (dotted_path
(identifier)
(identifier)
(identifier))
value: (value (string)))
(field
name: (dotted_path
(identifier)
(identifier)
(identifier))
value: (value (integer)))))))

View File

@@ -0,0 +1,24 @@
{
"grammars": [
{
"name": "storybook",
"path": ".",
"scope": "source.storybook",
"file-types": ["sb"],
"highlights": ["queries/highlights.scm"],
"injections": ["queries/injections.scm"]
}
],
"bindings": {
"c": {},
"node": {},
"python": {},
"rust": {},
"go": {},
"swift": {}
},
"metadata": {
"version": "0.1.0",
"license": "MIT"
}
}