diff --git a/ast/src/lib.rs b/ast/src/lib.rs index c8020f7..eab4bcb 100644 --- a/ast/src/lib.rs +++ b/ast/src/lib.rs @@ -24,6 +24,32 @@ impl For { } } +#[derive(Debug, Clone, PartialEq)] +pub struct If { + pub expr: Box, + pub body: Box, +} + +impl If { + pub fn new(if_expr: Box, if_body: Box) -> Self { + If { + expr: if_expr, + body: if_body, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Destructure { + pub elements: Vec>, +} + +impl Destructure { + pub fn new(elements: Vec>) -> Self { + Destructure { elements } + } +} + #[derive(Debug, Clone, PartialEq)] pub struct Match { pub expr: Box, @@ -300,9 +326,9 @@ pub struct EnumDecl { pub visibility: Option, pub mutability: Lexeme, pub identifier: Box, - pub declarators: Vec>, + pub variants: Vec>, pub sig: Option>, - pub variant: Option>, + pub enum_type: Option>, } impl EnumDecl { @@ -310,17 +336,17 @@ impl EnumDecl { visibility: Option, mutability: Lexeme, identifier: Box, - declarators: Vec>, + variants: Vec>, sig: Option>, - variant: Option>, + enum_type: Option>, ) -> Self { EnumDecl { visibility, mutability, identifier, - declarators, + variants, sig, - variant, + enum_type, } } } @@ -330,6 +356,7 @@ pub struct ErrorDecl { pub visibility: Option, pub mutability: Lexeme, pub identifier: Box, + pub variants: Vec>, pub sig: Option>, } @@ -338,12 +365,14 @@ impl ErrorDecl { visibility: Option, mutability: Lexeme, identifier: Box, + variants: Vec>, sig: Option>, ) -> Self { ErrorDecl { visibility, mutability, identifier, + variants, sig, } } @@ -676,6 +705,8 @@ pub enum Expr { ArrayDecl(ArrayDecl), Rest(Rest), For(For), + Destructure(Destructure), + If(If), While(While), Match(Match), Arm(Arm), diff --git a/ebnf.txt b/ebnf.txt index 63bb5a3..9e841bf 100644 --- a/ebnf.txt +++ b/ebnf.txt @@ -11,7 +11,7 @@ ::= "fn " "(" ? ") " ::= "struct " "{ " ? "}" ::= "error " ("| " )+ - ::= "if " "(" ")" ( | ) ("else if "(" ")" ( | ))* ("else " ( | ))? + ::= "if " "(" ")" ( | ) ::= "for " "(" ")" ( | ) ::= "while " "(" ")" ( | ) ::= "match " "(" ")" "{ " + "}" @@ -41,7 +41,7 @@ ::= "[" "]" ::= "." ::= "(" ( ("," )*)? ")" - ::= "{" ( ":" )* "}" + ::= "{" ( ":" ("," ":" ))? "}" ::= "_" | "true " | "false " | "undefined " | "self " | "never " | | | | | | "(" ")" ::= "fn " "(" ? ") " ::= ([a-z] | [A-Z]) ([A-Z] | [a-z] | [0-9] | "_")* diff --git a/linter/src/lib.rs b/linter/src/lib.rs index 369b718..6968aa8 100644 --- a/linter/src/lib.rs +++ b/linter/src/lib.rs @@ -58,6 +58,7 @@ impl<'buf, 'ttb, 'sco> LintSource<'buf, 'ttb, 'sco> { Expr::Declarator(declarator) => self.check_declarator(&declarator), Expr::Match(_match) => self.check_match(&_match), Expr::For(_for) => self.check_for(&_for), + Expr::If(_if) => self.check_if(&_if), Expr::Invoke(invoke) => self.check_invoke(&invoke), Expr::PropAccess(prop) => self.check_prop_access(&prop), Expr::Arm(arm) => self.check_arm(&arm), @@ -152,6 +153,19 @@ impl<'buf, 'ttb, 'sco> LintSource<'buf, 'ttb, 'sco> { ok_simple_tree!(UndefinedValue, typ) } + pub fn check_if(&mut self, _if: &If) -> ResultTreeType { + let res = self.lint_recurse(&_if.expr)?; + let body = self.lint_recurse(&_if.body)?; + let if_op = IfOp { + in_expr: res.0, + in_curried: res.1, + body: body.0, + body_curried: body.1, + }; + let cur = if_op.body_curried.clone(); + ok_tree!(If, if_op, cur) + } + pub fn check_for(&mut self, _for: &For) -> ResultTreeType { let res = self.lint_recurse(&_for.expr)?; let body = self.lint_recurse(&_for.var_loop)?; diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 5363770..1a449a6 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -58,7 +58,7 @@ impl<'s> Parser<'s> { ) -> ResultExpr { let mut variants: Vec> = vec![]; while let Some(_) = self.lexer.collect_if(Token::Bar) { - let x = self.ident().xconvert_to_sym_decl()?; + let x = self.ident().xconvert_to_decl()?; variants.push(x); } result_expr!(TagDecl, visibility, mutability, identifier, variants, sig) @@ -71,15 +71,12 @@ impl<'s> Parser<'s> { identifier: Box, sig: Option>, ) -> ResultExpr { - let _ = self - .lexer - .collect_if(Token::OBrace) - .xexpect_token(&self, "expected '{'".to_string())?; - let _ = self - .lexer - .collect_if(Token::CBrace) - .xexpect_token(&self, "expected '}'".to_string())?; - result_expr!(ErrorDecl, visibility, mutability, identifier, sig) + let mut variants: Vec> = vec![]; + while let Some(_) = self.lexer.collect_if(Token::Bar) { + let x = self.ident().xconvert_to_decl()?; + variants.push(x); + } + result_expr!(ErrorDecl, visibility, mutability, identifier, variants, sig) } pub fn _enum( @@ -99,7 +96,7 @@ impl<'s> Parser<'s> { .xexpect_token(&self, "expected ')'".to_string())?; } while let Some(_) = self.lexer.collect_if(Token::Bar) { - let x = self.ident().xconvert_to_sym_decl()?; + let x = self.ident().xconvert_to_decl()?; variants.push(x); } result_expr!(EnumDecl, visibility, mutability, identifier, variants, sig, enum_type) @@ -131,9 +128,9 @@ impl<'s> Parser<'s> { .collect_of_if(&[Token::Let, Token::Const, Token::Type, Token::Impl]) .xexpect_token(&self, "expected mutability".to_string())?; let identifier = self - .ident() - .xexpect_expr(&self, "expected identifier".to_string()) - .xconvert_to_sym_decl()?; + .destructure() + .xexpect_expr(&self, "expected identifier, or destructure".to_string()) + .xconvert_to_decl()?; let sig = self.opt_signature()?; let _ = self .lexer @@ -266,7 +263,7 @@ impl<'s> Parser<'s> { let sig = self .opt_signature() .xexpect_expr(&self, "expected signature".to_string())?; - return result_expr!(Declarator, id.xconvert_to_sym_decl().unwrap(), sig) + return result_expr!(Declarator, id.xconvert_to_decl().unwrap(), sig) .xconvert_to_result_opt(); } pub fn args(&mut self) -> Result>>> { @@ -421,8 +418,9 @@ impl<'s> Parser<'s> { let mutability = self.lexer.collect_of_if(&[Token::Let, Token::Const]); if let Some(muta) = mutability { let identifier = self - .ident() - .xexpect_expr(&self, "expected an identifier".to_string())?; + .destructure() + .xexpect_expr(&self, "expected identifier, or destructure".to_string()) + .xconvert_to_decl()?; let sig = self.opt_signature()?; let _ = self .lexer @@ -449,8 +447,11 @@ impl<'s> Parser<'s> { .lexer .collect_if(Token::CParen) .xexpect_token(&self, "expected ')'".to_string())?; + if let Some(_fn) = self.anon_fn()? { + return bubble_expr!(If, x, _fn); + } let blk = self.block()?; - return bubble_expr!(For, x, blk); + return bubble_expr!(If, x, blk); } pub fn _while(&mut self) -> ResultOptExpr { let f = self.lexer.collect_if(Token::While); @@ -492,6 +493,27 @@ impl<'s> Parser<'s> { let blk = self.block()?; return bubble_expr!(For, x, blk); } + pub fn destructure(&mut self) -> ResultOptExpr { + let brace = self.lexer.collect_if(Token::OBrace); + if brace.is_some() { + let mut idents: Vec> = vec![]; + loop { + match self.ident() { + Some(x) => { + idents.push(Box::new(Expr::SymbolDecl(x.into_symbol()))); + let _ = self.lexer.collect_if(Token::Comma); + } + None => break, + } + } + let _ = self + .lexer + .collect_if(Token::CBrace) + .xexpect_token(&self, "expected '}' or more identifiers".to_string())?; + return bubble_expr!(Destructure, idents); + } + return Ok(self.ident()); + } pub fn block(&mut self) -> ResultExpr { self.lexer .collect_if(Token::OBrace) @@ -996,12 +1018,12 @@ trait ConvertToResult { fn xconvert_to_result(self, parser: &Parser, title: String) -> ResultExpr; } -trait ConvertToSymbolDecl { - fn xconvert_to_sym_decl(self) -> ResultExpr; +trait ConvertToDecl { + fn xconvert_to_decl(self) -> ResultExpr; } -trait ConvertToSymbolDeclResult { - fn xconvert_to_sym_decl(self) -> ResultExpr; +trait ConvertToDeclResult { + fn xconvert_to_decl(self) -> ResultExpr; } trait ConvertToResultOpt { @@ -1077,22 +1099,25 @@ impl ExpectExpr for OptExpr { } } -impl ConvertToSymbolDeclResult for ResultExpr { - fn xconvert_to_sym_decl(self) -> ResultExpr { +impl ConvertToDeclResult for ResultExpr { + fn xconvert_to_decl(self) -> ResultExpr { match self { Err(x) => Err(x), Ok(val) => match *val { Expr::Symbol(x) => { return Ok(Box::new(Expr::SymbolDecl(x))); } + Expr::Destructure(_) => { + return Ok(val); + } _ => panic!("type lang issue, expected into symbol"), }, } } } -impl ConvertToSymbolDecl for OptExpr { - fn xconvert_to_sym_decl(self) -> ResultExpr { +impl ConvertToDecl for OptExpr { + fn xconvert_to_decl(self) -> ResultExpr { match self { None => panic!("type lang issue, expected a symbol"), Some(val) => match *val { @@ -1310,14 +1335,11 @@ mod tests { token: Token::Let, span: 2..5, }, - expr!( - Symbol, - Lexeme { - slice: String::from("x"), - token: Token::Symbol, - span: 6..7 - } - ), + Box::new(Expr::SymbolDecl(Symbol::new(Lexeme { + slice: String::from("x"), + token: Token::Symbol, + span: 6..7 + }))), None, expr!( Number, diff --git a/playground.ty b/playground.ty index 2dd31db..d334793 100644 --- a/playground.ty +++ b/playground.ty @@ -1,42 +1,158 @@ -type Motorcycle = struct { +pub type Motorcycle = struct { name: [char], wheels: u8 } -type Car = struct { +// Data has a type. can be manually declared with : +const x = 7; +const y: usize = 7; + +// Types have types +// this is usually redundant +type Car: struct {[char],u8} = struct { name: [char], wheels: u8 } -// or make a props type -type Movable: struct {wheels: u8} = trait { - // linter will fail self without signature right now - fn set_wheels(self, wheels: u8) { - self.wheels = wheels +// you can achieve polymorphism and generics with traits +type Movable = trait { + set_wheels: fn(self, wheels: u8) void, + drive: fn(self) void +} + +// you can then implement those traits +impl Car = trait(Movable) { + pub set_wheels = fn(self, wheels: u8) { + // do a thing! + } +} + +// default implementations can be achieved +pub const set_wheels_default = fn(self: Movable, wheels: u8) void { + self.wheels = wheels; +} + +// default implementation +impl Motorcycle = trait(Movable) { + pub set_wheels = set_wheels_default +} + +// macro/comptime + +type Newable = trait { + new: fn(var: any[]) self {} +} + +const add_new = macro(def: ident, var: any[]) { + impl def = trait(Newable) { + pub new = fn(expander(var)) { + return def { + dec_expand(var) + } + } } - fn drive(self) { +} + +const expander = macro(name: ident, sig: signature, var: any[]) { + name: sig, expander(var) +} + +const dec_expand = macro(name: ident, var: any[]) { + name: name, dec_expand(var) +} + +add_new(Motorcycle, name, [char], wheels, u8) +// this expands to. +impl Motorcycle = trait(Newable) { + pub new = fn(name: [char], wheels: u8) { + return Motorcycle { + name: name, + wheels: wheels, + } } } +// these macro expressions are provided in the standard library +// to implement, simply declare your structs type -impl Movable: Car = self -impl Movable: Motorcycle = self +type MyThing = struct { + cool_string: [char] +} +// then call the compile time expansion macro +add_new(MyThing, cool_string, [char]) -let toyoba = Car { - name = "Toyoba" +// comptime factorial +const factorial_table = comptime(name: ident, calculate: usize) { + const name = for (x in 0..calculate) { + return + } } -toyoba.set_wheels(4) -toyoba.drive() -let bonda = Motorcycle { - name = "Bonda" +const count_things = fn(to_check: &[char]) f64 { + let count: f64 = 0 + for (to_check) fn(x) void { + if (x == ' ' || x == '7') { + count += 1 + } else { + print('hello') + } + // possible match syntax + // forces exhaustion rather than if else chains missing a case + match (x) { + ' ' || '7' => { count +=1 }, + _ => { print('hello') }, + } + match (x, y, z) { + 'x', 'y', 'z' => { count +=1 }, + _, 'y', 'z' => { print('hello') }, + } + } + return count } -bonda.set_wheels(2) -bonda.drive() +type Kenobi = enum(u8) | Hello | There | General + +// support line/block/fn +const thing = match (Kenobi.Hello) { + Kenobi.Hello => fn(m) usize { + return m as usize + }, + Kenobi.There => { return 1 }, + _ => return 2 + +// truthy values allow capturing. +// the truthy table is quite simple. if it has a value that is not empty or undefined. it is truthy. -const assert_wheels = fn(self: Movable) !void { - if (self.wheels == 0) { - return error {} +const counter = fn() f64 { + let count: f64 = 0 + let to_check = [] + // to_check is an empty array. falsey + for (to_check) fn(x) void { + // unreachable! + } + if (to_check) fn(x) void { + // unreachable! + // because there is nothing in to_check, we can't capture the value. this makes truthy intuitive in type-lang + + } + let my_str = "" + if (my_str) { + // unreachable! + // even when not capturing it follows the truthy rules, this block will never ran + } + let undef_example = undefined + if (undef_example) fn(x) void { + // unreachable! + // undefined is fine as a value, but we can never capture an undefined. undefined is never readable. + } + let actual = "captureable!" + for (actual) fn(x) void { + if (x.is_ascii()) { + + } } - return } + +const x: error!?usize = 7 +const y: ?usize = try x +const z: usize = y? + diff --git a/test/test.ty b/test/test.ty index 1be17e7..66a6104 100644 --- a/test/test.ty +++ b/test/test.ty @@ -33,7 +33,7 @@ const next_weekday = fn(d: Day) Day { const c = [0,1,2,3].map(fn(x: usize) usize { return x + 2 }) // errors are first class citizens where control flow is designed to work well with errors. -type InvalidWeekday = error {} +type InvalidWeekday = error const assert_weekday = fn(d: Day) InvalidWeekday!Day { return match (d) { @@ -139,3 +139,6 @@ let t = { const mul2: fn(usize) usize = fn(a) usize { return a * 2 } + +// destructuring is supported +const { a, b, c } = import "std.thing" diff --git a/todos.txt b/todos.txt index e7d14fe..7916e87 100644 --- a/todos.txt +++ b/todos.txt @@ -3,6 +3,5 @@ - Bar '|' ebnf doesn't match parser, update parser for every '|' syntax - "all function declarations must have types in arguments." this can be changed to support deducing types at some point - impl -- destructuring -- rework import -- make types typey instead of values "function declarations" tags, enums, etc. +- trait +- modify match syntax to take place of else branches diff --git a/types/src/lib.rs b/types/src/lib.rs index 7b7bb6d..ef8810e 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -64,6 +64,14 @@ pub struct ForOp { pub body_curried: Ty, } +#[derive(Debug)] +pub struct IfOp { + pub in_expr: Rc>, + pub in_curried: Ty, + pub body: Rc>, + pub body_curried: Ty, +} + #[derive(Debug)] pub struct BinaryOp { pub left: Rc>, @@ -164,6 +172,7 @@ pub enum TypeTree { SigInfo(SigInfo), // flow For(ForOp), + If(IfOp), Invoke(Invoke), Match(MatchOp), Arm(BinaryOp), @@ -240,6 +249,7 @@ impl TypeTree { TypeTree::SigInfo(x) => x.right.clone(), TypeTree::ErrorInfo(x) => x.curried.clone(), TypeTree::For(x) => x.body_curried.clone(), + TypeTree::If(x) => x.body_curried.clone(), TypeTree::Invoke(x) => x.curried.clone(), TypeTree::Match(x) => x.curried_arms.clone(), TypeTree::Arm(x) => x.curried.clone(), @@ -339,6 +349,7 @@ impl TypeTree { TypeTree::SigInfo(_) => "type signature", TypeTree::ErrorInfo(_) => "error declaration", TypeTree::For(_) => "for loop", + TypeTree::If(_) => "if statement", TypeTree::Invoke(_) => "function invocation", TypeTree::Match(_) => "match", TypeTree::Arm(_) => "pattern match arm",