Revision control
Copy as Markdown
Other Tools
use crate::queue_stack::QueueStack;
use crate::simulator::Simulator;
use ast::arena;
use ast::SourceLocation;
use generated_parser::{
full_actions, AstBuilder, AstBuilderDelegate, ErrorCode, ParseError, ParserTrait, Result,
StackValue, TermValue, TerminalId, Token, TABLES,
};
use json_log::json_trace;
pub struct Parser<'alloc> {
/// Vector of states visited in the LR parse table.
state_stack: Vec<usize>,
/// Stack and Queue of terms and their associated values. The Queue
/// corresponds to terms which are added as lookahead as well as terms which
/// are replayed, and the stack matches the state_stack.
node_stack: QueueStack<TermValue<StackValue<'alloc>>>,
/// Build the AST stored in the TermValue vectors.
handler: AstBuilder<'alloc>,
}
impl<'alloc> AstBuilderDelegate<'alloc> for Parser<'alloc> {
fn ast_builder_refmut(&mut self) -> &mut AstBuilder<'alloc> {
&mut self.handler
}
}
impl<'alloc> ParserTrait<'alloc, StackValue<'alloc>> for Parser<'alloc> {
fn shift(&mut self, tv: TermValue<StackValue<'alloc>>) -> Result<'alloc, bool> {
// The shift function should exit either by accepting the input or
// emptying its queue of lookahead.
debug_assert!(self.node_stack.queue_empty());
self.node_stack.enqueue(tv);
// Shift the new terminal/nonterminal and its associated value.
json_trace!({ "enter": "shift" });
let mut state = self.state();
debug_assert!(state < TABLES.shift_count);
while !self.node_stack.queue_empty() {
let term_index: usize = self.node_stack.next().unwrap().term.into();
debug_assert!(term_index < TABLES.shift_width);
let index = state * TABLES.shift_width + term_index;
let goto = TABLES.shift_table[index];
json_trace!({
"from": state,
"to": goto,
"term": format!("{:?}", { let s: &'static str = tv.term.into(); s }),
});
if goto < 0 {
self.node_stack.shift();
let tv = self.node_stack.pop().unwrap();
// Error handling is in charge of shifting an ErrorSymbol from the
// current state.
self.try_error_handling(tv)?;
continue;
}
state = goto as usize;
self.shift_replayed(state);
// Execute any actions, such as reduce actions ast builder actions.
if state >= TABLES.shift_count {
assert!(state < TABLES.action_count + TABLES.shift_count);
json_trace!({ "action": state });
if full_actions(self, state)? {
return Ok(true);
}
state = self.state();
}
debug_assert!(state < TABLES.shift_count);
}
Ok(false)
}
#[inline(always)]
fn shift_replayed(&mut self, state: usize) {
// let term_index: usize = self.node_stack.next().unwrap().term.into();
// assert!(term_index < TABLES.shift_width);
// let from_state = self.state();
// let index = from_state * TABLES.shift_width + term_index;
// let goto = TABLES.shift_table[index];
// assert!((goto as usize) == state);
self.state_stack.push(state);
self.node_stack.shift();
}
fn unshift(&mut self) {
self.state_stack.pop().unwrap();
self.node_stack.unshift()
}
fn pop(&mut self) -> TermValue<StackValue<'alloc>> {
self.state_stack.pop().unwrap();
self.node_stack.pop().unwrap()
}
fn replay(&mut self, tv: TermValue<StackValue<'alloc>>) {
self.node_stack.push_next(tv)
}
fn epsilon(&mut self, state: usize) {
*self.state_stack.last_mut().unwrap() = state;
}
fn top_state(&self) -> usize {
self.state()
}
fn check_not_on_new_line(&mut self, peek: usize) -> Result<'alloc, bool> {
let sv = {
let stack = self.node_stack.stack_slice();
&stack[stack.len() - peek].value
};
if let StackValue::Token(ref token) = sv {
if !token.is_on_new_line {
return Ok(true);
}
self.rewind(peek - 1);
let tv = self.pop();
self.try_error_handling(tv)?;
return Ok(false);
}
Err(ParseError::NoLineTerminatorHereExpectedToken.into())
}
}
impl<'alloc> Parser<'alloc> {
pub fn new(handler: AstBuilder<'alloc>, entry_state: usize) -> Self {
TABLES.check();
assert!(entry_state < TABLES.shift_count);
let mut state_stack = Vec::with_capacity(128);
state_stack.push(entry_state);
Self {
state_stack,
node_stack: QueueStack::with_capacity(128),
handler,
}
}
fn state(&self) -> usize {
*self.state_stack.last().unwrap()
}
pub fn write_token(&mut self, token: arena::Box<'alloc, Token>) -> Result<'alloc, ()> {
json_trace!({
"method": "write_token",
"is_on_new_line": token.is_on_new_line,
"start": token.loc.start,
"end": token.loc.end,
});
// Shift the token with the associated StackValue.
let term = token.terminal_id.into();
let accept = self.shift(TermValue {
term,
value: StackValue::Token(token),
})?;
// JavaScript grammar accepts empty inputs, therefore we can never
// accept any program before receiving a TerminalId::End.
assert!(!accept);
Ok(())
}
pub fn close(&mut self, position: usize) -> Result<'alloc, StackValue<'alloc>> {
// Shift the End terminal with the associated StackValue.
json_trace!({
"method": "close",
"position": position,
});
let loc = SourceLocation::new(position, position);
let token = Token::basic_token(TerminalId::End, loc);
let accept = self.shift(TermValue {
term: TerminalId::End.into(),
value: StackValue::Token(self.handler.alloc(token)),
})?;
// Adding a TerminalId::End would either lead to a parse error, or to
// accepting the current input. In which case we return matching node
// value.
assert!(accept);
// We can either reduce a Script/Module, or a Script/Module followed by
// an <End> terminal.
assert!(self.node_stack.stack_len() >= 1);
assert!(self.node_stack.stack_len() <= 2);
if self.node_stack.stack_len() > 1 {
self.node_stack.pop();
}
Ok(self.node_stack.pop().unwrap().value)
}
pub(crate) fn parse_error(t: &Token) -> ParseError<'alloc> {
if t.terminal_id == TerminalId::End {
ParseError::UnexpectedEnd
} else {
ParseError::SyntaxError(t.clone())
}
}
fn try_error_handling(&mut self, t: TermValue<StackValue<'alloc>>) -> Result<'alloc, bool> {
json_trace!({
"try_error_handling_term": format!("{}", {
let s: &'static str = t.term.into();
s
}),
});
if let StackValue::Token(ref token) = t.value {
// Error tokens might them-self cause more errors to be reported.
// This happens due to the fact that the ErrorToken can be replayed,
// and while the ErrorToken might be in the lookahead rules, it
// might not be in the shifted terms coming after the reduced
// nonterminal.
if t.term == TerminalId::ErrorToken.into() {
return Err(Self::parse_error(token).into());
}
// Otherwise, check if the current rule accept an Automatic
// Semi-Colon insertion (ASI).
let state = self.state();
assert!(state < TABLES.shift_count);
let error_code = TABLES.error_codes[state];
if let Some(error_code) = error_code {
let err_token = (*token).clone();
Self::recover(token, error_code)?;
self.replay(t);
let err_token = self.handler.alloc(err_token);
self.replay(TermValue {
term: TerminalId::ErrorToken.into(),
value: StackValue::Token(err_token),
});
return Ok(false);
}
// On error, don't attempt error handling again.
return Err(Self::parse_error(token).into());
}
Err(ParseError::ParserCannotUnpackToken.into())
}
pub(crate) fn recover(t: &Token, error_code: ErrorCode) -> Result<'alloc, ()> {
match error_code {
ErrorCode::Asi => {
if t.is_on_new_line
|| t.terminal_id == TerminalId::End
|| t.terminal_id == TerminalId::CloseBrace
{
Ok(())
} else {
Err(Self::parse_error(t).into())
}
}
ErrorCode::DoWhileAsi => Ok(()),
}
}
fn simulator<'a>(&'a self) -> Simulator<'alloc, 'a> {
assert_eq!(self.node_stack.queue_len(), 0);
Simulator::new(&self.state_stack, self.node_stack.stack_slice())
}
pub fn can_accept_terminal(&self, t: TerminalId) -> bool {
let result = self.simulator().write_token(t).is_ok();
json_trace!({
"can_accept": result,
"terminal": format!("{:?}", t),
});
result
}
/// Return true if self.close() would succeed.
pub fn can_close(&self) -> bool {
self.simulator().close(0).is_ok()
}
}