Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/parser/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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),
}
}
}
19 changes: 15 additions & 4 deletions src/parser/internal/ident.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions src/parser/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
86 changes: 86 additions & 0 deletions src/parser/internal/namespace.rs
Original file line number Diff line number Diff line change
@@ -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 })
}
}
52 changes: 2 additions & 50 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();
Expand Down
59 changes: 43 additions & 16 deletions src/parser/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 {
Expand All @@ -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(),
}
}

Expand All @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions tests/0157/ast.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
BracedNamespace {
name: Some(
"Foo\Bar",
),
body: [
Function {
name: Identifier {
name: "foo",
},
params: [],
body: [],
return_type: None,
by_ref: false,
},
],
},
]
5 changes: 5 additions & 0 deletions tests/0157/code.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?php

namespace Foo\Bar {
function foo() {}
}
Loading