From 35bcab847542c97c6e97deb4c50d18fef977d0a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jasper=20Gr=C3=A4flich?= <graeflich@cyberagentur.de> Date: Fri, 27 Oct 2023 18:21:27 +0200 Subject: [PATCH] Add function calls --- use-based-refs/interpreter/README.md | 11 +++++--- use-based-refs/interpreter/src/eval.rs | 28 +++++++++++++++++++ use-based-refs/interpreter/src/grammar.pest | 10 ++++--- use-based-refs/interpreter/src/lib.rs | 4 +++ use-based-refs/interpreter/src/main.rs | 2 +- use-based-refs/interpreter/src/parser.rs | 28 ++++++++++++++++--- use-based-refs/interpreter/src/typecheck.rs | 30 +++++++++++++++++++++ 7 files changed, 103 insertions(+), 10 deletions(-) diff --git a/use-based-refs/interpreter/README.md b/use-based-refs/interpreter/README.md index 1febbde..60f7666 100644 --- a/use-based-refs/interpreter/README.md +++ b/use-based-refs/interpreter/README.md @@ -22,13 +22,18 @@ Right now, the interpreter only executes a program hardcoded in `main`. Change t - [x] References - [x] Reference types - [x] Reference types in type annotations - - [ ]Â Allow for `&*` reborrowing + - [ ]Â Allow `&*` reborrowing + - [ ] Allow calling through references as in `(*foo)(arg)` - [x] Function definitions - - [ ] Function calls + - [x] Function calls + - [x]Â Parse error? - [x] Allocations - [x] Dereference -- read - [x] Dereference -- write -- [ ] Add tests for typechecking functions + - [ ] Builtins + - [ ] `NoAlias` specification + - [ ] Alias checking, of course +- [ ] Add tests for typechecking functions/function calls ## Design decisions to make diff --git a/use-based-refs/interpreter/src/eval.rs b/use-based-refs/interpreter/src/eval.rs index cba4044..e44715a 100644 --- a/use-based-refs/interpreter/src/eval.rs +++ b/use-based-refs/interpreter/src/eval.rs @@ -40,6 +40,7 @@ impl std::fmt::Display for Value { } } +#[derive(Debug, PartialEq, Eq, Clone)] struct Context { names: HashMap<String, Value>, // Index serves as address @@ -154,6 +155,33 @@ fn eval_ctx(ast: Node, ctx: &mut Context) -> Result<Value> { ); Value::Unit } + Node::Call { ident, args } => { + // Eager evaluation of the arguments + let mut evals = Vec::with_capacity(args.len()); + for arg in args { + evals.push(eval_ctx(arg, ctx)?) + } + + // Look up function + let Value::Func { + name: _, + args, + body, + } = ctx + .names + .get(&ident) + .ok_or(Error::Name(format!("Function `{ident}` not defined")))? + else { + return Err(Error::Type(format!("`{ident}` is not a function."))); + }; + + // Create new context for evaluating the function body, + // with the (evaluated) arguments added. + let mut ctx_inner = ctx.clone(); + ctx_inner.names.extend(args.iter().cloned().zip(evals)); + + eval_ctx(body.as_ref().clone(), &mut ctx_inner)? + } }) } diff --git a/use-based-refs/interpreter/src/grammar.pest b/use-based-refs/interpreter/src/grammar.pest index de53893..7f6fb51 100644 --- a/use-based-refs/interpreter/src/grammar.pest +++ b/use-based-refs/interpreter/src/grammar.pest @@ -19,7 +19,8 @@ Seq = { (Expr ~ ";")* ~ Expr } /// An expression. // I must put DerefWrite first because of shenanigans; // I don't know the reason, but it fails to parse otherwise. -Expr = _{ DerefWrite | Value | TypedLet | Let | Func | Ident | Block | DerefRead } +Expr = _{ DerefWrite | Value | TypedLet | Let + | Func | Call | Ident | Block | DerefRead } Value = _{ Unit | Num | Ref } @@ -29,8 +30,11 @@ Ref = { "&" ~ (Value | Ident)} DerefRead = {"*" ~ Expr } DerefWrite = { Ident ~ ":=" ~ Expr } -Func = { "fn" ~ Ident ~ "(" ~ ArgList ~ ")" ~ "->" ~ Type ~ "=" ~ Expr } -ArgList = { (TypedIdent ~ ",")* ~ TypedIdent? } +Func = { "fn" ~ Ident ~ "(" ~ Params ~ ")" ~ "->" ~ Type ~ "=" ~ Expr } +Params = { (TypedIdent ~ ",")* ~ TypedIdent? } +// We can currently only call functions from their identifier. +Call = { Ident ~ "(" ~ Args ~ ")" } +Args = { (Expr ~ ",")* ~ Expr? } /// Add the identifier to to context /// diff --git a/use-based-refs/interpreter/src/lib.rs b/use-based-refs/interpreter/src/lib.rs index 43d5aee..917c2c9 100644 --- a/use-based-refs/interpreter/src/lib.rs +++ b/use-based-refs/interpreter/src/lib.rs @@ -37,6 +37,10 @@ pub enum Node { ty: typecheck::Type, body: Box<Node>, }, + Call { + ident: String, + args: Vec<Node>, + }, } /// Generalized interpreter error. diff --git a/use-based-refs/interpreter/src/main.rs b/use-based-refs/interpreter/src/main.rs index 9d66e1b..0a109b5 100644 --- a/use-based-refs/interpreter/src/main.rs +++ b/use-based-refs/interpreter/src/main.rs @@ -3,7 +3,7 @@ use interpreter::{ast, eval, typecheck}; fn main() { - let program = "fn fst(x: Num, y: Num) -> Num = x; fst"; + let program = "fn infinite() -> Num = {infinite()}; infinite()"; match ast(program).and_then(typecheck).and_then(eval) { Ok(val) => println!("{val}"), diff --git a/use-based-refs/interpreter/src/parser.rs b/use-based-refs/interpreter/src/parser.rs index ae86857..20cf53f 100644 --- a/use-based-refs/interpreter/src/parser.rs +++ b/use-based-refs/interpreter/src/parser.rs @@ -41,9 +41,15 @@ pub fn ast(input: &str) -> Result<Node> { pair.as_str() ))); } - Rule::ArgList => { + Rule::Params => { return Err(Error::General(format!( - "ArgLust `{}` in unexpected position", + "Params `{}` in unexpected position", + pair.as_str() + ))) + } + Rule::Args => { + return Err(Error::General(format!( + "Args `{}` in unexpected position", pair.as_str() ))) } @@ -135,7 +141,7 @@ pub fn ast(input: &str) -> Result<Node> { .to_string(); let args: Result<Vec<(String, crate::typecheck::Type)>> = it .next() - .expect("Function definition always has an arglist") + .expect("Function definition always has params") .into_inner() .map(|p| { // TODO: Maybe this `should` be handled by a specialized match arm? @@ -161,6 +167,22 @@ pub fn ast(input: &str) -> Result<Node> { body, } } + Rule::Call => { + let mut it = pair.into_inner(); + let ident = it + .next() + .expect("Function call must have an identifier") + .as_str() + .to_string(); + let args: Result<_> = it + .next() + .expect("Function call must have an argument list") + .into_inner() + .map(ast_pair) + .collect(); + + Node::Call { ident, args: args? } + } }) } let pair = tokenize(input)?; diff --git a/use-based-refs/interpreter/src/typecheck.rs b/use-based-refs/interpreter/src/typecheck.rs index be83a31..1327826 100644 --- a/use-based-refs/interpreter/src/typecheck.rs +++ b/use-based-refs/interpreter/src/typecheck.rs @@ -61,6 +61,7 @@ impl std::fmt::Display for Type { } /// Typecheck given AST using given context. +#[allow(clippy::too_many_lines)] fn typecheck_ctx(ast: &Node, ctx: &mut Context) -> Result<Type> { Ok(match &ast { Node::Num(_) => Type::Num, @@ -145,6 +146,35 @@ fn typecheck_ctx(ast: &Node, ctx: &mut Context) -> Result<Type> { } Type::Unit } + #[allow(clippy::manual_let_else)] // For unpacking `(tys, func_ty)` + Node::Call { ident, args } => { + let (tys, func_ty) = if let Type::Func { args, ty } = ctx + .idents + .get(ident) + .ok_or(Error::Name(format!("Function `{ident}` not found.")))? + { + (args, ty) + } else { + return Err(Error::Type(format!("`{ident}` is not a function."))); + }; + if args.len() != tys.len() { + return Err(Error::Type(format!( + "`{ident} takes {} arguments, got {}", + tys.len(), + args.len() + ))); + } + for (arg, ty) in args.iter().zip(tys) { + let arg_ty = typecheck_ctx(arg, &mut ctx.clone())?; + if ty != &arg_ty { + return Err(Error::Type(format!( + "Argument has type `{arg_ty}`, expected `{ty}`." + ))); + } + } + + *func_ty.clone() + } }) } -- GitLab