From ccf996df3e0d42d9149e37fb914769f18e6484c1 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Fri, 19 Apr 2024 08:50:07 +0800 Subject: [PATCH 01/13] Update exampels --- compiler/oxidate/src/tests.rs | 17 ++++++++++++++++- example/concurrency-01.rst | 6 +++++- example/simple-spawn.rst | 8 ++++---- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/compiler/oxidate/src/tests.rs b/compiler/oxidate/src/tests.rs index c3ba6e5..bdf51a8 100644 --- a/compiler/oxidate/src/tests.rs +++ b/compiler/oxidate/src/tests.rs @@ -20,7 +20,7 @@ mod tests { fn test_comp(inp: &str, exp: Vec) { let res = exp_compile_str(inp); - dbg!(&res); + dbg!(&res[28]); assert_eq!(res, exp); } @@ -1027,6 +1027,21 @@ mod tests { ); } + #[test] + fn test_compile_loop2() { + let t = r" + let x = 0; + loop x < 5 { + let j = x + 1; + break; + x = x + 1; + } + + x + "; + // test_comp(t, vec![]); + } + #[test] fn test_compile_fn_call() { let t = "print(2, 3)"; diff --git a/example/concurrency-01.rst b/example/concurrency-01.rst index 9e0b62a..dda5f80 100644 --- a/example/concurrency-01.rst +++ b/example/concurrency-01.rst @@ -1,3 +1,5 @@ +// Expected: prints in order + fn loop_and_print(x: int) { let count = 0; @@ -12,6 +14,8 @@ fn loop_and_print(x: int) { } } +print("Spawning 3 threads"); + let thread_id_1 = spawn loop_and_print(1); let thread_id_2 = spawn loop_and_print(2); let thread_id_3 = spawn loop_and_print(3); @@ -20,4 +24,4 @@ join thread_id_3; join thread_id_2; join thread_id_1; -println(true); +println("Done"); diff --git a/example/simple-spawn.rst b/example/simple-spawn.rst index e8ce81f..95f0bc0 100644 --- a/example/simple-spawn.rst +++ b/example/simple-spawn.rst @@ -1,7 +1,7 @@ fn func() { - let j = 500; - loop j < 600 { - println(j); + let j = 0; + loop j < 100 { + println("in func"); j = j + 1; } } @@ -12,7 +12,7 @@ let tid = spawn func(); let i = 0; loop i < 100 { - println(i); + println("in main"); i = i + 1; } From f3e63c6048a1af522e31150765853703ce3488a0 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Fri, 19 Apr 2024 09:11:58 +0800 Subject: [PATCH 02/13] Update example --- example/simple-spawn.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/example/simple-spawn.rst b/example/simple-spawn.rst index 95f0bc0..3a1b04d 100644 --- a/example/simple-spawn.rst +++ b/example/simple-spawn.rst @@ -4,6 +4,8 @@ fn func() { println("in func"); j = j + 1; } + + println("func is done"); } @@ -11,10 +13,12 @@ fn func() { let tid = spawn func(); let i = 0; -loop i < 100 { +loop i < 200 { println("in main"); i = i + 1; } // Uncomment to ensure func finishes before main //join tid; + +println("main is done"); From 710d01079349603596c10e02ef78dd91cb1679da Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Fri, 19 Apr 2024 10:56:08 +0800 Subject: [PATCH 03/13] Rm dbg --- compiler/oxidate/src/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/oxidate/src/tests.rs b/compiler/oxidate/src/tests.rs index bdf51a8..d6cfebf 100644 --- a/compiler/oxidate/src/tests.rs +++ b/compiler/oxidate/src/tests.rs @@ -20,7 +20,7 @@ mod tests { fn test_comp(inp: &str, exp: Vec) { let res = exp_compile_str(inp); - dbg!(&res[28]); + // dbg!(&res[28]); assert_eq!(res, exp); } From e064fe09120de341a0b946d0e28b93b114f541fd Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 13:48:08 +0800 Subject: [PATCH 04/13] Type check fn decl --- compiler/oxidate/src/tests.rs | 1 + src/parser/src/structs.rs | 10 +- src/types/src/check_fn_call.rs | 13 +-- src/types/src/check_fn_decl.rs | 196 ++++++++++++++++++++++++++++++--- vm/ignite/tests/e2e.rs | 2 +- 5 files changed, 191 insertions(+), 31 deletions(-) diff --git a/compiler/oxidate/src/tests.rs b/compiler/oxidate/src/tests.rs index d6cfebf..1400ad2 100644 --- a/compiler/oxidate/src/tests.rs +++ b/compiler/oxidate/src/tests.rs @@ -13,6 +13,7 @@ mod tests { fn exp_compile_str(inp: &str) -> Vec { let parser = Parser::new_from_string(inp); let parsed = parser.parse().expect("Should parse"); + dbg!(inp); dbg!("parsed:", &parsed); let comp = Compiler::new(parsed); comp.compile().expect("Should compile") diff --git a/src/parser/src/structs.rs b/src/parser/src/structs.rs index b65a0d8..47ab233 100644 --- a/src/parser/src/structs.rs +++ b/src/parser/src/structs.rs @@ -395,9 +395,10 @@ impl Display for ParseError { impl std::error::Error for ParseError {} // Type of a function value - subset of FnDeclData +// Params: care only about types not names #[derive(Debug, Clone, PartialEq)] pub struct FnTypeData { - pub params: Vec, + pub params: Vec, pub ret_type: Type, } @@ -407,12 +408,7 @@ impl Display for FnTypeData { let params_str = if self.params.is_empty() { "()".to_string() } else { - let params_display: Vec = self - .params - .iter() - .map(|p| format!("{}", p.type_ann.clone().unwrap_or(Type::Unit))) - .map(|x| x.to_string()) - .collect(); + let params_display: Vec = self.params.iter().map(|x| x.to_string()).collect(); format!("({})", params_display.join(", ")) }; diff --git a/src/types/src/check_fn_call.rs b/src/types/src/check_fn_call.rs index eb853f3..ae161a0 100644 --- a/src/types/src/check_fn_call.rs +++ b/src/types/src/check_fn_call.rs @@ -382,16 +382,10 @@ impl<'prog> TypeChecker<'prog> { // TODO: lookup type of the user fn in env, cast to function type and use its return type let fn_ty = self.get_type(&fn_call.name)?.to_fn_type(); if let Some(ty) = fn_ty { - let param_types: Vec = ty - .params - .iter() - .map(|x| x.type_ann.clone().unwrap()) - .collect(); + let param_types: Vec = ty.params.iter().map(|x| x.to_owned()).collect(); TypeChecker::check_arg_params_match(&fn_call.name, &arg_types, ¶m_types)?; check_res.ty = ty.ret_type; - } else { - panic!("Fn name not in env"); } // dbg!("fn_ty", fn_ty); // check_res.ty = fn_ty; @@ -410,6 +404,11 @@ mod tests { #[test] fn test_type_check_userfn_call() { + let t = r" + f(); + "; + expect_err(t, "Identifier 'f' not declared", true); + let t = r" fn fac(n : int) -> int { 2 diff --git a/src/types/src/check_fn_decl.rs b/src/types/src/check_fn_decl.rs index 0c6be3e..bad70bf 100644 --- a/src/types/src/check_fn_decl.rs +++ b/src/types/src/check_fn_decl.rs @@ -3,33 +3,81 @@ use parser::structs::{FnDeclData, FnTypeData, Type}; use crate::type_checker::{CheckResult, TypeChecker, TypeErrors}; impl<'prog> TypeChecker<'prog> { - // 1. all nested returns should have same type as annotated ret type: use fn_stack to track this + // 1. all nested returns belonging to fn should have same type as annotated ret type: use fn_stack to track this + // 2. Last expr (if it exists) must have same type as annotated, unless there was must_return before + // 3. Fn decl well-typed iff - all nested return stmts belonging to the function return the same type as the ty_ann, + // AND (somewhere in the block we encounter a terminating decl/ last_expr OR the + // last expression of the block has the same type as the ty_ann) + // Everything after a must_return is ignored pub(crate) fn check_fn_decl( &mut self, fn_decl: &FnDeclData, ) -> Result { + // Assert all params have type ann and add their types + let mut param_types: Vec = vec![]; + + for param in fn_decl.params.iter() { + if let Some(ty) = ¶m.type_ann { + param_types.push(ty.to_owned()); + } else { + let e = format!("Parameter '{}' has no type annotation", param.name); + return Err(TypeErrors::new_err(&e)); + } + } + let fn_ty = FnTypeData { - params: fn_decl.params.clone(), + params: param_types, ret_type: fn_decl.ret_type.clone(), }; let fn_ty = Type::UserFn(Box::new(fn_ty)); // let mut ty_errs = TypeErrors::new(); + let fn_res = CheckResult { + ty: fn_ty.clone(), + must_break: false, + must_return: false, + }; + // Before checking block, add this fn to env to support recursion self.assign_ident(&fn_decl.name, fn_ty.clone())?; // should work because of enterscope // dbg!("FN_PARAMS:", &fn_decl.params, &fn_decl.name); - self.check_block(&fn_decl.body, fn_decl.params.clone())?; + let blk_res = self.check_block(&fn_decl.body, fn_decl.params.clone())?; + // dbg!("FN BLK TYPE:", &blk_res); + + // If must_return encountered in block, we assume nested returns are correct type so just stop here + if blk_res.must_return { + return Ok(fn_res); + } + + // check blk_ty matches overall ret type only if last_expr exists + if fn_decl.body.last_expr.is_some() { + if blk_res.ty.eq(&fn_decl.ret_type) { + return Ok(fn_res); + } else { + let e = format!( + "Function '{}' has return type '{}' but found block type '{}'", + fn_decl.name, fn_decl.ret_type, blk_res.ty + ); + return Err(TypeErrors::new_err(&e)); + } + } + + // if no must_return, and no last_expr, and overall type is not Unit, err + if !fn_decl.ret_type.eq(&Type::Unit) { + let e = format!( + "Function '{}' might not return '{}'", + fn_decl.name, fn_decl.ret_type + ); + return Err(TypeErrors::new_err(&e)); + } + + Ok(fn_res) + // If everything is ok, return the annotated types // Fn decl doesn't contribute to overall must_ret / must_break of the outer block - let res = CheckResult { - ty: fn_ty, - must_break: false, - must_return: false, - }; - Ok(res) } } @@ -94,13 +142,121 @@ mod tests { #[test] fn test_type_check_fn_rettype() { - // should fail because blk has unit - // let t = r" - // fn f(x: int) -> int { - // f(x-1); - // } - // f - // "; + // should fail because blk has type bool + let t = r" + fn f(x: int) -> int { + true + } + f + "; + expect_err(t, "has return type 'int' but found block type 'bool'", true); + + // last expr ret + let t = r" + fn f() -> int { + 20+30 + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + return 20; + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + { + return 30; + } + true + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f() -> int { + if true { + return 20; + } else { + return 30; + } + } + "; + expect_pass(t, Type::Unit); + + // // if only, loop are not must_ret + // // although inf loop that would definitely return here, we are conservative + let t = r" + fn f() -> int { + if true { + return 20; + } + + loop { + return 30; + } + } + "; + expect_err(t, "might not return", true); + + // unit + let t = r" + fn f() { + if true { + return 20; + } + + loop { + return 30; + } + + fn g() -> int { + 200 + } + } + "; + expect_pass(t, Type::Unit); + + // if else + let t = r" + fn f() -> int { + if true { + return 20; + } else { + 30 + } + } + "; + expect_pass(t, Type::Unit); + + let t = r" + fn f(x: int) -> int { + 2; + } + f + "; + expect_err(t, "might not return", true); + } + + #[test] + fn test_type_check_fn_ret_stmt() { + let t = r" + fn f() -> int { + if true { + return true; + } else { + return 2.56; + } + + return 5; + } + "; + // expect_err(t, "", false); + + // check that it ignores inner return for hof } #[test] @@ -124,5 +280,13 @@ mod tests { fac(1) "; expect_err(t, "'x' has declared type bool but assigned type int", true); + + let t = r" + fn fac(n: int, b: bool) { + n + b + } + fac(1) + "; + expect_err(t, "Can't apply '+' to types 'int' and 'bool'", true); } } diff --git a/vm/ignite/tests/e2e.rs b/vm/ignite/tests/e2e.rs index 6b15ce8..5e11ecc 100644 --- a/vm/ignite/tests/e2e.rs +++ b/vm/ignite/tests/e2e.rs @@ -557,7 +557,7 @@ fn test_e2e_fib() -> Result<()> { #[test] fn test_e2e_fn_decl() -> Result<()> { let t = r" - fn fac(n: int) { + fn fac(n: int) -> int { 2 + n } From f82163e8e857dd41011784e18145ff0a3b13d3c2 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 14:09:30 +0800 Subject: [PATCH 05/13] Check nested return matches annotated type --- src/types/src/check_fn_decl.rs | 48 +++++++++++++++++++++++++++------- src/types/src/type_checker.rs | 40 ++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/src/types/src/check_fn_decl.rs b/src/types/src/check_fn_decl.rs index bad70bf..2c72bc7 100644 --- a/src/types/src/check_fn_decl.rs +++ b/src/types/src/check_fn_decl.rs @@ -3,16 +3,24 @@ use parser::structs::{FnDeclData, FnTypeData, Type}; use crate::type_checker::{CheckResult, TypeChecker, TypeErrors}; impl<'prog> TypeChecker<'prog> { + pub(crate) fn check_fn_decl( + &mut self, + fn_decl: &FnDeclData, + ) -> Result { + self.fn_type_stack.push(fn_decl.ret_type.clone()); + let res = self.check_fn_decl_inner(fn_decl); + self.fn_type_stack.pop(); + res + } + // 1. all nested returns belonging to fn should have same type as annotated ret type: use fn_stack to track this // 2. Last expr (if it exists) must have same type as annotated, unless there was must_return before + // 3. Fn decl well-typed iff - all nested return stmts belonging to the function return the same type as the ty_ann, // AND (somewhere in the block we encounter a terminating decl/ last_expr OR the // last expression of the block has the same type as the ty_ann) - // Everything after a must_return is ignored - pub(crate) fn check_fn_decl( - &mut self, - fn_decl: &FnDeclData, - ) -> Result { + // Everything after a must_return is ignored. function returns unit => don't need must_return, but nested ret cannot return anything else + fn check_fn_decl_inner(&mut self, fn_decl: &FnDeclData) -> Result { // Assert all params have type ann and add their types let mut param_types: Vec = vec![]; @@ -202,15 +210,15 @@ mod tests { "; expect_err(t, "might not return", true); - // unit + // unit - don't have to must_return let t = r" fn f() { if true { - return 20; + return; } loop { - return 30; + return; } fn g() -> int { @@ -254,9 +262,31 @@ mod tests { return 5; } "; - // expect_err(t, "", false); + expect_err(t, "[TypeError]: Expected function return type 'int' but return statement has type 'bool'\n[TypeError]: Expected function return type 'int' but return statement has type 'float'", false); // check that it ignores inner return for hof + let t = r" + fn f() -> int { + fn g() -> bool { + return true; + } + + return 20; + } + f + "; + expect_pass_str(t, "fn() -> int"); + + // when return expr has error - keeps checking rest + let t = r" + fn f() -> int { + if true { + return 2+ !2; + } + return !true; + } + "; + expect_err(t, "[TypeError]: Can't apply logical NOT to type int\n[TypeError]: Expected function return type 'int' but return statement has type 'bool'", false); } #[test] diff --git a/src/types/src/type_checker.rs b/src/types/src/type_checker.rs index 0e03939..6edf92d 100644 --- a/src/types/src/type_checker.rs +++ b/src/types/src/type_checker.rs @@ -91,6 +91,8 @@ impl CheckResult { pub struct TypeChecker<'prog> { program: &'prog BlockSeq, pub(crate) envs: Vec, + // stores type of function currently being checked at top (empty if not checking function) + pub(crate) fn_type_stack: Vec, } impl<'prog> TypeChecker<'prog> { @@ -98,6 +100,7 @@ impl<'prog> TypeChecker<'prog> { TypeChecker { program, envs: vec![], + fn_type_stack: vec![], } } @@ -449,11 +452,38 @@ impl<'prog> TypeChecker<'prog> { } Decl::FnDeclStmt(fn_decl) => self.check_fn_decl(fn_decl), // TODO: check nested returns with fn stack - Decl::ReturnStmt(_) => Ok(CheckResult { - ty: Type::Unit, - must_break: true, - must_return: true, - }), + Decl::ReturnStmt(ret_expr) => { + // dbg!("fn_stack at return:", &self.fn_type_stack); + let mut res = CheckResult { + ty: Type::Unit, + must_break: true, + must_return: true, + }; + + if let Some(expr) = ret_expr { + let expr_res = self.check_expr(expr)?; + res = CheckResult::combine(&res, &expr_res); + res.ty = expr_res.ty; + } + + // now it's either unit or the type of the ret_expr + // return type must match fn annotated + + // expect because parser rejects return outside function + let fn_ty = self + .fn_type_stack + .last() + .expect("Should have type in fn_stack"); + if !res.ty.eq(fn_ty) { + let e = format!( + "Expected function return type '{}' but return statement has type '{}'", + fn_ty, res.ty + ); + return Err(TypeErrors::new_err(&e)); + } + + Ok(res) + } Decl::WaitStmt(_) => Ok(CheckResult { ty: Type::Unit, must_break: false, From 5442027c415628a8513ef7d6f75f3b3da67b6161 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 14:24:52 +0800 Subject: [PATCH 06/13] Move parse_type_ann --- src/parser/src/lib.rs | 74 ++++--------------------------- src/parser/src/parse_type_ann.rs | 76 ++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 66 deletions(-) create mode 100644 src/parser/src/parse_type_ann.rs diff --git a/src/parser/src/lib.rs b/src/parser/src/lib.rs index 0d34f88..0ff7639 100644 --- a/src/parser/src/lib.rs +++ b/src/parser/src/lib.rs @@ -10,6 +10,7 @@ pub mod ident; pub mod if_else; pub mod let_stmt; pub mod parse_loop; +pub mod parse_type_ann; pub mod seq; pub mod structs; @@ -150,39 +151,6 @@ impl<'inp> Parser<'inp> { )) } } - - /// Parse and return type annotation. Expect lexer.peek() to be at Colon before call - // Should only consume tokens belonging to the annotation, starting peek at first token and ending - // peek at the last token of the annotation - fn parse_type_annotation(&mut self) -> Result { - // self.consume_token_type(Token::Colon, "Expected a colon")?; - // expect_token_body!(self.lexer.peek(), Ident, "identifier")?; - Parser::expect_token_for_type_ann(self.lexer.peek())?; - - // if ident, get the string and try to convert type. else, handle specially - let peek = self - .lexer - .peek() - .unwrap() - .to_owned() - .expect("Lexer should not fail"); // would have erred earlier - - let type_ann = match peek { - Token::Ident(id) => Type::from_string(&id), - Token::OpenParen => { - self.advance(); - if let Some(Ok(Token::CloseParen)) = self.lexer.peek() { - Ok(Type::Unit) - } else { - Err(ParseError::new("Expected '()' for unit type annotation")) - } - } - _ => unreachable!(), - }?; - - Ok(type_ann) - } - /* Precedence */ // Return (left bp, right bp) @@ -384,39 +352,6 @@ mod tests { ); } - #[test] - fn test_parse_type_annotations() { - test_parse("let x : int = 2;", "let x : int = 2;"); - test_parse("let x : bool = true;", "let x : bool = true;"); - test_parse("let x : float = true;", "let x : float = true;"); - test_parse("let x : () = true;", "let x : () = true;"); - } - - #[test] - fn test_parse_type_annotations_errs() { - // test_parse("let x : int = 2;", ""); - test_parse_err( - "let x : let ", - "Expected identifier or '(' for type annotation, got 'let'", - true, - ); - test_parse_err( - "let x : 2 ", - "Expected identifier or '(' for type annotation, got '2'", - true, - ); - test_parse_err( - "let x : ", - "Expected identifier or '(' for type annotation, got end of input", - true, - ); - test_parse_err( - "let x : (2 ", - "Expected '()' for unit type annotation", - true, - ); - } - #[test] fn test_parse_concurrency() { let t = r" @@ -488,4 +423,11 @@ mod tests { let t = r#"let t = "hello world"; println(t);"#; test_parse(t, "let t = hello world;println(t);"); } + + #[test] + fn test_parse_type_annotations_more() { + let t = r" + let g : fn(int) -> int = f; + "; + } } diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs new file mode 100644 index 0000000..7129546 --- /dev/null +++ b/src/parser/src/parse_type_ann.rs @@ -0,0 +1,76 @@ +use crate::ParseError; +use crate::Parser; +use crate::Type; +use lexer::Token; + +impl<'inp> Parser<'inp> { + /// Parse and return type annotation. Expect lexer.peek() to be at Colon before call + // Should only consume tokens belonging to the annotation, starting peek at first token and ending + // peek at the last token of the annotation + pub(crate) fn parse_type_annotation(&mut self) -> Result { + // self.consume_token_type(Token::Colon, "Expected a colon")?; + // expect_token_body!(self.lexer.peek(), Ident, "identifier")?; + Parser::expect_token_for_type_ann(self.lexer.peek())?; + + // if ident, get the string and try to convert type. else, handle specially + let peek = self + .lexer + .peek() + .unwrap() + .to_owned() + .expect("Lexer should not fail"); // would have erred earlier + + let type_ann = match peek { + Token::Ident(id) => Type::from_string(&id), + Token::OpenParen => { + self.advance(); + if let Some(Ok(Token::CloseParen)) = self.lexer.peek() { + Ok(Type::Unit) + } else { + Err(ParseError::new("Expected '()' for unit type annotation")) + } + } + _ => unreachable!(), + }?; + + Ok(type_ann) + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{test_parse, test_parse_err}; + + #[test] + fn test_parse_type_annotations() { + test_parse("let x : int = 2;", "let x : int = 2;"); + test_parse("let x : bool = true;", "let x : bool = true;"); + test_parse("let x : float = true;", "let x : float = true;"); + test_parse("let x : () = true;", "let x : () = true;"); + } + + #[test] + fn test_parse_type_annotations_errs() { + // test_parse("let x : int = 2;", ""); + test_parse_err( + "let x : let ", + "Expected identifier or '(' for type annotation, got 'let'", + true, + ); + test_parse_err( + "let x : 2 ", + "Expected identifier or '(' for type annotation, got '2'", + true, + ); + test_parse_err( + "let x : ", + "Expected identifier or '(' for type annotation, got end of input", + true, + ); + test_parse_err( + "let x : (2 ", + "Expected '()' for unit type annotation", + true, + ); + } +} From a8958e65173fde46b8662b558d55bf584f3f25bb Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 14:51:31 +0800 Subject: [PATCH 07/13] Save prog --- src/parser/src/lib.rs | 9 +--- src/parser/src/parse_type_ann.rs | 87 ++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) diff --git a/src/parser/src/lib.rs b/src/parser/src/lib.rs index 0ff7639..94b981c 100644 --- a/src/parser/src/lib.rs +++ b/src/parser/src/lib.rs @@ -136,7 +136,7 @@ impl<'inp> Parser<'inp> { fn expect_token_for_type_ann(token: Option<&Result>) -> Result<(), ParseError> { if let Some(Ok(tok)) = token { match tok { - Token::Ident(_) | Token::OpenParen => Ok(()), + Token::Ident(_) | Token::OpenParen | Token::Fn => Ok(()), _ => { let e = format!( "Expected identifier or '(' for type annotation, got '{}'", @@ -423,11 +423,4 @@ mod tests { let t = r#"let t = "hello world"; println(t);"#; test_parse(t, "let t = hello world;println(t);"); } - - #[test] - fn test_parse_type_annotations_more() { - let t = r" - let g : fn(int) -> int = f; - "; - } } diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs index 7129546..8e4446e 100644 --- a/src/parser/src/parse_type_ann.rs +++ b/src/parser/src/parse_type_ann.rs @@ -1,3 +1,4 @@ +use crate::FnTypeData; use crate::ParseError; use crate::Parser; use crate::Type; @@ -30,6 +31,58 @@ impl<'inp> Parser<'inp> { Err(ParseError::new("Expected '()' for unit type annotation")) } } + Token::Fn => { + self.advance(); // go past fn + self.consume_token_type( + Token::OpenParen, + "Expected '(' for function type annotation", + )?; // go past ( + + let mut param_types: Vec = vec![]; + let mut ret_ty = Type::Unit; + + // Parse param types + while let Some(tok) = self.lexer.peek() { + let tok = tok.clone(); + // stop at ) + if tok.clone().unwrap().eq(&Token::CloseParen) { + break; + } + + let param_ty = self.parse_type_annotation()?; + param_types.push(param_ty); + self.advance(); // go past token of last ty_an + + // Comma or CloseParen + if !self.lexer.peek().eq(&Some(&Ok(Token::CloseParen))) { + self.consume_token_type( + Token::Comma, + "Expected ',' to separate function parameters", + )?; + } + } + + dbg!("PEEK AFTER LOOP:", &self.lexer.peek()); + + // self.advance(); // skip past open paren, peek is at return arrow or equals + + if self.consume_opt_token_type(Token::FnDeclReturn) { + // peek is now at type_ann first token + let ret_ty_ann = self.parse_type_annotation()?; + // self.advance(); // go past last token of ty_ann + dbg!(&ret_ty_ann); + ret_ty = ret_ty_ann; + } + + dbg!("PEEK AFTER:", &self.lexer.peek()); + + let fn_ty_data = FnTypeData { + params: param_types, + ret_type: ret_ty, + }; + + Ok(Type::UserFn(Box::new(fn_ty_data))) + } _ => unreachable!(), }?; @@ -73,4 +126,38 @@ mod tests { true, ); } + + #[test] + fn test_parse_type_annotations_more() { + // hof, semaphore, string + + // // empty + let t = r" + let g : fn() = f; + "; + test_parse(t, "let g : fn() = f;"); + + // // has params + let t = r" + let g : fn(int) = f; + "; + test_parse(t, "let g : fn(int) = f;"); + + let t = r" + let g : fn(int, bool) = f; + "; + test_parse(t, "let g : fn(int, bool) = f;"); + + // // param is fn + let t = r" + let g : fn(int, fn(int)) = f; + "; + test_parse(t, "let g : fn(int, fn(int)) = f;"); + + // ret type + let t = r" + let g : fn(int) -> bool = f; + "; + // test_parse(t, "let g : fn(int) -> bool = f;"); + } } From b1d9def0e01d3951d81b26c62c5ff92c48ee2b2d Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:00:40 +0800 Subject: [PATCH 08/13] Add hof type anotation --- src/parser/src/fn_decl.rs | 4 ++-- src/parser/src/let_stmt.rs | 2 +- src/parser/src/parse_type_ann.rs | 28 ++++++++++++++++++++++------ 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/parser/src/fn_decl.rs b/src/parser/src/fn_decl.rs index 7861c33..c905ebf 100644 --- a/src/parser/src/fn_decl.rs +++ b/src/parser/src/fn_decl.rs @@ -62,7 +62,7 @@ impl<'inp> Parser<'inp> { param_ty.replace(ty); // to go past last token of type_ann, so peek is at comma or close paren - self.advance(); + // self.advance(); } // Comma or CloseParen @@ -98,7 +98,7 @@ impl<'inp> Parser<'inp> { if self.consume_opt_token_type(Token::FnDeclReturn) { // peek is now at type_ann first token let ret_ty_ann = self.parse_type_annotation()?; - self.advance(); // go past last token of ty_ann + // self.advance(); // go past last token of ty_ann ret_ty = ret_ty_ann; } diff --git a/src/parser/src/let_stmt.rs b/src/parser/src/let_stmt.rs index bfc3437..ecb0323 100644 --- a/src/parser/src/let_stmt.rs +++ b/src/parser/src/let_stmt.rs @@ -25,7 +25,7 @@ impl<'inp> Parser<'inp> { type_ann.replace(ty); // call advance so peek is at equals - self.advance(); + // self.advance(); } self.consume_token_type(Token::Eq, "Expected '='")?; diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs index 8e4446e..662e197 100644 --- a/src/parser/src/parse_type_ann.rs +++ b/src/parser/src/parse_type_ann.rs @@ -7,7 +7,7 @@ use lexer::Token; impl<'inp> Parser<'inp> { /// Parse and return type annotation. Expect lexer.peek() to be at Colon before call // Should only consume tokens belonging to the annotation, starting peek at first token and ending - // peek at the last token of the annotation + // peek at token AFTER the last token of type annotation pub(crate) fn parse_type_annotation(&mut self) -> Result { // self.consume_token_type(Token::Colon, "Expected a colon")?; // expect_token_body!(self.lexer.peek(), Ident, "identifier")?; @@ -22,10 +22,15 @@ impl<'inp> Parser<'inp> { .expect("Lexer should not fail"); // would have erred earlier let type_ann = match peek { - Token::Ident(id) => Type::from_string(&id), + Token::Ident(id) => { + let res = Type::from_string(&id); + self.advance(); + res + } Token::OpenParen => { self.advance(); if let Some(Ok(Token::CloseParen)) = self.lexer.peek() { + self.advance(); Ok(Type::Unit) } else { Err(ParseError::new("Expected '()' for unit type annotation")) @@ -51,7 +56,7 @@ impl<'inp> Parser<'inp> { let param_ty = self.parse_type_annotation()?; param_types.push(param_ty); - self.advance(); // go past token of last ty_an + // self.advance(); // go past token of last ty_an // Comma or CloseParen if !self.lexer.peek().eq(&Some(&Ok(Token::CloseParen))) { @@ -64,7 +69,7 @@ impl<'inp> Parser<'inp> { dbg!("PEEK AFTER LOOP:", &self.lexer.peek()); - // self.advance(); // skip past open paren, peek is at return arrow or equals + self.advance(); // skip past open paren, peek is at return arrow or equals if self.consume_opt_token_type(Token::FnDeclReturn) { // peek is now at type_ann first token @@ -148,7 +153,7 @@ mod tests { "; test_parse(t, "let g : fn(int, bool) = f;"); - // // param is fn + // // // param is fn let t = r" let g : fn(int, fn(int)) = f; "; @@ -158,6 +163,17 @@ mod tests { let t = r" let g : fn(int) -> bool = f; "; - // test_parse(t, "let g : fn(int) -> bool = f;"); + test_parse(t, "let g : fn(int) -> bool = f;"); + + let t = r" + let g : fn(int, float) -> bool = f; + "; + test_parse(t, "let g : fn(int, float) -> bool = f;"); + + // returns fn + let t = r" + let g : fn(int, bool) -> fn(int) -> int = f; + "; + test_parse(t, "let g : fn(int, bool) -> fn(int) -> int = f;"); } } From 0ec30795282a4a877a91e519274191791f36ca0c Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:07:26 +0800 Subject: [PATCH 09/13] hof type check test --- compiler/oxidate/src/tests.rs | 15 ----------- src/parser/src/fn_decl.rs | 17 +++++++++++++ src/types/src/check_fn_decl.rs | 46 ++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/compiler/oxidate/src/tests.rs b/compiler/oxidate/src/tests.rs index 1400ad2..4c8fc08 100644 --- a/compiler/oxidate/src/tests.rs +++ b/compiler/oxidate/src/tests.rs @@ -1028,21 +1028,6 @@ mod tests { ); } - #[test] - fn test_compile_loop2() { - let t = r" - let x = 0; - loop x < 5 { - let j = x + 1; - break; - x = x + 1; - } - - x - "; - // test_comp(t, vec![]); - } - #[test] fn test_compile_fn_call() { let t = "print(2, 3)"; diff --git a/src/parser/src/fn_decl.rs b/src/parser/src/fn_decl.rs index c905ebf..a2d0917 100644 --- a/src/parser/src/fn_decl.rs +++ b/src/parser/src/fn_decl.rs @@ -426,4 +426,21 @@ mod tests { "fn fac (n:int) -> int { if (n==0) { return 1; };return (n*fac((n-1))); };", ); } + + #[test] + fn test_parse_fn_decl_hof_ret() { + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> bool { + x+y > 0 + } + + adder + } + "; + test_parse( + t, + "fn adder (x:int) -> fn(int) -> bool { fn f (y:int) -> bool { ((x+y)>0) };adder };", + ); + } } diff --git a/src/types/src/check_fn_decl.rs b/src/types/src/check_fn_decl.rs index 2c72bc7..7ff35b3 100644 --- a/src/types/src/check_fn_decl.rs +++ b/src/types/src/check_fn_decl.rs @@ -319,4 +319,50 @@ mod tests { "; expect_err(t, "Can't apply '+' to types 'int' and 'bool'", true); } + + #[test] + fn test_type_check_fn_hof() { + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> bool { + x+y > 0 + } + + adder + } + "; + expect_err( + t, + "has return type 'fn(int) -> bool' but found block type 'fn(int) -> fn(int) -> bool'", + true, + ); + + let t = r" + fn adder(x : int) -> fn(int) -> bool { + fn f(y:int) -> int { + x+y + } + + f + } + "; + expect_err( + t, + "has return type 'fn(int) -> bool' but found block type 'fn(int) -> int", + true, + ); + + // ok + let t = r" + fn adder(x : int) -> fn(int) -> int { + fn f(y:int) -> int { + x+y + } + + f + } + adder + "; + expect_pass_str(t, "fn(int) -> fn(int) -> int"); + } } From 0b969ef48d3da04960122d87b64162a7f379b358 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:24:43 +0800 Subject: [PATCH 10/13] Update exampels --- example/concurrency-03.rst | 3 +-- example/garbage-collection-02.rst | 5 ++--- example/higher-order-fn-01.rst | 12 +++++++----- example/loop-04.rst | 3 --- src/parser/src/parse_type_ann.rs | 6 ++---- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/example/concurrency-03.rst b/example/concurrency-03.rst index 60b17a6..219ac8a 100644 --- a/example/concurrency-03.rst +++ b/example/concurrency-03.rst @@ -1,5 +1,5 @@ // Expected: inconsistent count on each run due to race on count -// When running with a small quantum e.g 1 +// When running with a small quantum e.g -q 1 let count = 0; @@ -7,7 +7,6 @@ fn increment(times: int) { let i = 0; loop i < times { let tmp = count; - yield; count = tmp + 1; i = i + 1; } diff --git a/example/garbage-collection-02.rst b/example/garbage-collection-02.rst index 3a183fe..1c66273 100644 --- a/example/garbage-collection-02.rst +++ b/example/garbage-collection-02.rst @@ -1,6 +1,5 @@ -// Compile untyped: hof type annotation not added yet -fn higher_order(x) { - fn g(y) { +fn higher_order(x: int) -> fn(int) -> int { + fn g(y:int) -> int { x + y } diff --git a/example/higher-order-fn-01.rst b/example/higher-order-fn-01.rst index 833bcd7..c4ef3f0 100644 --- a/example/higher-order-fn-01.rst +++ b/example/higher-order-fn-01.rst @@ -1,14 +1,16 @@ -// Compile untyped: hof type annotation not added yet - -fn f(x) { +fn f(x: int) -> fn(int) -> int { let z = 3; - fn g(y) { + fn g(y: int) -> int { return x + y + z; } g } -let hof = f(2); +// try uncommenting this line to get type error when compiling +// let hof : fn(int) -> bool = f(2); + +let hof : fn(int) -> int = f(2); +// Expected: 9 hof(4) \ No newline at end of file diff --git a/example/loop-04.rst b/example/loop-04.rst index 83ec259..2249116 100644 --- a/example/loop-04.rst +++ b/example/loop-04.rst @@ -1,6 +1,3 @@ -// Doesn't do anything in particular, I asked ChatGPT to generate this and verified -// by converting to Rust - let count = 0; let x = 0; diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs index 662e197..57f8067 100644 --- a/src/parser/src/parse_type_ann.rs +++ b/src/parser/src/parse_type_ann.rs @@ -67,7 +67,7 @@ impl<'inp> Parser<'inp> { } } - dbg!("PEEK AFTER LOOP:", &self.lexer.peek()); + // dbg!("PEEK AFTER LOOP:", &self.lexer.peek()); self.advance(); // skip past open paren, peek is at return arrow or equals @@ -75,12 +75,10 @@ impl<'inp> Parser<'inp> { // peek is now at type_ann first token let ret_ty_ann = self.parse_type_annotation()?; // self.advance(); // go past last token of ty_ann - dbg!(&ret_ty_ann); + // dbg!(&ret_ty_ann); ret_ty = ret_ty_ann; } - dbg!("PEEK AFTER:", &self.lexer.peek()); - let fn_ty_data = FnTypeData { params: param_types, ret_type: ret_ty, From 6724ed80be5a0c8156a787a519ade29bdc9358a2 Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:28:43 +0800 Subject: [PATCH 11/13] Add e2e --- vm/ignite/tests/e2e.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/vm/ignite/tests/e2e.rs b/vm/ignite/tests/e2e.rs index 5e11ecc..d51f659 100644 --- a/vm/ignite/tests/e2e.rs +++ b/vm/ignite/tests/e2e.rs @@ -583,5 +583,33 @@ fn test_e2e_fn_decl() -> Result<()> { "; test_pass(hof, "24")?; + let hof = r" + fn adder(x: int) -> fn(int) -> int { + fn g(y: int) -> int { + x + y + } + + return g; + } + + let add5 : fn(int) -> int = adder(5); + add5(10) + "; + test_pass(hof, "15")?; + + // apply fn passed in + let hof = r" + fn apply(f: fn(int) -> int, x: int) -> int { + f(x) + } + + fn add(x: int) -> int { + return x + 5; + } + + apply(add, 9) + "; + test_pass(hof, "14")?; + Ok(()) } From 27ff7daed7ad47ac1bfb1915fd8db5747e7017bd Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:33:56 +0800 Subject: [PATCH 12/13] Add sem, str ty ann --- example/concurrency-04.rst | 2 +- src/parser/src/parse_type_ann.rs | 6 +++--- src/parser/src/structs.rs | 4 +++- src/types/src/type_checker.rs | 9 +++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/example/concurrency-04.rst b/example/concurrency-04.rst index 3ca25d1..ac562c4 100644 --- a/example/concurrency-04.rst +++ b/example/concurrency-04.rst @@ -1,7 +1,7 @@ // Expected: count = 3000 on each run let count = 0; -let sem = sem_create(); +let sem : sem = sem_create(); fn increment(times: int) { let i = 0; diff --git a/src/parser/src/parse_type_ann.rs b/src/parser/src/parse_type_ann.rs index 57f8067..318005d 100644 --- a/src/parser/src/parse_type_ann.rs +++ b/src/parser/src/parse_type_ann.rs @@ -103,6 +103,8 @@ mod tests { test_parse("let x : bool = true;", "let x : bool = true;"); test_parse("let x : float = true;", "let x : float = true;"); test_parse("let x : () = true;", "let x : () = true;"); + test_parse(r"let x : str = 2;", "let x : str = 2;"); + test_parse("let x : sem = 2;", "let x : sem = 2;"); } #[test] @@ -131,9 +133,7 @@ mod tests { } #[test] - fn test_parse_type_annotations_more() { - // hof, semaphore, string - + fn test_parse_type_annotations_fns() { // // empty let t = r" let g : fn() = f; diff --git a/src/parser/src/structs.rs b/src/parser/src/structs.rs index 47ab233..dcc2003 100644 --- a/src/parser/src/structs.rs +++ b/src/parser/src/structs.rs @@ -461,6 +461,8 @@ impl Type { "int" => Ok(Self::Int), "bool" => Ok(Self::Bool), "float" => Ok(Self::Float), + "str" => Ok(Self::String), + "sem" => Ok(Self::Semaphore), _ => Err(ParseError::new(&format!( "Unknown primitive type: {}", input @@ -478,7 +480,7 @@ impl Display for Type { Self::Unit => "()".to_string(), Self::Unitialised => "uninit".to_string(), Self::BuiltInFn => "builtin_fn".to_string(), - Self::String => "string".to_string(), + Self::String => "str".to_string(), Self::UserFn(fn_ty) => fn_ty.to_string(), Self::ThreadId => "tid".to_string(), Self::Semaphore => "sem".to_string(), diff --git a/src/types/src/type_checker.rs b/src/types/src/type_checker.rs index 6edf92d..be668fd 100644 --- a/src/types/src/type_checker.rs +++ b/src/types/src/type_checker.rs @@ -749,4 +749,13 @@ mod tests { true, ); } + + #[test] + fn type_check_sem_string() { + let t = r#"let t = "hello world"; t"#; + expect_pass(t, Type::String); + + let t = r"let t = sem_create(); t"; + expect_pass(t, Type::Semaphore); + } } From 7a063b7a24b0508233cd4cb9d43be17ecafb6a8d Mon Sep 17 00:00:00 2001 From: Huzaifa Raghav Date: Sat, 20 Apr 2024 15:54:22 +0800 Subject: [PATCH 13/13] Update example --- example/concurrency-03.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/concurrency-03.rst b/example/concurrency-03.rst index 219ac8a..97c5858 100644 --- a/example/concurrency-03.rst +++ b/example/concurrency-03.rst @@ -1,5 +1,4 @@ -// Expected: inconsistent count on each run due to race on count -// When running with a small quantum e.g -q 1 +// Expected: count != 3000 on each run, though we want 3000 let count = 0; @@ -7,6 +6,7 @@ fn increment(times: int) { let i = 0; loop i < times { let tmp = count; + yield; count = tmp + 1; i = i + 1; }