From 620be5dba18ea8b95cac7bdf68c832b0550f7aee Mon Sep 17 00:00:00 2001 From: fef Date: Fri, 29 Jul 2022 04:05:59 +0200 Subject: [PATCH] add functions --- src/ast/mod.rs | 71 +++++++++++++++++++++++++++++++++++++++++++++++- src/ast/tree.rs | 18 ++++++++++++ src/lex/mod.rs | 4 ++- src/lex/token.rs | 6 ++++ test.gaybuild | 12 ++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 2b87810..2b08790 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -15,6 +15,7 @@ enum Scope { Target, DepList, SourceList, + Function, } struct Parser { @@ -79,12 +80,14 @@ impl Parser { let token = self.lexer.peek_or_err()?; match token.kind { token::Kind::DependKeyword => self.parse_depend_stmt(), + token::Kind::FnKeyword => self.parse_fn(false), token::Kind::IfKeyword => self.parse_if_stmt(), + token::Kind::ReturnKeyword => self.parse_return_stmt(), token::Kind::SetKeyword => self.parse_set_stmt(), token::Kind::SourceKeyword => self.parse_source_stmt(), token::Kind::TargetKeyword => self.parse_target_stmt(), token::Kind::TypeKeyword => self.parse_type_stmt(), - token::Kind::Ident => self.parse_expr_stmt(), + k if k.is_start_of_expr() => self.parse_expr_stmt(), _ => self.syntax_error(format!("Unexpected token {}", token), &token), } } @@ -111,6 +114,17 @@ impl Parser { Ok(tree::Node::Block(nodes)) } + /// ```notrust + /// ReturnStatement + /// : "return" Expression ";" + /// ``` + fn parse_return_stmt(&mut self) -> Result { + self.assert_scope(Scope::Function)?; + self.lexer.expect_kind(token::Kind::ReturnKeyword)?; + let expr = self.parse_expr(&[token::Kind::Semi])?; + Ok(tree::Node::ReturnStmt(Box::new(expr))) + } + /// ```notrust /// TargetStatement /// : "target" Expression BlockStatement @@ -452,6 +466,10 @@ impl Parser { token::Kind::StringLiteral => Ok(tree::Node::String(token.raw)), token::Kind::TrueKeyword => Ok(tree::Node::Bool(true)), token::Kind::FalseKeyword => Ok(tree::Node::Bool(false)), + token::Kind::FnKeyword => { + self.lexer.prev(); // parse_fn() expects to consume the keyword + self.parse_fn(true) + } token::Kind::OBracket => { let elements = self.parse_delimited_list(token::Kind::Comma, token::Kind::CBracket, true)?; @@ -469,6 +487,7 @@ impl Parser { /// matrix[y][x] /// array_of_functions[index](params) /// function_returning_an_array(params)[index] + /// (fn(a, b) { return a + b; })(1, 2) /// ``` fn parse_primary_expr_rest(&mut self, start: tree::Node) -> Result { if let Some(Ok(token)) = self.lexer.peek() { @@ -499,6 +518,56 @@ impl Parser { } } + /// ```notrust + /// Function + /// : "fn" [ Identifier ] "(" [ ParameterList ] ")" BlockStatement + /// + /// ParameterList + /// : Identifier [ "," ParameterList ] + /// ``` + fn parse_fn(&mut self, allow_anonymous: bool) -> Result { + self.scope.push(Scope::Function); + self.lexer.expect_kind(token::Kind::FnKeyword)?; + + // function name is optional (there are inline anonymous functions) + let name = if let Some(Ok(token)) = self.lexer.peek() { + if token.kind == token::Kind::Ident { + self.lexer.next(); + Some(Box::new(tree::Node::Ident(token.raw))) + } else { + None + } + } else { + None + }; + + let oparen = self.lexer.expect_kind(token::Kind::OParen)?; + if name.is_none() && !allow_anonymous { + // anonymous function are not allowed for definitions as a block + // statement (you can only do that with inline functions) + return self.syntax_error(String::from("Function name required"), &oparen); + } + let params = self.parse_delimited_list(token::Kind::Comma, token::Kind::CParen, false)?; + for p in ¶ms { + match p { + tree::Node::Ident(_) => continue, + _ => { + return self + .syntax_error(format!("Not an identifier"), &self.lexer.current().unwrap()) + } + } + } + + let body = self.parse_block_stmt()?; + + self.scope.pop(); + Ok(tree::Node::Fn { + name, + params, + body: Box::new(body), + }) + } + /// Parse a terminated, delimited list of expressions. This is used for /// parameter lists in function calls and elements in array literals. fn parse_delimited_list( diff --git a/src/ast/tree.rs b/src/ast/tree.rs index 1665d7a..9bf0069 100644 --- a/src/ast/tree.rs +++ b/src/ast/tree.rs @@ -16,6 +16,11 @@ pub enum Node { String(String), Array(Vec), Block(Vec), + Fn { + name: Option>, + params: Vec, + body: Box, + }, ArrayExpr { // array access array: Box, @@ -31,6 +36,7 @@ pub enum Node { then_block: Box, else_block: Option>, }, + ReturnStmt(Box), UnaryExpr { op: Operator, node: Box, @@ -110,6 +116,15 @@ impl Node { node.visit(cb, depth); } } + Node::Fn { name, params, body } => { + if let Some(n) = name { + n.visit(cb, depth); + } + for p in params { + p.visit(cb, depth); + } + body.visit(cb, depth); + } Node::ArrayExpr { array, index } => { array.visit(cb, depth); index.visit(cb, depth); @@ -131,6 +146,7 @@ impl Node { eb.visit(cb, depth); } } + Node::ReturnStmt(node) => node.visit(cb, depth), Node::UnaryExpr { op, node } => node.visit(cb, depth), Node::BinaryExpr { op, lhs, rhs } => { lhs.visit(cb, depth); @@ -173,6 +189,8 @@ impl fmt::Display for Node { } Node::String(s) => s.as_str(), Node::Block(_) => "", + Node::Fn { name, params, body } => "fn", + Node::ReturnStmt(_) => "return", Node::DepList(_) => "depend", Node::SourceList(_) => "source", Node::Array(_) => "", diff --git a/src/lex/mod.rs b/src/lex/mod.rs index 574a7e5..d374675 100644 --- a/src/lex/mod.rs +++ b/src/lex/mod.rs @@ -35,12 +35,14 @@ const fn kw(raw: &'static str, kind: token::Kind) -> KeywordMap { KeywordMap { raw, kind } } -static KEYWORDS: [KeywordMap; 10] = [ +static KEYWORDS: [KeywordMap; 12] = [ kw("depend", token::Kind::DependKeyword), kw("else", token::Kind::ElseKeyword), kw("false", token::Kind::FalseKeyword), + kw("fn", token::Kind::FnKeyword), kw("if", token::Kind::IfKeyword), kw("include", token::Kind::IncludeKeyword), + kw("return", token::Kind::ReturnKeyword), kw("set", token::Kind::SetKeyword), kw("source", token::Kind::SourceKeyword), kw("target", token::Kind::TargetKeyword), diff --git a/src/lex/token.rs b/src/lex/token.rs index 27a45ca..c8ecd13 100644 --- a/src/lex/token.rs +++ b/src/lex/token.rs @@ -71,8 +71,10 @@ pub enum Kind { DependKeyword, ElseKeyword, FalseKeyword, + FnKeyword, IfKeyword, IncludeKeyword, + ReturnKeyword, SetKeyword, SourceKeyword, TargetKeyword, @@ -98,9 +100,11 @@ impl Kind { match self { k if k.is_start_of_lhs_expr() => true, Kind::FalseKeyword => true, + Kind::FnKeyword => true, Kind::Minus => true, Kind::OBracket => true, Kind::OParen => true, + Kind::ReturnKeyword => true, Kind::TrueKeyword => true, _ => false, } @@ -214,8 +218,10 @@ impl fmt::Display for Kind { Kind::DependKeyword => "keyword", Kind::ElseKeyword => "keyword", Kind::FalseKeyword => "keyword", + Kind::FnKeyword => "keyword", Kind::IfKeyword => "keyword", Kind::IncludeKeyword => "keyword", + Kind::ReturnKeyword => "keyword", Kind::SetKeyword => "keyword", Kind::SourceKeyword => "keyword", Kind::TargetKeyword => "keyword", diff --git a/test.gaybuild b/test.gaybuild index 18f05c2..1826d9e 100644 --- a/test.gaybuild +++ b/test.gaybuild @@ -6,6 +6,13 @@ set CC_EXE = "clang"; set LINK_EXE = "ld.lld"; set BUILD_PREFIX = "build"; +# regular function definitions work very much like they do in ECMAScript 5, +# except that the rather verbose "function" keyword is shortened to "fn". +fn hello(p1, p2) { + print("hello, world"); + return p1 + p1; +} + # a target is a single component. # targets can depend on other targets. target kern { @@ -15,6 +22,11 @@ target kern { print("2 <= 3"); } + # functions are first-class citizens; they can be defined as inline + # anonymous functions that you can treat like any other value + fn_with_callback(fn(a, b) { return a + b; }); + (fn(a, b) { return a + b; })(1, 2); + owo = 1 + 2 * 3 * 4 - 5; # the type keyword defines whether the target is supposed to be