From eb49b7ffaa1198cf7bb0deb8f119d1970071a10d Mon Sep 17 00:00:00 2001 From: azjezz Date: Thu, 1 Dec 2022 13:48:54 +0100 Subject: [PATCH] feat: add parser state scope Signed-off-by: azjezz --- src/lexer/error.rs | 8 +- src/lexer/mod.rs | 80 ++-- src/lexer/state.rs | 34 +- src/parser/ast.rs | 4 + src/parser/classish.rs | 263 ------------- src/parser/error.rs | 34 +- src/parser/functions.rs | 155 -------- src/parser/{ => internal}/block.rs | 0 src/parser/internal/classish.rs | 274 +++++++++++++ .../{ => internal}/classish_statement.rs | 43 +- src/parser/{ => internal}/flags.rs | 0 src/parser/internal/functions.rs | 274 +++++++++++++ src/parser/{ => internal}/ident.rs | 0 src/parser/internal/mod.rs | 11 + src/parser/{ => internal}/params.rs | 62 +-- src/parser/{ => internal}/precedence.rs | 0 src/parser/{ => internal}/punc.rs | 0 src/parser/internal/types.rs | 144 +++++++ src/parser/{ => internal}/vars.rs | 2 +- src/parser/macros.rs | 14 + src/parser/mod.rs | 371 ++---------------- src/parser/state.rs | 69 ++++ src/prelude.rs | 1 - tests/0002/parser-error.txt | 2 +- tests/0003/parser-error.txt | 2 +- tests/0004/parser-error.txt | 2 +- tests/0005/parser-error.txt | 2 +- tests/0016/parser-error.txt | 2 +- tests/0019/ast.txt | 8 +- tests/0044/parser-error.txt | 2 +- tests/0087/parser-error.txt | 2 +- tests/0088/parser-error.txt | 2 +- tests/0111/parser-error.txt | 2 +- tests/0112/parser-error.txt | 2 +- tests/0113/parser-error.txt | 2 +- tests/0114/parser-error.txt | 2 +- tests/0115/parser-error.txt | 2 +- tests/0116/parser-error.txt | 2 +- tests/0120/parser-error.txt | 2 +- tests/0121/parser-error.txt | 2 +- tests/0122/parser-error.txt | 2 +- tests/0123/parser-error.txt | 2 +- tests/0124/parser-error.txt | 2 +- tests/0125/parser-error.txt | 2 +- tests/0126/parser-error.txt | 2 +- tests/0128/parser-error.txt | 2 +- tests/0131/parser-error.txt | 2 +- tests/0132/parser-error.txt | 2 +- tests/0141/parser-error.txt | 2 +- tests/0142/parser-error.txt | 2 +- tests/0144/parser-error.txt | 2 +- tests/0146/code.php | 9 + tests/0146/parser-error.txt | 1 + tests/0146/tokens.txt | 115 ++++++ 54 files changed, 1123 insertions(+), 907 deletions(-) delete mode 100644 src/parser/classish.rs delete mode 100644 src/parser/functions.rs rename src/parser/{ => internal}/block.rs (100%) create mode 100644 src/parser/internal/classish.rs rename src/parser/{ => internal}/classish_statement.rs (89%) rename src/parser/{ => internal}/flags.rs (100%) create mode 100644 src/parser/internal/functions.rs rename src/parser/{ => internal}/ident.rs (100%) create mode 100644 src/parser/internal/mod.rs rename src/parser/{ => internal}/params.rs (71%) rename src/parser/{ => internal}/precedence.rs (100%) rename src/parser/{ => internal}/punc.rs (100%) create mode 100644 src/parser/internal/types.rs rename src/parser/{ => internal}/vars.rs (95%) create mode 100644 tests/0146/code.php create mode 100644 tests/0146/parser-error.txt create mode 100644 tests/0146/tokens.txt diff --git a/src/lexer/error.rs b/src/lexer/error.rs index 5576a58b..9b48eb78 100644 --- a/src/lexer/error.rs +++ b/src/lexer/error.rs @@ -2,7 +2,7 @@ use std::fmt::Display; use crate::lexer::token::Span; -pub type LexResult = Result; +pub type SyntaxResult = Result; #[derive(Debug, Eq, PartialEq)] pub enum SyntaxError { @@ -13,6 +13,7 @@ pub enum SyntaxError { InvalidOctalEscape(Span), InvalidOctalLiteral(Span), InvalidUnicodeEscape(Span), + UnpredictableState(Span), } impl Display for SyntaxError { @@ -53,6 +54,11 @@ impl Display for SyntaxError { "Syntax Error: invalid unicode escape on line {} column {}", span.0, span.1 ), + Self::UnpredictableState(span) => write!( + f, + "Syntax Error: Reached an unpredictable state on line {} column {}", + span.0, span.1 + ), } } } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index de47b286..d0cf09eb 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -9,7 +9,8 @@ use std::num::IntErrorKind; use crate::lexer::byte_string::ByteString; use crate::lexer::error::SyntaxError; -use crate::lexer::state::StackState; +use crate::lexer::error::SyntaxResult; +use crate::lexer::state::StackFrame; use crate::lexer::state::State; use crate::lexer::token::OpenTagKind; use crate::lexer::token::Span; @@ -27,21 +28,21 @@ impl Lexer { Self {} } - pub fn tokenize>(&self, input: &B) -> Result, SyntaxError> { + pub fn tokenize>(&self, input: &B) -> SyntaxResult> { let mut state = State::new(input); let mut tokens = Vec::new(); while state.current.is_some() { - match state.stack.last().unwrap() { + match state.frame()? { // The "Initial" state is used to parse inline HTML. It is essentially a catch-all // state that will build up a single token buffer until it encounters an open tag // of some description. - StackState::Initial => { + StackFrame::Initial => { tokens.append(&mut self.initial(&mut state)?); } // The scripting state is entered when an open tag is encountered in the source code. // This tells the lexer to start analysing characters at PHP tokens instead of inline HTML. - StackState::Scripting => { + StackFrame::Scripting => { self.skip_whitespace(&mut state); // If we have consumed whitespace and then reached the end of the file, we should break. @@ -54,7 +55,7 @@ impl Lexer { // The "Halted" state is entered when the `__halt_compiler` token is encountered. // In this state, all the text that follows is no longer parsed as PHP as is collected // into a single "InlineHtml" token (kind of cheating, oh well). - StackState::Halted => { + StackFrame::Halted => { tokens.push(Token { kind: TokenKind::InlineHtml(state.chars[state.cursor..].into()), span: state.span, @@ -63,22 +64,22 @@ impl Lexer { } // The double quote state is entered when inside a double-quoted string that // contains variables. - StackState::DoubleQuote => tokens.extend(self.double_quote(&mut state)?), + StackFrame::DoubleQuote => tokens.extend(self.double_quote(&mut state)?), // LookingForProperty is entered inside double quotes, // backticks, or a heredoc, expecting a variable name. // If one isn't found, it switches to scripting. - StackState::LookingForVarname => { - if let Some(token) = self.looking_for_varname(&mut state) { + StackFrame::LookingForVarname => { + if let Some(token) = self.looking_for_varname(&mut state)? { tokens.push(token); } } // LookingForProperty is entered inside double quotes, // backticks, or a heredoc, expecting an arrow followed by a // property name. - StackState::LookingForProperty => { + StackFrame::LookingForProperty => { tokens.push(self.looking_for_property(&mut state)?); } - StackState::VarOffset => { + StackFrame::VarOffset => { if state.current.is_none() { break; } @@ -97,7 +98,7 @@ impl Lexer { } } - fn initial(&self, state: &mut State) -> Result, SyntaxError> { + fn initial(&self, state: &mut State) -> SyntaxResult> { let inline_span = state.span; let mut buffer = Vec::new(); while let Some(char) = state.current { @@ -105,7 +106,7 @@ impl Lexer { let tag_span = state.span; state.skip(5); - state.enter_state(StackState::Scripting); + state.set(StackFrame::Scripting)?; let mut tokens = vec![]; @@ -134,7 +135,7 @@ impl Lexer { }]) } - fn scripting(&self, state: &mut State) -> Result { + fn scripting(&self, state: &mut State) -> SyntaxResult { let span = state.span; let kind = match state.peek_buf() { [b'@', ..] => { @@ -170,7 +171,7 @@ impl Lexer { // This is a close tag, we can enter "Initial" mode again. state.skip(2); - state.enter_state(StackState::Initial); + state.set(StackFrame::Initial)?; TokenKind::CloseTag } @@ -304,7 +305,7 @@ impl Lexer { match state.peek_buf() { [b'(', b')', b';', ..] => { state.skip(3); - state.enter_state(StackState::Halted); + state.set(StackFrame::Halted)?; } _ => return Err(SyntaxError::InvalidHaltCompiler(state.span)), } @@ -411,12 +412,12 @@ impl Lexer { } [b'{', ..] => { state.next(); - state.push_state(StackState::Scripting); + state.enter(StackFrame::Scripting); TokenKind::LeftBrace } [b'}', ..] => { state.next(); - state.pop_state(); + state.exit(); TokenKind::RightBrace } [b'(', ..] => { @@ -591,25 +592,25 @@ impl Lexer { Ok(Token { kind, span }) } - fn double_quote(&self, state: &mut State) -> Result, SyntaxError> { + fn double_quote(&self, state: &mut State) -> SyntaxResult> { let span = state.span; let mut buffer = Vec::new(); let kind = loop { match state.peek_buf() { [b'$', b'{', ..] => { state.skip(2); - state.push_state(StackState::LookingForVarname); + state.enter(StackFrame::LookingForVarname); break TokenKind::DollarLeftBrace; } [b'{', b'$', ..] => { // Intentionally only consume the left brace. state.next(); - state.push_state(StackState::Scripting); + state.enter(StackFrame::Scripting); break TokenKind::LeftBrace; } [b'"', ..] => { state.next(); - state.enter_state(StackState::Scripting); + state.set(StackFrame::Scripting)?; break TokenKind::DoubleQuote; } [b'$', ident_start!(), ..] => { @@ -617,10 +618,10 @@ impl Lexer { let ident = self.consume_identifier(state); match state.peek_buf() { - [b'[', ..] => state.push_state(StackState::VarOffset), + [b'[', ..] => state.enter(StackFrame::VarOffset), [b'-', b'>', ident_start!(), ..] | [b'?', b'-', b'>', ident_start!(), ..] => { - state.push_state(StackState::LookingForProperty) + state.enter(StackFrame::LookingForProperty) } _ => {} } @@ -647,7 +648,7 @@ impl Lexer { Ok(tokens) } - fn looking_for_varname(&self, state: &mut State) -> Option { + fn looking_for_varname(&self, state: &mut State) -> SyntaxResult> { let identifier = self.peek_identifier(state); if let Some(ident) = identifier { @@ -655,19 +656,20 @@ impl Lexer { let ident = ident.to_vec(); let span = state.span; state.skip(ident.len()); - state.enter_state(StackState::Scripting); - return Some(Token { + state.set(StackFrame::Scripting)?; + return Ok(Some(Token { kind: TokenKind::Identifier(ident.into()), span, - }); + })); } } - state.enter_state(StackState::Scripting); - None + state.set(StackFrame::Scripting)?; + + Ok(None) } - fn looking_for_property(&self, state: &mut State) -> Result { + fn looking_for_property(&self, state: &mut State) -> SyntaxResult { let span = state.span; let kind = match state.peek_buf() { [b'-', b'>', ..] => { @@ -680,7 +682,7 @@ impl Lexer { } &[ident_start!(), ..] => { let buffer = self.consume_identifier(state); - state.pop_state(); + state.exit(); TokenKind::Identifier(buffer.into()) } // Should be impossible as we already looked ahead this far inside double_quote. @@ -689,7 +691,7 @@ impl Lexer { Ok(Token { kind, span }) } - fn var_offset(&self, state: &mut State) -> Result { + fn var_offset(&self, state: &mut State) -> SyntaxResult { let span = state.span; let kind = match state.peek_buf() { [b'$', ident_start!(), ..] => { @@ -712,7 +714,7 @@ impl Lexer { } [b']', ..] => { state.next(); - state.pop_state(); + state.exit(); TokenKind::RightBracket } &[ident_start!(), ..] => { @@ -730,7 +732,7 @@ impl Lexer { Ok(Token { kind, span }) } - fn tokenize_single_quote_string(&self, state: &mut State) -> Result { + fn tokenize_single_quote_string(&self, state: &mut State) -> SyntaxResult { let mut buffer = Vec::new(); loop { @@ -754,7 +756,7 @@ impl Lexer { Ok(TokenKind::LiteralString(buffer.into())) } - fn tokenize_double_quote_string(&self, state: &mut State) -> Result { + fn tokenize_double_quote_string(&self, state: &mut State) -> SyntaxResult { let mut buffer = Vec::new(); let constant = loop { @@ -864,7 +866,7 @@ impl Lexer { Ok(if constant { TokenKind::LiteralString(buffer.into()) } else { - state.enter_state(StackState::DoubleQuote); + state.set(StackFrame::DoubleQuote)?; TokenKind::StringPart(buffer.into()) }) } @@ -893,7 +895,7 @@ impl Lexer { TokenKind::Variable(self.consume_identifier(state).into()) } - fn tokenize_number(&self, state: &mut State) -> Result { + fn tokenize_number(&self, state: &mut State) -> SyntaxResult { let mut buffer = String::new(); let (base, kind) = match state.peek_buf() { @@ -1000,7 +1002,7 @@ impl Lexer { // Parses an integer literal in the given base and converts errors to SyntaxError. // It returns a float token instead on overflow. -fn parse_int(buffer: &str, base: u32, span: Span) -> Result { +fn parse_int(buffer: &str, base: u32, span: Span) -> SyntaxResult { match i64::from_str_radix(buffer, base) { Ok(i) => Ok(TokenKind::LiteralInteger(i)), Err(err) if err.kind() == &IntErrorKind::InvalidDigit => { diff --git a/src/lexer/state.rs b/src/lexer/state.rs index 59a0ba80..2815b7af 100644 --- a/src/lexer/state.rs +++ b/src/lexer/state.rs @@ -1,7 +1,11 @@ +use std::collections::VecDeque; + +use crate::lexer::error::SyntaxError; +use crate::lexer::error::SyntaxResult; use crate::lexer::token::Span; #[derive(Debug, PartialEq, Eq, Clone)] -pub enum StackState { +pub enum StackFrame { Initial, Scripting, Halted, @@ -11,10 +15,9 @@ pub enum StackState { VarOffset, } -// TODO(azjezz): make `chars` a `[u8, N]`, and `State`, `State` #[derive(Debug, PartialEq, Eq, Clone)] pub struct State { - pub stack: Vec, + pub stack: VecDeque, pub chars: Vec, pub cursor: usize, pub current: Option, @@ -27,7 +30,7 @@ impl State { let current = chars.first().copied(); Self { - stack: vec![StackState::Initial], + stack: VecDeque::from([StackFrame::Initial]), chars, current, cursor: 0, @@ -35,16 +38,27 @@ impl State { } } - pub fn enter_state(&mut self, state: StackState) { - *self.stack.last_mut().unwrap() = state; + pub fn set(&mut self, state: StackFrame) -> SyntaxResult<()> { + *self + .stack + .back_mut() + .ok_or(SyntaxError::UnpredictableState(self.span))? = state; + + Ok(()) + } + + pub fn frame(&self) -> SyntaxResult<&StackFrame> { + self.stack + .back() + .ok_or(SyntaxError::UnpredictableState(self.span)) } - pub fn push_state(&mut self, state: StackState) { - self.stack.push(state); + pub fn enter(&mut self, state: StackFrame) { + self.stack.push_back(state); } - pub fn pop_state(&mut self) { - self.stack.pop(); + pub fn exit(&mut self) { + self.stack.pop_back(); } pub fn peek_buf(&self) -> &[u8] { diff --git a/src/parser/ast.rs b/src/parser/ast.rs index f61ad1c5..3c53862d 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -397,6 +397,10 @@ pub enum Statement { expr: Expression, }, Namespace { + name: ByteString, + body: Block, + }, + BracedNamespace { name: Option, body: Block, }, diff --git a/src/parser/classish.rs b/src/parser/classish.rs deleted file mode 100644 index 7dd5f033..00000000 --- a/src/parser/classish.rs +++ /dev/null @@ -1,263 +0,0 @@ -use crate::lexer::token::TokenKind; -use crate::parser::ast::BackedEnumType; -use crate::parser::ast::Block; -use crate::parser::ast::ClassFlag; -use crate::parser::ast::Expression; -use crate::parser::ast::Identifier; -use crate::parser::ast::Statement; -use crate::parser::error::ParseResult; -use crate::parser::state::State; -use crate::parser::Parser; - -use crate::expect_token; -use crate::expected_token_err; - -impl Parser { - pub(in crate::parser) fn class_definition(&self, state: &mut State) -> ParseResult { - let flags: Vec = self.class_flags(state)?.iter().map(|f| f.into()).collect(); - - expect_token!([TokenKind::Class], state, ["`class`"]); - - let name = self.ident(state)?; - let mut extends: Option = None; - - if state.current.kind == TokenKind::Extends { - state.next(); - extends = Some(self.full_name(state)?.into()); - } - - let implements = if state.current.kind == TokenKind::Implements { - state.next(); - - self.at_least_one_comma_separated::(state, &|parser, state| { - Ok(parser.full_name(state)?.into()) - })? - } else { - Vec::new() - }; - - self.lbrace(state)?; - - let mut body = Vec::new(); - while state.current.kind != TokenKind::RightBrace { - state.gather_comments(); - - if state.current.kind == TokenKind::RightBrace { - state.clear_comments(); - break; - } - - body.push(self.class_statement(state, flags.clone())?); - } - self.rbrace(state)?; - - Ok(Statement::Class { - name: name.into(), - extends, - implements, - body, - flags, - }) - } - - pub(in crate::parser) fn interface_definition( - &self, - state: &mut State, - ) -> ParseResult { - expect_token!([TokenKind::Interface], state, ["`interface`"]); - - let name = self.ident(state)?; - - let extends = if state.current.kind == TokenKind::Extends { - state.next(); - - self.at_least_one_comma_separated::(state, &|parser, state| { - Ok(parser.full_name(state)?.into()) - })? - } else { - Vec::new() - }; - - self.lbrace(state)?; - - let mut body = Vec::new(); - while state.current.kind != TokenKind::RightBrace && !state.is_eof() { - state.gather_comments(); - - if state.current.kind == TokenKind::RightBrace { - state.clear_comments(); - break; - } - - body.push(self.interface_statement(state)?); - } - self.rbrace(state)?; - - Ok(Statement::Interface { - name: name.into(), - extends, - body, - }) - } - - pub(in crate::parser) fn trait_definition(&self, state: &mut State) -> ParseResult { - expect_token!([TokenKind::Trait], state, ["`trait`"]); - - let name = self.ident(state)?; - - self.lbrace(state)?; - - let mut body = Vec::new(); - while state.current.kind != TokenKind::RightBrace && !state.is_eof() { - state.gather_comments(); - - if state.current.kind == TokenKind::RightBrace { - state.clear_comments(); - break; - } - - body.push(self.trait_statement(state)?); - } - self.rbrace(state)?; - - Ok(Statement::Trait { - name: name.into(), - body, - }) - } - - pub(in crate::parser) fn anonymous_class_definition( - &self, - state: &mut State, - ) -> ParseResult { - state.next(); - - expect_token!([TokenKind::Class], state, ["`class`"]); - - let mut args = vec![]; - - if state.current.kind == TokenKind::LeftParen { - self.lparen(state)?; - - args = self.args_list(state)?; - - self.rparen(state)?; - } - - let mut extends: Option = None; - - if state.current.kind == TokenKind::Extends { - state.next(); - extends = Some(self.full_name(state)?.into()); - } - - let mut implements = Vec::new(); - if state.current.kind == TokenKind::Implements { - state.next(); - - while state.current.kind != TokenKind::LeftBrace { - self.optional_comma(state)?; - - implements.push(self.full_name(state)?.into()); - } - } - - self.lbrace(state)?; - - let mut body = Vec::new(); - while state.current.kind != TokenKind::RightBrace && !state.is_eof() { - body.push(self.anonymous_class_statement(state)?); - } - - self.rbrace(state)?; - - Ok(Expression::New { - target: Box::new(Expression::AnonymousClass { - extends, - implements, - body, - }), - args, - }) - } - - pub(in crate::parser) fn enum_definition(&self, state: &mut State) -> ParseResult { - state.next(); - - let name = self.ident(state)?; - - let backed_type: Option = if state.current.kind == TokenKind::Colon { - self.colon(state)?; - - match state.current.kind.clone() { - TokenKind::Identifier(s) if s == b"string" || s == b"int" => { - state.next(); - - Some(match &s[..] { - b"string" => BackedEnumType::String, - b"int" => BackedEnumType::Int, - _ => unreachable!(), - }) - } - _ => { - return expected_token_err!(["`string`", "`int`"], state); - } - } - } else { - None - }; - - let mut implements = Vec::new(); - if state.current.kind == TokenKind::Implements { - state.next(); - - while state.current.kind != TokenKind::LeftBrace { - implements.push(self.full_name(state)?.into()); - - self.optional_comma(state)?; - } - } - - self.lbrace(state)?; - - let mut body = Block::new(); - while state.current.kind != TokenKind::RightBrace { - state.skip_comments(); - body.push(self.enum_statement(state, backed_type.is_some())?); - } - - self.rbrace(state)?; - - match backed_type { - Some(backed_type) => Ok(Statement::BackedEnum { - name: name.into(), - backed_type, - implements, - body, - }), - None => Ok(Statement::UnitEnum { - name: name.into(), - implements, - body, - }), - } - } - - fn at_least_one_comma_separated( - &self, - state: &mut State, - func: &(dyn Fn(&Parser, &mut State) -> ParseResult), - ) -> ParseResult> { - let mut result: Vec = vec![]; - loop { - result.push(func(self, state)?); - if state.current.kind != TokenKind::Comma { - break; - } - - state.next(); - } - - Ok(result) - } -} diff --git a/src/parser/error.rs b/src/parser/error.rs index 8dd99607..adb963f8 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -18,11 +18,13 @@ pub enum ParseError { PromotedPropertyOutsideConstructor(Span), PromotedPropertyOnAbstractConstructor(Span), AbstractModifierOnNonAbstractClassMethod(Span), + ConstructorInEnum(String, Span), StaticModifierOnConstant(Span), ReadonlyModifierOnConstant(Span), FinalModifierOnAbstractClassMember(Span), FinalModifierOnPrivateConstant(Span), FinalModifierOnAbstractClass(Span), + UnpredictableState(Span), } impl Display for ParseError { @@ -39,25 +41,27 @@ impl Display for ParseError { }; match found { - Some(token) => write!(f, "Parse error: unexpected token `{}`, expecting {} on line {} column {}", token, expected, span.0, span.1), - None => write!(f, "Parse error: unexpected end of file, expecting {} on line {} column {}", expected, span.0, span.1), + Some(token) => write!(f, "Parse Error: unexpected token `{}`, expecting {} on line {} column {}", token, expected, span.0, span.1), + None => write!(f, "Parse Error: unexpected end of file, expecting {} on line {} column {}", expected, span.0, span.1), } }, Self::MultipleModifiers(modifier, span) => write!(f, "Parse Error: Multiple {} modifiers are not allowed on line {} column {}", modifier, span.0, span.1), Self::MultipleAccessModifiers( span) => write!(f, "Parse Error: Multiple access type modifiers are not allowed on line {} column {}", span.0, span.1), - Self::UnexpectedToken(message, span) => write!(f, "Parse error: unexpected token {} on line {} column {}", message, span.0, span.1), - Self::UnexpectedEndOfFile => write!(f, "Parse error: unexpected end of file."), - Self::FinalModifierOnAbstractClassMember(span) => write!(f, "Parse error: Cannot use the final modifier on an abstract class member on line {} column {}", span.0, span.1), - Self::StaticModifierOnConstant(span) => write!(f, "Parse error: Cannot use 'static' as constant modifier on line {} column {}", span.0, span.1), - Self::ReadonlyModifierOnConstant(span) => write!(f, "Parse error: Cannot use 'readonly' as constant modifier on line {} column {}", span.0, span.1), - Self::FinalModifierOnPrivateConstant(span) => write!(f, "Parse error: Private constant cannot be final as it is not visible to other classes on line {} column {}", span.0, span.1), - Self::TryWithoutCatchOrFinally(span) => write!(f, "Parse error: cannot use try without catch or finally on line {} column {}", span.0, span.1), - Self::StandaloneTypeUsedInCombination(r#type, span) => write!(f, "Parse error: {} can only be used as a standalone type on line {} column {}", r#type, span.0, span.1), - Self::VariadicPromotedProperty(span) => write!(f, "Parse error: Cannot declare variadic promoted property on line {} column {}", span.0, span.1), - Self::PromotedPropertyOutsideConstructor(span) => write!(f, "Parse error: Cannot declare promoted property outside a constructor on line {} column {}", span.0, span.1), - Self::PromotedPropertyOnAbstractConstructor(span) => write!(f, "Parse error: Cannot declare promoted property in an abstract constructor on line {} column {}", span.0, span.1), - Self::AbstractModifierOnNonAbstractClassMethod(span) => write!(f, "Parse error: Cannot declare abstract methods on a non-abstract class on line {} column {}", span.0, span.1), - Self::FinalModifierOnAbstractClass(span) => write!(f, "Parse error: Cannot use the final modifier on an abstract class on line {} column {}", span.0, span.1), + Self::UnexpectedToken(message, span) => write!(f, "Parse Error: Unexpected token {} on line {} column {}", message, span.0, span.1), + Self::UnexpectedEndOfFile => write!(f, "Parse Error: unexpected end of file."), + Self::FinalModifierOnAbstractClassMember(span) => write!(f, "Parse Error: Cannot use the final modifier on an abstract class member on line {} column {}", span.0, span.1), + Self::StaticModifierOnConstant(span) => write!(f, "Parse Error: Cannot use 'static' as constant modifier on line {} column {}", span.0, span.1), + Self::ReadonlyModifierOnConstant(span) => write!(f, "Parse Error: Cannot use 'readonly' as constant modifier on line {} column {}", span.0, span.1), + Self::FinalModifierOnPrivateConstant(span) => write!(f, "Parse Error: Private constant cannot be final as it is not visible to other classes on line {} column {}", span.0, span.1), + Self::TryWithoutCatchOrFinally(span) => write!(f, "Parse Error: Cannot use try without catch or finally on line {} column {}", span.0, span.1), + Self::StandaloneTypeUsedInCombination(r#type, span) => write!(f, "Parse error: '{}' can only be used as a standalone type on line {} column {}", r#type, span.0, span.1), + Self::VariadicPromotedProperty(span) => write!(f, "Parse Error: Cannot declare variadic promoted property on line {} column {}", span.0, span.1), + Self::PromotedPropertyOutsideConstructor(span) => write!(f, "Parse Error: Cannot declare promoted property outside a constructor on line {} column {}", span.0, span.1), + Self::PromotedPropertyOnAbstractConstructor(span) => write!(f, "Parse Error: Cannot declare promoted property in an abstract constructor on line {} column {}", span.0, span.1), + Self::AbstractModifierOnNonAbstractClassMethod(span) => write!(f, "Parse Error: Cannot declare abstract methods on a non-abstract class on line {} column {}", span.0, span.1), + Self::FinalModifierOnAbstractClass(span) => write!(f, "Parse Error: Cannot use the final modifier on an abstract class on line {} column {}", span.0, span.1), + Self::ConstructorInEnum(name, span) => write!(f, "Parse Error: Enum '{}' cannot have a constructor on line {} column {}", name, span.0, span.1), + Self::UnpredictableState(span) => write!(f, "Parse Error: Reached an unpredictable state on line {} column {}", span.0, span.1) } } } diff --git a/src/parser/functions.rs b/src/parser/functions.rs deleted file mode 100644 index a2093fda..00000000 --- a/src/parser/functions.rs +++ /dev/null @@ -1,155 +0,0 @@ -use crate::lexer::byte_string::ByteString; -use crate::lexer::token::TokenKind; -use crate::parser::ast::ClassFlag; -use crate::parser::ast::MethodFlag; -use crate::parser::ast::Statement; -use crate::parser::classish_statement::ClassishDefinitionType; -use crate::parser::error::ParseError; -use crate::parser::error::ParseResult; -use crate::parser::params::ParamPosition; -use crate::parser::state::State; -use crate::parser::Parser; - -impl Parser { - pub(in crate::parser) fn function(&self, state: &mut State) -> ParseResult { - state.next(); - - let by_ref = if state.current.kind == TokenKind::Ampersand { - state.next(); - true - } else { - false - }; - - let name = self.ident(state)?; - - self.lparen(state)?; - - let params = self.param_list(state, ParamPosition::Function)?; - - self.rparen(state)?; - - let mut return_type = None; - - if state.current.kind == TokenKind::Colon { - self.colon(state)?; - - return_type = Some(self.type_string(state)?); - } - - self.lbrace(state)?; - - let body = self.block(state, &TokenKind::RightBrace)?; - - self.rbrace(state)?; - - Ok(Statement::Function { - name: name.into(), - params, - body, - return_type, - by_ref, - }) - } - - pub(in crate::parser) fn method( - &self, - state: &mut State, - class_type: ClassishDefinitionType, - flags: Vec, - ) -> ParseResult { - // TODO: more verification goes here, we know what type of class and what method flags there are. - match class_type { - ClassishDefinitionType::Class(cf) - if !cf.contains(&ClassFlag::Abstract) && flags.contains(&MethodFlag::Abstract) => - { - return Err(ParseError::AbstractModifierOnNonAbstractClassMethod( - state.current.span, - )); - } - _ => (), - } - - state.next(); - - let has_body = match &class_type { - ClassishDefinitionType::Class(_) | ClassishDefinitionType::Trait => { - !flags.contains(&MethodFlag::Abstract) - } - ClassishDefinitionType::Interface => false, - ClassishDefinitionType::Enum | ClassishDefinitionType::AnonymousClass => true, - }; - - let by_ref = if state.current.kind == TokenKind::Ampersand { - state.next(); - true - } else { - false - }; - - let name = self.ident_maybe_reserved(state)?; - - self.lparen(state)?; - - let position = position_from_flags_and_name(class_type, flags.clone(), name.clone()); - - let params = self.param_list(state, position)?; - - self.rparen(state)?; - - let mut return_type = None; - - if state.current.kind == TokenKind::Colon { - self.colon(state)?; - - return_type = Some(self.type_string(state)?); - } - - if !has_body { - self.semi(state)?; - - Ok(Statement::AbstractMethod { - name: name.into(), - params, - return_type, - flags: flags.to_vec(), - by_ref, - }) - } else { - self.lbrace(state)?; - - let body = self.block(state, &TokenKind::RightBrace)?; - - self.rbrace(state)?; - - Ok(Statement::Method { - name: name.into(), - params, - body, - return_type, - by_ref, - flags, - }) - } - } -} - -fn position_from_flags_and_name( - class_type: ClassishDefinitionType, - flags: Vec, - name: ByteString, -) -> ParamPosition { - match class_type { - ClassishDefinitionType::Enum - | ClassishDefinitionType::Class(_) - | ClassishDefinitionType::Trait - | ClassishDefinitionType::AnonymousClass => { - if !flags.contains(&MethodFlag::Abstract) { - ParamPosition::Method(name.to_string()) - } else { - ParamPosition::AbstractMethod(name.to_string()) - } - } - ClassishDefinitionType::Interface => ParamPosition::AbstractMethod(name.to_string()), - } -} diff --git a/src/parser/block.rs b/src/parser/internal/block.rs similarity index 100% rename from src/parser/block.rs rename to src/parser/internal/block.rs diff --git a/src/parser/internal/classish.rs b/src/parser/internal/classish.rs new file mode 100644 index 00000000..0dd32a28 --- /dev/null +++ b/src/parser/internal/classish.rs @@ -0,0 +1,274 @@ +use crate::lexer::token::TokenKind; +use crate::parser::ast::BackedEnumType; +use crate::parser::ast::Block; +use crate::parser::ast::ClassFlag; +use crate::parser::ast::Expression; +use crate::parser::ast::Identifier; +use crate::parser::ast::Statement; +use crate::parser::error::ParseResult; +use crate::parser::state::Scope; +use crate::parser::state::State; +use crate::parser::Parser; + +use crate::expect_token; +use crate::expected_token_err; +use crate::scoped; + +impl Parser { + pub(in crate::parser) fn class_definition(&self, state: &mut State) -> ParseResult { + let flags: Vec = self.class_flags(state)?.iter().map(|f| f.into()).collect(); + + expect_token!([TokenKind::Class], state, ["`class`"]); + + let name = self.ident(state)?; + + scoped!(state, Scope::Class(name.clone(), flags.clone()), { + let mut extends: Option = None; + + if state.current.kind == TokenKind::Extends { + state.next(); + extends = Some(self.full_name(state)?.into()); + } + + let implements = if state.current.kind == TokenKind::Implements { + state.next(); + + self.at_least_one_comma_separated::(state, &|parser, state| { + Ok(parser.full_name(state)?.into()) + })? + } else { + Vec::new() + }; + + self.lbrace(state)?; + + let mut body = Vec::new(); + while state.current.kind != TokenKind::RightBrace { + state.gather_comments(); + + if state.current.kind == TokenKind::RightBrace { + state.clear_comments(); + break; + } + + body.push(self.class_like_statement(state)?); + } + self.rbrace(state)?; + + Ok(Statement::Class { + name: name.into(), + extends, + implements, + body, + flags, + }) + }) + } + + pub(in crate::parser) fn interface_definition( + &self, + state: &mut State, + ) -> ParseResult { + expect_token!([TokenKind::Interface], state, ["`interface`"]); + let name = self.ident(state)?; + + scoped!(state, Scope::Interface(name.clone()), { + let extends = if state.current.kind == TokenKind::Extends { + state.next(); + + self.at_least_one_comma_separated::(state, &|parser, state| { + Ok(parser.full_name(state)?.into()) + })? + } else { + Vec::new() + }; + + self.lbrace(state)?; + + let mut body = Vec::new(); + while state.current.kind != TokenKind::RightBrace && !state.is_eof() { + state.gather_comments(); + + if state.current.kind == TokenKind::RightBrace { + state.clear_comments(); + break; + } + + body.push(self.interface_statement(state)?); + } + self.rbrace(state)?; + + Ok(Statement::Interface { + name: name.into(), + extends, + body, + }) + }) + } + + pub(in crate::parser) fn trait_definition(&self, state: &mut State) -> ParseResult { + expect_token!([TokenKind::Trait], state, ["`trait`"]); + + let name = self.ident(state)?; + + scoped!(state, Scope::Trait(name.clone()), { + self.lbrace(state)?; + + let mut body = Vec::new(); + while state.current.kind != TokenKind::RightBrace && !state.is_eof() { + state.gather_comments(); + + if state.current.kind == TokenKind::RightBrace { + state.clear_comments(); + break; + } + + body.push(self.class_like_statement(state)?); + } + self.rbrace(state)?; + + Ok(Statement::Trait { + name: name.into(), + body, + }) + }) + } + + pub(in crate::parser) fn anonymous_class_definition( + &self, + state: &mut State, + ) -> ParseResult { + expect_token!([TokenKind::New], state, ["`new`"]); + expect_token!([TokenKind::Class], state, ["`class`"]); + + scoped!(state, Scope::AnonymousClass, { + let mut args = vec![]; + + if state.current.kind == TokenKind::LeftParen { + self.lparen(state)?; + + args = self.args_list(state)?; + + self.rparen(state)?; + } + + let mut extends: Option = None; + + if state.current.kind == TokenKind::Extends { + state.next(); + extends = Some(self.full_name(state)?.into()); + } + + let mut implements = Vec::new(); + if state.current.kind == TokenKind::Implements { + state.next(); + + while state.current.kind != TokenKind::LeftBrace { + self.optional_comma(state)?; + + implements.push(self.full_name(state)?.into()); + } + } + + self.lbrace(state)?; + + let mut body = Vec::new(); + while state.current.kind != TokenKind::RightBrace && !state.is_eof() { + body.push(self.class_like_statement(state)?); + } + + self.rbrace(state)?; + + Ok(Expression::New { + target: Box::new(Expression::AnonymousClass { + extends, + implements, + body, + }), + args, + }) + }) + } + + pub(in crate::parser) fn enum_definition(&self, state: &mut State) -> ParseResult { + expect_token!([TokenKind::Enum], state, ["`enum`"]); + + let name = self.ident(state)?; + + scoped!(state, Scope::Enum(name.clone()), { + let backed_type: Option = if state.current.kind == TokenKind::Colon { + self.colon(state)?; + + match state.current.kind.clone() { + TokenKind::Identifier(s) if s == b"string" || s == b"int" => { + state.next(); + + Some(match &s[..] { + b"string" => BackedEnumType::String, + b"int" => BackedEnumType::Int, + _ => unreachable!(), + }) + } + _ => { + return expected_token_err!(["`string`", "`int`"], state); + } + } + } else { + None + }; + + let mut implements = Vec::new(); + if state.current.kind == TokenKind::Implements { + state.next(); + + while state.current.kind != TokenKind::LeftBrace { + implements.push(self.full_name(state)?.into()); + + self.optional_comma(state)?; + } + } + + self.lbrace(state)?; + + let mut body = Block::new(); + while state.current.kind != TokenKind::RightBrace { + state.skip_comments(); + body.push(self.enum_statement(state, backed_type.is_some())?); + } + + self.rbrace(state)?; + + match backed_type { + Some(backed_type) => Ok(Statement::BackedEnum { + name: name.into(), + backed_type, + implements, + body, + }), + None => Ok(Statement::UnitEnum { + name: name.into(), + implements, + body, + }), + } + }) + } + + fn at_least_one_comma_separated( + &self, + state: &mut State, + func: &(dyn Fn(&Parser, &mut State) -> ParseResult), + ) -> ParseResult> { + let mut result: Vec = vec![]; + loop { + result.push(func(self, state)?); + if state.current.kind != TokenKind::Comma { + break; + } + + state.next(); + } + + Ok(result) + } +} diff --git a/src/parser/classish_statement.rs b/src/parser/internal/classish_statement.rs similarity index 89% rename from src/parser/classish_statement.rs rename to src/parser/internal/classish_statement.rs index f8f3966c..7d846ea3 100644 --- a/src/parser/classish_statement.rs +++ b/src/parser/internal/classish_statement.rs @@ -1,12 +1,11 @@ use crate::lexer::token::TokenKind; -use crate::parser::ast::ClassFlag; use crate::parser::ast::Identifier; use crate::parser::ast::MethodFlag; use crate::parser::ast::Statement; use crate::parser::ast::TraitAdaptation; use crate::parser::error::ParseError; use crate::parser::error::ParseResult; -use crate::parser::precedence::Precedence; +use crate::parser::internal::precedence::Precedence; use crate::parser::state::State; use crate::parser::Parser; @@ -14,24 +13,7 @@ use crate::expect_token; use crate::expected_token_err; use crate::peek_token; -#[derive(Debug)] -pub enum ClassishDefinitionType { - Class(Vec), - AnonymousClass, - Trait, - Interface, - Enum, -} - impl Parser { - pub(in crate::parser) fn class_statement( - &self, - state: &mut State, - flags: Vec, - ) -> ParseResult { - self.complete_class_statement(state, ClassishDefinitionType::Class(flags)) - } - pub(in crate::parser) fn interface_statement( &self, state: &mut State, @@ -41,7 +23,7 @@ impl Parser { } if state.current.kind == TokenKind::Function { - return self.method(state, ClassishDefinitionType::Interface, vec![]); + return self.method(state, vec![]); } let member_flags = self.interface_members_flags(state)?; @@ -50,23 +32,11 @@ impl Parser { TokenKind::Const => self.parse_classish_const(state, member_flags), TokenKind::Function => self.method( state, - ClassishDefinitionType::Interface, member_flags.iter().map(|t| t.clone().into()).collect(), ) ], state, ["`const`", "`function`"]) } - pub(in crate::parser) fn trait_statement(&self, state: &mut State) -> ParseResult { - self.complete_class_statement(state, ClassishDefinitionType::Trait) - } - - pub(in crate::parser) fn anonymous_class_statement( - &self, - state: &mut State, - ) -> ParseResult { - self.complete_class_statement(state, ClassishDefinitionType::AnonymousClass) - } - pub(in crate::parser) fn enum_statement( &self, state: &mut State, @@ -99,7 +69,7 @@ impl Parser { } if state.current.kind == TokenKind::Function { - return self.method(state, ClassishDefinitionType::Enum, vec![]); + return self.method(state, vec![]); } let member_flags = self.enum_members_flags(state)?; @@ -108,16 +78,14 @@ impl Parser { TokenKind::Const => self.parse_classish_const(state, member_flags), TokenKind::Function => self.method( state, - ClassishDefinitionType::Enum, member_flags.iter().map(|t| t.clone().into()).collect(), ) ], state, ["`const`", "`function`"]) } - fn complete_class_statement( + pub(in crate::parser) fn class_like_statement( &self, state: &mut State, - class_type: ClassishDefinitionType, ) -> ParseResult { if state.current.kind == TokenKind::Use { return self.parse_classish_uses(state); @@ -132,7 +100,7 @@ impl Parser { } if state.current.kind == TokenKind::Function { - return self.method(state, class_type, vec![]); + return self.method(state, vec![]); } let member_flags = self.class_members_flags(state)?; @@ -141,7 +109,6 @@ impl Parser { TokenKind::Const => self.parse_classish_const(state, member_flags), TokenKind::Function => self.method( state, - class_type, member_flags.iter().map(|t| t.clone().into()).collect(), ), // TODO diff --git a/src/parser/flags.rs b/src/parser/internal/flags.rs similarity index 100% rename from src/parser/flags.rs rename to src/parser/internal/flags.rs diff --git a/src/parser/internal/functions.rs b/src/parser/internal/functions.rs new file mode 100644 index 00000000..5b07840d --- /dev/null +++ b/src/parser/internal/functions.rs @@ -0,0 +1,274 @@ +use crate::expect_token; +use crate::lexer::token::TokenKind; +use crate::parser::ast::ClassFlag; +use crate::parser::ast::ClosureUse; +use crate::parser::ast::Expression; +use crate::parser::ast::MethodFlag; +use crate::parser::ast::Statement; +use crate::parser::error::ParseError; +use crate::parser::error::ParseResult; +use crate::parser::internal::precedence::Precedence; +use crate::parser::state::Scope; +use crate::parser::state::State; +use crate::parser::Parser; +use crate::scoped; + +impl Parser { + pub(in crate::parser) fn anonymous_function( + &self, + state: &mut State, + ) -> ParseResult { + let is_static = if state.current.kind == TokenKind::Static { + state.next(); + + true + } else { + false + }; + + expect_token!([TokenKind::Function], state, ["`function`"]); + + let by_ref = if state.current.kind == TokenKind::Ampersand { + state.next(); + true + } else { + false + }; + + scoped!(state, Scope::AnonymousFunction(is_static), { + self.lparen(state)?; + + let params = self.param_list(state)?; + + self.rparen(state)?; + + let mut uses = vec![]; + if state.current.kind == TokenKind::Use { + state.next(); + + self.lparen(state)?; + + while state.current.kind != TokenKind::RightParen { + let mut by_ref = false; + if state.current.kind == TokenKind::Ampersand { + by_ref = true; + } + + // TODO(azjezz): this shouldn't call expr, we should have a function + // just for variables, so we don't have to go through the whole `match` in `expression(...)` + let var = match self.expression(state, Precedence::Lowest)? { + s @ Expression::Variable { .. } => ClosureUse { var: s, by_ref }, + _ => { + return Err(ParseError::UnexpectedToken( + "expected variable".into(), + state.current.span, + )) + } + }; + + uses.push(var); + + self.optional_comma(state)?; + } + + self.rparen(state)?; + } + + let mut return_type = None; + if state.current.kind == TokenKind::Colon { + self.colon(state)?; + + return_type = Some(self.type_string(state)?); + } + + self.lbrace(state)?; + + let body = self.block(state, &TokenKind::RightBrace)?; + + self.rbrace(state)?; + + Ok(Expression::Closure { + params, + uses, + return_type, + body, + r#static: is_static, + by_ref, + }) + }) + } + + pub(in crate::parser) fn arrow_function(&self, state: &mut State) -> ParseResult { + let is_static = if state.current.kind == TokenKind::Static { + state.next(); + + true + } else { + false + }; + + expect_token!([TokenKind::Fn], state, ["`fn`"]); + + let by_ref = if state.current.kind == TokenKind::Ampersand { + state.next(); + true + } else { + false + }; + + scoped!(state, Scope::ArrowFunction(is_static), { + self.lparen(state)?; + + let params = self.param_list(state)?; + + self.rparen(state)?; + + let mut return_type = None; + if state.current.kind == TokenKind::Colon { + self.colon(state)?; + + return_type = Some(self.type_string(state)?); + } + + expect_token!([TokenKind::DoubleArrow], state, ["`=>`"]); + + let value = self.expression(state, Precedence::Lowest)?; + + Ok(Expression::ArrowFunction { + params, + return_type, + expr: Box::new(value), + by_ref, + r#static: is_static, + }) + }) + } + + pub(in crate::parser) fn function(&self, state: &mut State) -> ParseResult { + expect_token!([TokenKind::Function], state, ["`function`"]); + + let by_ref = if state.current.kind == TokenKind::Ampersand { + state.next(); + true + } else { + false + }; + + let name = self.ident(state)?; + + scoped!(state, Scope::Function(name.clone()), { + self.lparen(state)?; + + let params = self.param_list(state)?; + + self.rparen(state)?; + + let mut return_type = None; + + if state.current.kind == TokenKind::Colon { + self.colon(state)?; + + return_type = Some(self.type_string(state)?); + } + + self.lbrace(state)?; + + let body = self.block(state, &TokenKind::RightBrace)?; + + self.rbrace(state)?; + + Ok(Statement::Function { + name: name.into(), + params, + body, + return_type, + by_ref, + }) + }) + } + + pub(in crate::parser) fn method( + &self, + state: &mut State, + flags: Vec, + ) -> ParseResult { + expect_token!([TokenKind::Function], state, ["`function`"]); + + let by_ref = if state.current.kind == TokenKind::Ampersand { + state.next(); + true + } else { + false + }; + + let name = self.ident_maybe_reserved(state)?; + + scoped!(state, Scope::Method(name.clone(), flags.clone()), { + let has_body = match state.parent()? { + Scope::Class(_, cf) => { + if !cf.contains(&ClassFlag::Abstract) && flags.contains(&MethodFlag::Abstract) { + return Err(ParseError::AbstractModifierOnNonAbstractClassMethod( + state.current.span, + )); + } + + !flags.contains(&MethodFlag::Abstract) + } + Scope::Trait(_) => !flags.contains(&MethodFlag::Abstract), + Scope::Interface(_) => false, + Scope::Enum(enum_name) => { + if name.to_string() == "__construct" { + return Err(ParseError::ConstructorInEnum( + state.named(enum_name), + state.current.span, + )); + } + + true + } + _ => true, + }; + + self.lparen(state)?; + + let params = self.param_list(state)?; + + self.rparen(state)?; + + let mut return_type = None; + + if state.current.kind == TokenKind::Colon { + self.colon(state)?; + + return_type = Some(self.type_string(state)?); + } + + if !has_body { + self.semi(state)?; + + Ok(Statement::AbstractMethod { + name: name.into(), + params, + return_type, + flags: flags.to_vec(), + by_ref, + }) + } else { + self.lbrace(state)?; + + let body = self.block(state, &TokenKind::RightBrace)?; + + self.rbrace(state)?; + + Ok(Statement::Method { + name: name.into(), + params, + body, + return_type, + by_ref, + flags, + }) + } + }) + } +} diff --git a/src/parser/ident.rs b/src/parser/internal/ident.rs similarity index 100% rename from src/parser/ident.rs rename to src/parser/internal/ident.rs diff --git a/src/parser/internal/mod.rs b/src/parser/internal/mod.rs new file mode 100644 index 00000000..27d965dd --- /dev/null +++ b/src/parser/internal/mod.rs @@ -0,0 +1,11 @@ +pub mod block; +pub mod classish; +pub mod classish_statement; +pub mod flags; +pub mod functions; +pub mod ident; +pub mod params; +pub mod precedence; +pub mod punc; +pub mod types; +pub mod vars; diff --git a/src/parser/params.rs b/src/parser/internal/params.rs similarity index 71% rename from src/parser/params.rs rename to src/parser/internal/params.rs index d09820a4..d62a9da6 100644 --- a/src/parser/params.rs +++ b/src/parser/internal/params.rs @@ -1,32 +1,50 @@ use crate::lexer::token::TokenKind; use crate::parser::ast::Arg; use crate::parser::ast::Expression; +use crate::parser::ast::MethodFlag; use crate::parser::ast::Param; use crate::parser::ast::ParamList; use crate::parser::ast::PropertyFlag; use crate::parser::error::ParseError; use crate::parser::error::ParseResult; -use crate::parser::precedence::Precedence; +use crate::parser::internal::precedence::Precedence; +use crate::parser::state::Scope; use crate::parser::state::State; use crate::parser::Parser; use crate::expect_token; -#[derive(Debug)] -pub enum ParamPosition { - Function, - Method(String), - AbstractMethod(String), -} - impl Parser { - pub(in crate::parser) fn param_list( - &self, - state: &mut State, - position: ParamPosition, - ) -> Result { + pub(in crate::parser) fn param_list(&self, state: &mut State) -> Result { let mut params = ParamList::new(); + let construct: i8 = match state.scope()? { + Scope::Function(_) | Scope::AnonymousFunction(_) | Scope::ArrowFunction(_) => 0, + Scope::Method(name, flags) => { + if name.to_string() != "__construct" { + 0 + } else { + match state.parent()? { + // can only have abstract ctor + Scope::Interface(_) => 1, + // can only have concret ctor + Scope::AnonymousClass => 2, + // can have either abstract or concret ctor, + // depens on method flag. + Scope::Class(_, _) | Scope::Trait(_) => { + if flags.contains(&MethodFlag::Abstract) { + 1 + } else { + 2 + } + } + _ => unreachable!(), + } + } + } + _ => unreachable!(), + }; + while !state.is_eof() && state.current.kind != TokenKind::RightParen { let mut param_type = None; @@ -37,22 +55,16 @@ impl Parser { .collect(); if !flags.is_empty() { - match position { - ParamPosition::Method(name) if name != "__construct" => { + match construct { + 0 => { return Err(ParseError::PromotedPropertyOutsideConstructor( state.current.span, )); } - ParamPosition::AbstractMethod(name) => { - if name == "__construct" { - return Err(ParseError::PromotedPropertyOnAbstractConstructor( - state.current.span, - )); - } else { - return Err(ParseError::PromotedPropertyOutsideConstructor( - state.current.span, - )); - } + 1 => { + return Err(ParseError::PromotedPropertyOnAbstractConstructor( + state.current.span, + )); } _ => {} } diff --git a/src/parser/precedence.rs b/src/parser/internal/precedence.rs similarity index 100% rename from src/parser/precedence.rs rename to src/parser/internal/precedence.rs diff --git a/src/parser/punc.rs b/src/parser/internal/punc.rs similarity index 100% rename from src/parser/punc.rs rename to src/parser/internal/punc.rs diff --git a/src/parser/internal/types.rs b/src/parser/internal/types.rs new file mode 100644 index 00000000..68d82262 --- /dev/null +++ b/src/parser/internal/types.rs @@ -0,0 +1,144 @@ +use crate::lexer::byte_string::ByteString; +use crate::lexer::token::TokenKind; +use crate::parser::ast::TryBlockCaughtType; +use crate::parser::ast::Type; +use crate::parser::error::ParseError; +use crate::parser::error::ParseResult; +use crate::parser::state::State; +use crate::parser::Parser; + +impl Parser { + pub(in crate::parser) fn try_block_caught_type_string( + &self, + state: &mut State, + ) -> ParseResult { + let id = self.full_name(state)?; + + if state.current.kind == TokenKind::Pipe { + state.next(); + + let mut types = vec![id.into()]; + + while !state.is_eof() { + let id = self.full_name(state)?; + types.push(id.into()); + + if state.current.kind != TokenKind::Pipe { + break; + } + + state.next(); + } + + return Ok(TryBlockCaughtType::Union(types)); + } + + Ok(TryBlockCaughtType::Identifier(id.into())) + } + + pub(in crate::parser) fn type_string(&self, state: &mut State) -> ParseResult { + if state.current.kind == TokenKind::Question { + state.next(); + let t = self.type_with_static(state)?; + return Ok(Type::Nullable(Box::new(parse_simple_type(t)))); + } + + let id = self.type_with_static(state)?; + + if state.current.kind == TokenKind::Pipe { + state.next(); + + let r#type = parse_simple_type(id); + if r#type.standalone() { + return Err(ParseError::StandaloneTypeUsedInCombination( + r#type, + state.current.span, + )); + } + + let mut types = vec![r#type]; + + while !state.is_eof() { + let id = self.type_with_static(state)?; + let r#type = parse_simple_type(id); + if r#type.standalone() { + return Err(ParseError::StandaloneTypeUsedInCombination( + r#type, + state.current.span, + )); + } + + types.push(r#type); + + if state.current.kind != TokenKind::Pipe { + break; + } else { + state.next(); + } + } + + return Ok(Type::Union(types)); + } + + if state.current.kind == TokenKind::Ampersand + && !matches!(state.peek.kind, TokenKind::Variable(_)) + { + state.next(); + + let r#type = parse_simple_type(id); + if r#type.standalone() { + return Err(ParseError::StandaloneTypeUsedInCombination( + r#type, + state.current.span, + )); + } + + let mut types = vec![r#type]; + + while !state.is_eof() { + let id = self.type_with_static(state)?; + let r#type = parse_simple_type(id); + if r#type.standalone() { + return Err(ParseError::StandaloneTypeUsedInCombination( + r#type, + state.current.span, + )); + } + + types.push(r#type); + + if state.current.kind != TokenKind::Ampersand { + break; + } else { + state.next(); + } + } + + return Ok(Type::Intersection(types)); + } + + Ok(parse_simple_type(id)) + } +} + +fn parse_simple_type(id: ByteString) -> Type { + let name = &id[..]; + let lowered_name = name.to_ascii_lowercase(); + match lowered_name.as_slice() { + b"void" => Type::Void, + b"never" => Type::Never, + b"null" => Type::Null, + b"true" => Type::True, + b"false" => Type::False, + b"float" => Type::Float, + b"bool" => Type::Boolean, + b"int" => Type::Integer, + b"string" => Type::String, + b"array" => Type::Array, + b"object" => Type::Object, + b"mixed" => Type::Mixed, + b"iterable" => Type::Iterable, + b"callable" => Type::Callable, + _ => Type::Identifier(id.into()), + } +} diff --git a/src/parser/vars.rs b/src/parser/internal/vars.rs similarity index 95% rename from src/parser/vars.rs rename to src/parser/internal/vars.rs index 58d5b5be..19920ca5 100644 --- a/src/parser/vars.rs +++ b/src/parser/internal/vars.rs @@ -2,7 +2,7 @@ use crate::lexer::token::TokenKind; use crate::parser::ast::Expression; use crate::parser::error::ParseError; use crate::parser::error::ParseResult; -use crate::parser::precedence::Precedence; +use crate::parser::internal::precedence::Precedence; use crate::parser::state::State; use crate::parser::Parser; diff --git a/src/parser/macros.rs b/src/parser/macros.rs index 54bb0779..e816fcc9 100644 --- a/src/parser/macros.rs +++ b/src/parser/macros.rs @@ -93,3 +93,17 @@ macro_rules! expected_token_err { $crate::expected_token_err!([$expected], $state) }; } + +#[macro_export] +macro_rules! scoped { + ($state:expr, $scope:expr, $block:block) => {{ + let scope = $scope; + $state.enter(scope.clone()); + + let result = $block?; + + $state.exit(); + + Ok(result) + }}; +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 66884d50..63adba7b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,36 +1,26 @@ use crate::expect_literal; use crate::expect_token; use crate::expected_token_err; -use crate::lexer::byte_string::ByteString; use crate::lexer::token::Token; use crate::lexer::token::TokenKind; use crate::parser::ast::{ - ArrayItem, Block, Case, Catch, ClosureUse, Constant, DeclareItem, ElseIf, Expression, - IncludeKind, MagicConst, MatchArm, Program, Statement, StaticVar, StringPart, - TryBlockCaughtType, Type, Use, UseKind, + ArrayItem, Block, Case, Catch, Constant, DeclareItem, ElseIf, Expression, IncludeKind, + MagicConst, MatchArm, Program, Statement, StaticVar, StringPart, Use, UseKind, }; use crate::parser::error::ParseError; use crate::parser::error::ParseResult; -use crate::parser::ident::is_reserved_ident; -use crate::parser::params::ParamPosition; -use crate::parser::precedence::{Associativity, Precedence}; +use crate::parser::internal::ident::is_reserved_ident; +use crate::parser::internal::precedence::{Associativity, Precedence}; +use crate::parser::state::Scope; use crate::parser::state::State; +use crate::scoped; pub mod ast; pub mod error; -mod block; -mod classish; -mod classish_statement; -mod flags; -mod functions; -mod ident; +mod internal; mod macros; -mod params; -mod precedence; -mod punc; mod state; -mod vars; #[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] pub struct Parser; @@ -68,115 +58,6 @@ impl Parser { Ok(ast.to_vec()) } - fn try_block_caught_type_string(&self, state: &mut State) -> ParseResult { - let id = self.full_name(state)?; - - if state.current.kind == TokenKind::Pipe { - state.next(); - - let mut types = vec![id.into()]; - - while !state.is_eof() { - let id = self.full_name(state)?; - types.push(id.into()); - - if state.current.kind != TokenKind::Pipe { - break; - } - - state.next(); - } - - return Ok(TryBlockCaughtType::Union(types)); - } - - Ok(TryBlockCaughtType::Identifier(id.into())) - } - - fn type_string(&self, state: &mut State) -> ParseResult { - if state.current.kind == TokenKind::Question { - state.next(); - let t = self.type_with_static(state)?; - return Ok(Type::Nullable(Box::new(parse_simple_type(t)))); - } - - let id = self.type_with_static(state)?; - - if state.current.kind == TokenKind::Pipe { - state.next(); - - let r#type = parse_simple_type(id); - if r#type.standalone() { - return Err(ParseError::StandaloneTypeUsedInCombination( - r#type, - state.current.span, - )); - } - - let mut types = vec![r#type]; - - while !state.is_eof() { - let id = self.type_with_static(state)?; - let r#type = parse_simple_type(id); - if r#type.standalone() { - return Err(ParseError::StandaloneTypeUsedInCombination( - r#type, - state.current.span, - )); - } - - types.push(r#type); - - if state.current.kind != TokenKind::Pipe { - break; - } else { - state.next(); - } - } - - return Ok(Type::Union(types)); - } - - if state.current.kind == TokenKind::Ampersand - && !matches!(state.peek.kind, TokenKind::Variable(_)) - { - state.next(); - - let r#type = parse_simple_type(id); - if r#type.standalone() { - return Err(ParseError::StandaloneTypeUsedInCombination( - r#type, - state.current.span, - )); - } - - let mut types = vec![r#type]; - - while !state.is_eof() { - let id = self.type_with_static(state)?; - let r#type = parse_simple_type(id); - if r#type.standalone() { - return Err(ParseError::StandaloneTypeUsedInCombination( - r#type, - state.current.span, - )); - } - - types.push(r#type); - - if state.current.kind != TokenKind::Ampersand { - break; - } else { - state.next(); - } - } - - return Ok(Type::Intersection(types)); - } - - Ok(parse_simple_type(id)) - } - fn top_level_statement(&self, state: &mut State) -> ParseResult { state.skip_comments(); @@ -184,40 +65,45 @@ impl Parser { TokenKind::Namespace => { state.next(); - let mut braced = false; - - let name = if state.current.kind == TokenKind::LeftBrace { - braced = true; - self.lbrace(state)?; - None - } else { - Some(self.name(state)?) - }; + if state.current.kind != TokenKind::LeftBrace { + let name = self.name(state)?; - if name.is_some() { if state.current.kind == TokenKind::LeftBrace { - braced = true; - state.next(); + self.lbrace(state)?; + + let body = scoped!(state, Scope::BracedNamespace(Some(name.clone())), { + self.block(state, &TokenKind::RightBrace) + })?; + + self.rbrace(state)?; + + Statement::BracedNamespace { + name: Some(name), + body, + } } else { - self.semi(state)?; - } - } + let body = scoped!(state, Scope::Namespace(name.clone()), { + let mut body = Block::new(); + while !state.is_eof() { + body.push(self.top_level_statement(state)?); + } - let body = if braced { - self.block(state, &TokenKind::RightBrace)? - } else { - let mut body = Block::new(); - while !state.is_eof() { - body.push(self.top_level_statement(state)?); + Ok(body) + })?; + + Statement::Namespace { name, body } } - body - }; + } else { + self.lbrace(state)?; + + let body = scoped!(state, Scope::BracedNamespace(None), { + self.block(state, &TokenKind::RightBrace) + })?; - if braced { self.rbrace(state)?; - } - Statement::Namespace { name, body } + Statement::BracedNamespace { name: None, body } + } } TokenKind::Use => { state.next(); @@ -1251,161 +1137,12 @@ impl Parser { Expression::Array { items } } - TokenKind::Static if matches!(state.peek.kind, TokenKind::Function | TokenKind::Fn) => { - state.next(); - - match self.expression(state, Precedence::Lowest)? { - Expression::Closure { - params, - uses, - return_type, - body, - by_ref, - .. - } => Expression::Closure { - params, - uses, - return_type, - body, - by_ref, - r#static: true, - }, - Expression::ArrowFunction { - params, - return_type, - expr, - by_ref, - .. - } => Expression::ArrowFunction { - params, - return_type, - expr, - by_ref, - r#static: true, - }, - _ => unreachable!(), - } - } - TokenKind::Function => { - state.next(); - - let by_ref = if state.current.kind == TokenKind::Ampersand { - state.next(); - true - } else { - false - }; - - self.lparen(state)?; - - let params = self.param_list(state, ParamPosition::Function)?; - - self.rparen(state)?; - - let mut uses = vec![]; - if state.current.kind == TokenKind::Use { - state.next(); - - self.lparen(state)?; - - while state.current.kind != TokenKind::RightParen { - let var = match state.current.kind { - TokenKind::Ampersand => { - state.next(); - - match self.expression(state, Precedence::Lowest)? { - s @ Expression::Variable { .. } => ClosureUse { - var: s, - by_ref: true, - }, - _ => { - return Err(ParseError::UnexpectedToken( - "expected variable".into(), - state.current.span, - )) - } - } - } - _ => match self.expression(state, Precedence::Lowest)? { - s @ Expression::Variable { .. } => ClosureUse { - var: s, - by_ref: false, - }, - _ => { - return Err(ParseError::UnexpectedToken( - "expected variable".into(), - state.current.span, - )) - } - }, - }; - - uses.push(var); - - self.optional_comma(state)?; - } - - self.rparen(state)?; - } - - let mut return_type = None; - if state.current.kind == TokenKind::Colon { - self.colon(state)?; - - return_type = Some(self.type_string(state)?); - } - - self.lbrace(state)?; - - let body = self.block(state, &TokenKind::RightBrace)?; - - self.rbrace(state)?; - - Expression::Closure { - params, - uses, - return_type, - body, - r#static: false, - by_ref, - } - } - TokenKind::Fn => { - state.next(); - - let by_ref = if state.current.kind == TokenKind::Ampersand { - state.next(); - true - } else { - false - }; - - self.lparen(state)?; - - let params = self.param_list(state, ParamPosition::Function)?; - - self.rparen(state)?; - - let mut return_type = None; - - if state.current.kind == TokenKind::Colon { - self.colon(state)?; - - return_type = Some(self.type_string(state)?); - } - - expect_token!([TokenKind::DoubleArrow], state, ["`=>`"]); - - let value = self.expression(state, Precedence::Lowest)?; - - Expression::ArrowFunction { - params, - return_type, - expr: Box::new(value), - by_ref, - r#static: false, - } + TokenKind::Static if state.peek.kind == TokenKind::Function => { + self.anonymous_function(state)? } + TokenKind::Static if state.peek.kind == TokenKind::Fn => self.arrow_function(state)?, + TokenKind::Function => self.anonymous_function(state)?, + TokenKind::Fn => self.arrow_function(state)?, TokenKind::New if state.peek.kind == TokenKind::Class => { self.anonymous_class_definition(state)? } @@ -1855,28 +1592,6 @@ impl Parser { } } -fn parse_simple_type(id: ByteString) -> Type { - let name = &id[..]; - let lowered_name = name.to_ascii_lowercase(); - match lowered_name.as_slice() { - b"void" => Type::Void, - b"never" => Type::Never, - b"null" => Type::Null, - b"true" => Type::True, - b"false" => Type::False, - b"float" => Type::Float, - b"bool" => Type::Boolean, - b"int" => Type::Integer, - b"string" => Type::String, - b"array" => Type::Array, - b"object" => Type::Object, - b"mixed" => Type::Mixed, - b"iterable" => Type::Iterable, - b"callable" => Type::Callable, - _ => Type::Identifier(id.into()), - } -} - fn is_prefix(op: &TokenKind) -> bool { matches!( op, diff --git a/src/parser/state.rs b/src/parser/state.rs index 2eb25d82..173ca892 100644 --- a/src/parser/state.rs +++ b/src/parser/state.rs @@ -1,10 +1,34 @@ +use std::collections::VecDeque; use std::vec::IntoIter; +use crate::lexer::byte_string::ByteString; use crate::lexer::token::Token; use crate::lexer::token::TokenKind; +use crate::parser::ast::ClassFlag; +use crate::parser::ast::MethodFlag; +use crate::parser::error::ParseError; +use crate::parser::error::ParseResult; + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum Scope { + Namespace(ByteString), + BracedNamespace(Option), + + Interface(ByteString), + Class(ByteString, Vec), + Trait(ByteString), + Enum(ByteString), + AnonymousClass, + + Function(ByteString), + Method(ByteString, Vec), + AnonymousFunction(bool), + ArrowFunction(bool), +} #[derive(Debug, Clone)] pub struct State { + pub stack: VecDeque, pub current: Token, pub peek: Token, pub iter: IntoIter, @@ -16,6 +40,7 @@ impl State { let mut iter = tokens.into_iter(); Self { + stack: VecDeque::new(), current: iter.next().unwrap_or_default(), peek: iter.next().unwrap_or_default(), iter, @@ -23,6 +48,50 @@ impl State { } } + pub fn named(&self, name: &ByteString) -> String { + let mut namespace = None; + for scope in &self.stack { + match scope { + Scope::Namespace(n) => { + namespace = Some(n.to_string()); + + break; + } + Scope::BracedNamespace(n) => { + namespace = n.as_ref().map(|s| s.to_string()); + + break; + } + _ => {} + } + } + + match namespace { + Some(v) => format!("{}\\{}", v, name), + None => name.to_string(), + } + } + + pub fn scope(&self) -> ParseResult<&Scope> { + self.stack + .back() + .ok_or(ParseError::UnpredictableState(self.current.span)) + } + + pub fn parent(&self) -> ParseResult<&Scope> { + self.stack + .get(self.stack.len() - 2) + .ok_or(ParseError::UnpredictableState(self.current.span)) + } + + pub fn enter(&mut self, state: Scope) { + self.stack.push_back(state); + } + + pub fn exit(&mut self) { + self.stack.pop_back(); + } + pub fn skip_comments(&mut self) { while matches!( self.current.kind, diff --git a/src/prelude.rs b/src/prelude.rs index 8298ab6a..a456ce2a 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2,7 +2,6 @@ pub use crate::lexer::byte_string::*; pub use crate::lexer::error::*; pub use crate::lexer::token::*; pub use crate::lexer::*; - pub use crate::parser::ast::*; pub use crate::parser::error::*; pub use crate::parser::*; diff --git a/tests/0002/parser-error.txt b/tests/0002/parser-error.txt index 5168e7d8..229da3de 100644 --- a/tests/0002/parser-error.txt +++ b/tests/0002/parser-error.txt @@ -1 +1 @@ -StandaloneTypeUsedInCombination(Never, (3, 23)) -> Parse error: never can only be used as a standalone type on line 3 column 23 +StandaloneTypeUsedInCombination(Never, (3, 23)) -> Parse error: 'never' can only be used as a standalone type on line 3 column 23 diff --git a/tests/0003/parser-error.txt b/tests/0003/parser-error.txt index af6dff75..1f44f522 100644 --- a/tests/0003/parser-error.txt +++ b/tests/0003/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some(")"), (6, 14)) -> Parse error: unexpected token `)`, expecting an identifier on line 6 column 14 +ExpectedToken(["an identifier"], Some(")"), (6, 14)) -> Parse Error: unexpected token `)`, expecting an identifier on line 6 column 14 diff --git a/tests/0004/parser-error.txt b/tests/0004/parser-error.txt index 2a15c0e9..242969bd 100644 --- a/tests/0004/parser-error.txt +++ b/tests/0004/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some("e"), (6, 14)) -> Parse error: unexpected token `e`, expecting an identifier on line 6 column 14 +ExpectedToken(["an identifier"], Some("e"), (6, 14)) -> Parse Error: unexpected token `e`, expecting an identifier on line 6 column 14 diff --git a/tests/0005/parser-error.txt b/tests/0005/parser-error.txt index d137201c..649f8191 100644 --- a/tests/0005/parser-error.txt +++ b/tests/0005/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`(`"], Some("{"), (6, 13)) -> Parse error: unexpected token `{`, expecting `(` on line 6 column 13 +ExpectedToken(["`(`"], Some("{"), (6, 13)) -> Parse Error: unexpected token `{`, expecting `(` on line 6 column 13 diff --git a/tests/0016/parser-error.txt b/tests/0016/parser-error.txt index 32dab20d..81b48573 100644 --- a/tests/0016/parser-error.txt +++ b/tests/0016/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["a literal"], Some("bar"), (3, 16)) -> Parse error: unexpected token `bar`, expecting a literal on line 3 column 16 +ExpectedToken(["a literal"], Some("bar"), (3, 16)) -> Parse Error: unexpected token `bar`, expecting a literal on line 3 column 16 diff --git a/tests/0019/ast.txt b/tests/0019/ast.txt index 428a671a..531fe195 100644 --- a/tests/0019/ast.txt +++ b/tests/0019/ast.txt @@ -1,5 +1,5 @@ [ - Namespace { + BracedNamespace { name: None, body: [ Function { @@ -13,7 +13,7 @@ }, ], }, - Namespace { + BracedNamespace { name: Some( "foo", ), @@ -198,7 +198,7 @@ }, ], }, - Namespace { + BracedNamespace { name: Some( "bar", ), @@ -383,7 +383,7 @@ }, ], }, - Namespace { + BracedNamespace { name: Some( "baz", ), diff --git a/tests/0044/parser-error.txt b/tests/0044/parser-error.txt index 750c5681..950193f0 100644 --- a/tests/0044/parser-error.txt +++ b/tests/0044/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`const`", "`function`", "an identifier", "a varaible"], Some("fn"), (1, 26)) -> Parse error: unexpected token `fn`, expecting `const`, `function`, an identifier, or a varaible on line 1 column 26 +ExpectedToken(["`const`", "`function`", "an identifier", "a varaible"], Some("fn"), (1, 26)) -> Parse Error: unexpected token `fn`, expecting `const`, `function`, an identifier, or a varaible on line 1 column 26 diff --git a/tests/0087/parser-error.txt b/tests/0087/parser-error.txt index 7a80e776..410594e8 100644 --- a/tests/0087/parser-error.txt +++ b/tests/0087/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`=`"], Some(";"), (5, 13)) -> Parse error: unexpected token `;`, expecting `=` on line 5 column 13 +ExpectedToken(["`=`"], Some(";"), (5, 13)) -> Parse Error: unexpected token `;`, expecting `=` on line 5 column 13 diff --git a/tests/0088/parser-error.txt b/tests/0088/parser-error.txt index 3fc6797e..d1c90531 100644 --- a/tests/0088/parser-error.txt +++ b/tests/0088/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`;`"], Some("class"), (1, 16)) -> Parse error: unexpected token `class`, expecting `;` on line 1 column 16 +ExpectedToken(["`;`"], Some("class"), (1, 16)) -> Parse Error: unexpected token `class`, expecting `;` on line 1 column 16 diff --git a/tests/0111/parser-error.txt b/tests/0111/parser-error.txt index 2a105e31..5369330f 100644 --- a/tests/0111/parser-error.txt +++ b/tests/0111/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some("..."), (5, 25)) -> Parse error: unexpected token `...`, expecting an identifier on line 5 column 25 +ExpectedToken(["an identifier"], Some("..."), (5, 25)) -> Parse Error: unexpected token `...`, expecting an identifier on line 5 column 25 diff --git a/tests/0112/parser-error.txt b/tests/0112/parser-error.txt index 4ae69a31..872d6967 100644 --- a/tests/0112/parser-error.txt +++ b/tests/0112/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some("&"), (5, 25)) -> Parse error: unexpected token `&`, expecting an identifier on line 5 column 25 +ExpectedToken(["an identifier"], Some("&"), (5, 25)) -> Parse Error: unexpected token `&`, expecting an identifier on line 5 column 25 diff --git a/tests/0113/parser-error.txt b/tests/0113/parser-error.txt index 0b0956c4..da1607de 100644 --- a/tests/0113/parser-error.txt +++ b/tests/0113/parser-error.txt @@ -1 +1 @@ -PromotedPropertyOutsideConstructor((5, 16)) -> Parse error: Cannot declare promoted property outside a constructor on line 5 column 16 +PromotedPropertyOutsideConstructor((5, 16)) -> Parse Error: Cannot declare promoted property outside a constructor on line 5 column 16 diff --git a/tests/0114/parser-error.txt b/tests/0114/parser-error.txt index 78046f26..9eec84bf 100644 --- a/tests/0114/parser-error.txt +++ b/tests/0114/parser-error.txt @@ -1 +1 @@ -PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse error: Cannot declare promoted property in an abstract constructor on line 5 column 16 +PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16 diff --git a/tests/0115/parser-error.txt b/tests/0115/parser-error.txt index 78046f26..9eec84bf 100644 --- a/tests/0115/parser-error.txt +++ b/tests/0115/parser-error.txt @@ -1 +1 @@ -PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse error: Cannot declare promoted property in an abstract constructor on line 5 column 16 +PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16 diff --git a/tests/0116/parser-error.txt b/tests/0116/parser-error.txt index 78046f26..9eec84bf 100644 --- a/tests/0116/parser-error.txt +++ b/tests/0116/parser-error.txt @@ -1 +1 @@ -PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse error: Cannot declare promoted property in an abstract constructor on line 5 column 16 +PromotedPropertyOnAbstractConstructor((5, 16)) -> Parse Error: Cannot declare promoted property in an abstract constructor on line 5 column 16 diff --git a/tests/0120/parser-error.txt b/tests/0120/parser-error.txt index f1ab1dd6..64c0675f 100644 --- a/tests/0120/parser-error.txt +++ b/tests/0120/parser-error.txt @@ -1 +1 @@ -AbstractModifierOnNonAbstractClassMethod((4, 14)) -> Parse error: Cannot declare abstract methods on a non-abstract class on line 4 column 14 +AbstractModifierOnNonAbstractClassMethod((4, 26)) -> Parse Error: Cannot declare abstract methods on a non-abstract class on line 4 column 26 diff --git a/tests/0121/parser-error.txt b/tests/0121/parser-error.txt index 18bec76e..51d3026f 100644 --- a/tests/0121/parser-error.txt +++ b/tests/0121/parser-error.txt @@ -1 +1 @@ -StaticModifierOnConstant((4, 12)) -> Parse error: Cannot use 'static' as constant modifier on line 4 column 12 +StaticModifierOnConstant((4, 12)) -> Parse Error: Cannot use 'static' as constant modifier on line 4 column 12 diff --git a/tests/0122/parser-error.txt b/tests/0122/parser-error.txt index 3421ec02..0e928e36 100644 --- a/tests/0122/parser-error.txt +++ b/tests/0122/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some("static"), (4, 11)) -> Parse error: unexpected token `static`, expecting an identifier on line 4 column 11 +ExpectedToken(["an identifier"], Some("static"), (4, 11)) -> Parse Error: unexpected token `static`, expecting an identifier on line 4 column 11 diff --git a/tests/0123/parser-error.txt b/tests/0123/parser-error.txt index e1698a5f..9d479a47 100644 --- a/tests/0123/parser-error.txt +++ b/tests/0123/parser-error.txt @@ -1 +1 @@ -ReadonlyModifierOnConstant((4, 14)) -> Parse error: Cannot use 'readonly' as constant modifier on line 4 column 14 +ReadonlyModifierOnConstant((4, 14)) -> Parse Error: Cannot use 'readonly' as constant modifier on line 4 column 14 diff --git a/tests/0124/parser-error.txt b/tests/0124/parser-error.txt index a59f86e0..a3fdb76c 100644 --- a/tests/0124/parser-error.txt +++ b/tests/0124/parser-error.txt @@ -1 +1 @@ -FinalModifierOnAbstractClassMember((4, 11)) -> Parse error: Cannot use the final modifier on an abstract class member on line 4 column 11 +FinalModifierOnAbstractClassMember((4, 11)) -> Parse Error: Cannot use the final modifier on an abstract class member on line 4 column 11 diff --git a/tests/0125/parser-error.txt b/tests/0125/parser-error.txt index 122e038f..94347b1a 100644 --- a/tests/0125/parser-error.txt +++ b/tests/0125/parser-error.txt @@ -1 +1 @@ -FinalModifierOnAbstractClass((3, 7)) -> Parse error: Cannot use the final modifier on an abstract class on line 3 column 7 +FinalModifierOnAbstractClass((3, 7)) -> Parse Error: Cannot use the final modifier on an abstract class on line 3 column 7 diff --git a/tests/0126/parser-error.txt b/tests/0126/parser-error.txt index a7979b63..28e82485 100644 --- a/tests/0126/parser-error.txt +++ b/tests/0126/parser-error.txt @@ -1 +1 @@ -FinalModifierOnPrivateConstant((4, 19)) -> Parse error: Private constant cannot be final as it is not visible to other classes on line 4 column 19 +FinalModifierOnPrivateConstant((4, 19)) -> Parse Error: Private constant cannot be final as it is not visible to other classes on line 4 column 19 diff --git a/tests/0128/parser-error.txt b/tests/0128/parser-error.txt index 92eb11ea..c9c94267 100644 --- a/tests/0128/parser-error.txt +++ b/tests/0128/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["an identifier"], Some("s"), (5, 25)) -> Parse error: unexpected token `s`, expecting an identifier on line 5 column 25 +ExpectedToken(["an identifier"], Some("s"), (5, 25)) -> Parse Error: unexpected token `s`, expecting an identifier on line 5 column 25 diff --git a/tests/0131/parser-error.txt b/tests/0131/parser-error.txt index a662eb6a..2d34187e 100644 --- a/tests/0131/parser-error.txt +++ b/tests/0131/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`(`"], Some("foreach"), (3, 10)) -> Parse error: unexpected token `foreach`, expecting `(` on line 3 column 10 +ExpectedToken(["`(`"], Some("foreach"), (3, 10)) -> Parse Error: unexpected token `foreach`, expecting `(` on line 3 column 10 diff --git a/tests/0132/parser-error.txt b/tests/0132/parser-error.txt index 892352d9..ac975017 100644 --- a/tests/0132/parser-error.txt +++ b/tests/0132/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`const`", "`function`"], Some("abstract"), (4, 12)) -> Parse error: unexpected token `abstract`, expecting `const`, or `function` on line 4 column 12 +ExpectedToken(["`const`", "`function`"], Some("abstract"), (4, 12)) -> Parse Error: unexpected token `abstract`, expecting `const`, or `function` on line 4 column 12 diff --git a/tests/0141/parser-error.txt b/tests/0141/parser-error.txt index ab5056d8..e4d28706 100644 --- a/tests/0141/parser-error.txt +++ b/tests/0141/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`const`", "`function`"], Some("private"), (4, 5)) -> Parse error: unexpected token `private`, expecting `const`, or `function` on line 4 column 5 +ExpectedToken(["`const`", "`function`"], Some("private"), (4, 5)) -> Parse Error: unexpected token `private`, expecting `const`, or `function` on line 4 column 5 diff --git a/tests/0142/parser-error.txt b/tests/0142/parser-error.txt index b293fc06..f1ee98c7 100644 --- a/tests/0142/parser-error.txt +++ b/tests/0142/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`const`", "`function`"], Some("protected"), (4, 5)) -> Parse error: unexpected token `protected`, expecting `const`, or `function` on line 4 column 5 +ExpectedToken(["`const`", "`function`"], Some("protected"), (4, 5)) -> Parse Error: unexpected token `protected`, expecting `const`, or `function` on line 4 column 5 diff --git a/tests/0144/parser-error.txt b/tests/0144/parser-error.txt index 24ca7203..f3f2a17b 100644 --- a/tests/0144/parser-error.txt +++ b/tests/0144/parser-error.txt @@ -1 +1 @@ -ExpectedToken(["`const`", "`function`"], Some("final"), (4, 5)) -> Parse error: unexpected token `final`, expecting `const`, or `function` on line 4 column 5 +ExpectedToken(["`const`", "`function`"], Some("final"), (4, 5)) -> Parse Error: unexpected token `final`, expecting `const`, or `function` on line 4 column 5 diff --git a/tests/0146/code.php b/tests/0146/code.php new file mode 100644 index 00000000..b3048f5b --- /dev/null +++ b/tests/0146/code.php @@ -0,0 +1,9 @@ + Parse Error: Enum 'Foo\Bar' cannot have a constructor on line 6 column 33 diff --git a/tests/0146/tokens.txt b/tests/0146/tokens.txt new file mode 100644 index 00000000..1259c99c --- /dev/null +++ b/tests/0146/tokens.txt @@ -0,0 +1,115 @@ +[ + Token { + kind: OpenTag( + Full, + ), + span: ( + 1, + 1, + ), + }, + Token { + kind: Namespace, + span: ( + 3, + 1, + ), + }, + Token { + kind: Identifier( + "Foo", + ), + span: ( + 3, + 11, + ), + }, + Token { + kind: SemiColon, + span: ( + 3, + 14, + ), + }, + Token { + kind: Enum, + span: ( + 5, + 1, + ), + }, + Token { + kind: Identifier( + "Bar", + ), + span: ( + 5, + 6, + ), + }, + Token { + kind: LeftBrace, + span: ( + 5, + 10, + ), + }, + Token { + kind: Public, + span: ( + 6, + 6, + ), + }, + Token { + kind: Function, + span: ( + 6, + 13, + ), + }, + Token { + kind: Identifier( + "__construct", + ), + span: ( + 6, + 22, + ), + }, + Token { + kind: LeftParen, + span: ( + 6, + 33, + ), + }, + Token { + kind: RightParen, + span: ( + 6, + 34, + ), + }, + Token { + kind: LeftBrace, + span: ( + 6, + 36, + ), + }, + Token { + kind: RightBrace, + span: ( + 8, + 6, + ), + }, + Token { + kind: RightBrace, + span: ( + 9, + 1, + ), + }, +]