add an LR(1) interpreter that executes the action tables and constructs

a parse-tree. Use is to test the results from the LR(1) table generation
algorithm.
This commit is contained in:
Niko Matsakis 2015-06-20 05:41:54 -04:00
parent 077ed9060d
commit a661d9ee94
5 changed files with 161 additions and 5 deletions

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::cell::RefCell;
use std::fmt::{Debug, Display, Error, Formatter};
use std::cmp::{PartialOrd, Ord, Ordering};
use util::{map, Map};
use util::{map};
#[cfg(test)]
mod test;

View File

@ -2,7 +2,7 @@
use grammar::repr::*;
use std::collections::{HashMap, HashSet};
use util::{map, Map};
use util::{map};
use super::Lookahead;

126
src/lr1/interpret.rs Normal file
View File

@ -0,0 +1,126 @@
//! LR(1) interpeter. Just builds up parse trees. Intended for testing.
use lr1::{Action, State, Lookahead};
use grammar::repr::*;
use std::fmt::{Debug, Display, Formatter, Error};
use util::Sep;
#[derive(PartialEq, Eq)]
pub enum ParseTree {
Nonterminal(NonterminalString, Vec<ParseTree>),
Terminal(TerminalString),
}
pub fn interpret<'g,TOKENS>(states: &'g [State<'g>], tokens: TOKENS)
-> Result<ParseTree, ()>
where TOKENS: Iterator<Item=TerminalString>
{
let mut m = Machine::new(states);
m.execute(tokens)
}
struct Machine<'grammar> {
states: &'grammar [State<'grammar>],
state_stack: Vec<&'grammar State<'grammar>>,
data_stack: Vec<ParseTree>,
}
impl<'grammar> Machine<'grammar> {
fn new(states: &'grammar [State<'grammar>]) -> Machine<'grammar> {
Machine { states: states,
state_stack: vec![],
data_stack: vec![] }
}
fn execute<TOKENS>(&mut self, mut tokens: TOKENS) -> Result<ParseTree, ()>
where TOKENS: Iterator<Item=TerminalString>
{
assert!(self.state_stack.is_empty());
assert!(self.data_stack.is_empty());
self.state_stack.push(&self.states[0]);
let mut token = tokens.next();
while let Some(terminal) = token {
let state = *self.state_stack.last().unwrap();
// check whether we can shift this token
match state.tokens.get(&Lookahead::Terminal(terminal)) {
None => { return Err(()); }
Some(&Action::Shift(next_index)) => {
self.data_stack.push(ParseTree::Terminal(terminal));
self.state_stack.push(&self.states[next_index.0]);
token = tokens.next();
}
Some(&Action::Reduce(production)) => {
let more = self.reduce(production);
assert!(more);
}
}
}
// drain now for EOF
loop {
let state = *self.state_stack.last().unwrap();
match state.tokens.get(&Lookahead::EOF) {
None => { return Err(()); }
Some(&Action::Shift(_)) => { unreachable!("cannot shift EOF") }
Some(&Action::Reduce(production)) => {
if !self.reduce(production) {
assert_eq!(self.data_stack.len(), 1);
return Ok(self.data_stack.pop().unwrap());
}
}
}
}
}
fn reduce(&mut self, production: &Production) -> bool {
let args = production.symbols.len();
// remove the top N items from the data stack
let mut popped = vec![];
for _ in 0 .. args {
popped.push(self.data_stack.pop().unwrap());
}
popped.reverse();
// remove the top N states
for _ in 0 .. args {
self.state_stack.pop().unwrap();
}
// construct the new, reduced tree and push it on the stack
let tree = ParseTree::Nonterminal(production.nonterminal, popped);
self.data_stack.push(tree);
// recover the state and extract the "Goto" action
let receiving_state = *self.state_stack.last().unwrap();
match receiving_state.gotos.get(&production.nonterminal) {
Some(goto_state) => {
self.state_stack.push(&self.states[goto_state.0]);
true // keep going
}
None => {
false // all done
}
}
}
}
impl Debug for ParseTree {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
Display::fmt(self, fmt)
}
}
impl Display for ParseTree {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> {
match *self {
ParseTree::Nonterminal(id, ref trees) => write!(fmt, "[{}: {}]", id, Sep(", ", trees)),
ParseTree::Terminal(id) => write!(fmt, "{}", id),
}
}
}

View File

@ -1,12 +1,12 @@
//! Naive LR(1) generation algorithm.
use grammar::repr::*;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Formatter, Error};
use std::rc::Rc;
use util::{map, Map, Multimap, Set, Prefix};
mod first;
mod interpret;
#[cfg(test)] mod test;
@ -16,7 +16,8 @@ struct LR1<'grammar> {
}
#[derive(Debug)]
struct State<'grammar> {
pub struct State<'grammar> {
index: StateIndex,
items: Items<'grammar>,
tokens: Map<Lookahead, Action<'grammar>>,
gotos: Map<NonterminalString, StateIndex>,
@ -234,7 +235,7 @@ impl<'grammar> StateSet<'grammar> {
let states = &mut self.states;
*self.state_map.entry(items.clone()).or_insert_with(|| {
let index = StateIndex(states.len());
states.push(State { items: items, tokens: map(), gotos: map() });
states.push(State { index: index, items: items, tokens: map(), gotos: map() });
index
})
}

View File

@ -3,11 +3,18 @@ use grammar::repr::*;
use test_util::{expect_debug, normalized_grammar};
use super::{Items, Lookahead, LR1};
use super::Lookahead::EOF;
use super::interpret::interpret;
fn nt(t: &str) -> NonterminalString {
NonterminalString(intern(t))
}
macro_rules! tokens {
($($x:expr),*) => {
vec![$(TerminalString(intern($x))),*].into_iter()
}
}
fn items<'g>(grammar: &'g Grammar, nonterminal: &str, index: usize, la: Lookahead)
-> Items<'g>
{
@ -97,4 +104,26 @@ grammar Foo {
// and yields expected number of states.
let states = lr1.build_states(nt("S")).unwrap();
assert_eq!(states.len(), 16);
// execute it on some sample inputs.
let tree = interpret(&states, tokens!["N", "-", "(", "N", "-", "N", ")"]).unwrap();
assert_eq!(
&format!("{:?}", tree)[..],
r#"[S: [E: [E: [T: "N"]], "-", [T: "(", [E: [E: [T: "N"]], "-", [T: "N"]], ")"]]]"#);
// incomplete:
assert!(interpret(&states, tokens!["N", "-", "(", "N", "-", "N"]).is_err());
// incomplete:
assert!(interpret(&states, tokens!["N", "-"]).is_err());
// unexpected character:
assert!(interpret(&states, tokens!["N", "-", ")", "N", "-", "N", "("]).is_err());
// parens first:
let tree = interpret(&states, tokens!["(", "N", "-", "N", ")", "-", "N"]).unwrap();
println!("{}", tree);
assert_eq!(
&format!("{}", tree)[..],
r#"[S: [E: [E: [T: "(", [E: [E: [T: "N"]], "-", [T: "N"]], ")"]], "-", [T: "N"]]]"#);
}