mirror of
https://github.com/danog/parser.git
synced 2025-01-22 21:11:55 +01:00
parser: support foreach statements
This commit is contained in:
parent
98fbc20291
commit
abd1559f31
4
phpast/samples/anon-class.php
Normal file
4
phpast/samples/anon-class.php
Normal file
@ -0,0 +1,4 @@
|
||||
<?php
|
||||
|
||||
$foo = new class {};
|
||||
$bar = new class(1) {};
|
9
phpast/samples/foreach.php
Normal file
9
phpast/samples/foreach.php
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
foreach ($foo as $bar) {}
|
||||
|
||||
foreach ($foo as $bar => $baz) {}
|
||||
|
||||
foreach ($foo as $bar => [$baz, $bob]) {
|
||||
|
||||
}
|
@ -648,6 +648,7 @@ fn identifier_to_keyword(ident: &str) -> Option<TokenKind> {
|
||||
"false" | "FALSE" => TokenKind::False,
|
||||
"final" => TokenKind::Final,
|
||||
"fn" => TokenKind::Fn,
|
||||
"foreach" => TokenKind::Foreach,
|
||||
"function" => TokenKind::Function,
|
||||
"if" => TokenKind::If,
|
||||
"implements" => TokenKind::Implements,
|
||||
@ -717,7 +718,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn keywords() {
|
||||
assert_tokens("<?php function if else elseif echo return class extends implements public protected private static null NULL true TRUE false FALSE use const namespace interface new", &[
|
||||
assert_tokens("<?php function if else elseif echo return class extends implements public protected private static null NULL true TRUE false FALSE use const namespace interface new foreach", &[
|
||||
open!(),
|
||||
TokenKind::Function,
|
||||
TokenKind::If,
|
||||
@ -743,6 +744,7 @@ mod tests {
|
||||
TokenKind::Namespace,
|
||||
TokenKind::Interface,
|
||||
TokenKind::New,
|
||||
TokenKind::Foreach,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -74,6 +74,7 @@ pub enum TokenKind {
|
||||
Final,
|
||||
Float(f64),
|
||||
Fn,
|
||||
Foreach,
|
||||
FullyQualifiedIdentifier(String),
|
||||
Function,
|
||||
GreaterThan,
|
||||
|
@ -133,6 +133,12 @@ pub enum UseKind {
|
||||
pub enum Statement {
|
||||
InlineHtml(String),
|
||||
// TODO: Look at removing this and unifying with Property.
|
||||
Foreach {
|
||||
expr: Expression,
|
||||
key_var: Option<Expression>,
|
||||
value_var: Expression,
|
||||
body: Block,
|
||||
},
|
||||
Var {
|
||||
var: String,
|
||||
value: Option<Expression>,
|
||||
@ -260,6 +266,7 @@ pub enum Expression {
|
||||
ConstantString(String),
|
||||
PropertyFetch(Box<Self>, Identifier),
|
||||
MethodCall(Box<Self>, Identifier, Vec<Self>),
|
||||
AnonymousClass(Option<Identifier>, Vec<Identifier>, Block)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
|
@ -111,6 +111,34 @@ impl Parser {
|
||||
self.next();
|
||||
s
|
||||
},
|
||||
TokenKind::Foreach => {
|
||||
self.next();
|
||||
|
||||
self.lparen()?;
|
||||
|
||||
let expr = self.expression(0)?;
|
||||
|
||||
expect!(self, TokenKind::As, "expected 'as'");
|
||||
|
||||
let mut key_var = None;
|
||||
let mut value_var = self.expression(0)?;
|
||||
|
||||
if self.current.kind == TokenKind::DoubleArrow {
|
||||
self.next();
|
||||
|
||||
key_var = Some(value_var.clone());
|
||||
value_var = self.expression(0)?;
|
||||
}
|
||||
|
||||
self.rparen()?;
|
||||
self.lbrace()?;
|
||||
|
||||
let body = self.block(&TokenKind::RightBrace)?;
|
||||
|
||||
self.rbrace()?;
|
||||
|
||||
Statement::Foreach { expr, key_var, value_var, body }
|
||||
},
|
||||
TokenKind::Abstract => {
|
||||
self.next();
|
||||
|
||||
@ -755,10 +783,60 @@ impl Parser {
|
||||
TokenKind::New => {
|
||||
self.next();
|
||||
|
||||
// TODO: Support dynamic instantiation targets here.
|
||||
let target = self.expression(20)?;
|
||||
|
||||
let mut args = vec![];
|
||||
let target = if self.current.kind == TokenKind::Class {
|
||||
self.next();
|
||||
|
||||
if self.current.kind == TokenKind::LeftParen {
|
||||
self.lparen()?;
|
||||
|
||||
while self.current.kind != TokenKind::RightParen {
|
||||
let value = self.expression(0)?;
|
||||
|
||||
args.push(value);
|
||||
|
||||
if self.current.kind == TokenKind::Comma {
|
||||
self.next();
|
||||
}
|
||||
}
|
||||
|
||||
self.rparen()?;
|
||||
}
|
||||
|
||||
let mut extends: Option<Identifier> = None;
|
||||
|
||||
if self.current.kind == TokenKind::Extends {
|
||||
self.next();
|
||||
extends = Some(self.ident()?.into());
|
||||
}
|
||||
|
||||
let mut implements = Vec::new();
|
||||
if self.current.kind == TokenKind::Implements {
|
||||
self.next();
|
||||
|
||||
while self.current.kind != TokenKind::LeftBrace {
|
||||
if self.current.kind == TokenKind::Comma {
|
||||
self.next();
|
||||
}
|
||||
|
||||
implements.push(self.ident()?.into());
|
||||
}
|
||||
}
|
||||
|
||||
self.lbrace()?;
|
||||
|
||||
let mut body = Vec::new();
|
||||
while self.current.kind != TokenKind::RightBrace && ! self.is_eof() {
|
||||
body.push(self.class_statement()?);
|
||||
}
|
||||
|
||||
self.rbrace()?;
|
||||
|
||||
Expression::AnonymousClass(extends, implements, body)
|
||||
} else {
|
||||
self.expression(20)?
|
||||
};
|
||||
|
||||
if self.current.kind == TokenKind::LeftParen {
|
||||
self.lparen()?;
|
||||
|
||||
@ -952,7 +1030,7 @@ impl Display for ParseError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use trunk_lexer::Lexer;
|
||||
use crate::{Statement, Param, Expression, ast::{InfixOp, ElseIf}, Type, Identifier};
|
||||
use crate::{Statement, Param, Expression, ast::{InfixOp, ElseIf, MethodFlag, ArrayItem}, Type, Identifier};
|
||||
use super::Parser;
|
||||
|
||||
macro_rules! function {
|
||||
@ -1472,6 +1550,121 @@ mod tests {
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_anon_class() {
|
||||
assert_ast("<?php new class{};", &[
|
||||
expr!(Expression::New(
|
||||
Box::new(Expression::AnonymousClass(
|
||||
None,
|
||||
vec![],
|
||||
vec![]
|
||||
)),
|
||||
vec![],
|
||||
))
|
||||
]);
|
||||
|
||||
assert_ast("<?php new class(1, 2) {};", &[
|
||||
expr!(Expression::New(
|
||||
Box::new(Expression::AnonymousClass(
|
||||
None,
|
||||
vec![],
|
||||
vec![]
|
||||
)),
|
||||
vec![
|
||||
Expression::Int(1),
|
||||
Expression::Int(2),
|
||||
],
|
||||
))
|
||||
]);
|
||||
|
||||
assert_ast("<?php new class extends Foo {};", &[
|
||||
expr!(Expression::New(
|
||||
Box::new(Expression::AnonymousClass(
|
||||
Some(Identifier::from("Foo")),
|
||||
vec![],
|
||||
vec![]
|
||||
)),
|
||||
vec![]
|
||||
))
|
||||
]);
|
||||
|
||||
assert_ast("<?php new class implements Foo, Bar {};", &[
|
||||
expr!(Expression::New(
|
||||
Box::new(Expression::AnonymousClass(
|
||||
None,
|
||||
vec![
|
||||
Identifier::from("Foo"),
|
||||
Identifier::from("Bar"),
|
||||
],
|
||||
vec![]
|
||||
)),
|
||||
vec![]
|
||||
))
|
||||
]);
|
||||
|
||||
assert_ast("<?php new class {
|
||||
public function foo() {}
|
||||
};", &[
|
||||
expr!(Expression::New(
|
||||
Box::new(Expression::AnonymousClass(
|
||||
None,
|
||||
vec![],
|
||||
vec![
|
||||
Statement::Method {
|
||||
name: "foo".into(),
|
||||
params: vec![],
|
||||
body: vec![],
|
||||
return_type: None,
|
||||
flags: vec![
|
||||
MethodFlag::Public,
|
||||
]
|
||||
}
|
||||
]
|
||||
)),
|
||||
vec![]
|
||||
))
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn foreach() {
|
||||
assert_ast("<?php foreach ($foo as $bar) {}", &[
|
||||
Statement::Foreach {
|
||||
expr: Expression::Variable("foo".into()),
|
||||
key_var: None,
|
||||
value_var: Expression::Variable("bar".into()),
|
||||
body: vec![],
|
||||
}
|
||||
]);
|
||||
|
||||
assert_ast("<?php foreach ($foo as $bar => $baz) {}", &[
|
||||
Statement::Foreach {
|
||||
expr: Expression::Variable("foo".into()),
|
||||
key_var: Some(Expression::Variable("bar".into())),
|
||||
value_var: Expression::Variable("baz".into()),
|
||||
body: vec![],
|
||||
}
|
||||
]);
|
||||
|
||||
assert_ast("<?php foreach ($foo as [$baz, $car]) {}", &[
|
||||
Statement::Foreach {
|
||||
expr: Expression::Variable("foo".into()),
|
||||
key_var: None,
|
||||
value_var: Expression::Array(vec![
|
||||
ArrayItem {
|
||||
key: None,
|
||||
value: Expression::Variable("baz".into())
|
||||
},
|
||||
ArrayItem {
|
||||
key: None,
|
||||
value: Expression::Variable("car".into())
|
||||
}
|
||||
]),
|
||||
body: vec![],
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn noop() {
|
||||
assert_ast("<?php ;", &[
|
||||
|
Loading…
x
Reference in New Issue
Block a user