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
13 changes: 13 additions & 0 deletions SYNTAX.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,19 @@ Expr(i32) : '('! Expr ')'! ;

Defines the entry point of the grammar. RustyLR automatically creates an augmented rule `Augmented -> NonTerminalName eof`.

### Multiple Start Symbols

You can define multiple start symbols by writing multiple `%start` directives:

```
%start Expr;
%start Stmt;
```

When multiple start symbols are defined, RustyLR generates individual wrapper structs for each start symbol (e.g., `ExprContext` and `StmtContext`).
- Initializing the context (via `ExprContext::new(...)` or `StmtContext::new(...)`) automatically transitions the parser to the correct starting state for that symbol.
- Calling `accept()` on a context wrapper returns the exact type of that start symbol.

---

## Userdata Type (Optional)
Expand Down
23 changes: 23 additions & 0 deletions example/glr/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ fn test_parser() {
assert_eq!(results, [answer]);
}

#[test]
fn test_multiple_start_symbols() {
// Test parsing with the EContext entry point
{
let mut context = parser::EContext::with_default_userdata();
for ch in "12+34".chars() {
context.feed(ch).unwrap();
}
let (val, _) = context.accept().unwrap();
assert_eq!(val, 46);
}

// Test parsing with the NumberContext entry point
{
let mut context = parser::NumberContext::with_default_userdata();
for ch in " 567 ".chars() {
context.feed(ch).unwrap();
}
let (val, _) = context.accept().unwrap();
assert_eq!(val, 567);
}
}

#[cfg(test)]
mod userdata_branch_tests {
use rusty_lr::lr1;
Expand Down
1 change: 1 addition & 0 deletions example/glr/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ lr1! {
%glr;
%tokentype char;
%start E;
%start Number;

WS0: ' '*;

Expand Down
11 changes: 11 additions & 0 deletions rusty_lr_buildscript/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,17 @@ impl Builder {
.with_labels(labels)
.with_notes(vec!["Name is reserved and cannot be used".to_string()])
}

ArgError::DuplicateStartSymbol { location, name } => {
let range = grammar_args
.span_manager
.get_byterange(&location)
.unwrap_or(0..0);
Diagnostic::error()
.with_message(format!("Duplicate start symbol definition: `{}`", name))
.with_labels(vec![Label::primary(file_id, range)
.with_message("duplicate start symbol defined here")])
}
};

let writer = self.stream();
Expand Down
1 change: 1 addition & 0 deletions rusty_lr_core/src/parser/data_stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub trait DataStack: Sized + Default {
fn pop(&mut self);
fn push_terminal(&mut self, term: Self::Term);
fn push_empty(&mut self);
fn set_branch_idx(&mut self, _branch_idx: u32) {}

fn clear(&mut self);
fn reserve(&mut self, additional: usize);
Expand Down
77 changes: 73 additions & 4 deletions rusty_lr_core/src/parser/deterministic/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,42 @@ impl<
{
Self::new(Default::default())
}
/// Create a new context with a virtual start branch.
pub fn new_with_branch(userdata: Data::UserData, branch_idx: u32) -> Self
where
P::Term: Clone,
P::NonTerm: std::fmt::Debug,
{
let mut ctx = Self::new(userdata);
ctx.data_stack.set_branch_idx(branch_idx);
let class = P::TermClass::from_virtual_start(branch_idx);
let shift_to = ctx.tables.shift_goto_class(0, class).unwrap_or_else(|| {
panic!(
"Failed to resolve shift for virtual start branch {}",
branch_idx
)
});
ctx.state_stack.push(shift_to.state);
ctx.data_stack.push_empty();
ctx.location_stack
.push(Data::Location::new(std::iter::empty(), 0));
#[cfg(feature = "tree")]
{
ctx.tree_stack.push(crate::tree::Tree::new_terminal(
TerminalSymbol::VirtualStart(branch_idx),
));
}
ctx
}
/// Create a new context with a virtual start branch using `Default::default()` as user data.
pub fn with_default_userdata_and_branch(branch_idx: u32) -> Self
where
Data::UserData: Default,
P::Term: Clone,
P::NonTerm: std::fmt::Debug,
{
Self::new_with_branch(Default::default(), branch_idx)
}
/// Create a new context with given capacity of `state_stack` and `data_stack`.
/// `state_stack` is initialized with [0] (root state).
pub fn with_capacity(capacity: usize, userdata: Data::UserData) -> Self {
Expand Down Expand Up @@ -90,6 +126,36 @@ impl<
{
Self::with_capacity(capacity, Default::default())
}
/// Create a new context with capacity and a virtual start branch.
pub fn with_capacity_and_branch(
capacity: usize,
userdata: Data::UserData,
branch_idx: u32,
) -> Self
where
P::Term: Clone,
P::NonTerm: std::fmt::Debug,
{
let mut ctx = Self::with_capacity(capacity, userdata);
let class = P::TermClass::from_virtual_start(branch_idx);
Comment on lines +139 to +140

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

In with_capacity_and_branch, the branch_idx is not set on the data_stack. This will cause the parser to use the default branch index (0) when popping the start symbol upon acceptance, leading to incorrect parsing results or panics for any branch other than 0.

        let mut ctx = Self::with_capacity(capacity, userdata);
        ctx.data_stack.set_branch_idx(branch_idx);
        let class = P::TermClass::from_virtual_start(branch_idx);

let shift_to = ctx.tables.shift_goto_class(0, class).unwrap_or_else(|| {
panic!(
"Failed to resolve shift for virtual start branch {}",
branch_idx
)
});
ctx.state_stack.push(shift_to.state);
ctx.data_stack.push_empty();
ctx.location_stack
.push(Data::Location::new(std::iter::empty(), 0));
#[cfg(feature = "tree")]
{
ctx.tree_stack.push(crate::tree::Tree::new_terminal(
TerminalSymbol::VirtualStart(branch_idx),
));
}
ctx
}
/// Borrow the user data owned by this context.
pub fn userdata(&self) -> &Data::UserData {
&self.userdata
Expand Down Expand Up @@ -496,13 +562,16 @@ impl<
if next_state_id.push {
match term {
TerminalSymbol::Terminal(t) => self.data_stack.push_terminal(t),
TerminalSymbol::Error | TerminalSymbol::Eof => self.data_stack.push_empty(),
TerminalSymbol::Error
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => self.data_stack.push_empty(),
}
} else {
match term {
TerminalSymbol::Terminal(_) | TerminalSymbol::Error | TerminalSymbol::Eof => {
self.data_stack.push_empty()
}
TerminalSymbol::Terminal(_)
| TerminalSymbol::Error
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => self.data_stack.push_empty(),
}
}

Expand Down
59 changes: 55 additions & 4 deletions rusty_lr_core/src/parser/nondeterministic/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use crate::parser::table::Index;
use crate::parser::table::ParserTables;
use crate::parser::terminalclass::TerminalClass;
use crate::parser::Parser;
use crate::Location;
use crate::TerminalSymbol;

/// Iterator for traverse node to root.
Expand Down Expand Up @@ -129,6 +130,48 @@ impl<
Self::new(Default::default())
}

pub fn new_with_branch(userdata: Data::UserData, branch_idx: u32) -> Self
where
P::Term: Clone,
P::NonTerm: std::fmt::Debug,
{
let mut context = Self::new(userdata);
let class = P::TermClass::from_virtual_start(branch_idx);
let shift_to = context
.tables
.shift_goto_class(0, class)
.unwrap_or_else(|| {
panic!(
"Failed to resolve shift for virtual start branch {}",
branch_idx
)
});
let root_node_idx = context.current_nodes[0];
let root_node = context.node_mut(root_node_idx);
root_node.data_stack.set_branch_idx(branch_idx);
root_node.state_stack.push(shift_to.state);
root_node.data_stack.push_empty();
root_node
.location_stack
.push(Data::Location::new(std::iter::empty(), 0));
#[cfg(feature = "tree")]
{
root_node.tree_stack.push(crate::tree::Tree::new_terminal(
TerminalSymbol::VirtualStart(branch_idx),
));
}
context
}

pub fn with_default_userdata_and_branch(branch_idx: u32) -> Self
where
Data::UserData: Default,
P::Term: Clone,
P::NonTerm: std::fmt::Debug,
{
Self::new_with_branch(Default::default(), branch_idx)
}

/// Borrow the user data for the first active path.
///
/// In GLR mode, each forked branch owns an independently cloned user data value.
Expand Down Expand Up @@ -915,15 +958,18 @@ impl<
TerminalSymbol::Terminal(term) => {
node_.data_stack.push_terminal(term);
}
TerminalSymbol::Error | TerminalSymbol::Eof => {
TerminalSymbol::Error
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => {
node_.data_stack.push_empty();
}
}
} else {
match term {
TerminalSymbol::Terminal(_)
| TerminalSymbol::Error
| TerminalSymbol::Eof => {
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => {
node_.data_stack.push_empty();
}
}
Expand Down Expand Up @@ -951,13 +997,18 @@ impl<
TerminalSymbol::Terminal(term) => {
node_.data_stack.push_terminal(term);
}
TerminalSymbol::Error | TerminalSymbol::Eof => {
TerminalSymbol::Error
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => {
node_.data_stack.push_empty();
}
}
} else {
match term {
TerminalSymbol::Terminal(_) | TerminalSymbol::Error | TerminalSymbol::Eof => {
TerminalSymbol::Terminal(_)
| TerminalSymbol::Error
| TerminalSymbol::Eof
| TerminalSymbol::VirtualStart(_) => {
node_.data_stack.push_empty();
}
}
Expand Down
5 changes: 5 additions & 0 deletions rusty_lr_core/src/parser/terminalclass.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ pub trait TerminalClass: Copy {
fn to_usize(&self) -> usize;

fn from_term(term: &Self::Term) -> Self;

/// Gets the terminal class for a virtual start branch.
fn from_virtual_start(_branch_idx: u32) -> Self {
panic!("from_virtual_start not supported on this terminal class")
}
}
17 changes: 10 additions & 7 deletions rusty_lr_core/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ use std::hash::Hash;
/// and future support for other special tokens.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum TerminalSymbol<Term> {
Terminal(Term), // index in the terminals vector
Error, // error token
Eof, // end of file token
Terminal(Term), // index in the terminals vector
Error, // error token
Eof, // end of file token
VirtualStart(u32), // virtual start branch token for multiple start symbols
}
impl<Term> TerminalSymbol<Term> {
pub fn is_error(&self) -> bool {
Expand All @@ -21,20 +22,21 @@ impl<Term> TerminalSymbol<Term> {
pub fn is_eof(&self) -> bool {
matches!(self, TerminalSymbol::Eof)
}
pub fn is_virtual_start(&self) -> bool {
matches!(self, TerminalSymbol::VirtualStart(_))
}
/// converts self to a terminal if it is a `Terminal` variant, otherwise returns `None`.
pub fn to_term(&self) -> Option<&Term> {
match self {
TerminalSymbol::Terminal(term) => Some(term),
TerminalSymbol::Error => None,
TerminalSymbol::Eof => None,
_ => None,
}
}
/// converts self to a terminal if it is a `Terminal` variant, otherwise returns `None`.
pub fn into_term(self) -> Option<Term> {
match self {
TerminalSymbol::Terminal(term) => Some(term),
TerminalSymbol::Error => None,
TerminalSymbol::Eof => None,
_ => None,
}
}
}
Expand All @@ -45,6 +47,7 @@ impl<Term: Display> std::fmt::Display for TerminalSymbol<Term> {
TerminalSymbol::Terminal(term) => write!(f, "{}", term),
TerminalSymbol::Error => write!(f, "error"),
TerminalSymbol::Eof => write!(f, "eof"),
TerminalSymbol::VirtualStart(i) => write!(f, "start_branch_{}", i),
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion rusty_lr_parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ quote = "1.0"
rusty_lr_core = { version = "4.2.0", path = "../rusty_lr_core", features = [
"builder",
] }
syn = { version = "2.0", features = ["extra-traits"] }
syn = { version = "2.0", features = ["extra-traits", "full"] }

[features]
default = []
Loading
Loading