use php_ast::*;
use php_lexer::TokenKind;
use crate::diagnostics::{ParseError, ERROR_PLACEHOLDER};
use crate::expr;
use crate::instrument;
use crate::parser::Parser;
use crate::version::PhpVersion;
mod class;
mod enum_decl;
mod trait_use;
pub use class::{parse_class_members, parse_name_list};
pub fn parse_stmt<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_stmt();
if parser.check(TokenKind::HashBracket) {
return parse_attributed_stmt(parser);
}
match parser.current_kind() {
TokenKind::CloseTag => {
parser.advance(); if parser.check(TokenKind::InlineHtml) {
let token = parser.advance();
let text = &parser.source[token.span.start as usize..token.span.end as usize];
return Stmt {
kind: StmtKind::InlineHtml(text),
span: token.span,
};
}
if parser.check(TokenKind::OpenTag) {
let tag = parser.advance();
if parser.source[tag.span.start as usize..tag.span.end as usize] == *"<?=" {
if let Some(echo_stmt) = parser.parse_short_echo() {
return echo_stmt;
}
}
}
let span = parser.current_span();
Stmt {
kind: StmtKind::Nop,
span,
}
}
TokenKind::OpenTag => {
let tag = parser.advance();
if parser.source[tag.span.start as usize..tag.span.end as usize] == *"<?=" {
if let Some(echo_stmt) = parser.parse_short_echo() {
return echo_stmt;
}
}
let span = parser.current_span();
Stmt {
kind: StmtKind::Nop,
span,
}
}
TokenKind::Semicolon => {
let span = parser.current_span();
parser.advance();
Stmt {
kind: StmtKind::Nop,
span,
}
}
TokenKind::Echo => parse_echo(parser),
TokenKind::Return => parse_return(parser),
TokenKind::LeftBrace => parse_block(parser),
TokenKind::If => parse_if(parser),
TokenKind::While => parse_while(parser),
TokenKind::Do => parse_do_while(parser),
TokenKind::For => parse_for(parser),
TokenKind::Foreach => parse_foreach(parser),
TokenKind::Function => {
let next = parser.peek_kind();
if next == Some(TokenKind::LeftParen)
|| (next == Some(TokenKind::Ampersand)
&& parser.peek2_kind() == Some(TokenKind::LeftParen))
{
parse_expression_stmt(parser)
} else {
parse_function(parser, parser.alloc_vec())
}
}
TokenKind::Break => parse_break(parser),
TokenKind::Continue => parse_continue(parser),
TokenKind::Switch => parse_switch(parser),
TokenKind::Throw => parse_throw_stmt(parser),
TokenKind::Try => parse_try_catch(parser),
TokenKind::Goto => parse_goto(parser),
TokenKind::Declare => parse_declare(parser),
TokenKind::Unset => parse_unset(parser),
TokenKind::Global => parse_global(parser),
TokenKind::Class => {
class::parse_class(parser, ClassModifiers::default(), parser.alloc_vec())
}
TokenKind::Abstract => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
..Default::default()
},
parser.alloc_vec(),
)
} else if parser.check(TokenKind::Readonly)
&& parser.peek_kind() == Some(TokenKind::Class)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php84, "abstract readonly class", span);
parser.advance(); class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else if parser.check(TokenKind::Final) {
parser.advance(); parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' and 'final' together on a class".into(),
span: Span::new(start, parser.previous_end()),
});
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_final: true,
..Default::default()
},
parser.alloc_vec(),
)
} else {
class_modifier_error(parser, start)
}
} else {
class_modifier_error(parser, start)
}
}
TokenKind::Final => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_final: true,
..Default::default()
},
parser.alloc_vec(),
)
} else if parser.check(TokenKind::Readonly) {
let span = parser.current_span();
parser.require_version(PhpVersion::Php82, "readonly class", span);
parser.advance();
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_final: true,
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else {
class_modifier_error(parser, start)
}
} else if parser.check(TokenKind::Abstract) {
parser.advance(); parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' and 'final' together on a class".into(),
span: Span::new(start, parser.previous_end()),
});
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_final: true,
..Default::default()
},
parser.alloc_vec(),
)
} else {
class_modifier_error(parser, start)
}
} else {
class_modifier_error(parser, start)
}
}
TokenKind::Readonly => {
if parser.peek_kind() == Some(TokenKind::Class) {
let span = parser.current_span();
parser.require_version(PhpVersion::Php82, "readonly class", span);
parser.advance(); class::parse_class(
parser,
ClassModifiers {
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else if parser.peek_kind() == Some(TokenKind::Final)
&& parser.peek2_kind() == Some(TokenKind::Class)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php82, "readonly class", span);
parser.advance(); parser.advance(); class::parse_class(
parser,
ClassModifiers {
is_final: true,
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else if parser.peek_kind() == Some(TokenKind::Abstract)
&& parser.peek2_kind() == Some(TokenKind::Class)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php84, "abstract readonly class", span);
parser.advance(); parser.advance(); class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else {
parse_expression_stmt(parser)
}
}
TokenKind::Interface => class::parse_interface(parser, parser.alloc_vec()),
TokenKind::Trait => class::parse_trait(parser, parser.alloc_vec()),
TokenKind::Enum_ => {
if matches!(parser.peek_kind(), Some(TokenKind::Identifier)) {
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "enums", span);
enum_decl::parse_enum(parser, parser.alloc_vec())
} else {
parse_expression_stmt(parser)
}
}
TokenKind::Namespace => {
if parser.peek_kind() == Some(TokenKind::Backslash) {
parse_expression_stmt(parser)
} else {
parse_namespace(parser)
}
}
TokenKind::Use => parse_use(parser),
TokenKind::Const => parse_const(parser),
TokenKind::HaltCompiler => parse_halt_compiler(parser),
TokenKind::Static => {
let next = parser.peek_kind();
if matches!(next, Some(TokenKind::Variable)) {
parse_static_var(parser)
} else {
parse_expression_stmt(parser)
}
}
TokenKind::Identifier => parse_expression_stmt_or_label(parser),
TokenKind::Eof => {
let span = parser.current_span();
parser.error(ParseError::ExpectedStatement { span });
Stmt {
kind: StmtKind::Error,
span,
}
}
_ => parse_expression_stmt(parser),
}
}
fn class_modifier_error<'arena, 'src>(
parser: &mut Parser<'arena, 'src>,
start: u32,
) -> Stmt<'arena, 'src> {
let span = Span::new(start, parser.previous_end());
parser.error(ParseError::Expected {
expected: "'class'".into(),
found: parser.current_kind(),
span,
});
parser.synchronize();
Stmt {
kind: StmtKind::Error,
span,
}
}
fn parse_attributed_stmt<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let attr_start = parser.start_span();
let attributes = parser.parse_attributes();
let is_closure_start = match parser.current_kind() {
TokenKind::Function => matches!(
parser.peek_kind(),
Some(TokenKind::LeftParen) | Some(TokenKind::Ampersand)
),
TokenKind::Fn_ => true,
TokenKind::Static => matches!(
parser.peek_kind(),
Some(TokenKind::Function) | Some(TokenKind::Fn_)
),
_ => false,
};
if is_closure_start {
let is_static = parser.eat(TokenKind::Static).is_some();
let expr = if parser.check(TokenKind::Function) {
expr::parse_closure(parser, is_static, attr_start, attributes)
} else {
expr::parse_arrow_function(parser, is_static, attr_start, attributes)
};
parser.expect_semicolon("expression statement");
let span = Span::new(attr_start, parser.previous_end());
return Stmt {
kind: StmtKind::Expression(parser.alloc(expr)),
span,
};
}
let stmt = match parser.current_kind() {
TokenKind::Function => return parse_function(parser, attributes),
TokenKind::Class => {
return class::parse_class(parser, ClassModifiers::default(), attributes)
}
TokenKind::Abstract => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::Class) {
return class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
..Default::default()
},
attributes,
);
} else if parser.check(TokenKind::Readonly)
&& parser.peek_kind() == Some(TokenKind::Class)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php84, "abstract readonly class", span);
parser.advance(); return class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_readonly: true,
..Default::default()
},
attributes,
);
} else if parser.check(TokenKind::Final) {
parser.advance();
parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' and 'final' together on a class".into(),
span: Span::new(start, parser.previous_end()),
});
if parser.check(TokenKind::Class) {
return class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_final: true,
..Default::default()
},
attributes,
);
} else {
class_modifier_error(parser, start)
}
} else {
class_modifier_error(parser, start)
}
}
TokenKind::Final => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_final: true,
..Default::default()
},
attributes,
)
} else if parser.check(TokenKind::Readonly) {
let span = parser.current_span();
parser.require_version(PhpVersion::Php82, "readonly class", span);
parser.advance();
class::parse_class(
parser,
ClassModifiers {
is_final: true,
is_readonly: true,
..Default::default()
},
attributes,
)
} else if parser.check(TokenKind::Abstract) {
parser.advance();
parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' and 'final' together on a class".into(),
span: Span::new(start, parser.previous_end()),
});
if parser.check(TokenKind::Class) {
class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_final: true,
..Default::default()
},
attributes,
)
} else {
class_modifier_error(parser, start)
}
} else {
class_modifier_error(parser, start)
}
}
TokenKind::Readonly => {
let readonly_span = parser.current_span();
parser.advance();
if parser.check(TokenKind::Class) {
parser.require_version(PhpVersion::Php82, "readonly class", readonly_span);
class::parse_class(
parser,
ClassModifiers {
is_readonly: true,
..Default::default()
},
attributes,
)
} else if parser.check(TokenKind::Final) && parser.peek_kind() == Some(TokenKind::Class)
{
parser.require_version(PhpVersion::Php82, "readonly class", readonly_span);
parser.advance(); return class::parse_class(
parser,
ClassModifiers {
is_final: true,
is_readonly: true,
..Default::default()
},
attributes,
);
} else if parser.check(TokenKind::Abstract)
&& parser.peek_kind() == Some(TokenKind::Class)
{
parser.require_version(PhpVersion::Php84, "abstract readonly class", readonly_span);
parser.advance(); return class::parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_readonly: true,
..Default::default()
},
attributes,
);
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'class'".into(),
found: parser.current_kind(),
span,
});
parser.synchronize();
Stmt {
kind: StmtKind::Error,
span,
}
}
}
TokenKind::Interface => return class::parse_interface(parser, attributes),
TokenKind::Trait => return class::parse_trait(parser, attributes),
TokenKind::Enum_ => {
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "enums", span);
return enum_decl::parse_enum(parser, attributes);
}
TokenKind::Const => {
let attr_span = parser.current_span();
parser.require_version(PhpVersion::Php85, "attributes on constants", attr_span);
let stmt = parse_const_with_attrs(parser, attributes);
if let StmtKind::Const(ref items) = stmt.kind {
if items.len() > 1 {
parser.error(ParseError::Forbidden {
message: "cannot use attributes on multi-constant declaration".into(),
span: stmt.span,
});
}
}
return stmt;
}
_ => {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "declaration after attributes".into(),
found: parser.current_kind(),
span,
});
parser.synchronize();
Stmt {
kind: StmtKind::Error,
span,
}
}
};
stmt
}
pub fn parse_block<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
let open = parser.expect(TokenKind::LeftBrace);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
parser.depth += 1;
let mut stmts = parser.alloc_vec_with_capacity(8);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
if parser.check(TokenKind::CloseTag) {
parser.advance();
if parser.check(TokenKind::InlineHtml) {
let token = parser.advance();
let text = &parser.source[token.span.start as usize..token.span.end as usize];
stmts.push(Stmt {
kind: StmtKind::InlineHtml(text),
span: token.span,
});
}
if parser.check(TokenKind::OpenTag) {
let tag = parser.advance();
if parser.source[tag.span.start as usize..tag.span.end as usize] == *"<?=" {
if let Some(echo_stmt) = parser.parse_short_echo() {
stmts.push(echo_stmt);
}
}
}
continue;
}
let span_before = parser.current_span();
stmts.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.depth -= 1;
parser.expect_closing(TokenKind::RightBrace, open_span);
let end = parser.previous_end();
let span = Span::new(start, end);
Stmt {
kind: StmtKind::Block(stmts),
span,
}
}
fn parse_stmt_or_block<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
if parser.check(TokenKind::LeftBrace) {
parse_block(parser)
} else {
parse_stmt(parser)
}
}
fn parse_stmts_until_end<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
ends: &[TokenKind],
) -> ArenaVec<'arena, Stmt<'arena, 'src>> {
let mut stmts = parser.alloc_vec_with_capacity(8);
while !ends.contains(&parser.current_kind()) && !parser.check(TokenKind::Eof) {
if parser.check(TokenKind::CloseTag) {
parser.advance();
if parser.check(TokenKind::InlineHtml) {
let token = parser.advance();
let text = &parser.source[token.span.start as usize..token.span.end as usize];
stmts.push(Stmt {
kind: StmtKind::InlineHtml(text),
span: token.span,
});
}
if parser.check(TokenKind::OpenTag) {
let tag = parser.advance();
if parser.source[tag.span.start as usize..tag.span.end as usize] == *"<?=" {
if let Some(echo_stmt) = parser.parse_short_echo() {
stmts.push(echo_stmt);
}
}
}
continue;
}
let span_before = parser.current_span();
stmts.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
stmts
}
fn parse_echo<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let mut exprs = parser.alloc_vec();
exprs.push(expr::parse_expr(parser));
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) {
break;
} exprs.push(expr::parse_expr(parser));
}
parser.expect_semicolon("echo statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Echo(exprs),
span,
}
}
fn parse_return<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let expr = if parser.check(TokenKind::Semicolon) {
None
} else {
Some(expr::parse_expr(parser))
};
parser.expect_semicolon("return statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Return(expr.map(|e| parser.alloc(e))),
span,
}
}
fn parse_if<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_if();
let start = parser.start_span();
parser.advance();
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let condition = expr::parse_expr(parser);
parser.expect_closing(TokenKind::RightParen, open_span);
if parser.eat(TokenKind::Colon).is_some() {
let stmts = parse_stmts_until_end(
parser,
&[TokenKind::ElseIf, TokenKind::Else, TokenKind::EndIf],
);
let then_branch = parser.alloc(Stmt {
kind: StmtKind::Block(stmts),
span: Span::new(start, parser.previous_end()),
});
let mut elseif_branches = parser.alloc_vec();
while parser.eat(TokenKind::ElseIf).is_some() {
let elseif_start = parser.start_span();
parser.expect(TokenKind::LeftParen);
let elseif_cond = expr::parse_expr(parser);
parser.expect(TokenKind::RightParen);
parser.expect(TokenKind::Colon);
let elseif_stmts = parse_stmts_until_end(
parser,
&[TokenKind::ElseIf, TokenKind::Else, TokenKind::EndIf],
);
let elseif_body = Stmt {
kind: StmtKind::Block(elseif_stmts),
span: Span::new(elseif_start, parser.previous_end()),
};
let elseif_span = Span::new(elseif_start, elseif_body.span.end);
elseif_branches.push(ElseIfBranch {
condition: elseif_cond,
body: elseif_body,
span: elseif_span,
});
}
let else_branch = if parser.eat(TokenKind::Else).is_some() {
parser.expect(TokenKind::Colon);
let else_stmts = parse_stmts_until_end(parser, &[TokenKind::EndIf]);
Some(parser.alloc(Stmt {
kind: StmtKind::Block(else_stmts),
span: Span::new(start, parser.previous_end()),
}))
} else {
None
};
parser.expect(TokenKind::EndIf);
parser.expect_semicolon(TokenKind::EndIf);
let span = Span::new(start, parser.previous_end());
return Stmt {
kind: StmtKind::If(parser.alloc(IfStmt {
condition,
then_branch,
elseif_branches,
else_branch,
})),
span,
};
}
let then_branch_stmt = parse_stmt_or_block(parser);
let then_branch = parser.alloc(then_branch_stmt);
let mut elseif_branches = parser.alloc_vec_with_capacity(2);
while parser.eat(TokenKind::ElseIf).is_some() {
let elseif_start = parser.start_span();
parser.expect(TokenKind::LeftParen);
let elseif_cond = expr::parse_expr(parser);
parser.expect(TokenKind::RightParen);
let elseif_body = parse_stmt_or_block(parser);
let elseif_span = Span::new(elseif_start, elseif_body.span.end);
elseif_branches.push(ElseIfBranch {
condition: elseif_cond,
body: elseif_body,
span: elseif_span,
});
}
let else_branch = if parser.eat(TokenKind::Else).is_some() {
{
let s = parse_stmt_or_block(parser);
Some(parser.alloc(s))
}
} else {
None
};
let end = else_branch
.as_ref()
.map(|b| b.span.end)
.or_else(|| elseif_branches.last().map(|b| b.span.end))
.unwrap_or(then_branch.span.end);
let span = Span::new(start, end);
Stmt {
kind: StmtKind::If(parser.alloc(IfStmt {
condition,
then_branch,
elseif_branches,
else_branch,
})),
span,
}
}
fn parse_while<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_loop();
let start = parser.start_span();
parser.advance();
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let condition = expr::parse_expr(parser);
parser.expect_closing(TokenKind::RightParen, open_span);
if parser.eat(TokenKind::Colon).is_some() {
parser.loop_depth += 1;
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndWhile]);
parser.loop_depth -= 1;
parser.expect(TokenKind::EndWhile);
parser.expect_semicolon(TokenKind::EndWhile);
let span = Span::new(start, parser.previous_end());
let body = parser.alloc(Stmt {
kind: StmtKind::Block(stmts),
span,
});
return Stmt {
kind: StmtKind::While(parser.alloc(WhileStmt { condition, body })),
span,
};
}
parser.loop_depth += 1;
let body_stmt = parse_stmt_or_block(parser);
parser.loop_depth -= 1;
let body = parser.alloc(body_stmt);
let span = Span::new(start, body.span.end);
Stmt {
kind: StmtKind::While(parser.alloc(WhileStmt { condition, body })),
span,
}
}
fn parse_do_while<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_loop();
let start = parser.start_span();
parser.advance();
parser.loop_depth += 1;
let body_stmt = parse_stmt_or_block(parser);
parser.loop_depth -= 1;
let body = parser.alloc(body_stmt);
parser.expect(TokenKind::While);
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let condition = expr::parse_expr(parser);
parser.expect_closing(TokenKind::RightParen, open_span);
parser.expect_semicolon("do-while statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::DoWhile(parser.alloc(DoWhileStmt { body, condition })),
span,
}
}
fn parse_for<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_loop();
let start = parser.start_span();
parser.advance();
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let init = parse_expr_list_until(parser, TokenKind::Semicolon);
parser.expect(TokenKind::Semicolon);
let condition = parse_expr_list_until(parser, TokenKind::Semicolon);
parser.expect(TokenKind::Semicolon);
let update = parse_expr_list_until(parser, TokenKind::RightParen);
parser.expect_closing(TokenKind::RightParen, open_span);
if parser.eat(TokenKind::Colon).is_some() {
parser.loop_depth += 1;
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndFor]);
parser.loop_depth -= 1;
parser.expect(TokenKind::EndFor);
parser.expect_semicolon(TokenKind::EndFor);
let span = Span::new(start, parser.previous_end());
let body = parser.alloc(Stmt {
kind: StmtKind::Block(stmts),
span,
});
return Stmt {
kind: StmtKind::For(parser.alloc(ForStmt {
init,
condition,
update,
body,
})),
span,
};
}
parser.loop_depth += 1;
let body_stmt = parse_stmt_or_block(parser);
parser.loop_depth -= 1;
let body = parser.alloc(body_stmt);
let span = Span::new(start, body.span.end);
Stmt {
kind: StmtKind::For(parser.alloc(ForStmt {
init,
condition,
update,
body,
})),
span,
}
}
fn parse_expr_list_until<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
stop: TokenKind,
) -> ArenaVec<'arena, Expr<'arena, 'src>> {
let mut exprs = parser.alloc_vec();
if parser.check(stop) {
return exprs;
}
exprs.push(expr::parse_expr(parser));
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(stop) {
break;
} exprs.push(expr::parse_expr(parser));
}
exprs
}
fn parse_foreach<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_foreach();
let start = parser.start_span();
parser.advance();
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let collection = expr::parse_expr(parser);
parser.expect(TokenKind::As);
if parser.check(TokenKind::Ampersand) {
parser.advance();
}
let first = expr::parse_expr(parser);
let (key, value) = if parser.eat(TokenKind::FatArrow).is_some() {
if parser.check(TokenKind::Ampersand) {
parser.advance();
}
let value = expr::parse_expr(parser);
(Some(first), value)
} else {
(None, first)
};
parser.expect_closing(TokenKind::RightParen, open_span);
if parser.eat(TokenKind::Colon).is_some() {
parser.loop_depth += 1;
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndForeach]);
parser.loop_depth -= 1;
parser.expect(TokenKind::EndForeach);
parser.expect_semicolon(TokenKind::EndForeach);
let span = Span::new(start, parser.previous_end());
let body = parser.alloc(Stmt {
kind: StmtKind::Block(stmts),
span,
});
return Stmt {
kind: StmtKind::Foreach(parser.alloc(ForeachStmt {
expr: collection,
key,
value,
body,
})),
span,
};
}
parser.loop_depth += 1;
let body_stmt = parse_stmt_or_block(parser);
parser.loop_depth -= 1;
let body = parser.alloc(body_stmt);
let span = Span::new(start, body.span.end);
Stmt {
kind: StmtKind::Foreach(parser.alloc(ForeachStmt {
expr: collection,
key,
value,
body,
})),
span,
}
}
fn parse_function<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
instrument::record_parse_function();
let start = parser.start_span();
parser.advance();
let by_ref = parser.eat(TokenKind::Ampersand).is_some();
let name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "function name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
ERROR_PLACEHOLDER
};
let open_paren = parser.expect(TokenKind::LeftParen);
let open_paren_span = open_paren.map(|t| t.span).unwrap_or(parser.current_span());
let params = parse_param_list(parser);
parser.expect_closing(TokenKind::RightParen, open_paren_span);
let return_type = if parser.eat(TokenKind::Colon).is_some() {
Some(parser.parse_type_hint())
} else {
None
};
let open_brace = parser.expect(TokenKind::LeftBrace);
let open_brace_span = open_brace.map(|t| t.span).unwrap_or(parser.current_span());
let mut body = parser.alloc_vec_with_capacity(4);
let saved_loop_depth = parser.loop_depth;
parser.loop_depth = 0;
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
body.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.loop_depth = saved_loop_depth;
parser.expect_closing(TokenKind::RightBrace, open_brace_span);
let end = parser.previous_end();
let span = Span::new(start, end);
let doc_comment = parser.take_doc_comment(start);
Stmt {
kind: StmtKind::Function(parser.alloc(FunctionDecl {
name,
params,
body,
return_type,
by_ref,
attributes,
doc_comment,
})),
span,
}
}
pub fn parse_param_list<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> ArenaVec<'arena, Param<'arena, 'src>> {
let mut params = parser.alloc_vec_with_capacity(4);
if parser.check(TokenKind::RightParen) {
return params;
}
loop {
if parser.check(TokenKind::RightParen) {
break;
}
let param_start = parser.start_span();
if parser.peek_kind() != Some(TokenKind::HashBracket) && !matches!(
parser.current_kind(),
TokenKind::Public | TokenKind::Protected | TokenKind::Private | TokenKind::Final | TokenKind::Readonly
) && parser.check(TokenKind::Variable)
{
if let Some(param) = try_parse_simple_param_fastpath_minimal(parser, param_start) {
params.push(param);
if parser.eat(TokenKind::Comma).is_none() {
break;
}
continue;
}
}
let param_attrs = parser.parse_attributes();
let mut visibility: Option<Visibility> = None;
let mut set_visibility: Option<Visibility> = None;
let mut is_final = false;
let mut is_readonly = false;
loop {
match parser.current_kind() {
TokenKind::Public | TokenKind::Protected | TokenKind::Private => {
let vis = match parser.current_kind() {
TokenKind::Public => Visibility::Public,
TokenKind::Protected => Visibility::Protected,
_ => Visibility::Private,
};
if parser.peek_kind() == Some(TokenKind::LeftParen)
&& parser.peek2_text() == Some("set")
{
let span = Span::new(param_start, parser.previous_end());
parser.require_version(PhpVersion::Php84, "asymmetric visibility", span);
if set_visibility.is_some() {
parser.error(ParseError::Forbidden {
message: "cannot use multiple set-visibility modifiers".into(),
span: Span::new(param_start, parser.previous_end()),
});
}
parser.advance(); parser.advance(); if parser.current_text() == "set" {
parser.advance(); }
parser.expect(TokenKind::RightParen);
set_visibility = Some(vis);
} else {
let span = parser.current_span();
parser.require_version(
PhpVersion::Php80,
"constructor property promotion",
span,
);
if visibility.is_some() {
parser.error(ParseError::Forbidden {
message: "cannot use multiple visibility modifiers".into(),
span: Span::new(param_start, parser.previous_end()),
});
}
parser.advance();
visibility = Some(vis);
}
}
TokenKind::Final => {
let span = parser.current_span();
parser.require_version(PhpVersion::Php85, "final promoted properties", span);
if is_final {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'final'".into(),
span,
});
}
parser.advance();
is_final = true;
}
TokenKind::Readonly => {
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "readonly parameters", span);
if is_readonly {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'readonly'".into(),
span,
});
}
parser.advance();
is_readonly = true;
}
_ => break,
}
}
let type_hint = if !(!parser.could_be_type_hint()
|| parser.check(TokenKind::Variable)
|| parser.check(TokenKind::Ellipsis)
|| parser.check(TokenKind::Ampersand)
&& matches!(parser.peek_kind(), Some(TokenKind::Variable)))
{
Some(parser.parse_type_hint())
} else {
None
};
if is_readonly && visibility.is_some() && type_hint.is_none() {
parser.error(ParseError::Forbidden {
message: "readonly promoted property must have type".into(),
span: Span::new(param_start, parser.previous_end()),
});
}
let by_ref = parser.eat(TokenKind::Ampersand).is_some();
let variadic = parser.eat(TokenKind::Ellipsis).is_some();
let name_token = parser.expect(TokenKind::Variable);
let name_span_end = name_token.as_ref().map(|t| t.span.end);
let name: &str = name_token
.map(|t| parser.variable_name(t))
.unwrap_or(ERROR_PLACEHOLDER);
let default = if parser.eat(TokenKind::Equals).is_some() {
if visibility.is_some() {
Some(parser.with_no_brace_subscript(expr::parse_expr))
} else {
Some(expr::parse_expr(parser))
}
} else {
None
};
let param_end = default
.as_ref()
.map(|e| e.span.end)
.or(name_span_end)
.unwrap_or(parser.previous_end());
let hooks = if visibility.is_some() && parser.check(TokenKind::LeftBrace) {
class::parse_property_hooks(parser)
} else {
parser.alloc_vec()
};
let param_end = if !hooks.is_empty() {
parser.previous_end()
} else {
param_end
};
params.push(Param {
name,
type_hint,
default,
by_ref,
variadic,
is_readonly,
is_final,
visibility,
set_visibility,
attributes: param_attrs,
hooks,
span: Span::new(param_start, param_end),
});
if parser.eat(TokenKind::Comma).is_none() {
break;
}
}
params
}
fn try_parse_simple_param_fastpath_minimal<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
param_start: u32,
) -> Option<Param<'arena, 'src>> {
if !parser.check(TokenKind::Variable) {
return None;
}
let peek_after_var = parser.peek_kind();
if !matches!(
peek_after_var,
Some(TokenKind::Comma) | Some(TokenKind::RightParen)
) {
return None;
}
let name_token = parser.advance();
let name_span_end = name_token.span.end;
let name: &str = parser.variable_name(name_token);
Some(Param {
name,
type_hint: None,
default: None,
by_ref: false,
variadic: false,
is_readonly: false,
is_final: false,
visibility: None,
set_visibility: None,
attributes: parser.alloc_vec(),
hooks: parser.alloc_vec(),
span: Span::new(param_start, name_span_end),
})
}
fn parse_break<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
let kw_span = parser.current_span();
parser.advance();
let expr = if !parser.check(TokenKind::Semicolon) {
Some(expr::parse_expr(parser))
} else {
None
};
validate_break_continue(parser, "break", parser.loop_depth, &expr, kw_span);
parser.expect_semicolon("break statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Break(expr.map(|e| parser.alloc(e))),
span,
}
}
fn parse_continue<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
let kw_span = parser.current_span();
parser.advance();
let expr = if !parser.check(TokenKind::Semicolon) {
Some(expr::parse_expr(parser))
} else {
None
};
validate_break_continue(parser, "continue", parser.loop_depth, &expr, kw_span);
parser.expect_semicolon("continue statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Continue(expr.map(|e| parser.alloc(e))),
span,
}
}
fn validate_break_continue<'arena, 'src>(
parser: &mut Parser<'arena, 'src>,
kw: &'static str,
loop_depth: u32,
expr: &Option<Expr<'arena, 'src>>,
span: Span,
) {
use php_ast::ExprKind;
match expr {
None => {
if loop_depth == 0 {
parser.error(ParseError::Forbidden {
message: format!("Cannot '{}' 1 level", kw).into(),
span,
});
}
}
Some(e) => {
if let ExprKind::Int(n) = e.kind {
if n <= 0 {
parser.error(ParseError::Forbidden {
message: format!("'{}' operator accepts only positive integers", kw).into(),
span,
});
} else if n as u32 > loop_depth {
let levels = if n == 1 {
"1 level".to_string()
} else {
format!("{} levels", n)
};
parser.error(ParseError::Forbidden {
message: format!("Cannot '{}' {}", kw, levels).into(),
span,
});
}
}
}
}
}
fn parse_switch<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_switch();
let start = parser.start_span();
parser.advance();
let open = parser.expect(TokenKind::LeftParen);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let switch_expr = expr::parse_expr(parser);
parser.expect_closing(TokenKind::RightParen, open_span);
let alt_syntax = parser.eat(TokenKind::Colon).is_some();
if !alt_syntax {
parser.expect(TokenKind::LeftBrace);
}
while parser.check(TokenKind::Semicolon) {
parser.advance();
}
let end_tokens: &[TokenKind] = if alt_syntax {
&[TokenKind::EndSwitch]
} else {
&[TokenKind::RightBrace]
};
let mut cases = parser.alloc_vec_with_capacity(8);
parser.loop_depth += 1;
while !end_tokens.contains(&parser.current_kind()) && !parser.check(TokenKind::Eof) {
let case_start = parser.start_span();
let value = if parser.eat(TokenKind::Case).is_some() {
let v = expr::parse_expr(parser);
if parser.eat(TokenKind::Colon).is_none() {
parser.expect(TokenKind::Semicolon);
}
Some(v)
} else if parser.eat(TokenKind::Default).is_some() {
if parser.eat(TokenKind::Colon).is_none() {
parser.expect(TokenKind::Semicolon);
}
None
} else {
break;
};
let mut body = parser.alloc_vec();
while !parser.check(TokenKind::Case)
&& !parser.check(TokenKind::Default)
&& !end_tokens.contains(&parser.current_kind())
&& !parser.check(TokenKind::Eof)
{
body.push(parse_stmt(parser));
}
cases.push(SwitchCase {
value,
body,
span: Span::new(case_start, parser.previous_end()),
});
}
parser.loop_depth -= 1;
if alt_syntax {
parser.expect(TokenKind::EndSwitch);
parser.expect_semicolon(TokenKind::EndSwitch);
} else {
parser.expect(TokenKind::RightBrace);
}
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Switch(parser.alloc(SwitchStmt {
expr: switch_expr,
cases,
})),
span,
}
}
fn parse_throw_stmt<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let expr = expr::parse_expr(parser);
parser.expect_semicolon("throw statement");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Throw(parser.alloc(expr)),
span,
}
}
fn parse_try_catch<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
instrument::record_parse_try();
let start = parser.start_span();
parser.advance();
parser.expect(TokenKind::LeftBrace);
let mut body = parser.alloc_vec_with_capacity(16);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
body.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
let mut catches = parser.alloc_vec_with_capacity(2);
while parser.eat(TokenKind::Catch).is_some() {
let catch_start = parser.start_span();
parser.expect(TokenKind::LeftParen);
let mut types = parser.alloc_vec();
types.push(parser.parse_name());
while parser.eat(TokenKind::Pipe).is_some() {
types.push(parser.parse_name());
}
let var = if parser.check(TokenKind::Variable) {
let t = parser.advance();
Some(parser.variable_name(t))
} else {
None
};
parser.expect(TokenKind::RightParen);
parser.expect(TokenKind::LeftBrace);
let mut catch_body = parser.alloc_vec_with_capacity(8);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
catch_body.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
catches.push(CatchClause {
types,
var,
body: catch_body,
span: Span::new(catch_start, parser.previous_end()),
});
}
let finally = if parser.eat(TokenKind::Finally).is_some() {
parser.expect(TokenKind::LeftBrace);
let mut finally_body = parser.alloc_vec();
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
finally_body.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
Some(finally_body)
} else {
None
};
if catches.is_empty() && finally.is_none() {
parser.error(ParseError::Expected {
expected: "catch or finally clause".into(),
found: parser.current_kind(),
span: Span::new(start, parser.previous_end()),
});
}
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::TryCatch(parser.alloc(TryCatchStmt {
body,
catches,
finally,
})),
span,
}
}
fn parse_goto<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let name_token = parser.expect(TokenKind::Identifier);
let src = parser.source;
let name: &str = name_token
.map(|t| &src[t.span.start as usize..t.span.end as usize])
.unwrap_or(ERROR_PLACEHOLDER);
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Goto(name),
span,
}
}
fn parse_declare<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
parser.expect(TokenKind::LeftParen);
let mut directives = parser.alloc_vec();
loop {
if parser.check(TokenKind::RightParen) {
break;
} if let Some(t) = parser.eat(TokenKind::Identifier) {
let src = parser.source;
let name = &src[t.span.start as usize..t.span.end as usize];
parser.expect(TokenKind::Equals);
let value = expr::parse_expr(parser);
directives.push((name, value));
}
if parser.eat(TokenKind::Comma).is_none() {
break;
}
}
parser.expect(TokenKind::RightParen);
let body = if parser.check(TokenKind::Semicolon) {
parser.advance();
None
} else if parser.eat(TokenKind::Colon).is_some() {
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndDeclare]);
parser.expect(TokenKind::EndDeclare);
parser.expect_semicolon(TokenKind::EndDeclare);
Some(parser.alloc(Stmt {
kind: StmtKind::Block(stmts),
span: Span::new(start, parser.previous_end()),
}))
} else {
{
let s = parse_stmt_or_block(parser);
Some(parser.alloc(s))
}
};
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Declare(parser.alloc(DeclareStmt { directives, body })),
span,
}
}
fn parse_unset<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
parser.expect(TokenKind::LeftParen);
let mut exprs = parser.alloc_vec();
exprs.push(expr::parse_expr(parser));
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::RightParen) {
break;
}
exprs.push(expr::parse_expr(parser));
}
parser.expect(TokenKind::RightParen);
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Unset(exprs),
span,
}
}
fn parse_global<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let mut exprs = parser.alloc_vec();
let e = expr::parse_expr(parser);
if !is_simple_variable(&e) {
parser.error(ParseError::Expected {
expected: "variable".into(),
found: parser.current_kind(),
span: e.span,
});
}
exprs.push(e);
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) {
break;
} let e = expr::parse_expr(parser);
if !is_simple_variable(&e) {
parser.error(ParseError::Expected {
expected: "variable".into(),
found: parser.current_kind(),
span: e.span,
});
}
exprs.push(e);
}
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Global(exprs),
span,
}
}
fn is_simple_variable<'arena, 'src>(expr: &Expr<'arena, 'src>) -> bool {
matches!(
&expr.kind,
ExprKind::Variable(_) | ExprKind::VariableVariable(_)
)
}
fn parse_namespace<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::LeftBrace) {
parser.expect(TokenKind::LeftBrace);
let mut stmts = parser.alloc_vec_with_capacity(16);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
stmts.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
return Stmt {
kind: StmtKind::Namespace(parser.alloc(NamespaceDecl {
name: None,
body: NamespaceBody::Braced(stmts),
})),
span: Span::new(start, end),
};
}
let name = parser.parse_name();
if parser.check(TokenKind::LeftBrace) {
parser.expect(TokenKind::LeftBrace);
let mut stmts = parser.alloc_vec_with_capacity(16);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let span_before = parser.current_span();
stmts.push(parse_stmt(parser));
if parser.current_span() == span_before {
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
Stmt {
kind: StmtKind::Namespace(parser.alloc(NamespaceDecl {
name: Some(name),
body: NamespaceBody::Braced(stmts),
})),
span: Span::new(start, end),
}
} else {
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Namespace(parser.alloc(NamespaceDecl {
name: Some(name),
body: NamespaceBody::Simple,
})),
span,
}
}
}
fn parse_use<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let kind = if parser.check(TokenKind::Function) {
parser.advance();
UseKind::Function
} else if parser.check(TokenKind::Const) {
parser.advance();
UseKind::Const
} else {
UseKind::Normal
};
let mut uses = parser.alloc_vec_with_capacity(4);
let item_start = parser.start_span();
let first_name = parser.parse_name();
if parser.check(TokenKind::LeftBrace) {
if first_name.kind() == NameKind::Unqualified {
let brace_pos = parser.current_span().start as usize;
let has_trailing_sep =
brace_pos > 0 && parser.source().as_bytes()[brace_pos - 1] == b'\\';
if !has_trailing_sep {
parser.error(ParseError::Expected {
expected: "namespace separator before '{'".into(),
found: parser.current_kind(),
span: first_name.span(),
});
}
}
parser.advance(); if parser.check(TokenKind::RightBrace) {
parser.error(ParseError::Expected {
expected: "at least one import in group use".into(),
found: TokenKind::RightBrace,
span: parser.current_span(),
});
}
let prefix_parts = first_name.parts_slice();
loop {
if parser.check(TokenKind::RightBrace) {
break;
} let sub_start = parser.start_span();
let item_kind = if parser.check(TokenKind::Function) {
parser.advance();
Some(UseKind::Function)
} else if parser.check(TokenKind::Const) {
parser.advance();
Some(UseKind::Const)
} else {
None
};
let sub_name = parser.parse_name();
if sub_name.kind() == NameKind::FullyQualified {
parser.error(ParseError::Expected {
expected: "non-fully-qualified name in group use".into(),
found: parser.current_kind(),
span: sub_name.span(),
});
}
let combined_parts = {
let sub_slice = sub_name.parts_slice();
let mut cp = parser.alloc_vec_with_capacity(prefix_parts.len() + sub_slice.len());
for p in prefix_parts.iter() {
cp.push(*p);
}
match sub_name {
Name::Simple { value, .. } => cp.push(value),
Name::Complex { parts, .. } => {
for p in parts.into_iter() {
cp.push(p);
}
}
}
cp
};
let sub_span = Span::new(item_start, parser.previous_end());
let combined_name = Name::Complex {
parts: combined_parts,
kind: if first_name.kind() == NameKind::FullyQualified {
NameKind::FullyQualified
} else {
NameKind::Qualified
},
span: sub_span,
};
let alias = if parser.eat(TokenKind::As).is_some() {
let alias_token = parser.expect(TokenKind::Identifier);
let src = parser.source;
alias_token.map(|t| &src[t.span.start as usize..t.span.end as usize])
} else {
None
};
let use_span = Span::new(sub_start, parser.previous_end());
uses.push(UseItem {
name: combined_name,
alias,
kind: item_kind,
span: use_span,
});
if parser.eat(TokenKind::Comma).is_none() {
break;
}
}
parser.expect(TokenKind::RightBrace);
} else {
let alias = if parser.eat(TokenKind::As).is_some() {
let alias_token = parser.expect(TokenKind::Identifier);
let src = parser.source;
alias_token.map(|t| &src[t.span.start as usize..t.span.end as usize])
} else {
None
};
let item_span = Span::new(item_start, parser.previous_end());
uses.push(UseItem {
name: first_name,
alias,
kind: None,
span: item_span,
});
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) {
break;
} let next_start = parser.start_span();
let name = parser.parse_name();
let alias = if parser.eat(TokenKind::As).is_some() {
let alias_token = parser.expect(TokenKind::Identifier);
let src = parser.source;
alias_token.map(|t| &src[t.span.start as usize..t.span.end as usize])
} else {
None
};
let next_span = Span::new(next_start, parser.previous_end());
uses.push(UseItem {
name,
alias,
kind: None,
span: next_span,
});
}
}
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Use(parser.alloc(UseDecl { kind, uses })),
span,
}
}
fn parse_const<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
parse_const_with_attrs(parser, parser.alloc_vec())
}
fn parse_const_with_attrs<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let mut items = parser.alloc_vec();
let mut pending_attrs = Some(attributes);
let mut pending_doc = parser.take_doc_comment(start);
loop {
let item_start = parser.start_span();
let const_name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "constant name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
ERROR_PLACEHOLDER
};
parser.expect(TokenKind::Equals);
let value = expr::parse_expr(parser);
let item_span = Span::new(item_start, value.span.end);
let item_attrs = pending_attrs.take().unwrap_or_else(|| parser.alloc_vec());
let doc_comment = pending_doc.take();
items.push(ConstItem {
name: const_name,
value,
attributes: item_attrs,
span: item_span,
doc_comment,
});
if parser.eat(TokenKind::Comma).is_none() {
break;
}
if parser.check(TokenKind::Semicolon) {
break;
} }
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Const(items),
span,
}
}
fn parse_halt_compiler<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
if parser.depth > 0 {
parser.error(ParseError::Forbidden {
message: "__halt_compiler() can only be used at the outermost scope".into(),
span: parser.current_span(),
});
}
parser.advance(); parser.expect(TokenKind::LeftParen);
parser.expect(TokenKind::RightParen);
if parser.check(TokenKind::Semicolon) {
parser.advance();
} else if parser.check(TokenKind::CloseTag) {
parser.advance(); } else {
parser.error(ParseError::ExpectedAfter {
expected: "';' or '?>'".into(),
after: "__halt_compiler()".into(),
span: parser.current_span(),
});
}
let current_pos = parser.current_span().start as usize;
let remaining = &parser.source[current_pos..];
while !parser.check(TokenKind::Eof) {
parser.advance();
}
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::HaltCompiler(remaining),
span,
}
}
fn parse_static_var<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let mut vars = parser.alloc_vec();
loop {
let var_start = parser.start_span();
let var_token = parser.expect(TokenKind::Variable);
let name: &str = var_token
.map(|t| parser.variable_name(t))
.unwrap_or(ERROR_PLACEHOLDER);
let default = if parser.eat(TokenKind::Equals).is_some() {
Some(expr::parse_expr(parser))
} else {
None
};
let var_span = Span::new(
var_start,
default
.as_ref()
.map(|e| e.span.end)
.unwrap_or(parser.previous_end()),
);
vars.push(StaticVar {
name,
default,
span: var_span,
});
if parser.eat(TokenKind::Comma).is_none() {
break;
}
if parser.check(TokenKind::Semicolon) {
break;
} }
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::StaticVar(vars),
span,
}
}
fn parse_expression_stmt_or_label<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> Stmt<'arena, 'src> {
let start = parser.start_span();
let expr = expr::parse_expr(parser);
if let ExprKind::Identifier(name) = expr.kind {
if parser.eat(TokenKind::Colon).is_some() {
let span = Span::new(start, parser.previous_end());
let label: &'arena str = match name {
NameStr::Src(s) => parser.arena.alloc_str(s),
NameStr::Arena(s) => s,
};
return Stmt {
kind: StmtKind::Label(label),
span,
};
}
}
if matches!(expr.kind, ExprKind::Error) {
parser.synchronize();
return Stmt {
kind: StmtKind::Error,
span: Span::new(start, parser.previous_end()),
};
}
parser.expect_semicolon("expression");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Expression(parser.alloc(expr)),
span,
}
}
fn parse_expression_stmt<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
let expr = expr::parse_expr(parser);
if matches!(expr.kind, ExprKind::Error) {
parser.synchronize();
return Stmt {
kind: StmtKind::Error,
span: Span::new(start, parser.previous_end()),
};
}
parser.expect_semicolon("expression");
let span = Span::new(start, parser.previous_end());
Stmt {
kind: StmtKind::Expression(parser.alloc(expr)),
span,
}
}