Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ members = ["derive"]

[package]
name = "from-pest"
version = "0.3.3"
version = "0.3.4"
edition = "2021"
authors = ["cad97 <cad97@cad97.com>"]
readme = "./README.md"
Expand Down
2 changes: 1 addition & 1 deletion derive/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pest-ast"
version = "0.3.5"
version = "0.3.6"
edition = "2021"
authors = ["cad97 <cad97@cad97.com>"]
description = "Derive to convert from pest parse tree to typed syntax tree"
Expand Down
63 changes: 63 additions & 0 deletions derive/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -306,3 +306,66 @@ And doing the actual parse is as simple as
let mut parse_tree = csv::Parser::parse(csv::Rule::file, &source)?;
let syntax_tree = File::from_pest(&mut parse_tree).expect("infallible");
```

## Default Values for Optional Rules

A powerful feature for handling optional grammar rules without requiring `Option<T>` in your AST is the `#[pest_ast(default(...))]` attribute. This allows you to specify default values that will be used when optional rules are not present in the input.

### The Problem

When using optional rules in Pest grammar, you typically need `Option<T>` in your AST:

```rust
// Grammar: function = { "fn" ~ id ~ ("->" ~ type)? ~ "{" ~ "}" }

#[derive(FromPest, Debug)]
#[pest_ast(rule(Rule::function))]
pub struct Function {
pub name: String,
pub return_type: Option<Type>, // Optional field
}
```

### The Solution

With the `default` attribute, you can eliminate `Option<T>` and specify a default value:

```rust
#[derive(FromPest, Debug)]
#[pest_ast(rule(Rule::function))]
pub struct Function {
pub name: String,

#[pest_ast(default(Type::Void))] // Specify default value
pub return_type: Type, // No Option<T> needed!
}
```

### Usage Examples

```rust
// Simple defaults
#[pest_ast(default(Type::Void))]
pub return_type: Type,

// Complex defaults with expressions
#[pest_ast(default(Vec::new()))]
pub parameters: Vec<Parameter>,

#[pest_ast(default({
Config {
debug: false,
optimization_level: 2,
}
}))]
pub config: Config,
```

### How It Works

The `default` attribute generates code that:
1. First tries to parse the field normally using `FromPest`
2. If conversion fails with `NoMatch` (optional rule not present), uses the default value
3. If parsing fails with other errors, propagates the error

This provides a clean, type-safe way to handle optional grammar elements while keeping your AST representation simple and avoiding the complexity of `Option<T>` handling.
21 changes: 21 additions & 0 deletions derive/examples/defaults_showcase.pest
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Simple grammar showcasing default values

WHITESPACE = _{ " " | "\t" | "\n" | "\r" }

// Variable declarations with optional types and initialization
var_decl = { var_kind ~ id ~ type_annotation? ~ initializer? ~ ";" }
var_kind = { "let" | "const" }
type_annotation = { ":" ~ type_name }
initializer = { "=" ~ expr }

// Expressions
expr = { number | string | id }
number = { ASCII_DIGIT+ }
string = { "\"" ~ (!("\"") ~ ANY)* ~ "\"" }

// Basic constructs
id = { ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
type_name = { "int" | "string" | "bool" | "void" }

// Program
program = { var_decl* }
239 changes: 239 additions & 0 deletions derive/examples/defaults_showcase.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
#![allow(
bad_style,
dead_code,
clippy::clone_on_copy,
clippy::upper_case_acronyms
)]

#[macro_use]
extern crate pest_derive;
extern crate from_pest;
#[macro_use]
extern crate pest_ast;
extern crate pest;

use from_pest::FromPest;
use pest::Parser;

#[derive(Parser)]
#[grammar = "../examples/defaults_showcase.pest"]
pub struct ShowcaseParser;

// Define enum types that can have defaults
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Int,
String,
Bool,
Void,
}

impl<'pest> FromPest<'pest> for Type {
type Rule = Rule;
type FatalError = from_pest::Void;

fn from_pest(
pest: &mut pest::iterators::Pairs<'pest, Rule>,
) -> Result<Self, from_pest::ConversionError<from_pest::Void>> {
let pair = pest.next().ok_or(from_pest::ConversionError::NoMatch)?;
if pair.as_rule() == Rule::type_name {
match pair.as_str() {
"int" => Ok(Type::Int),
"string" => Ok(Type::String),
"bool" => Ok(Type::Bool),
"void" => Ok(Type::Void),
_ => Err(from_pest::ConversionError::NoMatch),
}
} else {
Err(from_pest::ConversionError::NoMatch)
}
}
}

#[derive(Debug, Clone, PartialEq)]
pub enum Expr {
Number(i32),
String(String),
Id(String),
}

impl<'pest> FromPest<'pest> for Expr {
type Rule = Rule;
type FatalError = from_pest::Void;

fn from_pest(
pest: &mut pest::iterators::Pairs<'pest, Rule>,
) -> Result<Self, from_pest::ConversionError<from_pest::Void>> {
let pair = pest.next().ok_or(from_pest::ConversionError::NoMatch)?;
match pair.as_rule() {
Rule::expr => {
// The expr rule contains nested rules, so we need to look at its inner content
let mut inner = pair.into_inner();
let inner_pair = inner.next().ok_or(from_pest::ConversionError::NoMatch)?;
match inner_pair.as_rule() {
Rule::number => Ok(Expr::Number(inner_pair.as_str().parse().unwrap())),
Rule::string => {
let s = inner_pair.as_str();
Ok(Expr::String(s[1..s.len() - 1].to_string())) // Remove quotes
}
Rule::id => Ok(Expr::Id(inner_pair.as_str().to_string())),
_ => Err(from_pest::ConversionError::NoMatch),
}
}
Rule::number => Ok(Expr::Number(pair.as_str().parse().unwrap())),
Rule::string => {
let s = pair.as_str();
Ok(Expr::String(s[1..s.len() - 1].to_string())) // Remove quotes
}
Rule::id => Ok(Expr::Id(pair.as_str().to_string())),
_ => Err(from_pest::ConversionError::NoMatch),
}
}
}

#[derive(Debug, Clone, PartialEq)]
pub enum VarKind {
Let,
Const,
}

impl<'pest> FromPest<'pest> for VarKind {
type Rule = Rule;
type FatalError = from_pest::Void;

fn from_pest(
pest: &mut pest::iterators::Pairs<'pest, Rule>,
) -> Result<Self, from_pest::ConversionError<from_pest::Void>> {
let pair = pest.next().ok_or(from_pest::ConversionError::NoMatch)?;
if pair.as_rule() == Rule::var_kind {
match pair.as_str() {
"let" => Ok(VarKind::Let),
"const" => Ok(VarKind::Const),
_ => Err(from_pest::ConversionError::NoMatch),
}
} else {
Err(from_pest::ConversionError::NoMatch)
}
}
}

#[derive(FromPest, Debug, Clone, PartialEq)]
#[pest_ast(rule(Rule::id))]
pub struct Id<'pest> {
#[pest_ast(outer())]
pub span: pest::Span<'pest>,
}

impl<'pest> Id<'pest> {
pub fn name(&self) -> &str {
self.span.as_str()
}
}

#[derive(FromPest, Debug, Clone, PartialEq)]
#[pest_ast(rule(Rule::type_annotation))]
pub struct TypeAnnotation {
pub type_name: Type,
}

#[derive(FromPest, Debug, Clone, PartialEq)]
#[pest_ast(rule(Rule::initializer))]
pub struct Initializer {
pub expr: Expr,
}

// Variable declaration showcasing multiple default values
#[derive(FromPest, Debug, Clone, PartialEq)]
#[pest_ast(rule(Rule::var_decl))]
pub struct VarDecl<'pest> {
// Now this should parse correctly from the var_kind rule
#[pest_ast(default(VarKind::Let))]
pub kind: VarKind,

pub id: Id<'pest>,

// Type annotation defaults to 'void' if not specified
#[pest_ast(default(TypeAnnotation { type_name: Type::Void }))]
pub type_annotation: TypeAnnotation,

// Initialization defaults to a placeholder value
#[pest_ast(default(Initializer { expr: Expr::Number(0) }))]
pub initializer: Initializer,
}

#[derive(FromPest, Debug, Clone, PartialEq)]
#[pest_ast(rule(Rule::program))]
pub struct Program<'pest> {
pub declarations: Vec<VarDecl<'pest>>,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Default Values Showcase ===\n");

// Test 1: Minimal declaration (all defaults)
let input1 = "let x;";
println!("Input 1: {input1}");
let pairs1 = ShowcaseParser::parse(Rule::program, input1)?;
let program1: Program = Program::from_pest(&mut pairs1.clone())?;
println!("Parsed: {program1:#?}\n");

// Test 2: With type annotation
let input2 = "let y: int;";
println!("Input 2: {input2}");
let pairs2 = ShowcaseParser::parse(Rule::program, input2)?;
let program2: Program = Program::from_pest(&mut pairs2.clone())?;
println!("Parsed: {program2:#?}\n");

// Test 3: With initialization
let input3 = "let z = 42;";
println!("Input 3: {input3}");
let pairs3 = ShowcaseParser::parse(Rule::program, input3)?;
let program3: Program = Program::from_pest(&mut pairs3.clone())?;
println!("Parsed: {program3:#?}\n");

// Test 4: Fully specified
let input4 = "const w: string = \"hello\";";
println!("Input 4: {input4}");
let pairs4 = ShowcaseParser::parse(Rule::program, input4)?;
let program4: Program = Program::from_pest(&mut pairs4.clone())?;
println!("Parsed: {program4:#?}\n");

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_defaults_applied() {
let input = "let x;";
let pairs = ShowcaseParser::parse(Rule::program, input).unwrap();
let program: Program = Program::from_pest(&mut pairs.clone()).unwrap();

assert_eq!(program.declarations.len(), 1);
let decl = &program.declarations[0];

// All defaults should be applied
assert_eq!(decl.kind, VarKind::Let);
assert_eq!(decl.type_annotation.type_name, Type::Void);
assert_eq!(decl.initializer.expr, Expr::Number(0));
assert_eq!(decl.id.name(), "x");
}

#[test]
fn test_explicit_values_override_defaults() {
let input = "const y: int = 42;";
let pairs = ShowcaseParser::parse(Rule::program, input).unwrap();
let program: Program = Program::from_pest(&mut pairs.clone()).unwrap();

assert_eq!(program.declarations.len(), 1);
let decl = &program.declarations[0];

// Explicit values should override defaults
assert_eq!(decl.kind, VarKind::Const); // Now should be correctly parsed
assert_eq!(decl.type_annotation.type_name, Type::Int); // Explicit
assert_eq!(decl.initializer.expr, Expr::Number(42)); // Explicit
assert_eq!(decl.id.name(), "y");
}
}
18 changes: 18 additions & 0 deletions derive/examples/function_defaults.pest
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Grammar for a simple language with function declarations
// Functions can have optional return types that default to "void"

WHITESPACE = _{ " " | "\t" | "\n" | "\r" }

// Basic types
id = { ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* }
type_name = { "void" | "int" | "string" | id }

// Function parameter
param = { id ~ ":" ~ type_name }
params = { param ~ ("," ~ param)* }

// Function declaration with optional return type
function = { "fn" ~ id ~ "(" ~ params? ~ ")" ~ ("->" ~ type_name)? ~ "{" ~ "}" }

// Program is a sequence of functions
program = { function* }
Loading