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, /// 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>>, /// 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>) -> 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> { self.state_stack.pop().unwrap(); self.node_stack.pop().unwrap() } fn replay(&mut self, tv: TermValue>) { 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 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>) -> 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() } }