fix: namespace parser

Signed-off-by: azjezz <azjezz@protonmail.com>
This commit is contained in:
Saif Eddin Gmati 2022-12-02 19:00:16 +01:00 committed by Ryan Chandler
parent 1f4875c802
commit 974c04db85
30 changed files with 1350 additions and 70 deletions

View File

@ -30,6 +30,8 @@ pub enum ParseError {
UnpredictableState(Span),
StaticPropertyUsingReadonlyModifier(String, String, Span),
ReadonlyPropertyHasDefaultValue(String, String, Span),
MixingBracedAndUnBracedNamespaceDeclarations(Span),
NestedNamespaceDeclarations(Span),
}
impl Display for ParseError {
@ -72,6 +74,8 @@ impl Display for ParseError {
Self::UnpredictableState(span) => write!(f, "Parse Error: Reached an unpredictable state on line {} column {}", span.0, span.1),
Self::StaticPropertyUsingReadonlyModifier(class, prop, span) => write!(f, "Parse Error: Static property {}:${} cannot be readonly on line {} column {}", class, prop, span.0, span.1),
Self::ReadonlyPropertyHasDefaultValue(class, prop, span) => write!(f, "Parse Error: Readonly property {}:${} cannot have a default value on line {} column {}", class, prop, span.0, span.1),
Self::MixingBracedAndUnBracedNamespaceDeclarations(span) => write!(f, "Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line {} column {}", span.0, span.1),
Self::NestedNamespaceDeclarations(span) => write!(f, "Parse Error: Namespace declarations cannot be mixed on line {} column {}", span.0, span.1),
}
}
}

View File

@ -16,10 +16,21 @@ impl Parser {
/// Expect an unqualified or qualified identifier such as Foo, Bar or Foo\Bar.
pub(in crate::parser) fn name(&self, state: &mut State) -> ParseResult<ByteString> {
Ok(expect_token!([
TokenKind::Identifier(identifier) => identifier,
TokenKind::QualifiedIdentifier(qualified) => qualified,
], state, "an identifier"))
expect_token!([
TokenKind::Identifier(name) | TokenKind::QualifiedIdentifier(name) => Ok(name),
], state, "an identifier")
}
/// Expect an optional unqualified or qualified identifier such as Foo, Bar or Foo\Bar.
pub(in crate::parser) fn optional_name(&self, state: &mut State) -> Option<ByteString> {
match state.current.kind.clone() {
TokenKind::Identifier(name) | TokenKind::QualifiedIdentifier(name) => {
state.next();
Some(name)
}
_ => None,
}
}
/// Expect an unqualified, qualified or fully qualified identifier such as Foo, Foo\Bar or \Foo\Bar.

View File

@ -4,6 +4,7 @@ pub mod classish_statement;
pub mod flags;
pub mod functions;
pub mod ident;
pub mod namespace;
pub mod params;
pub mod precedence;
pub mod punc;

View File

@ -0,0 +1,86 @@
use crate::lexer::token::TokenKind;
use crate::parser::ast::Block;
use crate::parser::ast::Statement;
use crate::parser::error::ParseError;
use crate::parser::error::ParseResult;
use crate::parser::state::NamespaceType;
use crate::parser::state::Scope;
use crate::parser::state::State;
use crate::parser::Parser;
use crate::prelude::ByteString;
use crate::scoped;
impl Parser {
pub(in crate::parser) fn namespace(&self, state: &mut State) -> ParseResult<Statement> {
state.next();
let name = self.optional_name(state);
if let Some(name) = &name {
if state.current.kind != TokenKind::LeftBrace {
match state.namespace_type() {
Some(NamespaceType::Braced) => {
return Err(ParseError::MixingBracedAndUnBracedNamespaceDeclarations(
state.current.span,
));
}
Some(NamespaceType::Unbraced) => {
// exit the current namespace scope.
// we don't need to check if the current scope is a namespace
// because we know it is, it is not possible for it to be anything else.
// as using `namespace` anywhere aside from a top-level stmt would result
// in a parse error.
state.exit();
}
_ => {}
}
return self.unbraced_namespace(state, name.clone());
}
}
match state.namespace_type() {
Some(NamespaceType::Unbraced) => Err(
ParseError::MixingBracedAndUnBracedNamespaceDeclarations(state.current.span),
),
Some(NamespaceType::Braced) if state.namespace().is_some() => {
Err(ParseError::NestedNamespaceDeclarations(state.current.span))
}
_ => self.braced_namespace(state, name),
}
}
fn unbraced_namespace(&self, state: &mut State, name: ByteString) -> ParseResult<Statement> {
let body = scoped!(state, Scope::Namespace(name.clone()), {
let mut body = Block::new();
while !state.is_eof() {
body.push(self.top_level_statement(state)?);
}
Ok(body)
})?;
Ok(Statement::Namespace { name, body })
}
fn braced_namespace(
&self,
state: &mut State,
name: Option<ByteString>,
) -> ParseResult<Statement> {
self.lbrace(state)?;
let body = scoped!(state, Scope::BracedNamespace(name.clone()), {
let mut body = Block::new();
while state.current.kind != TokenKind::RightBrace && !state.is_eof() {
body.push(self.top_level_statement(state)?);
}
Ok(body)
})?;
self.rbrace(state)?;
Ok(Statement::BracedNamespace { name, body })
}
}

View File

@ -11,9 +11,7 @@ use crate::parser::error::ParseError;
use crate::parser::error::ParseResult;
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;
@ -62,49 +60,7 @@ impl Parser {
state.skip_comments();
let statement = match &state.current.kind {
TokenKind::Namespace => {
state.next();
if state.current.kind != TokenKind::LeftBrace {
let name = self.name(state)?;
if state.current.kind == TokenKind::LeftBrace {
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 {
let body = scoped!(state, Scope::Namespace(name.clone()), {
let mut body = Block::new();
while !state.is_eof() {
body.push(self.top_level_statement(state)?);
}
Ok(body)
})?;
Statement::Namespace { name, body }
}
} else {
self.lbrace(state)?;
let body = scoped!(state, Scope::BracedNamespace(None), {
self.block(state, &TokenKind::RightBrace)
})?;
self.rbrace(state)?;
Statement::BracedNamespace { name: None, body }
}
}
TokenKind::Namespace => self.namespace(state)?,
TokenKind::Use => {
state.next();
@ -521,11 +477,7 @@ impl Parser {
};
let mut cases = Vec::new();
loop {
if state.current.kind == end_token {
break;
}
while state.current.kind != end_token {
match state.current.kind {
TokenKind::Case => {
state.next();

View File

@ -9,6 +9,12 @@ use crate::parser::ast::MethodFlag;
use crate::parser::error::ParseError;
use crate::parser::error::ParseResult;
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum NamespaceType {
Braced,
Unbraced,
}
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Scope {
Namespace(ByteString),
@ -33,6 +39,7 @@ pub struct State {
pub peek: Token,
pub iter: IntoIter<Token>,
pub comments: Vec<Token>,
pub namespace_type: Option<NamespaceType>,
}
impl State {
@ -45,30 +52,40 @@ impl State {
peek: iter.next().unwrap_or_default(),
iter,
comments: vec![],
namespace_type: None,
}
}
pub fn named(&self, name: &ByteString) -> String {
let mut namespace = None;
/// Return the namespace type used in the current state
///
/// The namespace type is retrieve from the last entered
/// namespace scope.
///
/// Note: even when a namespace scope is exited, the namespace type
/// is retained, until the next namespace scope is entered.
pub fn namespace_type(&self) -> Option<NamespaceType> {
self.namespace_type.clone()
}
pub fn namespace(&self) -> Option<&Scope> {
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;
Scope::Namespace(_) | Scope::BracedNamespace(_) => {
return Some(scope);
}
_ => {}
}
}
match namespace {
Some(v) => format!("{}\\{}", v, name),
None => name.to_string(),
None
}
pub fn named(&self, name: &ByteString) -> String {
match self.namespace() {
Some(Scope::Namespace(n)) | Some(Scope::BracedNamespace(Some(n))) => {
format!("{}\\{}", n, name)
}
_ => name.to_string(),
}
}
@ -84,8 +101,18 @@ impl State {
.ok_or(ParseError::UnpredictableState(self.current.span))
}
pub fn enter(&mut self, state: Scope) {
self.stack.push_back(state);
pub fn enter(&mut self, scope: Scope) {
match &scope {
Scope::Namespace(_) => {
self.namespace_type = Some(NamespaceType::Unbraced);
}
Scope::BracedNamespace(_) => {
self.namespace_type = Some(NamespaceType::Braced);
}
_ => {}
}
self.stack.push_back(scope);
}
pub fn exit(&mut self) {

18
tests/0157/ast.txt Normal file
View File

@ -0,0 +1,18 @@
[
BracedNamespace {
name: Some(
"Foo\Bar",
),
body: [
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
],
},
]

5
tests/0157/code.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace Foo\Bar {
function foo() {}
}

85
tests/0157/tokens.txt Normal file
View File

@ -0,0 +1,85 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
3,
11,
),
},
Token {
kind: LeftBrace,
span: (
3,
19,
),
},
Token {
kind: Function,
span: (
4,
6,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
4,
15,
),
},
Token {
kind: LeftParen,
span: (
4,
18,
),
},
Token {
kind: RightParen,
span: (
4,
19,
),
},
Token {
kind: LeftBrace,
span: (
4,
21,
),
},
Token {
kind: RightBrace,
span: (
4,
22,
),
},
Token {
kind: RightBrace,
span: (
5,
1,
),
},
]

32
tests/0158/ast.txt Normal file
View File

@ -0,0 +1,32 @@
[
Namespace {
name: "Foo\Bar",
body: [
Noop,
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
Namespace {
name: "Foo\Baz",
body: [
Noop,
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
],
},
],
},
]

9
tests/0158/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Foo\Bar;
function foo() {}
namespace Foo\Baz;
function foo() {}

145
tests/0158/tokens.txt Normal file
View File

@ -0,0 +1,145 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
3,
11,
),
},
Token {
kind: SemiColon,
span: (
3,
18,
),
},
Token {
kind: Function,
span: (
5,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
5,
10,
),
},
Token {
kind: LeftParen,
span: (
5,
13,
),
},
Token {
kind: RightParen,
span: (
5,
14,
),
},
Token {
kind: LeftBrace,
span: (
5,
16,
),
},
Token {
kind: RightBrace,
span: (
5,
17,
),
},
Token {
kind: Namespace,
span: (
7,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Baz",
),
span: (
7,
11,
),
},
Token {
kind: SemiColon,
span: (
7,
18,
),
},
Token {
kind: Function,
span: (
9,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
9,
10,
),
},
Token {
kind: LeftParen,
span: (
9,
13,
),
},
Token {
kind: RightParen,
span: (
9,
14,
),
},
Token {
kind: LeftBrace,
span: (
9,
16,
),
},
Token {
kind: RightBrace,
span: (
9,
17,
),
},
]

9
tests/0159/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Foo\Bar;
function foo() {}
namespace Foo\Baz {
function foo() {}
}

View File

@ -0,0 +1 @@
MixingBracedAndUnBracedNamespaceDeclarations((7, 19)) -> Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line 7 column 19

152
tests/0159/tokens.txt Normal file
View File

@ -0,0 +1,152 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
3,
11,
),
},
Token {
kind: SemiColon,
span: (
3,
18,
),
},
Token {
kind: Function,
span: (
5,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
5,
10,
),
},
Token {
kind: LeftParen,
span: (
5,
13,
),
},
Token {
kind: RightParen,
span: (
5,
14,
),
},
Token {
kind: LeftBrace,
span: (
5,
16,
),
},
Token {
kind: RightBrace,
span: (
5,
17,
),
},
Token {
kind: Namespace,
span: (
7,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Baz",
),
span: (
7,
11,
),
},
Token {
kind: LeftBrace,
span: (
7,
19,
),
},
Token {
kind: Function,
span: (
8,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
8,
14,
),
},
Token {
kind: LeftParen,
span: (
8,
17,
),
},
Token {
kind: RightParen,
span: (
8,
18,
),
},
Token {
kind: LeftBrace,
span: (
8,
20,
),
},
Token {
kind: RightBrace,
span: (
8,
21,
),
},
Token {
kind: RightBrace,
span: (
9,
1,
),
},
]

9
tests/0160/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Foo\Baz {
function foo() {}
namespace Foo\Bar;
function foo() {}
}

View File

@ -0,0 +1 @@
MixingBracedAndUnBracedNamespaceDeclarations((6, 22)) -> Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line 6 column 22

152
tests/0160/tokens.txt Normal file
View File

@ -0,0 +1,152 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Baz",
),
span: (
3,
11,
),
},
Token {
kind: LeftBrace,
span: (
3,
19,
),
},
Token {
kind: Function,
span: (
4,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
4,
14,
),
},
Token {
kind: LeftParen,
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
4,
18,
),
},
Token {
kind: LeftBrace,
span: (
4,
20,
),
},
Token {
kind: RightBrace,
span: (
4,
21,
),
},
Token {
kind: Namespace,
span: (
6,
5,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
6,
15,
),
},
Token {
kind: SemiColon,
span: (
6,
22,
),
},
Token {
kind: Function,
span: (
8,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
8,
14,
),
},
Token {
kind: LeftParen,
span: (
8,
17,
),
},
Token {
kind: RightParen,
span: (
8,
18,
),
},
Token {
kind: LeftBrace,
span: (
8,
20,
),
},
Token {
kind: RightBrace,
span: (
8,
21,
),
},
Token {
kind: RightBrace,
span: (
9,
1,
),
},
]

9
tests/0161/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace Foo\Baz {
function foo() {}
}
namespace Foo\Bar;
function foo() {}

View File

@ -0,0 +1 @@
MixingBracedAndUnBracedNamespaceDeclarations((7, 18)) -> Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line 7 column 18

152
tests/0161/tokens.txt Normal file
View File

@ -0,0 +1,152 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Baz",
),
span: (
3,
11,
),
},
Token {
kind: LeftBrace,
span: (
3,
19,
),
},
Token {
kind: Function,
span: (
4,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
4,
14,
),
},
Token {
kind: LeftParen,
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
4,
18,
),
},
Token {
kind: LeftBrace,
span: (
4,
20,
),
},
Token {
kind: RightBrace,
span: (
4,
21,
),
},
Token {
kind: RightBrace,
span: (
5,
1,
),
},
Token {
kind: Namespace,
span: (
7,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
7,
11,
),
},
Token {
kind: SemiColon,
span: (
7,
18,
),
},
Token {
kind: Function,
span: (
9,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
9,
10,
),
},
Token {
kind: LeftParen,
span: (
9,
13,
),
},
Token {
kind: RightParen,
span: (
9,
14,
),
},
Token {
kind: LeftBrace,
span: (
9,
16,
),
},
Token {
kind: RightBrace,
span: (
9,
17,
),
},
]

9
tests/0162/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace {
function foo() {}
}
namespace Foo\Bar;
function foo() {}

View File

@ -0,0 +1 @@
MixingBracedAndUnBracedNamespaceDeclarations((7, 18)) -> Parse Error: Cannot mix braced namespace declarations with unbraced namespace declarations on line 7 column 18

143
tests/0162/tokens.txt Normal file
View File

@ -0,0 +1,143 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: LeftBrace,
span: (
3,
11,
),
},
Token {
kind: Function,
span: (
4,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
4,
14,
),
},
Token {
kind: LeftParen,
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
4,
18,
),
},
Token {
kind: LeftBrace,
span: (
4,
20,
),
},
Token {
kind: RightBrace,
span: (
4,
21,
),
},
Token {
kind: RightBrace,
span: (
5,
1,
),
},
Token {
kind: Namespace,
span: (
7,
1,
),
},
Token {
kind: QualifiedIdentifier(
"Foo\Bar",
),
span: (
7,
11,
),
},
Token {
kind: SemiColon,
span: (
7,
18,
),
},
Token {
kind: Function,
span: (
9,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
9,
10,
),
},
Token {
kind: LeftParen,
span: (
9,
13,
),
},
Token {
kind: RightParen,
span: (
9,
14,
),
},
Token {
kind: LeftBrace,
span: (
9,
16,
),
},
Token {
kind: RightBrace,
span: (
9,
17,
),
},
]

5
tests/0163/code.php Normal file
View File

@ -0,0 +1,5 @@
<?php
namespace;
function foo() {}

View File

@ -0,0 +1 @@
ExpectedToken(["`{`"], Some(";"), (3, 10)) -> Parse Error: unexpected token `;`, expecting `{` on line 3 column 10

69
tests/0163/tokens.txt Normal file
View File

@ -0,0 +1,69 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: SemiColon,
span: (
3,
10,
),
},
Token {
kind: Function,
span: (
5,
1,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
5,
10,
),
},
Token {
kind: LeftParen,
span: (
5,
13,
),
},
Token {
kind: RightParen,
span: (
5,
14,
),
},
Token {
kind: LeftBrace,
span: (
5,
16,
),
},
Token {
kind: RightBrace,
span: (
5,
17,
),
},
]

32
tests/0164/ast.txt Normal file
View File

@ -0,0 +1,32 @@
[
BracedNamespace {
name: None,
body: [
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
],
},
BracedNamespace {
name: Some(
"a",
),
body: [
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
],
},
]

9
tests/0164/code.php Normal file
View File

@ -0,0 +1,9 @@
<?php
namespace {
function foo() {}
}
namespace a {
function foo() {}
}

150
tests/0164/tokens.txt Normal file
View File

@ -0,0 +1,150 @@
[
Token {
kind: OpenTag(
Full,
),
span: (
1,
1,
),
},
Token {
kind: Namespace,
span: (
3,
1,
),
},
Token {
kind: LeftBrace,
span: (
3,
11,
),
},
Token {
kind: Function,
span: (
4,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
4,
14,
),
},
Token {
kind: LeftParen,
span: (
4,
17,
),
},
Token {
kind: RightParen,
span: (
4,
18,
),
},
Token {
kind: LeftBrace,
span: (
4,
20,
),
},
Token {
kind: RightBrace,
span: (
4,
21,
),
},
Token {
kind: RightBrace,
span: (
5,
1,
),
},
Token {
kind: Namespace,
span: (
7,
1,
),
},
Token {
kind: Identifier(
"a",
),
span: (
7,
11,
),
},
Token {
kind: LeftBrace,
span: (
7,
13,
),
},
Token {
kind: Function,
span: (
8,
5,
),
},
Token {
kind: Identifier(
"foo",
),
span: (
8,
14,
),
},
Token {
kind: LeftParen,
span: (
8,
17,
),
},
Token {
kind: RightParen,
span: (
8,
18,
),
},
Token {
kind: LeftBrace,
span: (
8,
20,
),
},
Token {
kind: RightBrace,
span: (
8,
21,
),
},
Token {
kind: RightBrace,
span: (
9,
1,
),
},
]