parser: class methods with visibility

This commit is contained in:
Ryan Chandler 2022-07-19 12:04:40 +01:00
parent aed95d9036
commit cfa199e81e
No known key found for this signature in database
GPG Key ID: F113BCADDB3B0CCA
4 changed files with 69 additions and 6 deletions

View File

@ -238,6 +238,7 @@ fn identifier_to_keyword(ident: &str) -> Option<TokenKind> {
"public" => TokenKind::Public,
"protected" => TokenKind::Protected,
"private" => TokenKind::Private,
"static" => TokenKind::Static,
_ => return None,
})
}
@ -290,7 +291,7 @@ mod tests {
#[test]
fn keywords() {
assert_tokens("<?php function if echo return class public protected private", &[
assert_tokens("<?php function if echo return class public protected private static", &[
open!(),
TokenKind::Function,
TokenKind::If,
@ -300,6 +301,7 @@ mod tests {
TokenKind::Public,
TokenKind::Protected,
TokenKind::Private,
TokenKind::Static,
]);
}

View File

@ -14,6 +14,7 @@ pub enum TokenKind {
Public,
Protected,
Private,
Static,
If,
Return,
Echo,

View File

@ -32,7 +32,7 @@ impl From<&str> for Param {
}
#[derive(Debug, Clone, PartialEq)]
pub enum MethodFlags {
pub enum MethodFlag {
Public,
Protected,
Private,
@ -55,7 +55,7 @@ pub enum Statement {
name: Identifier,
params: Vec<Param>,
body: Block,
flags: Vec<MethodFlags>,
flags: Vec<MethodFlag>,
},
If {
condition: Expression,

View File

@ -1,7 +1,7 @@
use std::vec::IntoIter;
use std::{vec::IntoIter};
use std::iter::Peekable;
use trunk_lexer::{Token, TokenKind};
use crate::{Program, Statement, Block, Expression};
use crate::{Program, Statement, Block, Expression, ast::MethodFlag};
macro_rules! expect {
($actual:expr, $expected:pat, $out:expr, $message:literal) => {
@ -81,6 +81,7 @@ impl Parser {
Statement::Function { name, params, body } => {
Statement::Method { name, params, body, flags: vec![] }
},
s @ Statement::Method { .. } => s,
_ => return Err(ParseError::InvalidClassStatement(format!("Classes can only contain properties, constants and methods.")))
};
@ -148,6 +149,22 @@ impl Parser {
Statement::Function { name: name.into(), params, body }
},
_ if is_method_visibility_modifier(&t.kind) => {
let mut flags = vec![visibility_token_to_flag(&t.kind)];
while let Some(t) = tokens.peek() && is_method_visibility_modifier(&t.kind) {
let next = tokens.next().unwrap();
flags.push(visibility_token_to_flag(&next.kind));
}
match self.statement(tokens.next().unwrap(), tokens)? {
Statement::Function { name, params, body } => {
Statement::Method { name, params, body, flags }
},
_ => return Err(ParseError::InvalidClassStatement("Classes can only contain properties, constants and methods.".into()))
}
},
_ => todo!("unhandled token: {:?}", t)
})
}
@ -226,6 +243,20 @@ impl Parser {
}
}
fn is_method_visibility_modifier(kind: &TokenKind) -> bool {
[TokenKind::Public, TokenKind::Protected, TokenKind::Private, TokenKind::Static].contains(kind)
}
fn visibility_token_to_flag(kind: &TokenKind) -> MethodFlag {
match kind {
TokenKind::Public => MethodFlag::Public,
TokenKind::Protected => MethodFlag::Protected,
TokenKind::Private => MethodFlag::Private,
TokenKind::Static => MethodFlag::Static,
_ => unreachable!()
}
}
fn infix(lhs: Expression, op: TokenKind, rhs: Expression) -> Expression {
Expression::Infix(Box::new(lhs), op.into(), Box::new(rhs))
}
@ -255,7 +286,7 @@ pub enum ParseError {
#[cfg(test)]
mod tests {
use trunk_lexer::Lexer;
use crate::{Statement, Block, Param, Expression, ast::InfixOp};
use crate::{Statement, Block, Param, Expression, ast::{InfixOp, MethodFlag}};
use super::Parser;
macro_rules! function {
@ -403,6 +434,35 @@ mod tests {
]);
}
#[test]
fn class_with_method_visibility() {
assert_ast("\
<?php
class Foo {
public function bar() {
echo 1;
}
private static function baz() {}
}
", &[
class!("Foo", &[
method!("bar", &[], &[
MethodFlag::Public,
], &[
Statement::Echo { values: vec![
Expression::Int(1),
] }
]),
method!("baz", &[], &[
MethodFlag::Private,
MethodFlag::Static,
], &[])
])
]);
}
fn assert_ast(source: &str, expected: &[Statement]) {
let mut lexer = Lexer::new(None);
let tokens = lexer.tokenize(source).unwrap();