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:
[]
= { = "path/to/hypen-parser" }
Usage
Parsing a Single Component
use parse_component;
let input = r#"
Text("Hello, World!")
.fontSize(18)
.color(blue)
"#;
match parse_component
Parsing Multiple Components
use parse_components;
let input = r#"
Text("First")
Text("Second")
Text("Third")
"#;
let components = parse_components.unwrap;
assert_eq!;
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.unwrap;
assert_eq!;
assert_eq!;
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 parse_component;
let input = r#"Text("Unclosed string"#;
match parse_component
Pretty Error Reports with Ariadne
For production use, use the print_parse_errors function for beautiful, colored error messages:
use ;
let input = r#"
Column {
Text("Hello")
.fontSize(18
Text("Footer")
}
"#;
match parse_component
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
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
# Run tests matching a pattern
# Show test output
Run Examples
# Basic usage example
# Error handling examples
# Pretty error display
# Comprehensive usage examples
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.