From abd1559f31b55ea0d297b1321dd532eb85bb1273 Mon Sep 17 00:00:00 2001 From: Ryan Chandler Date: Thu, 28 Jul 2022 12:29:44 +0100 Subject: [PATCH] parser: support foreach statements --- phpast/samples/anon-class.php | 4 + phpast/samples/foreach.php | 9 ++ trunk_lexer/src/lexer.rs | 4 +- trunk_lexer/src/token.rs | 1 + trunk_parser/src/ast.rs | 7 ++ trunk_parser/src/parser/mod.rs | 201 ++++++++++++++++++++++++++++++++- 6 files changed, 221 insertions(+), 5 deletions(-) create mode 100644 phpast/samples/anon-class.php create mode 100644 phpast/samples/foreach.php diff --git a/phpast/samples/anon-class.php b/phpast/samples/anon-class.php new file mode 100644 index 0000000..54a0558 --- /dev/null +++ b/phpast/samples/anon-class.php @@ -0,0 +1,4 @@ + $baz) {} + +foreach ($foo as $bar => [$baz, $bob]) { + +} \ No newline at end of file diff --git a/trunk_lexer/src/lexer.rs b/trunk_lexer/src/lexer.rs index d98af31..098397d 100644 --- a/trunk_lexer/src/lexer.rs +++ b/trunk_lexer/src/lexer.rs @@ -648,6 +648,7 @@ fn identifier_to_keyword(ident: &str) -> Option { "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(", + value_var: Expression, + body: Block, + }, Var { var: String, value: Option, @@ -260,6 +266,7 @@ pub enum Expression { ConstantString(String), PropertyFetch(Box, Identifier), MethodCall(Box, Identifier, Vec), + AnonymousClass(Option, Vec, Block) } #[derive(Debug, Clone, PartialEq, Serialize)] diff --git a/trunk_parser/src/parser/mod.rs b/trunk_parser/src/parser/mod.rs index 8de1aef..50eee81 100644 --- a/trunk_parser/src/parser/mod.rs +++ b/trunk_parser/src/parser/mod.rs @@ -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 = 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(" $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("