parser: support foreach statements

This commit is contained in:
Ryan Chandler 2022-07-28 12:29:44 +01:00
parent 98fbc20291
commit abd1559f31
No known key found for this signature in database
GPG Key ID: F113BCADDB3B0CCA
6 changed files with 221 additions and 5 deletions

View File

@ -0,0 +1,4 @@
<?php
$foo = new class {};
$bar = new class(1) {};

View File

@ -0,0 +1,9 @@
<?php
foreach ($foo as $bar) {}
foreach ($foo as $bar => $baz) {}
foreach ($foo as $bar => [$baz, $bob]) {
}

View File

@ -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,
]);
}

View File

@ -74,6 +74,7 @@ pub enum TokenKind {
Final,
Float(f64),
Fn,
Foreach,
FullyQualifiedIdentifier(String),
Function,
GreaterThan,

View File

@ -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)]

View File

@ -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 ;", &[