-
Notifications
You must be signed in to change notification settings - Fork 10
Project 5 — First-Class Functions: #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8023c54
26d45ea
346b025
0862d5a
e75c651
17f297d
59bb468
a1ac64a
3922e08
f55bfcd
b54d831
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -47,6 +47,12 @@ pub fn exec_stmt(stmt: &CheckedStmt, env: &mut Environment<Value>) -> ExecResult | |||||||||||||||||||||||
| match &stmt.stmt { | ||||||||||||||||||||||||
| // --- Variable declaration --- | ||||||||||||||||||||||||
| Statement::Decl { name, init, .. } => { | ||||||||||||||||||||||||
| let init = init.as_ref().ok_or_else(|| { | ||||||||||||||||||||||||
| RuntimeError::new(format!( | ||||||||||||||||||||||||
| "variable '{}' declared without initializer", | ||||||||||||||||||||||||
| name | ||||||||||||||||||||||||
| )) | ||||||||||||||||||||||||
| })?; | ||||||||||||||||||||||||
| let val = eval_expr(init, env)?; | ||||||||||||||||||||||||
|
Comment on lines
+50
to
56
|
||||||||||||||||||||||||
| let init = init.as_ref().ok_or_else(|| { | |
| RuntimeError::new(format!( | |
| "variable '{}' declared without initializer", | |
| name | |
| )) | |
| })?; | |
| let val = eval_expr(init, env)?; | |
| let val = match init.as_ref() { | |
| Some(init) => eval_expr(init, env)?, | |
| None => Value::Uninitialized, | |
| }; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -61,6 +61,7 @@ | |||||||||||||||||||||||
| //! needs `Value`, and `Value` needs to reference the callable type. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| use std::fmt; | ||||||||||||||||||||||||
| use std::collections::HashMap; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| use crate::ir::ast::CheckedFunDecl; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -72,13 +73,19 @@ pub type NativeFn = fn(Vec<Value>) -> Result<Value, RuntimeError>; | |||||||||||||||||||||||
| pub enum FnValue { | ||||||||||||||||||||||||
| UserDefined(CheckedFunDecl), | ||||||||||||||||||||||||
| Native(NativeFn), | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| Closure { | ||||||||||||||||||||||||
| decl: CheckedFunDecl, | ||||||||||||||||||||||||
| captured: HashMap<String, Value>, | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| impl PartialEq for FnValue { | ||||||||||||||||||||||||
| fn eq(&self, other: &Self) -> bool { | ||||||||||||||||||||||||
| match (self, other) { | ||||||||||||||||||||||||
| (FnValue::UserDefined(a), FnValue::UserDefined(b)) => a == b, | ||||||||||||||||||||||||
| (FnValue::Native(a), FnValue::Native(b)) => (*a as usize) == (*b as usize), | ||||||||||||||||||||||||
| (FnValue::Closure { decl: da, .. }, FnValue::Closure { decl: db, .. }) => da == db, | ||||||||||||||||||||||||
|
||||||||||||||||||||||||
| (FnValue::Closure { decl: da, .. }, FnValue::Closure { decl: db, .. }) => da == db, | |
| ( | |
| FnValue::Closure { | |
| decl: da, | |
| captured: ca, | |
| }, | |
| FnValue::Closure { | |
| decl: db, | |
| captured: cb, | |
| }, | |
| ) => da == db && ca == cb, |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -103,6 +103,25 @@ pub enum Expr<Ty> { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name: String, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: Vec<ExprD<Ty>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Chamada de função por expressão: chmd(args) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// apenas para não mexer em Call, mas depois podemos mesclar os dois (Call pode ser um caso especial de CallExpr onde callee é um Ident). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Ex.: 'f(42)', '(funçãolambda)(42)', etc.) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CallExpr { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| chmd: Box<ExprD<Ty>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args: Vec<ExprD<Ty>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Função Lambda: `fn(params) -> return_tipo { crp }` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// regra pra não ficar ambiguo: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// 'fn(...) -> ...' é tipo, ou seja 'Type::Fun' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// 'fn(...) -> ... { ... }' é expressão, ou seja 'Expr::Lambda' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Lambda { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| params: Vec<Param>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return_tipo: Type, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| crp: Box<StatementD<Ty>>, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+107
to
+122
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Chamada de função por expressão: chmd(args) | |
| /// apenas para não mexer em Call, mas depois podemos mesclar os dois (Call pode ser um caso especial de CallExpr onde callee é um Ident). | |
| /// Ex.: 'f(42)', '(funçãolambda)(42)', etc.) | |
| CallExpr { | |
| chmd: Box<ExprD<Ty>>, | |
| args: Vec<ExprD<Ty>>, | |
| }, | |
| /// Função Lambda: `fn(params) -> return_tipo { crp }` | |
| /// regra pra não ficar ambiguo: | |
| /// 'fn(...) -> ...' é tipo, ou seja 'Type::Fun' | |
| /// 'fn(...) -> ... { ... }' é expressão, ou seja 'Expr::Lambda' | |
| Lambda { | |
| params: Vec<Param>, | |
| return_tipo: Type, | |
| crp: Box<StatementD<Ty>>, | |
| /// Function call through an expression: callee(args) | |
| /// Kept separate from `Call` for now, though the two could be merged later | |
| /// (`Call` can be treated as a special case of `CallExpr` where `callee` | |
| /// is an `Ident`). Examples: `f(42)`, `(lambda_fn)(42)`, etc. | |
| CallExpr { | |
| callee: Box<ExprD<Ty>>, | |
| args: Vec<ExprD<Ty>>, | |
| }, | |
| /// Lambda function: `fn(params) -> return_type { body }` | |
| /// To avoid ambiguity: | |
| /// `fn(...) -> ...` is a type, i.e. `Type::Fun` | |
| /// `fn(...) -> ... { ... }` is an expression, i.e. `Expr::Lambda` | |
| Lambda { | |
| params: Vec<Param>, | |
| return_type: Type, | |
| body: Box<StatementD<Ty>>, |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -39,6 +39,7 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { | |
| preceded( | ||
| multispace0, | ||
| alt(( | ||
| fun_type, | ||
| // 2D arrays must be tried before 1D (longer prefix first) | ||
| map(tag("int[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Int))))), | ||
| map(tag("float[][]"), |_| Type::Array(Box::new(Type::Array(Box::new(Type::Float))))), | ||
|
|
@@ -57,6 +58,36 @@ pub fn type_name(input: &str) -> IResult<&str, Type> { | |
| )(input) | ||
| } | ||
|
|
||
| /// Parse a function type: `fn(T1, T2, ...) -> Ret`. | ||
| /// | ||
| /// This parser must reject lambdas like: | ||
| /// `fn(int x) -> int { return x; }` | ||
| fn fun_type(input: &str) -> IResult<&str, Type> { | ||
| let (rest, _) = preceded(multispace0, tag("fn"))(input)?; | ||
|
|
||
| let (rest, params) = delimited( | ||
| preceded(multispace0, tag("(")), | ||
| separated_list0( | ||
| preceded(multispace0, tag(",")), | ||
| preceded(multispace0, type_name), | ||
| ), | ||
| preceded(multispace0, tag(")")), | ||
| )(rest)?; | ||
|
|
||
| let (rest, _) = preceded(multispace0, tag("->"))(rest)?; | ||
| let (rest, ret) = preceded(multispace0, type_name)(rest)?; | ||
|
|
||
| // avoid confusing function types with lambdas | ||
| if rest.trim_start().starts_with("{") { | ||
| return Err(nom::Err::Error(nom::error::Error::new( | ||
| rest, | ||
| nom::error::ErrorKind::Tag, | ||
| ))); | ||
| } | ||
|
Comment on lines
+80
to
+86
|
||
|
|
||
| Ok((rest, Type::Fun(params, Box::new(ret)))) | ||
| } | ||
|
|
||
| /// Parse a typed parameter (C-style): `Type name`. | ||
| fn param(input: &str) -> IResult<&str, (String, Type)> { | ||
| map( | ||
|
|
@@ -79,6 +110,7 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { | |
| preceded(multispace0, tag(")")), | ||
| )(rest)?; | ||
| let (rest, body) = preceded(multispace0, statement)(rest)?; | ||
|
|
||
| Ok(( | ||
| rest, | ||
| FunDecl { | ||
|
|
@@ -88,4 +120,4 @@ pub fn fun_decl(input: &str) -> IResult<&str, UncheckedFunDecl> { | |
| body: Box::new(body), | ||
| }, | ||
| )) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eval_call_valuesupportsFnValue::Closure, buteval_call(used by call-statements and as the interpreter entrypoint formain) does not. As a result, a closure stored in a variable will work in expression position (print(f(1))viaExpr::Call), but will fail at runtime as a statement (f(1);becomesStatement::Calland routes througheval_call). Consider implementing theClosurebranch ineval_callas well, or refactoringeval_callto just look up the callee in the environment and delegate toeval_call_value.