Tree-sitter grammar for Shuttle Notation
Project description
Shuttle Notation
Tree-sitter grammar for Shuttle Notation — a domain-specific language for defining sequential data with rich metadata (indices, named arguments, alternations, repetition, and section inheritance).
This repository is the canonical language specification. Parsing is handled by the reference Python implementation at shuttle-notation-python.
MIT — Copyright (c) 2026 Emil Strandvik
Shuttle Notation Language Specification
Version: 1.0
Last Updated: 2026-06-03
Table of Contents
Overview
Shuttle Notation is a domain-specific language for defining sequential data with rich metadata. It is designed to be:
- Concise: Easy to type and read
- Expressive: Supports complex patterns through alternations and repetitions
- Flexible: Purpose-agnostic sequential data with user-defined arguments
- Hierarchical: Nested sections with inherited properties
Primary Use Case
Musical note sequencing, but applicable to any sequential, time-based data.
Core Features
- Atomic Elements: Individual sequence entries with indices
- Arguments: Named numerical parameters (e.g.,
amplitude,duration) - Sections: Grouping with shared properties
- Alternations: Patterns that vary on each iteration
- Repetition: Compact notation for repeated patterns
- Inheritance: Arguments cascade through nested structures
Lexical Structure
Tokens
Whitespace
- Space (
): Primary element separator - Newlines/Tabs: Treated as whitespace, no semantic meaning
Identifiers
- Pattern:
[a-zA-Z_][a-zA-Z0-9_]* - Examples:
c4,note,amplitude,sus - Usage: Element prefixes, argument names
Numbers
- Pattern:
[0-9]+(\.[0-9]+)? - Examples:
1,3.14,0.5 - Usage: Indices, argument values, repetition counts
Operators and Delimiters
(): Section grouping/: Alternation separator*: Repetition operator:: Argument/information separator+-*=: Argument operators for relative modifications,: Argument separator.: Decimal point in numbers
Comments
- Not currently defined in the language
Grammar
EBNF Notation
(* Top-level *)
sequence = element_list ;
element_list = element { whitespace element } ;
element = atomic_element
| section ;
(* Atomic Elements *)
atomic_element = [ prefix ] [ index ] [ suffix ] [ repeat ] [ information ] ;
prefix = identifier ;
index = number ;
suffix = identifier ;
information = ":" args ;
(* Arguments *)
args = arg { "," arg } ;
argument = [ arg_name ] [ operator ] number [ ref ] ;
arg_name = identifier ;
operator = "+" | "-" | "*" | "=" ;
ref = identifier ;
(* Sections *)
section = "(" section_content ")" [ suffix ] [ repeat ] [ information ] ;
section_content = alternation_list
| element_list ;
alternation_list = element_list { "/" element_list } ;
repeat = "*" number ;
(* Primitives *)
identifier = letter { letter | digit | "_" } ;
number = digit { digit } [ "." digit { digit } ] ;
letter = "a" | "b" | ... | "z" | "A" | "B" | ... | "Z" | "_" ;
digit = "0" | "1" | ... | "9" ;
whitespace = " " | "\t" | "\n" | "\r" ;
Precedence and Associativity
- Parentheses
( ): Highest precedence, creates scope - Repetition
*: Applies to immediately preceding element/section - Information
:: Binds to immediately preceding element/section - Alternation
/: Separates alternatives within a section - Whitespace: Lowest precedence, separates elements
Semantic Rules
Element Resolution
Each element in the sequence resolves to a ResolvedElement with:
prefix: String identifier (default:"")index: Integer or float (default:0)suffix: String identifier (default:"")args: Dictionary of argument name → value
Argument Inheritance
Arguments flow top-down through the tree structure:
- Default arguments: Provided at parse time
- Section arguments: Override defaults for all children
- Element arguments: Override section/default arguments
Argument Operators
Arguments can use operators to modify inherited values:
+: Add to inherited value-: Subtract from inherited value (or negate if no inherited value)*: Multiply inherited value=: Explicitly set (same as no operator)
"c:amp0.5 (d:amp+0.2 e:amp-0.1 f:amp*2)"
chasamp=0.5dhasamp=0.7(0.5 + 0.2)ehasamp=0.4(0.5 - 0.1)fhasamp=1.0(0.5 * 2)
Argument References
Arguments can reference other resolved argument values:
"c:time1.0,sus0.5time"
susis set to0.5 * time=0.5 * 1.0=0.5
The syntax is: argname<value><referenced_arg_name>
References are resolved after all non-referential arguments are computed.
Alternations
Sections with / create alternating patterns:
"(a / b / c)*6"
Expands to: a b c a b c
- Each iteration cycles through alternatives
- Nested alternations multiply combinations
Repetition
The * operator repeats the preceding element or section:
"a*3" → a a a
"(a b)*2" → a b a b
Note: The repetition syntax is *N not xN as shown in some examples.
Section Expansion
Sections are expanded into flat sequences:
- Parse section content into tree
- Expand alternations based on repetition count
- Flatten into sequence of atomic elements
- Resolve arguments for each element
Data Model
Element Types (Enum)
class ElementType(Enum):
ATOMIC # Single element
SECTION # Grouped elements
ALTERNATION # Multiple alternatives
Element Structure
class Element:
elements: list[Element] # Child elements (for sections)
information: str # Raw information string
type: ElementType # Element classification
Resolved Element
@dataclass
class ResolvedElement:
prefix: str # Element identifier
index: int | float # Primary value
suffix: str # Additional identifier
args: dict[str, Decimal] # Named arguments
Element Information
@dataclass
class ElementInformation:
prefix: str = "" # Contents before first numeric
index_string: str = "" # First numeric or special symbol
suffix: str = "" # Contents after first numeric
repetition: int = 1 # Contents after "*", before ":"
arg_source: str = "" # Final contents after ":"
Dynamic Arguments
@dataclass
class DynamicArg:
value: Decimal
operator: str = "" # "+", "-", "*", "=" or ""
other_arg_reference: str = "" # Reference to another arg name
Examples
Basic Sequences
"c d e f"
→ [c, d, e, f]
"1 2 3 4"
→ [1, 2, 3, 4]
"c4 d4 e4 f4"
→ [c4, d4, e4, f4]
With Arguments
"c:amp0.5 d:amp0.8"
→ c with amp=0.5, d with amp=0.8
"c:amp0.5,dur2 d"
→ c with amp=0.5, dur=2; d inherits nothing
"c:0.5"
→ c with first unnamed arg (typically "time") = 0.5
Note: Multiple arguments are separated by commas. The first unnamed argument defaults to "time" if no name is provided.
Sections with Inheritance
"(c d e):amp0.5"
→ All three notes have amp=0.5
"(c:amp0.8 d e):amp0.5"
→ c has amp=0.8, d and e have amp=0.5
Relative Arguments
"c:amp0.5 (d:amp+0.2 e:amp-0.1)"
→ c: amp=0.5, d: amp=0.7, e: amp=0.4
Argument References
"c:time1.0,sus0.5time"
→ c: time=1.0, sus=0.5 (0.5 * time)
Alternations
"(a / b)*4"
→ a b a b
"(a b / c d)*2"
→ a b c d
"(a / b / c)*5"
→ a b c a b
Repetition
"a*3"
→ a a a
"(a b)*2"
→ a b a b
"(a / b)*2:amp0.5"
→ a b, both with amp=0.5
Complex Example
"c3:sus3.4 c3:0.5 (a3 / b3:sus+1 / f3 / g3)*2:0.5,sus0.2"
Breakdown:
c3:sus3.4→ c, index=3, sus=3.4c3:0.5→ c, index=3, arg value 0.5 (unnamed, defaults to "time")- Section with 4 alternations, repeated 2 times
- Section has args: unnamed=0.5, sus=0.2
b3:sus+1→ b, index=3, sus=0.2+1=1.2 (relative)
Implementation Notes
Parser Architecture
The reference implementation uses a multi-stage parser:
- Lexical Analysis (
cursor.py): Character-by-character scanning withCursorclass - Section Parsing (
section_parsing.py): Build tree structure from()and/syntax - Information Parsing (
information_parsing.py): Extract prefix, index, suffix, repetition, and arguments - Tree Expansion (
util.py): Handle alternations and repetitions withTreeExpanderclass - Resolution (
full_parse.py): Produce finalResolvedElementlist with inherited arguments
Parsing Stages Detail
Stage 1: Section Parsing
- Uses
section_split()to divide input by spaces (respecting parentheses) - Builds tree of
Elementobjects with types: ATOMIC, SECTION, ALTERNATION_SECTION - Handles nested sections recursively
Stage 2: Information Division
- Uses
divide_information()to parse each element's information string - Extracts: prefix, index_string, suffix, repetition, arg_source
- Sections have no prefix/index, only suffix and args
Stage 3: Tree Expansion
TreeExpander.tree_expand()flattens the tree into a sequence- Handles alternations by cycling through alternatives
- Applies repetitions at each level
- Uses "ticking" mechanism to track alternation state
Stage 4: Argument Resolution
resolve_full_arguments()walks up the tree collecting argument definitions- Applies operators (+, -, *, =) to modify inherited values
- Resolves argument references in two passes
- Returns dict of argument name → Decimal value
Stage 5: Element Resolution
- Converts each
ElementtoResolvedElement - Combines information and resolved arguments
- Produces final flat list ready for interpretation
Key Classes
Cursor: Stateful string scannerElement: Tree node representationParser: Main entry point withparse()methodTreeExpander: Handles alternation expansionResolvedElement: Final output format
Argument Aliases
The parser supports argument name aliases:
parser = Parser()
parser.arg_aliases = {"amp": "amplitude", "dur": "duration"}
This allows amp to be used in notation but resolved as amplitude.
Default Arguments
Default values can be provided at parse time:
parser.parse("c d e", default_args={"amp": 0.5, "dur": 1.0})
Edge Cases
- Empty sections:
()is valid, produces no elements - Whitespace: Multiple spaces treated as single separator
- Malformed input: Parser attempts to handle gracefully
- Nested alternations: Multiply to create all combinations
- Zero repetition:
*0produces no elements
Tree-Sitter Considerations
For implementing a tree-sitter grammar:
- Whitespace handling: Space is significant as separator
- Precedence: Parentheses > repetition > information > alternation
- Ambiguity: Identifiers vs numbers resolved by first character
- Nesting: Sections can nest arbitrarily deep
- Context-free: Grammar is context-free, semantics require tree walking
Extension Points
The language is designed to be extensible:
- New argument types: Currently only numeric
- Additional operators: Could add more relative operators
- Comments: Could add
#or//style comments - Macros: Could add named pattern definitions
- Imports: Could reference external notation files
Formal Grammar Summary
For quick reference, the core grammar in compact form:
sequence ::= element*
element ::= atomic | section
atomic ::= prefix? index? suffix? repeat? info?
section ::= '(' content ')' suffix? repeat? info?
content ::= alternation | sequence
alternation ::= sequence ('/' sequence)+
repeat ::= '*' number
info ::= ':' args
args ::= arg (',' arg)*
arg ::= [name] [operator] number [ref]
operator ::= '+' | '-' | '*' | '='
ref ::= identifier
prefix ::= identifier
suffix ::= identifier
name ::= identifier
index ::= number
Version History
- 1.0 (2026-06-03): Initial specification based on reference implementation
References
- Reference Implementation: shuttle-notation-python
- Billboard Integration: tree-sitter-jdw-billboarding / BILLBOARD_SPEC.md
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tree_sitter_shuttle_notation-0.3.0-cp310-abi3-manylinux_2_17_x86_64.whl.
File metadata
- Download URL: tree_sitter_shuttle_notation-0.3.0-cp310-abi3-manylinux_2_17_x86_64.whl
- Upload date:
- Size: 20.2 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a29903cf8fdeb4e91d6c4f2a4acf54d2d0099a85e06f37b40d4f4042cba04e36
|
|
| MD5 |
0f33a1382d474ba9acbc193169eeac47
|
|
| BLAKE2b-256 |
5f933107aa8365b101c1eddb3655c4c4c62b8a0c1801d1ae5910fc8de0e41b5f
|
Provenance
The following attestation bundles were made for tree_sitter_shuttle_notation-0.3.0-cp310-abi3-manylinux_2_17_x86_64.whl:
Publisher:
ci.yml on estrandv/tree-sitter-shuttle-notation
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tree_sitter_shuttle_notation-0.3.0-cp310-abi3-manylinux_2_17_x86_64.whl -
Subject digest:
a29903cf8fdeb4e91d6c4f2a4acf54d2d0099a85e06f37b40d4f4042cba04e36 - Sigstore transparency entry: 1735148235
- Sigstore integration time:
-
Permalink:
estrandv/tree-sitter-shuttle-notation@fa25abc6d1a4712f7677764980fccb2a0e503780 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/estrandv
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@fa25abc6d1a4712f7677764980fccb2a0e503780 -
Trigger Event:
release
-
Statement type: