use php_ast::*;
use php_lexer::TokenKind;
use crate::diagnostics::ParseError;
use crate::expr;
use crate::instrument;
use crate::parser::Parser;
use crate::version::PhpVersion;
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,
}
}
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 => parse_class(parser, ClassModifiers::default(), parser.alloc_vec()),
TokenKind::Abstract => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::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(); 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) {
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) {
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) {
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) {
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(); 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(); 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(); parse_class(
parser,
ClassModifiers {
is_abstract: true,
is_readonly: true,
..Default::default()
},
parser.alloc_vec(),
)
} else {
parse_expression_stmt(parser)
}
}
TokenKind::Interface => parse_interface(parser, parser.alloc_vec()),
TokenKind::Trait => parse_trait(parser, parser.alloc_vec()),
TokenKind::Enum_ => {
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "enums", span);
parse_enum(parser, parser.alloc_vec())
}
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 parse_attributed_stmt<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let attributes = parser.parse_attributes();
let stmt = match parser.current_kind() {
TokenKind::Function => return parse_function(parser, attributes),
TokenKind::Class => return parse_class(parser, ClassModifiers::default(), attributes),
TokenKind::Abstract => {
let start = parser.start_span();
parser.advance();
if parser.check(TokenKind::Class) {
return 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 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 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) {
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();
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) {
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);
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 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 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 parse_interface(parser, attributes),
TokenKind::Trait => return parse_trait(parser, attributes),
TokenKind::Enum_ => {
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "enums", span);
return 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(TokenKind::Semicolon);
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() {
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndWhile]);
parser.expect(TokenKind::EndWhile);
parser.expect(TokenKind::Semicolon);
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,
};
}
let body_stmt = parse_stmt_or_block(parser);
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();
let body_stmt = parse_stmt_or_block(parser);
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() {
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndFor]);
parser.expect(TokenKind::EndFor);
parser.expect(TokenKind::Semicolon);
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,
};
}
let body_stmt = parse_stmt_or_block(parser);
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() {
let stmts = parse_stmts_until_end(parser, &[TokenKind::EndForeach]);
parser.expect(TokenKind::EndForeach);
parser.expect(TokenKind::Semicolon);
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,
};
}
let body_stmt = parse_stmt_or_block(parser);
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>"
};
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);
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_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 visibility = parse_optional_visibility(parser);
if visibility.is_some() {
let span = Span::new(param_start, parser.previous_end());
parser.require_version(PhpVersion::Php80, "constructor property promotion", span);
}
let set_visibility = if visibility.is_some()
&& matches!(
parser.current_kind(),
TokenKind::Public | TokenKind::Protected | TokenKind::Private
)
&& parser.peek_kind() == Some(TokenKind::LeftParen)
{
let set_vis = match parser.current_kind() {
TokenKind::Public => Visibility::Public,
TokenKind::Protected => Visibility::Protected,
_ => Visibility::Private,
};
let span = Span::new(param_start, parser.previous_end());
parser.require_version(PhpVersion::Php84, "asymmetric visibility", span);
parser.advance(); parser.advance(); if parser.current_text() == "set" {
parser.advance(); }
parser.expect(TokenKind::RightParen);
Some(set_vis)
} else {
None
};
let final_token = parser
.check(TokenKind::Final)
.then(|| parser.current_span());
let is_final = parser.eat(TokenKind::Final).is_some();
if let Some(span) = final_token {
parser.require_version(PhpVersion::Php85, "final promoted properties", span);
}
let readonly_token = parser
.check(TokenKind::Readonly)
.then(|| parser.current_span());
let is_readonly = parser.eat(TokenKind::Readonly).is_some();
if let Some(span) = readonly_token {
parser.require_version(PhpVersion::Php81, "readonly parameters", span);
}
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
};
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>");
let default = if parser.eat(TokenKind::Equals).is_some() {
if visibility.is_some() {
Some(expr::parse_expr_bp(parser, 45))
} 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) {
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_optional_visibility(parser: &mut Parser) -> Option<Visibility> {
match parser.current_kind() {
TokenKind::Public => {
parser.advance();
Some(Visibility::Public)
}
TokenKind::Protected => {
parser.advance();
Some(Visibility::Protected)
}
TokenKind::Private => {
parser.advance();
Some(Visibility::Private)
}
_ => None,
}
}
fn parse_break<'arena, 'src>(parser: &'_ mut Parser<'arena, 'src>) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let expr = if !parser.check(TokenKind::Semicolon) {
Some(expr::parse_expr(parser))
} else {
None
};
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();
parser.advance();
let expr = if !parser.check(TokenKind::Semicolon) {
Some(expr::parse_expr(parser))
} else {
None
};
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 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);
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()),
});
}
if alt_syntax {
parser.expect(TokenKind::EndSwitch);
parser.expect(TokenKind::Semicolon);
} 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>");
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(TokenKind::Semicolon);
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 is_reserved_class_name(name: &str) -> bool {
let lower = name.to_ascii_lowercase();
matches!(lower.as_str(), "self" | "parent" | "static" | "readonly")
}
fn validate_class_ref<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
name: &Name<'arena, 'src>,
) {
if let Name::Simple { value, span } = name {
if is_reserved_class_name(value) {
parser.error(ParseError::Forbidden {
message: format!("cannot use '{}' as class name", value).into(),
span: *span,
});
}
}
}
fn parse_class<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
modifiers: ClassModifiers,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
instrument::record_parse_class();
let start = parser.start_span();
parser.advance();
let (name, name_span) = if let Some((text, span)) = parser.eat_identifier_or_keyword() {
(text, span)
} else {
parser.error(ParseError::Expected {
expected: "class name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
("<error>", parser.current_span())
};
if is_reserved_class_name(name) {
parser.error(ParseError::Forbidden {
message: format!("cannot use '{}' as class name", name).into(),
span: name_span,
});
}
let extends = if parser.eat(TokenKind::Extends).is_some() {
let n = parser.parse_name();
validate_class_ref(parser, &n);
Some(n)
} else {
None
};
let implements = if parser.eat(TokenKind::Implements).is_some() {
let names = parse_name_list(parser);
for n in names.iter() {
validate_class_ref(parser, n);
}
names
} else {
parser.alloc_vec()
};
parser.expect(TokenKind::LeftBrace);
let members = parse_class_members(parser, false);
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
let doc_comment = parser.take_doc_comment(start);
Stmt {
kind: StmtKind::Class(parser.alloc(ClassDecl {
name: Some(name),
modifiers,
extends,
implements,
members,
attributes,
doc_comment,
})),
span: Span::new(start, end),
}
}
pub fn parse_name_list<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> ArenaVec<'arena, Name<'arena, 'src>> {
let mut names = parser.alloc_vec();
names.push(parser.parse_name());
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::LeftBrace) || parser.check(TokenKind::Semicolon) {
break;
} names.push(parser.parse_name());
}
names
}
fn parse_trait_adaptations<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> ArenaVec<'arena, TraitAdaptation<'arena, 'src>> {
let mut adaptations = parser.alloc_vec();
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let start = parser.start_span();
let first_name = parser.parse_name();
if parser.eat(TokenKind::DoubleColon).is_some() {
let method = if let Some((text, span)) = parser.eat_identifier_or_keyword() {
Name::Simple { value: text, span }
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "method name".into(),
found: parser.current_kind(),
span,
});
Name::Simple {
value: "<error>",
span,
}
};
if parser.check(TokenKind::Identifier) && parser.current_text() == "insteadof" {
parser.advance(); let mut insteadof = {
let mut _v = parser.alloc_vec_with_capacity(1);
_v.push(parser.parse_name());
_v
};
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) {
break;
} insteadof.push(parser.parse_name());
}
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Precedence {
trait_name: first_name,
method,
insteadof,
},
span,
});
} else if parser.eat(TokenKind::As).is_some() {
let (new_modifier, new_name) = parse_alias_rhs(parser);
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Alias {
trait_name: Some(first_name),
method,
new_modifier,
new_name,
},
span,
});
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'insteadof' or 'as'".into(),
found: parser.current_kind(),
span,
});
parser.advance();
}
} else if parser.eat(TokenKind::As).is_some() {
let (new_modifier, new_name) = parse_alias_rhs(parser);
parser.expect(TokenKind::Semicolon);
let span = Span::new(start, parser.previous_end());
adaptations.push(TraitAdaptation {
kind: TraitAdaptationKind::Alias {
trait_name: None,
method: first_name,
new_modifier,
new_name,
},
span,
});
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'::' or 'as'".into(),
found: parser.current_kind(),
span,
});
parser.advance();
}
}
parser.expect(TokenKind::RightBrace);
adaptations
}
fn parse_alias_rhs<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> (Option<Visibility>, Option<Name<'arena, 'src>>) {
let new_modifier = match parser.current_kind() {
TokenKind::Public => {
parser.advance();
Some(Visibility::Public)
}
TokenKind::Protected => {
parser.advance();
Some(Visibility::Protected)
}
TokenKind::Private => {
parser.advance();
Some(Visibility::Private)
}
_ => None,
};
let new_name = if parser.check(TokenKind::Identifier) || parser.is_semi_reserved_keyword() {
let (text, span) = parser.eat_identifier_or_keyword().unwrap();
Some(Name::Simple { value: text, span })
} else {
None
};
(new_modifier, new_name)
}
fn parse_property_hooks<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
) -> ArenaVec<'arena, PropertyHook<'arena, 'src>> {
let open = parser.expect(TokenKind::LeftBrace);
let open_span = open.map(|t| t.span).unwrap_or(parser.current_span());
let mut hooks = parser.alloc_vec();
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
let hook_start = parser.start_span();
let hook_attrs = parser.parse_attributes();
let mut is_final = false;
let mut by_ref = false;
loop {
match parser.current_kind() {
TokenKind::Final => {
parser.advance();
is_final = true;
}
TokenKind::Ampersand => {
parser.advance();
by_ref = true;
break;
}
TokenKind::Public
| TokenKind::Protected
| TokenKind::Private
| TokenKind::Abstract
| TokenKind::Static
| TokenKind::Readonly => {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'get' or 'set'".into(),
found: parser.current_kind(),
span,
});
parser.advance();
}
_ => break,
}
}
let kind = if parser.check(TokenKind::Identifier) {
match parser.current_text() {
"get" => {
parser.advance();
PropertyHookKind::Get
}
"set" => {
parser.advance();
PropertyHookKind::Set
}
_ => {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'get' or 'set'".into(),
found: parser.current_kind(),
span,
});
while !parser.check(TokenKind::Semicolon)
&& !parser.check(TokenKind::RightBrace)
&& !parser.check(TokenKind::Eof)
{
parser.advance();
}
parser.eat(TokenKind::Semicolon);
continue;
}
}
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "'get' or 'set'".into(),
found: parser.current_kind(),
span,
});
while !parser.check(TokenKind::Semicolon)
&& !parser.check(TokenKind::RightBrace)
&& !parser.check(TokenKind::Eof)
{
parser.advance();
}
parser.eat(TokenKind::Semicolon);
continue;
};
let params = if parser.check(TokenKind::LeftParen) {
parser.advance();
let p = parse_param_list(parser);
parser.expect(TokenKind::RightParen);
p
} else {
parser.alloc_vec()
};
let body = if parser.check(TokenKind::LeftBrace) {
let open_brace = parser.expect(TokenKind::LeftBrace);
let brace_span = open_brace.map(|t| t.span).unwrap_or(parser.current_span());
let mut stmts = parser.alloc_vec_with_capacity(8);
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_closing(TokenKind::RightBrace, brace_span);
PropertyHookBody::Block(stmts)
} else if parser.eat(TokenKind::FatArrow).is_some() {
let e = expr::parse_expr(parser);
parser.expect(TokenKind::Semicolon);
PropertyHookBody::Expression(e)
} else {
parser.expect(TokenKind::Semicolon);
PropertyHookBody::Abstract
};
let hook_span = Span::new(hook_start, parser.previous_end());
hooks.push(PropertyHook {
kind,
body,
is_final,
by_ref,
params,
attributes: hook_attrs,
span: hook_span,
});
}
parser.expect_closing(TokenKind::RightBrace, open_span);
hooks
}
pub fn parse_class_members<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
in_interface: bool,
) -> ArenaVec<'arena, ClassMember<'arena, 'src>> {
let mut members = parser.alloc_vec_with_capacity(4);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
if parser.check(TokenKind::Semicolon) {
parser.advance();
continue;
}
let member_start = parser.start_span();
let member_attrs = parser.parse_attributes();
if parser.check(TokenKind::Use) {
parser.advance();
let mut traits = parser.alloc_vec_with_capacity(2);
traits.push(parser.parse_name());
while parser.eat(TokenKind::Comma).is_some() {
if parser.check(TokenKind::Semicolon) || parser.check(TokenKind::LeftBrace) {
break;
} traits.push(parser.parse_name());
}
let adaptations = if parser.check(TokenKind::LeftBrace) {
parser.advance();
parse_trait_adaptations(parser)
} else {
parser.expect(TokenKind::Semicolon);
parser.alloc_vec()
};
let span = Span::new(member_start, parser.previous_end());
members.push(ClassMember {
kind: ClassMemberKind::TraitUse(TraitUseDecl {
traits,
adaptations,
}),
span,
});
continue;
}
let mut visibility = None;
let mut set_visibility = None;
let mut asym_vis_span: Option<Span> = None;
let mut is_static = false;
let mut is_abstract = false;
let mut is_final = false;
let mut is_readonly = false;
if parser.check(TokenKind::Identifier) && parser.current_text() == "var" {
parser.advance();
visibility = Some(Visibility::Public);
}
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,
};
parser.advance();
if visibility.is_none() {
visibility = Some(vis);
if matches!(
parser.current_kind(),
TokenKind::Public | TokenKind::Protected | TokenKind::Private
) && parser.peek_kind() == Some(TokenKind::LeftParen)
{
let set_vis = match parser.current_kind() {
TokenKind::Public => Visibility::Public,
TokenKind::Protected => Visibility::Protected,
_ => Visibility::Private,
};
asym_vis_span = Some(Span::new(member_start, parser.previous_end()));
parser.advance(); parser.advance(); if parser.current_text() == "set" {
parser.advance(); }
parser.expect(TokenKind::RightParen);
set_visibility = Some(set_vis);
}
} else {
if parser.check(TokenKind::LeftParen) {
asym_vis_span = Some(Span::new(member_start, parser.previous_end()));
parser.advance(); if parser.current_text() == "set" {
parser.advance(); }
parser.expect(TokenKind::RightParen);
set_visibility = Some(vis);
} else {
parser.error(ParseError::Forbidden {
message: "cannot use multiple visibility modifiers".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
}
}
TokenKind::Static => {
if is_static {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'static'".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
parser.advance();
is_static = true;
}
TokenKind::Abstract => {
if is_abstract {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'abstract'".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
parser.advance();
is_abstract = true;
}
TokenKind::Final => {
if is_final {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'final'".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
parser.advance();
is_final = true;
}
TokenKind::Readonly => {
if is_readonly {
parser.error(ParseError::Forbidden {
message: "duplicate modifier 'readonly'".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
let span = parser.current_span();
parser.require_version(PhpVersion::Php81, "readonly properties", span);
parser.advance();
is_readonly = true;
}
_ => break,
}
}
if is_abstract && is_final {
parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' and 'final' together".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
if is_static && is_readonly {
parser.error(ParseError::Forbidden {
message: "static properties cannot be readonly".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
if let Some(span) = asym_vis_span {
if is_static {
parser.require_version(
PhpVersion::Php85,
"asymmetric visibility on static properties",
span,
);
} else {
parser.require_version(PhpVersion::Php84, "asymmetric visibility", span);
}
}
if visibility.is_none()
&& !is_static
&& !is_abstract
&& !is_final
&& !is_readonly
&& parser.check(TokenKind::Identifier)
&& parser.peek_kind() == Some(TokenKind::Variable)
{
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "modifier".into(),
found: parser.current_kind(),
span,
});
}
if parser.check(TokenKind::Const) {
if is_static {
parser.error(ParseError::Forbidden {
message: "cannot use 'static' as constant modifier".into(),
span: parser.current_span(),
});
}
if is_abstract {
parser.error(ParseError::Forbidden {
message: "cannot use 'abstract' as constant modifier".into(),
span: parser.current_span(),
});
}
if is_readonly {
parser.error(ParseError::Forbidden {
message: "cannot use 'readonly' as constant modifier".into(),
span: parser.current_span(),
});
}
parser.advance();
let const_type = if parser.could_be_type_hint()
&& !parser.check(TokenKind::Variable)
&& parser.peek_kind() != Some(TokenKind::Equals)
&& parser.peek_kind() != Some(TokenKind::Comma)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php83, "typed class constants", span);
Some(parser.parse_type_hint())
} else {
None
};
let mut const_items = parser.alloc_vec();
loop {
let const_name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
let span = parser.current_span();
parser.error(ParseError::Expected {
expected: "constant name".into(),
found: parser.current_kind(),
span,
});
"<error>"
};
parser.expect(TokenKind::Equals);
let value = expr::parse_expr(parser);
const_items.push((const_name, value));
if parser.eat(TokenKind::Comma).is_none() {
break;
}
if parser.check(TokenKind::Semicolon) {
break; }
}
parser.expect(TokenKind::Semicolon);
let span = Span::new(member_start, parser.previous_end());
if !member_attrs.is_empty() && const_items.len() > 1 {
parser.error(ParseError::Forbidden {
message: "cannot use attributes on multi-constant declaration".into(),
span,
});
}
{
let shared_type_hint: Option<&'arena _> = const_type.map(|th| parser.alloc(th));
let mut const_iter = const_items.into_iter();
if let Some((first_name, first_value)) = const_iter.next() {
members.push(ClassMember {
kind: ClassMemberKind::ClassConst(ClassConstDecl {
name: first_name,
visibility,
type_hint: shared_type_hint,
value: first_value,
attributes: member_attrs,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
for (rest_name, rest_value) in const_iter {
members.push(ClassMember {
kind: ClassMemberKind::ClassConst(ClassConstDecl {
name: rest_name,
visibility,
type_hint: shared_type_hint,
value: rest_value,
attributes: parser.alloc_vec(),
doc_comment: None,
}),
span,
});
}
}
}
continue;
}
if parser.check(TokenKind::Function) {
parser.advance();
let by_ref = parser.eat(TokenKind::Ampersand).is_some();
let method_name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "method name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
"<error>"
};
parser.expect(TokenKind::LeftParen);
let params = parse_param_list(parser);
parser.expect(TokenKind::RightParen);
let return_type = if parser.eat(TokenKind::Colon).is_some() {
Some(parser.parse_type_hint())
} else {
None
};
let body = 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);
Some(stmts)
} else {
parser.expect(TokenKind::Semicolon);
None
};
if is_abstract && body.is_some() {
parser.error(ParseError::Forbidden {
message: "abstract method cannot contain a body".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
if in_interface && body.is_some() {
parser.error(ParseError::Forbidden {
message: "interface method cannot contain a body".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
let span = Span::new(member_start, parser.previous_end());
members.push(ClassMember {
kind: ClassMemberKind::Method(MethodDecl {
name: method_name,
visibility,
is_static,
is_abstract,
is_final,
by_ref,
params,
return_type,
body,
attributes: member_attrs,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
continue;
}
let type_hint = if parser.could_be_type_hint() && !parser.check(TokenKind::Variable) {
Some(parser.parse_type_hint())
} else {
None
};
if parser.check(TokenKind::Variable) {
let var_token = parser.advance();
let prop_name = parser.variable_name(var_token);
let default = if parser.eat(TokenKind::Equals).is_some() {
Some(expr::parse_expr(parser))
} else {
None
};
let had_hooks_block = parser.check(TokenKind::LeftBrace);
if is_abstract && !had_hooks_block {
parser.error(ParseError::Forbidden {
message: "properties cannot be abstract".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
let hooks = if had_hooks_block {
let span = parser.current_span();
parser.require_version(PhpVersion::Php84, "property hooks", span);
parse_property_hooks(parser)
} else {
parser.alloc_vec()
};
let span = Span::new(member_start, parser.previous_end());
members.push(ClassMember {
kind: ClassMemberKind::Property(PropertyDecl {
name: prop_name,
visibility,
set_visibility,
is_static,
is_readonly,
type_hint,
default,
attributes: member_attrs,
hooks,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
if had_hooks_block {
} else if parser.eat(TokenKind::Comma).is_some() {
while parser.check(TokenKind::Variable) {
let var_token = parser.advance();
let pname = parser.variable_name(var_token);
let pdefault = if parser.eat(TokenKind::Equals).is_some() {
Some(expr::parse_expr(parser))
} else {
None
};
let phooks = if parser.check(TokenKind::LeftBrace) {
parser.error(ParseError::Forbidden {
message: "cannot have hooks on comma-separated property".into(),
span: parser.current_span(),
});
parse_property_hooks(parser)
} else {
parser.alloc_vec()
};
let pspan = Span::new(member_start, parser.previous_end());
members.push(ClassMember {
kind: ClassMemberKind::Property(PropertyDecl {
name: pname,
visibility: None,
set_visibility: None,
is_static,
is_readonly,
type_hint: None, default: pdefault,
attributes: parser.alloc_vec(), hooks: phooks,
doc_comment: None,
}),
span: pspan,
});
if parser.eat(TokenKind::Comma).is_none() {
break;
}
}
if !parser.check(TokenKind::RightBrace) {
parser.expect(TokenKind::Semicolon);
}
} else {
parser.expect(TokenKind::Semicolon);
}
continue;
}
parser.advance();
}
members
}
fn parse_interface<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
instrument::record_parse_class();
let start = parser.start_span();
parser.advance();
let (name, name_span) = if let Some((text, span)) = parser.eat_identifier_or_keyword() {
(text, span)
} else {
parser.error(ParseError::Expected {
expected: "interface name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
("<error>", parser.current_span())
};
if is_reserved_class_name(name) {
parser.error(ParseError::Forbidden {
message: format!("cannot use '{}' as interface name", name).into(),
span: name_span,
});
}
let extends = if parser.eat(TokenKind::Extends).is_some() {
let names = parse_name_list(parser);
for n in names.iter() {
validate_class_ref(parser, n);
}
names
} else {
parser.alloc_vec()
};
parser.expect(TokenKind::LeftBrace);
let members = parse_class_members(parser, true);
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
let doc_comment = parser.take_doc_comment(start);
Stmt {
kind: StmtKind::Interface(parser.alloc(InterfaceDecl {
name,
extends,
members,
attributes,
doc_comment,
})),
span: Span::new(start, end),
}
}
fn parse_trait<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
instrument::record_parse_class();
let start = parser.start_span();
parser.advance();
let name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "trait name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
"<error>"
};
parser.expect(TokenKind::LeftBrace);
let members = parse_class_members(parser, false);
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
let doc_comment = parser.take_doc_comment(start);
Stmt {
kind: StmtKind::Trait(parser.alloc(TraitDecl {
name,
members,
attributes,
doc_comment,
})),
span: Span::new(start, end),
}
}
fn parse_enum<'arena, 'src>(
parser: &'_ mut Parser<'arena, 'src>,
attributes: ArenaVec<'arena, Attribute<'arena, 'src>>,
) -> Stmt<'arena, 'src> {
let start = parser.start_span();
parser.advance();
let name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "enum name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
"<error>"
};
let scalar_type = if parser.eat(TokenKind::Colon).is_some() {
Some(parser.parse_name())
} else {
None
};
let implements = if parser.eat(TokenKind::Implements).is_some() {
parse_name_list(parser)
} else {
parser.alloc_vec()
};
parser.expect(TokenKind::LeftBrace);
let mut members = parser.alloc_vec_with_capacity(4);
while !parser.check(TokenKind::RightBrace) && !parser.check(TokenKind::Eof) {
if parser.check(TokenKind::Semicolon) {
parser.advance();
continue;
}
let member_attrs = parser.parse_attributes();
let member_start = parser.start_span();
if parser.check(TokenKind::Use) {
parser.advance();
let mut traits = parser.alloc_vec();
traits.push(parser.parse_name());
while parser.eat(TokenKind::Comma).is_some() {
traits.push(parser.parse_name());
}
let adaptations = if parser.check(TokenKind::LeftBrace) {
parser.advance();
parse_trait_adaptations(parser)
} else {
parser.expect(TokenKind::Semicolon);
parser.alloc_vec()
};
let span = Span::new(member_start, parser.previous_end());
members.push(EnumMember {
kind: EnumMemberKind::TraitUse(TraitUseDecl {
traits,
adaptations,
}),
span,
});
continue;
}
if parser.check(TokenKind::Case) {
parser.advance();
if parser.check(TokenKind::Class) {
let span = parser.current_span();
parser.error(ParseError::Forbidden {
message: "'class' cannot be used as an enum case name".into(),
span,
});
}
let case_name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
parser.error(ParseError::Expected {
expected: "case name".into(),
found: parser.current_kind(),
span: parser.current_span(),
});
"<error>"
};
let value = if parser.eat(TokenKind::Equals).is_some() {
Some(expr::parse_expr(parser))
} else {
None
};
parser.expect(TokenKind::Semicolon);
let span = Span::new(member_start, parser.previous_end());
members.push(EnumMember {
kind: EnumMemberKind::Case(EnumCase {
name: case_name,
value,
attributes: member_attrs,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
continue;
}
let mut visibility = None;
let mut is_static = false;
let mut is_abstract = false;
let mut is_final = false;
loop {
match parser.current_kind() {
TokenKind::Public => {
parser.advance();
visibility = Some(Visibility::Public);
}
TokenKind::Protected => {
parser.advance();
visibility = Some(Visibility::Protected);
}
TokenKind::Private => {
parser.advance();
visibility = Some(Visibility::Private);
}
TokenKind::Static => {
parser.advance();
is_static = true;
}
TokenKind::Abstract => {
parser.advance();
is_abstract = true;
}
TokenKind::Final => {
parser.advance();
is_final = true;
}
_ => break,
}
}
if parser.check(TokenKind::Const) {
parser.advance();
let const_type = if parser.could_be_type_hint()
&& !parser.check(TokenKind::Variable)
&& parser.peek_kind() != Some(TokenKind::Equals)
&& parser.peek_kind() != Some(TokenKind::Comma)
{
let span = parser.current_span();
parser.require_version(PhpVersion::Php83, "typed enum constants", span);
let th = parser.parse_type_hint();
Some(parser.alloc(th))
} else {
None
};
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>"
};
parser.expect(TokenKind::Equals);
let value = expr::parse_expr(parser);
parser.expect(TokenKind::Semicolon);
let span = Span::new(member_start, parser.previous_end());
members.push(EnumMember {
kind: EnumMemberKind::ClassConst(ClassConstDecl {
name: const_name,
visibility,
type_hint: const_type,
value,
attributes: member_attrs,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
continue;
}
if parser.check(TokenKind::Function) {
if is_abstract {
parser.error(ParseError::Forbidden {
message: "enum methods cannot be abstract".into(),
span: Span::new(member_start, parser.previous_end()),
});
}
parser.advance();
let by_ref = parser.eat(TokenKind::Ampersand).is_some();
let method_name = if let Some((text, _)) = parser.eat_identifier_or_keyword() {
text
} else {
"<error>"
};
parser.expect(TokenKind::LeftParen);
let params = parse_param_list(parser);
parser.expect(TokenKind::RightParen);
let return_type = if parser.eat(TokenKind::Colon).is_some() {
Some(parser.parse_type_hint())
} else {
None
};
let body = 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);
Some(stmts)
} else {
parser.expect(TokenKind::Semicolon);
None
};
let span = Span::new(member_start, parser.previous_end());
members.push(EnumMember {
kind: EnumMemberKind::Method(MethodDecl {
name: method_name,
visibility,
is_static,
is_abstract,
is_final,
by_ref,
params,
return_type,
body,
attributes: member_attrs,
doc_comment: parser.take_doc_comment(member_start),
}),
span,
});
continue;
}
parser.advance();
}
parser.expect(TokenKind::RightBrace);
let end = parser.previous_end();
let doc_comment = parser.take_doc_comment(start);
Stmt {
kind: StmtKind::Enum(parser.alloc(EnumDecl {
name,
scalar_type,
implements,
members,
attributes,
doc_comment,
})),
span: Span::new(start, end),
}
}
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);
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>"
};
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());
items.push(ConstItem {
name: const_name,
value,
attributes: item_attrs,
span: item_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::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>");
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,
}
}