11 unstable releases (3 breaking)
| new 0.4.11 | Feb 13, 2026 |
|---|---|
| 0.4.2 | Feb 12, 2026 |
| 0.3.3 | Feb 6, 2026 |
| 0.3.2 | Jan 31, 2026 |
| 0.1.2 | Dec 19, 2025 |
#348 in Rust patterns
Used in hypen-engine
85KB
2K
SLoC
Hypen Parser (Rust)
A Rust implementation of the Hypen DSL parser using Chumsky, a parser combinator library.
Overview
This parser recreates the functionality of the original Kotlin Multiplatform parser (hypen-parser-core) in Rust, using Chumsky's combinator-based approach for elegant and composable parsing.
Features
- ✅ Component parsing: Parse Hypen component declarations
- ✅ Argument parsing: Supports positional and named arguments
- ✅ Value types: Strings, numbers, booleans, lists, maps, and references (@state, @actions)
- ✅ Nested components: Full support for component hierarchies
- ✅ Applicators: Dot notation styling (e.g.,
.padding(16),.backgroundColor(blue)) - ✅ Error handling: Rich error messages with Chumsky's error reporting
Installation
Add to your Cargo.toml:
[dependencies]
hypen-parser = { path = "path/to/hypen-parser" }
Usage
Parsing a Single Component
use hypen_parser::parse_component;
let input = r#"
Text("Hello, World!")
.fontSize(18)
.color(blue)
"#;
match parse_component(input) {
Ok(component) => {
println!("Component: {}", component.name);
println!("Applicators: {}", component.applicators.len());
}
Err(errors) => {
for error in errors {
println!("Parse error: {}", error);
}
}
}
Parsing Multiple Components
use hypen_parser::parse_components;
let input = r#"
Text("First")
Text("Second")
Text("Third")
"#;
let components = parse_components(input).unwrap();
assert_eq!(components.len(), 3);
Complex Example
let input = r#"
Column {
Text("Welcome, @state.username")
.fontSize(18)
.color(blue)
Button("@actions.signIn") {
Text("Sign In")
}
.padding(16)
Row {
Image(src: "logo.png")
.width(50)
.height(50)
Text("Logo")
}
}
.backgroundColor(white)
"#;
let component = parse_component(input).unwrap();
assert_eq!(component.name, "Column");
assert_eq!(component.children.len(), 3);
Hypen Syntax
Component Declaration
Hypen supports three types of declarations:
- Regular components (stateless, wiped on re-render):
ComponentName(arg1, arg2, key: value)
- Module declarations (stateful, maintains state across lifecycle):
module ProfilePage(userId: 123) {
Text("User Profile")
}
- Component keyword declarations (explicit stateless components):
component Button(text: "Click Me") {
Text(@state.text)
}
Key Differences:
module- Maintains state throughout its lifecycle (persistent state)component- State is wiped on each re-render (ephemeral state)- No keyword - Treated as a regular component (default behavior)
Arguments
- Positional:
Text("Hello") - Named:
Text(text: "Hello", color: red) - Numbers:
width: 100,height: 50.5 - Booleans:
enabled: true,disabled: false - Lists:
items: [1, 2, 3] - Maps:
config: {width: 100, height: 200} - References:
@state.username,@actions.login
Nested Components
Column {
Text("First")
Text("Second")
}
Applicators (Styling)
Text("Styled")
.fontSize(18)
.color(blue)
.padding(16)
Architecture
AST Types
ComponentSpecification: Represents a parsed component with name, arguments, applicators, and childrenArgumentList: Collection of named or positional argumentsArgument: Named or positional argument with a valueValue: Enum for different value types (String, Number, Boolean, List, Map, Reference)ApplicatorSpecification: Styling/modifier applied to a componentMetaData: Source location information
Parser Structure
The parser uses recursive descent with combinator composition:
- Value Parser: Parses all value types (strings, numbers, etc.)
- Component Parser: Main recursive parser for components
- Applicator Folding: Post-processing to attach applicators to their parent components
Differences from Kotlin Parser
- Uses Chumsky's combinator approach instead of manual token processing
- Simpler ID generation (atomic counter vs UUID)
- More concise implementation due to Rust's type system and Chumsky's abstractions
- Better error messages out-of-the-box with Chumsky
Error Handling
The parser uses Chumsky for parsing and Ariadne for beautiful error reporting.
Basic Error Handling
use hypen_parser::parse_component;
let input = r#"Text("Unclosed string"#;
match parse_component(input) {
Ok(component) => {
// Success - work with component
println!("Parsed: {}", component.name);
}
Err(errors) => {
// Handle errors
for error in errors {
eprintln!("{}", error);
}
}
}
Pretty Error Reports with Ariadne
For production use, use the print_parse_errors function for beautiful, colored error messages:
use hypen_parser::{parse_component, print_parse_errors};
let input = r#"
Column {
Text("Hello")
.fontSize(18
Text("Footer")
}
"#;
match parse_component(input) {
Ok(component) => { /* ... */ }
Err(errors) => {
// Prints beautiful error with context
print_parse_errors("myfile.hypen", input, &errors);
}
}
Example error output:
Error: Parse error
╭─[myfile.hypen:4:24]
│
4 │ .fontSize(18
│ ┬
│ ╰── found end of input expected ',', or ')'
───╯
Error Types
Chumsky provides detailed error information including:
- Span: Exact location of the error in the source
- Expected tokens: What the parser was expecting
- Found token: What was actually encountered
- Context: Full parsing context
Testing
Run All Tests
cargo test
This runs 15 comprehensive tests covering:
- Simple components
- Named and positional arguments
- All value types (strings, numbers, booleans, lists, maps, references)
- Nested components
- Applicators (styling)
- Complex hierarchies
- Whitespace handling
Run Specific Tests
# Run a specific test
cargo test test_component_with_applicators
# Run tests matching a pattern
cargo test component
# Show test output
cargo test -- --nocapture
Run Examples
# Basic usage example
cargo run
# Error handling examples
cargo run --example errors
# Pretty error display
cargo run --example pretty_errors
# Comprehensive usage examples
cargo run --example usage
Test Coverage
Current test coverage includes:
| Feature | Tests | Status |
|---|---|---|
| Simple components | ✓ | Passing |
| Arguments (named/positional) | ✓ | Passing |
| Value types | ✓ | Passing |
| Nested components | ✓ | Passing |
| Applicators | ✓ | Passing |
| Complex hierarchies | ✓ | Passing |
| Error handling | ✓ | Passing |
| Whitespace handling | ✓ | Passing |
Performance
The parser is designed for correctness and clarity first. For production use, consider:
- Using
ariadnefor pretty error reporting - Caching parsed results
- Implementing incremental parsing for large files
Future Enhancements
- Source location tracking in AST nodes
- Better error recovery
- LSP integration for IDE support
- Incremental parsing support
- Performance benchmarks
- WASM target compilation
License
Same as the parent Hypen project.
Contributing
This parser is designed to match the behavior of the Kotlin parser in hypen-parser-core. When adding features, ensure compatibility with the original implementation.
Dependencies
~2.3–4MB
~66K SLoC