44 files changed, 1941 insertions(+), 1245 deletions(-)
Cargo.lock+13-9| ... | @@ -70,9 +70,9 @@ name = "ast" | ... | @@ -70,9 +70,9 @@ name = "ast" |
| 70 | version = "0.1.0" | 70 | version = "0.1.0" |
| 71 | dependencies = [ | 71 | dependencies = [ |
| 72 | "miette", | 72 | "miette", |
| 73 | "num", | ||
| 73 | "thiserror", | 74 | "thiserror", |
| 74 | "tokenizer", | 75 | "tokenizer", |
| 75 | "util", | ||
| 76 | ] | 76 | ] |
| 77 | 77 | ||
| 78 | [[package]] | 78 | [[package]] |
| ... | @@ -134,7 +134,6 @@ dependencies = [ | ... | @@ -134,7 +134,6 @@ dependencies = [ |
| 134 | "miette", | 134 | "miette", |
| 135 | "num", | 135 | "num", |
| 136 | "thiserror", | 136 | "thiserror", |
| 137 | "util", | ||
| 138 | ] | 137 | ] |
| 139 | 138 | ||
| 140 | [[package]] | 139 | [[package]] |
| ... | @@ -187,6 +186,7 @@ dependencies = [ | ... | @@ -187,6 +186,7 @@ dependencies = [ |
| 187 | "js_ast", | 186 | "js_ast", |
| 188 | "js_printer", | 187 | "js_printer", |
| 189 | "miette", | 188 | "miette", |
| 189 | "parser", | ||
| 190 | "thiserror", | 190 | "thiserror", |
| 191 | "tokenizer", | 191 | "tokenizer", |
| 192 | ] | 192 | ] |
| ... | @@ -250,7 +250,6 @@ dependencies = [ | ... | @@ -250,7 +250,6 @@ dependencies = [ |
| 250 | "miette", | 250 | "miette", |
| 251 | "num", | 251 | "num", |
| 252 | "thiserror", | 252 | "thiserror", |
| 253 | "util", | ||
| 254 | ] | 253 | ] |
| 255 | 254 | ||
| 256 | [[package]] | 255 | [[package]] |
| ... | @@ -261,7 +260,6 @@ dependencies = [ | ... | @@ -261,7 +260,6 @@ dependencies = [ |
| 261 | "miette", | 260 | "miette", |
| 262 | "num", | 261 | "num", |
| 263 | "thiserror", | 262 | "thiserror", |
| 264 | "util", | ||
| 265 | ] | 263 | ] |
| 266 | 264 | ||
| 267 | [[package]] | 265 | [[package]] |
| ... | @@ -420,6 +418,17 @@ version = "3.5.0" | ... | @@ -420,6 +418,17 @@ version = "3.5.0" |
| 420 | source = "registry+https://github.com/rust-lang/crates.io-index" | 418 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 421 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" | 419 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" |
| 422 | 420 | ||
| 421 | [[package]] | ||
| 422 | name = "parser" | ||
| 423 | version = "0.1.0" | ||
| 424 | dependencies = [ | ||
| 425 | "ast", | ||
| 426 | "miette", | ||
| 427 | "num", | ||
| 428 | "thiserror", | ||
| 429 | "tokenizer", | ||
| 430 | ] | ||
| 431 | |||
| 423 | [[package]] | 432 | [[package]] |
| 424 | name = "proc-macro2" | 433 | name = "proc-macro2" |
| 425 | version = "1.0.76" | 434 | version = "1.0.76" |
| ... | @@ -555,7 +564,6 @@ version = "0.1.0" | ... | @@ -555,7 +564,6 @@ version = "0.1.0" |
| 555 | dependencies = [ | 564 | dependencies = [ |
| 556 | "miette", | 565 | "miette", |
| 557 | "thiserror", | 566 | "thiserror", |
| 558 | "util", | ||
| 559 | ] | 567 | ] |
| 560 | 568 | ||
| 561 | [[package]] | 569 | [[package]] |
| ... | @@ -582,10 +590,6 @@ version = "0.2.1" | ... | @@ -582,10 +590,6 @@ version = "0.2.1" |
| 582 | source = "registry+https://github.com/rust-lang/crates.io-index" | 590 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 583 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | 591 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" |
| 584 | 592 | ||
| 585 | [[package]] | ||
| 586 | name = "util" | ||
| 587 | version = "0.1.0" | ||
| 588 | |||
| 589 | [[package]] | 593 | [[package]] |
| 590 | name = "winapi" | 594 | name = "winapi" |
| 591 | version = "0.3.9" | 595 | version = "0.3.9" |
Cargo.toml+10-2| ... | @@ -3,12 +3,20 @@ version = "0.1.0" | ... | @@ -3,12 +3,20 @@ version = "0.1.0" |
| 3 | name = "blight" | 3 | name = "blight" |
| 4 | edition = "2023" | 4 | edition = "2023" |
| 5 | license = "MIT" | 5 | license = "MIT" |
| 6 | authors = ["Lino Le Van"] | 6 | authors = ["Lino Le Van", "Dave Caruso"] |
| 7 | repository = "https://github.com/lino-levan/blight" | 7 | repository = "https://github.com/lino-levan/blight" |
| 8 | 8 | ||
| 9 | [workspace] | 9 | [workspace] |
| 10 | resolver = "2" | 10 | resolver = "2" |
| 11 | members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crates/js_ast", "crates/js_printer"] | 11 | members = [ |
| 12 | "crates/ast", | ||
| 13 | "crates/checker", | ||
| 14 | "crates/cli", | ||
| 15 | "crates/js_ast", | ||
| 16 | "crates/js_printer", | ||
| 17 | "crates/parser", | ||
| 18 | "crates/tokenizer", | ||
| 19 | ] | ||
| 12 | 20 | ||
| 13 | [workspace.dependencies] | 21 | [workspace.dependencies] |
| 14 | clap = { version = "4.4.12", features = ["derive"] } | 22 | clap = { version = "4.4.12", features = ["derive"] } |
crates/ast/Cargo.toml+2-1| ... | @@ -4,10 +4,11 @@ version = "0.1.0" | ... | @@ -4,10 +4,11 @@ version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
| 7 | path = "./lib.rs" | ||
| 7 | 8 | ||
| 8 | [dependencies] | 9 | [dependencies] |
| 9 | miette.workspace = true | 10 | miette.workspace = true |
| 10 | thiserror.workspace = true | 11 | thiserror.workspace = true |
| 12 | num.workspace = true | ||
| 11 | 13 | ||
| 12 | tokenizer = { path = "../tokenizer" } | 14 | tokenizer = { path = "../tokenizer" } |
| 13 | util = { path = "../util" } |
crates/ast/ast.rs created+174| ... | @@ -0,0 +1,174 @@ | ||
| 1 | use tokenizer::Loc; | ||
| 2 | |||
| 3 | use crate::{ | ||
| 4 | types::{Literal, Type}, | ||
| 5 | BinaryOperator, UnaryOperator, | ||
| 6 | }; | ||
| 7 | |||
| 8 | #[derive(Debug, Clone)] | ||
| 9 | pub enum BuiltinId { | ||
| 10 | Raw, | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Debug, Clone)] | ||
| 14 | pub struct Module { | ||
| 15 | pub statements: Vec<TopLevelStatement>, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[derive(Debug, Clone)] | ||
| 19 | pub struct Block { | ||
| 20 | pub statements: Vec<Statement>, | ||
| 21 | pub has_implicit_return: bool, | ||
| 22 | } | ||
| 23 | |||
| 24 | #[derive(Debug, Clone)] | ||
| 25 | pub struct Param { | ||
| 26 | pub name: String, | ||
| 27 | pub param_type: Type, | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Debug, Clone)] | ||
| 31 | |||
| 32 | pub struct Decl { | ||
| 33 | pub name: String, | ||
| 34 | pub decl_type: Type, | ||
| 35 | pub init: Expr, | ||
| 36 | pub is_constant: bool, | ||
| 37 | pub is_pub: bool, | ||
| 38 | pub loc: Loc, | ||
| 39 | pub comment: Option<DocComment>, | ||
| 40 | } | ||
| 41 | |||
| 42 | #[derive(Debug, Clone)] | ||
| 43 | pub struct Expr { | ||
| 44 | pub data: ExprData, | ||
| 45 | pub loc: Loc, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl Expr { | ||
| 49 | pub fn new(data: ExprData, loc: Loc) -> Self { | ||
| 50 | Self { data, loc } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | #[derive(Debug, Clone)] | ||
| 55 | pub struct DocComment { | ||
| 56 | pub data: String, | ||
| 57 | pub loc: Loc, | ||
| 58 | } | ||
| 59 | |||
| 60 | #[derive(Debug, Clone)] | ||
| 61 | pub struct BlockWithLoc { | ||
| 62 | pub block: Block, | ||
| 63 | pub loc: Loc, | ||
| 64 | } | ||
| 65 | |||
| 66 | #[derive(Debug, Clone)] | ||
| 67 | pub struct If { | ||
| 68 | pub condition: Box<Expr>, | ||
| 69 | pub yes: BlockWithLoc, | ||
| 70 | pub no: Option<BlockWithLoc>, | ||
| 71 | } | ||
| 72 | |||
| 73 | #[derive(Debug, Clone)] | ||
| 74 | pub enum ExprData { | ||
| 75 | Block(Block), | ||
| 76 | Binary { | ||
| 77 | op: BinaryOperator, | ||
| 78 | left: Box<Expr>, | ||
| 79 | right: Box<Expr>, | ||
| 80 | }, | ||
| 81 | Unary { | ||
| 82 | op: UnaryOperator, | ||
| 83 | value: Box<Expr>, | ||
| 84 | }, | ||
| 85 | Fn { | ||
| 86 | name: String, | ||
| 87 | params: Vec<Param>, | ||
| 88 | return_type: Type, | ||
| 89 | block: Block, | ||
| 90 | }, | ||
| 91 | Call { | ||
| 92 | base: Box<Expr>, | ||
| 93 | arguments: Vec<Expr>, | ||
| 94 | }, | ||
| 95 | Field { | ||
| 96 | base: Box<Expr>, | ||
| 97 | field: Box<Expr>, | ||
| 98 | }, | ||
| 99 | Builtin { | ||
| 100 | id: BuiltinId, | ||
| 101 | arguments: Vec<Expr>, | ||
| 102 | }, | ||
| 103 | If(If), | ||
| 104 | Return(Box<Expr>), | ||
| 105 | Literal(Literal), | ||
| 106 | Identifier(String), | ||
| 107 | } | ||
| 108 | |||
| 109 | #[derive(Debug, Clone)] | ||
| 110 | pub enum Statement { | ||
| 111 | Expr(Expr), | ||
| 112 | Decl(Decl), | ||
| 113 | } | ||
| 114 | |||
| 115 | #[derive(Debug, Clone)] | ||
| 116 | pub enum TopLevelStatement { | ||
| 117 | Expr(Expr), | ||
| 118 | Decl(Decl), | ||
| 119 | } | ||
| 120 | |||
| 121 | impl BuiltinId { | ||
| 122 | pub fn display_name(&self) -> &'static str { | ||
| 123 | match self { | ||
| 124 | Self::Raw => "@raw", | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | pub fn from_str(name: &str) -> Option<Self> { | ||
| 129 | match name { | ||
| 130 | "@raw" => Some(Self::Raw), | ||
| 131 | _ => None, | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | impl Statement { | ||
| 137 | pub fn requires_semicolon(&self) -> bool { | ||
| 138 | match &self { | ||
| 139 | Statement::Expr(expr) => match expr.data { | ||
| 140 | ExprData::Block(_) => false, | ||
| 141 | ExprData::If(_) => false, | ||
| 142 | _ => true, | ||
| 143 | }, | ||
| 144 | _ => false, | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | pub fn to_top_level(self) -> TopLevelStatement { | ||
| 149 | match self { | ||
| 150 | Statement::Expr(expr) => TopLevelStatement::Expr(expr), | ||
| 151 | Statement::Decl(decl) => TopLevelStatement::Decl(decl), | ||
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | impl TopLevelStatement { | ||
| 157 | pub fn requires_semicolon(&self) -> bool { | ||
| 158 | match &self { | ||
| 159 | TopLevelStatement::Expr(expr) => match expr.data { | ||
| 160 | ExprData::Block(_) => false, | ||
| 161 | ExprData::If(_) => false, | ||
| 162 | _ => true, | ||
| 163 | }, | ||
| 164 | _ => false, | ||
| 165 | } | ||
| 166 | } | ||
| 167 | |||
| 168 | pub fn to_statement(self) -> Statement { | ||
| 169 | match self { | ||
| 170 | TopLevelStatement::Expr(expr) => Statement::Expr(expr), | ||
| 171 | TopLevelStatement::Decl(decl) => Statement::Decl(decl), | ||
| 172 | } | ||
| 173 | } | ||
| 174 | } | ||
crates/ast/lib.rs created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | mod ast; | ||
| 2 | mod operators; | ||
| 3 | mod types; | ||
| 4 | |||
| 5 | pub use ast::*; | ||
| 6 | pub use operators::*; | ||
| 7 | pub use types::*; | ||
crates/ast/operators.rs created+171| ... | @@ -0,0 +1,171 @@ | ||
| 1 | use tokenizer::Token; | ||
| 2 | |||
| 3 | use crate::Literal; | ||
| 4 | |||
| 5 | #[derive(Debug, Clone, PartialEq)] | ||
| 6 | pub enum BinaryOperator { | ||
| 7 | Add, | ||
| 8 | Sub, | ||
| 9 | Mul, | ||
| 10 | Div, | ||
| 11 | Pow, | ||
| 12 | Eq, | ||
| 13 | Gt, | ||
| 14 | Lt, | ||
| 15 | Gte, | ||
| 16 | Lte, | ||
| 17 | Lshift, | ||
| 18 | Rshift, | ||
| 19 | RshiftUnsigned, | ||
| 20 | BitAnd, | ||
| 21 | BitOr, | ||
| 22 | BitXor, | ||
| 23 | Mod, | ||
| 24 | NotEq, | ||
| 25 | And, | ||
| 26 | Or, | ||
| 27 | } | ||
| 28 | |||
| 29 | #[derive(Debug, Clone, PartialEq)] | ||
| 30 | pub enum UnaryOperator { | ||
| 31 | Neg, | ||
| 32 | Not, | ||
| 33 | BitNot, | ||
| 34 | } | ||
| 35 | |||
| 36 | impl BinaryOperator { | ||
| 37 | pub fn to_string(&self) -> String { | ||
| 38 | String::from(match self { | ||
| 39 | Self::Add => "+", | ||
| 40 | Self::Sub => "-", | ||
| 41 | Self::Div => "/", | ||
| 42 | Self::Mul => "*", | ||
| 43 | Self::Pow => "**", | ||
| 44 | Self::Eq => "==", | ||
| 45 | Self::Gt => ">", | ||
| 46 | Self::Lt => "<", | ||
| 47 | Self::Gte => ">=", | ||
| 48 | Self::Lte => "<=", | ||
| 49 | Self::Lshift => "<<", | ||
| 50 | Self::Rshift => ">>", | ||
| 51 | Self::RshiftUnsigned => ">>>", | ||
| 52 | Self::BitAnd => "&", | ||
| 53 | Self::BitOr => "|", | ||
| 54 | Self::BitXor => "^", | ||
| 55 | Self::Mod => "%", | ||
| 56 | Self::NotEq => "!=", | ||
| 57 | Self::And => "&&", | ||
| 58 | Self::Or => "||", | ||
| 59 | }) | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn from_token(token: &Token) -> Option<BinaryOperator> { | ||
| 63 | match token { | ||
| 64 | Token::And => Some(Self::BitAnd), | ||
| 65 | Token::AndAnd => Some(Self::And), | ||
| 66 | Token::BangEqual => Some(Self::NotEq), | ||
| 67 | Token::Caret => Some(Self::BitXor), | ||
| 68 | Token::EqualEqual => Some(Self::Eq), | ||
| 69 | Token::Gt => Some(Self::Gt), | ||
| 70 | Token::GtEqual => Some(Self::Gte), | ||
| 71 | Token::GtGt => Some(Self::Rshift), | ||
| 72 | Token::GtGtGt => Some(Self::RshiftUnsigned), | ||
| 73 | Token::Lt => Some(Self::Lt), | ||
| 74 | Token::LtEqual => Some(Self::Lte), | ||
| 75 | Token::LtLt => Some(Self::Lshift), | ||
| 76 | Token::Minus => Some(Self::Sub), | ||
| 77 | Token::Or => Some(Self::BitOr), | ||
| 78 | Token::OrOr => Some(Self::Or), | ||
| 79 | Token::Percent => Some(Self::Mod), | ||
| 80 | Token::Plus => Some(Self::Add), | ||
| 81 | Token::Slash => Some(Self::Div), | ||
| 82 | Token::Star => Some(Self::Mul), | ||
| 83 | Token::StarStar => Some(Self::Pow), | ||
| 84 | _ => None, | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | /// Error case of this means 'types do not allow this operation' | ||
| 89 | pub fn perform(&self, left: Literal, right: Literal) -> Result<Literal, ()> { | ||
| 90 | match self { | ||
| 91 | BinaryOperator::Eq => Ok(Literal::Bool(left == right)), | ||
| 92 | BinaryOperator::NotEq => Ok(Literal::Bool(left != right)), | ||
| 93 | |||
| 94 | BinaryOperator::Or | BinaryOperator::And => match (left, right) { | ||
| 95 | (Literal::Bool(a), Literal::Bool(b)) => Ok(Literal::Bool(match self { | ||
| 96 | BinaryOperator::Or => a || b, | ||
| 97 | BinaryOperator::And => a && b, | ||
| 98 | _ => unreachable!(), | ||
| 99 | })), | ||
| 100 | _ => Err(()), | ||
| 101 | }, | ||
| 102 | |||
| 103 | BinaryOperator::Add | ||
| 104 | | BinaryOperator::Sub | ||
| 105 | | BinaryOperator::Mul | ||
| 106 | | BinaryOperator::Div | ||
| 107 | | BinaryOperator::Pow | ||
| 108 | | BinaryOperator::Gt | ||
| 109 | | BinaryOperator::Lt | ||
| 110 | | BinaryOperator::Gte | ||
| 111 | | BinaryOperator::Lte | ||
| 112 | | BinaryOperator::Lshift | ||
| 113 | | BinaryOperator::Rshift | ||
| 114 | | BinaryOperator::RshiftUnsigned | ||
| 115 | | BinaryOperator::BitAnd | ||
| 116 | | BinaryOperator::BitOr | ||
| 117 | | BinaryOperator::BitXor | ||
| 118 | | BinaryOperator::Mod => match (left, right) { | ||
| 119 | (Literal::Number(a), Literal::Number(b)) => Ok(match self { | ||
| 120 | BinaryOperator::Add => Literal::Number(a + b), | ||
| 121 | BinaryOperator::Sub => Literal::Number(a - b), | ||
| 122 | BinaryOperator::Mul => Literal::Number(a * b), | ||
| 123 | BinaryOperator::Div => Literal::Number(a / b), | ||
| 124 | BinaryOperator::Pow => Literal::Number(a.powf(b)), | ||
| 125 | BinaryOperator::Gt => Literal::Bool(a > b), | ||
| 126 | BinaryOperator::Lt => Literal::Bool(a < b), | ||
| 127 | BinaryOperator::Gte => Literal::Bool(a >= b), | ||
| 128 | BinaryOperator::Lte => Literal::Bool(a <= b), | ||
| 129 | BinaryOperator::Lshift => Literal::Number(((a as i32) << (b as i32)).into()), | ||
| 130 | BinaryOperator::Rshift => Literal::Number(((a as i32) >> (b as i32)).into()), | ||
| 131 | BinaryOperator::RshiftUnsigned => { | ||
| 132 | Literal::Number(((a as u32) >> (b as u32)).into()) | ||
| 133 | } | ||
| 134 | BinaryOperator::BitAnd => Literal::Number(((a as i32) & (b as i32)).into()), | ||
| 135 | BinaryOperator::BitOr => Literal::Number(((a as i32) | (b as i32)).into()), | ||
| 136 | BinaryOperator::BitXor => Literal::Number(((a as i32) ^ (b as i32)).into()), | ||
| 137 | BinaryOperator::Mod => Literal::Number(a % b), | ||
| 138 | _ => unreachable!(), | ||
| 139 | }), | ||
| 140 | _ => Err(()), | ||
| 141 | }, | ||
| 142 | } | ||
| 143 | } | ||
| 144 | } | ||
| 145 | |||
| 146 | impl UnaryOperator { | ||
| 147 | pub fn to_string(self) -> String { | ||
| 148 | String::from(match self { | ||
| 149 | Self::Neg => "-", | ||
| 150 | Self::Not => "!", | ||
| 151 | Self::BitNot => "~", | ||
| 152 | }) | ||
| 153 | } | ||
| 154 | |||
| 155 | pub fn perform(self, value: Literal) -> Result<Literal, ()> { | ||
| 156 | match self { | ||
| 157 | UnaryOperator::Neg => match value { | ||
| 158 | Literal::Number(a) => Ok(Literal::Number(-a)), | ||
| 159 | _ => Err(()), | ||
| 160 | }, | ||
| 161 | UnaryOperator::Not => match value { | ||
| 162 | Literal::Bool(a) => Ok(Literal::Bool(!a)), | ||
| 163 | _ => Err(()), | ||
| 164 | }, | ||
| 165 | UnaryOperator::BitNot => match value { | ||
| 166 | Literal::Number(a) => Ok(Literal::Number((!(a as i32)).into())), | ||
| 167 | _ => Err(()), | ||
| 168 | }, | ||
| 169 | } | ||
| 170 | } | ||
| 171 | } | ||
crates/ast/src/error.rs deleted-21| ... | @@ -1,21 +0,0 @@ | ||
| 1 | use miette::Diagnostic; | ||
| 2 | use thiserror::Error; | ||
| 3 | |||
| 4 | #[derive(Error, Diagnostic, Debug)] | ||
| 5 | #[error("AstError")] | ||
| 6 | pub struct AstError { | ||
| 7 | #[help] | ||
| 8 | error: String, | ||
| 9 | } | ||
| 10 | |||
| 11 | impl AstError { | ||
| 12 | pub fn new(error: &'static str) -> AstError { | ||
| 13 | AstError { | ||
| 14 | error: String::from(error), | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | pub fn from_string(error: String) -> AstError { | ||
| 19 | AstError { error } | ||
| 20 | } | ||
| 21 | } | ||
crates/ast/src/lib.rs deleted-27| ... | @@ -1,27 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | mod error; | ||
| 4 | mod parse_block; | ||
| 5 | mod parse_expression; | ||
| 6 | mod parse_function_call; | ||
| 7 | mod parse_statement; | ||
| 8 | mod parse_type; | ||
| 9 | mod types; | ||
| 10 | |||
| 11 | use tokenizer::Token; | ||
| 12 | |||
| 13 | use parse_statement::parse_statement; | ||
| 14 | use types::ParserContext; | ||
| 15 | |||
| 16 | pub use types::{Ast, Block, Module, Type}; | ||
| 17 | |||
| 18 | pub fn parse(tokens: Vec<Token>) -> Result<Module> { | ||
| 19 | let mut ctx = ParserContext::new(tokens); | ||
| 20 | let mut ast: Vec<Ast> = Vec::new(); | ||
| 21 | |||
| 22 | while !ctx.empty() { | ||
| 23 | ast.push(parse_statement(&mut ctx)?); | ||
| 24 | } | ||
| 25 | |||
| 26 | Ok(Module { value: ast }) | ||
| 27 | } | ||
crates/ast/src/parse_block.rs deleted-55| ... | @@ -1,55 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use super::error::AstError; | ||
| 6 | use super::parse_expression::parse_expression; | ||
| 7 | use super::parse_statement::parse_statement; | ||
| 8 | use super::types::Block; | ||
| 9 | use super::types::ParserContext; | ||
| 10 | |||
| 11 | pub fn parse_block(tokens: &mut ParserContext) -> Result<Block> { | ||
| 12 | match tokens.next() { | ||
| 13 | Some(Token::OpenBracket) => (), | ||
| 14 | _ => return Err(AstError::new("Expected { when parsing block").into()), | ||
| 15 | } | ||
| 16 | |||
| 17 | let mut value = Vec::new(); | ||
| 18 | let mut implicit_return = false; | ||
| 19 | |||
| 20 | loop { | ||
| 21 | match tokens.peek() { | ||
| 22 | Some(Token::ClosedBracket) => { | ||
| 23 | tokens.next(); | ||
| 24 | break; | ||
| 25 | } | ||
| 26 | _ => (), | ||
| 27 | } | ||
| 28 | |||
| 29 | let current_cursor = tokens.cursor; | ||
| 30 | let result = parse_statement(tokens); | ||
| 31 | |||
| 32 | match result { | ||
| 33 | Ok(ast) => value.push(ast), | ||
| 34 | Err(_) => { | ||
| 35 | tokens.cursor = current_cursor; | ||
| 36 | value.push(parse_expression(tokens)?); | ||
| 37 | implicit_return = true; | ||
| 38 | match tokens.peek() { | ||
| 39 | Some(Token::ClosedBracket) => (), | ||
| 40 | _ => { | ||
| 41 | return Err(AstError::new( | ||
| 42 | "Expected closed bracket after expression in function body", | ||
| 43 | ) | ||
| 44 | .into()); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | } | ||
| 50 | |||
| 51 | Ok(Block { | ||
| 52 | value, | ||
| 53 | implicit_return, | ||
| 54 | }) | ||
| 55 | } | ||
crates/ast/src/parse_expression.rs deleted-105| ... | @@ -1,105 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use super::error::AstError; | ||
| 6 | use super::parse_block::parse_block; | ||
| 7 | use super::parse_function_call::parse_function_call; | ||
| 8 | use super::types::{Ast, Block, ParserContext}; | ||
| 9 | |||
| 10 | pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> { | ||
| 11 | let left = match tokens.next() { | ||
| 12 | Some(token) => match token { | ||
| 13 | Token::Number(number) => Ast::Number(number), | ||
| 14 | Token::String(string) => Ast::String(string.clone()), | ||
| 15 | Token::BuiltIn(name) => { | ||
| 16 | match tokens.peek() { | ||
| 17 | Some(Token::OpenParanthesis) => { | ||
| 18 | // FRICK we consumed the open paranthesis | ||
| 19 | tokens.back(); | ||
| 20 | parse_function_call(tokens)? | ||
| 21 | } | ||
| 22 | _ => Ast::BuiltIn(name.clone()), | ||
| 23 | } | ||
| 24 | } | ||
| 25 | Token::True => Ast::Bool(true), | ||
| 26 | Token::False => Ast::Bool(false), | ||
| 27 | Token::OpenBracket => { | ||
| 28 | // FRICK we consumed the open bracket | ||
| 29 | // we need to put the cursor back | ||
| 30 | tokens.back(); | ||
| 31 | Ast::Block(parse_block(tokens)?) | ||
| 32 | } | ||
| 33 | Token::If => { | ||
| 34 | let condition = parse_expression(tokens)?; | ||
| 35 | |||
| 36 | let value = parse_block(tokens)?; | ||
| 37 | |||
| 38 | let mut else_value: Option<Block> = None; | ||
| 39 | |||
| 40 | match tokens.peek() { | ||
| 41 | Some(Token::Else) => { | ||
| 42 | tokens.next(); | ||
| 43 | match tokens.peek() { | ||
| 44 | Some(Token::If) => { | ||
| 45 | let else_expression = parse_expression(tokens)?; | ||
| 46 | else_value = Some(Block { | ||
| 47 | value: vec![else_expression], | ||
| 48 | implicit_return: true, | ||
| 49 | }); | ||
| 50 | } | ||
| 51 | Some(Token::OpenBracket) => { | ||
| 52 | else_value = Some(parse_block(tokens)?); | ||
| 53 | } | ||
| 54 | _ => { | ||
| 55 | return Err(AstError::new( | ||
| 56 | "Expected if or open bracket after else in if statement", | ||
| 57 | ) | ||
| 58 | .into()) | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | _ => (), | ||
| 63 | } | ||
| 64 | |||
| 65 | return Ok(Ast::If { | ||
| 66 | condition: Box::new(condition), | ||
| 67 | value, | ||
| 68 | else_value, | ||
| 69 | }); | ||
| 70 | } | ||
| 71 | Token::Identifier(name) => { | ||
| 72 | match tokens.peek() { | ||
| 73 | Some(Token::OpenParanthesis) => { | ||
| 74 | // FRICK we consumed the open paranthesis | ||
| 75 | tokens.back(); | ||
| 76 | parse_function_call(tokens)? | ||
| 77 | } | ||
| 78 | _ => Ast::Identifier(name.clone()), | ||
| 79 | } | ||
| 80 | } | ||
| 81 | _ => { | ||
| 82 | println!("token: {:?}", token); | ||
| 83 | return Err(AstError::new("Expected expression, but did not get it").into()); | ||
| 84 | } | ||
| 85 | }, | ||
| 86 | None => return Err(AstError::new("Expected expression, but got nothing instead").into()), | ||
| 87 | }; | ||
| 88 | |||
| 89 | match tokens.peek() { | ||
| 90 | Some(token) => match token { | ||
| 91 | Token::Operator(operator) => { | ||
| 92 | tokens.next(); | ||
| 93 | return Ok(Ast::Operation { | ||
| 94 | operator: operator.clone(), | ||
| 95 | left: Box::new(left), | ||
| 96 | right: Box::new(parse_expression(tokens)?), | ||
| 97 | }); | ||
| 98 | } | ||
| 99 | _ => (), | ||
| 100 | }, | ||
| 101 | _ => (), | ||
| 102 | } | ||
| 103 | |||
| 104 | Ok(left) | ||
| 105 | } | ||
crates/ast/src/parse_function_call.rs deleted-56| ... | @@ -1,56 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use super::error::AstError; | ||
| 6 | use super::parse_expression::parse_expression; | ||
| 7 | use super::types::{Ast, ParserContext}; | ||
| 8 | |||
| 9 | /// Parses a function call AS AN EXPRESSION | ||
| 10 | /// This does not consume the semicolon at the end of the function call | ||
| 11 | /// if it is a statement. | ||
| 12 | pub fn parse_function_call(tokens: &mut ParserContext) -> Result<Ast> { | ||
| 13 | let identifier = match tokens.next() { | ||
| 14 | Some(Token::Identifier(identifier)) => Ast::Identifier(identifier), | ||
| 15 | Some(Token::BuiltIn(identifier)) => Ast::BuiltIn(identifier), | ||
| 16 | _ => return Err(AstError::new("Expected identifier").into()), | ||
| 17 | }; | ||
| 18 | |||
| 19 | match tokens.peek() { | ||
| 20 | Some(Token::OpenParanthesis) => { | ||
| 21 | tokens.next(); | ||
| 22 | |||
| 23 | let mut arguments = Vec::new(); | ||
| 24 | |||
| 25 | loop { | ||
| 26 | match tokens.peek() { | ||
| 27 | Some(Token::ClosedParanthesis) => { | ||
| 28 | tokens.next(); | ||
| 29 | break; | ||
| 30 | } | ||
| 31 | _ => (), | ||
| 32 | } | ||
| 33 | arguments.push(parse_expression(tokens)?); | ||
| 34 | |||
| 35 | match tokens.peek() { | ||
| 36 | Some(Token::Comma) => { | ||
| 37 | tokens.next(); | ||
| 38 | } | ||
| 39 | Some(Token::ClosedParanthesis) => (), | ||
| 40 | _ => { | ||
| 41 | return Err(AstError::new( | ||
| 42 | "Expected ) or , but found something else instead.", | ||
| 43 | ) | ||
| 44 | .into()) | ||
| 45 | } | ||
| 46 | } | ||
| 47 | } | ||
| 48 | |||
| 49 | Ok(Ast::FunctionCall { | ||
| 50 | name: Box::new(identifier), | ||
| 51 | arguments, | ||
| 52 | }) | ||
| 53 | } | ||
| 54 | _ => Err(AstError::new("Unexpected identifier").into()), | ||
| 55 | } | ||
| 56 | } | ||
crates/ast/src/parse_statement.rs deleted-245| ... | @@ -1,245 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::Type; | ||
| 6 | |||
| 7 | use super::error::AstError; | ||
| 8 | use super::parse_block::parse_block; | ||
| 9 | use super::parse_expression::parse_expression; | ||
| 10 | use super::parse_function_call::parse_function_call; | ||
| 11 | use super::parse_type::parse_type; | ||
| 12 | use super::types::{Argument, Ast, Block, ParserContext}; | ||
| 13 | |||
| 14 | /// Precondition: There is at least one token in the iterator | ||
| 15 | pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ||
| 16 | let token = tokens.next().unwrap(); | ||
| 17 | match token { | ||
| 18 | Token::BuiltIn(name) => { | ||
| 19 | match tokens.peek() { | ||
| 20 | Some(Token::OpenParanthesis) => { | ||
| 21 | // Okay, so we messed up. We shouldn't have consumed that token. | ||
| 22 | tokens.back(); | ||
| 23 | parse_function_call(tokens) | ||
| 24 | } | ||
| 25 | _ => Ok(Ast::BuiltIn(name)), | ||
| 26 | } | ||
| 27 | } | ||
| 28 | Token::OpenBracket => { | ||
| 29 | // FRICK, we have to backtrack | ||
| 30 | tokens.back(); | ||
| 31 | Ok(Ast::Block(parse_block(tokens)?)) | ||
| 32 | } | ||
| 33 | Token::Pub => { | ||
| 34 | match tokens.peek() { | ||
| 35 | Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (), | ||
| 36 | _ => return Err(AstError::new("Expected let, var, or fn after pub").into()), | ||
| 37 | } | ||
| 38 | |||
| 39 | let inner = parse_statement(tokens)?; | ||
| 40 | |||
| 41 | return Ok(Ast::Public(Box::new(inner))); | ||
| 42 | } | ||
| 43 | Token::Var | Token::Let => { | ||
| 44 | let is_constant = match token { | ||
| 45 | Token::Var => false, | ||
| 46 | Token::Let => true, | ||
| 47 | _ => unreachable!(), | ||
| 48 | }; | ||
| 49 | let name = match tokens.next() { | ||
| 50 | Some(Token::Identifier(name)) => name, | ||
| 51 | _ => return Err(AstError::new("Expected identifier after var").into()), | ||
| 52 | }; | ||
| 53 | let return_type = match tokens.peek() { | ||
| 54 | Some(Token::Colon) => { | ||
| 55 | tokens.next(); | ||
| 56 | parse_type(tokens)? | ||
| 57 | } | ||
| 58 | _ => Type::Infer, | ||
| 59 | }; | ||
| 60 | let value = match tokens.next() { | ||
| 61 | Some(Token::Equals) => parse_expression(tokens)?, | ||
| 62 | _ => return Err(AstError::new("Expected equals after identifier").into()), | ||
| 63 | }; | ||
| 64 | match tokens.next() { | ||
| 65 | Some(Token::Semicolon) => (), | ||
| 66 | _ => { | ||
| 67 | return Err( | ||
| 68 | AstError::new("Expected semicolon after constant declaration").into(), | ||
| 69 | ) | ||
| 70 | } | ||
| 71 | }; | ||
| 72 | Ok(Ast::Declaration { | ||
| 73 | name: name.clone(), | ||
| 74 | decl_type: return_type, | ||
| 75 | value: Box::new(value), | ||
| 76 | is_constant, | ||
| 77 | }) | ||
| 78 | } | ||
| 79 | Token::Return => { | ||
| 80 | let expression = parse_expression(tokens)?; | ||
| 81 | |||
| 82 | match tokens.next() { | ||
| 83 | Some(Token::Semicolon) => (), | ||
| 84 | _ => return Err(AstError::new("Expected semicolon after return statement").into()), | ||
| 85 | }; | ||
| 86 | |||
| 87 | Ok(Ast::Return(Box::new(expression))) | ||
| 88 | } | ||
| 89 | Token::Function => { | ||
| 90 | let name = match tokens.next() { | ||
| 91 | Some(Token::Identifier(name)) => name.clone(), | ||
| 92 | _ => return Err(AstError::new("Expected identifier after fn").into()), | ||
| 93 | }; | ||
| 94 | |||
| 95 | match tokens.next() { | ||
| 96 | Some(Token::OpenParanthesis) => (), | ||
| 97 | _ => { | ||
| 98 | return Err(AstError::new( | ||
| 99 | "Expected open paranthesis after identifier in function", | ||
| 100 | ) | ||
| 101 | .into()) | ||
| 102 | } | ||
| 103 | }; | ||
| 104 | |||
| 105 | let mut arguments = Vec::new(); | ||
| 106 | |||
| 107 | loop { | ||
| 108 | match tokens.peek() { | ||
| 109 | Some(Token::ClosedParanthesis) => { | ||
| 110 | tokens.next(); | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | _ => (), | ||
| 114 | } | ||
| 115 | |||
| 116 | let argument_name = match tokens.next() { | ||
| 117 | Some(Token::Identifier(name)) => name, | ||
| 118 | _ => { | ||
| 119 | return Err(AstError::new( | ||
| 120 | "Expected identifier for argument name in function", | ||
| 121 | ) | ||
| 122 | .into()) | ||
| 123 | } | ||
| 124 | }; | ||
| 125 | |||
| 126 | let return_type = match tokens.next() { | ||
| 127 | Some(Token::Colon) => parse_type(tokens)?, | ||
| 128 | _ => return Err(AstError::new("Expected colon after identifier").into()), | ||
| 129 | }; | ||
| 130 | |||
| 131 | match tokens.peek() { | ||
| 132 | Some(Token::Comma) => { | ||
| 133 | tokens.next(); | ||
| 134 | } | ||
| 135 | _ => (), | ||
| 136 | } | ||
| 137 | |||
| 138 | arguments.push(Argument { | ||
| 139 | name: argument_name.clone(), | ||
| 140 | return_type, | ||
| 141 | }) | ||
| 142 | } | ||
| 143 | |||
| 144 | let return_type = match tokens.next() { | ||
| 145 | Some(Token::RightArrow) => parse_type(tokens)?, | ||
| 146 | _ => { | ||
| 147 | return Err(AstError::new( | ||
| 148 | "Expected right arrow after argument list in function", | ||
| 149 | ) | ||
| 150 | .into()) | ||
| 151 | } | ||
| 152 | }; | ||
| 153 | |||
| 154 | match tokens.peek() { | ||
| 155 | Some(Token::OpenBracket) => (), | ||
| 156 | _ => { | ||
| 157 | return Err(AstError::new( | ||
| 158 | "Expected open bracket after type in function declaration", | ||
| 159 | ) | ||
| 160 | .into()) | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | |||
| 164 | let block = parse_block(tokens)?; | ||
| 165 | |||
| 166 | return Ok(Ast::Function { | ||
| 167 | name, | ||
| 168 | arguments, | ||
| 169 | return_type, | ||
| 170 | block, | ||
| 171 | }); | ||
| 172 | } | ||
| 173 | Token::If => { | ||
| 174 | let condition = parse_expression(tokens)?; | ||
| 175 | |||
| 176 | let value = parse_block(tokens)?; | ||
| 177 | |||
| 178 | let mut else_value: Option<Block> = None; | ||
| 179 | |||
| 180 | match tokens.peek() { | ||
| 181 | Some(Token::Else) => { | ||
| 182 | tokens.next(); | ||
| 183 | match tokens.peek() { | ||
| 184 | Some(Token::If) => { | ||
| 185 | let else_if = parse_statement(tokens)?; | ||
| 186 | else_value = Some(Block { | ||
| 187 | value: vec![else_if], | ||
| 188 | implicit_return: false, | ||
| 189 | }); | ||
| 190 | } | ||
| 191 | Some(Token::OpenBracket) => { | ||
| 192 | else_value = Some(parse_block(tokens)?); | ||
| 193 | } | ||
| 194 | _ => { | ||
| 195 | return Err(AstError::new( | ||
| 196 | "Expected if or open bracket after else in if statement", | ||
| 197 | ) | ||
| 198 | .into()) | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | _ => (), | ||
| 203 | } | ||
| 204 | |||
| 205 | return Ok(Ast::If { | ||
| 206 | condition: Box::new(condition), | ||
| 207 | value, | ||
| 208 | else_value, | ||
| 209 | }); | ||
| 210 | } | ||
| 211 | Token::Identifier(_) => match tokens.peek() { | ||
| 212 | Some(Token::OpenParanthesis) => { | ||
| 213 | // We need to give the identifier back to the parser | ||
| 214 | tokens.back(); | ||
| 215 | let result = parse_function_call(tokens); | ||
| 216 | match tokens.next() { | ||
| 217 | Some(Token::Semicolon) => (), | ||
| 218 | _ => { | ||
| 219 | return Err(AstError::new( | ||
| 220 | "Expected semicolon after function call statement", | ||
| 221 | ) | ||
| 222 | .into()) | ||
| 223 | } | ||
| 224 | }; | ||
| 225 | result | ||
| 226 | } | ||
| 227 | _ => Err(AstError::new("Unexpected identifier").into()), | ||
| 228 | }, | ||
| 229 | Token::Comment(comment) => Ok(Ast::Comment(comment.clone())), | ||
| 230 | Token::DocComment(comment) => { | ||
| 231 | let mut concatted_comments = comment.clone(); | ||
| 232 | loop { | ||
| 233 | match tokens.peek() { | ||
| 234 | Some(Token::DocComment(comment)) => { | ||
| 235 | concatted_comments.push_str(format!("\n{}", comment).as_str()); | ||
| 236 | tokens.next(); | ||
| 237 | } | ||
| 238 | _ => break, | ||
| 239 | } | ||
| 240 | } | ||
| 241 | Ok(Ast::DocComment(concatted_comments)) | ||
| 242 | } | ||
| 243 | _ => return Err(AstError::new("Unexpected token").into()), | ||
| 244 | } | ||
| 245 | } | ||
crates/ast/src/parse_type.rs deleted-28| ... | @@ -1,28 +0,0 @@ | ||
| 1 | use miette::Result; | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use super::error::AstError; | ||
| 6 | use super::types::{ParserContext, Type}; | ||
| 7 | |||
| 8 | pub fn parse_type(tokens: &mut ParserContext) -> Result<Type> { | ||
| 9 | match tokens.next() { | ||
| 10 | Some(Token::Identifier(typ)) => match typ.as_str() { | ||
| 11 | "int" => Ok(Type::Int), | ||
| 12 | "float" => Ok(Type::Float), | ||
| 13 | "bigint" => Ok(Type::BigInt), | ||
| 14 | "bool" => Ok(Type::Bool), | ||
| 15 | "string" => Ok(Type::String), | ||
| 16 | _ => { | ||
| 17 | return Err( | ||
| 18 | AstError::from_string(format!( | ||
| 19 | "Invalid type '{}' (TODO: add custom type support)", | ||
| 20 | typ | ||
| 21 | ) | ||
| 22 | ).into()) | ||
| 23 | } | ||
| 24 | }, | ||
| 25 | Some(unexpected) => return Err(AstError::from_string(format!("Expected type, got {}", unexpected)).into()), | ||
| 26 | None => return Err(AstError::from_string("Expected type, got EOF".to_string()).into()), | ||
| 27 | } | ||
| 28 | } | ||
crates/ast/src/types.rs deleted-153| ... | @@ -1,153 +0,0 @@ | ||
| 1 | // TODO(lino-levan): Split up AST into expressions, statements, etc. | ||
| 2 | |||
| 3 | use tokenizer::Token; | ||
| 4 | use util::Operator; | ||
| 5 | |||
| 6 | #[derive(Debug)] | ||
| 7 | pub struct ParserContext { | ||
| 8 | pub tokens: Vec<Token>, | ||
| 9 | pub cursor: usize, | ||
| 10 | } | ||
| 11 | |||
| 12 | impl ParserContext { | ||
| 13 | pub fn new(tokens: Vec<Token>) -> ParserContext { | ||
| 14 | ParserContext { tokens, cursor: 0 } | ||
| 15 | } | ||
| 16 | |||
| 17 | pub fn empty(&self) -> bool { | ||
| 18 | self.cursor == self.tokens.len() | ||
| 19 | } | ||
| 20 | |||
| 21 | pub fn back(&mut self) { | ||
| 22 | self.cursor -= 1; | ||
| 23 | } | ||
| 24 | |||
| 25 | pub fn next(&mut self) -> Option<Token> { | ||
| 26 | if self.cursor == self.tokens.len() { | ||
| 27 | return None; | ||
| 28 | } | ||
| 29 | |||
| 30 | let token = self.tokens[self.cursor].clone(); | ||
| 31 | self.cursor += 1; | ||
| 32 | Some(token) | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn peek(&self) -> Option<Token> { | ||
| 36 | if self.cursor == self.tokens.len() { | ||
| 37 | return None; | ||
| 38 | } | ||
| 39 | |||
| 40 | Some(self.tokens[self.cursor].clone()) | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | #[derive(Debug, Clone, PartialEq)] | ||
| 45 | pub enum Type { | ||
| 46 | /// Variables without an explicit type are assigned this until | ||
| 47 | /// the type inferencer figures out what this is. There is not a | ||
| 48 | /// keyword for this. | ||
| 49 | Infer, | ||
| 50 | /// The type assigned to numeric literals. This allows integer literals | ||
| 51 | /// to coerce to integer or float depending on the context, but prevents | ||
| 52 | /// implicit casting of runtime values. | ||
| 53 | NumberLiteral(f64), | ||
| 54 | /// Return value of `@raw`. All type checks are disabled. | ||
| 55 | Any, | ||
| 56 | /// noreturn is the type of 'return', aka a value that never exists | ||
| 57 | NoReturn, | ||
| 58 | /// 32 signed integer | ||
| 59 | Int, | ||
| 60 | /// 64 signed JS float | ||
| 61 | Float, | ||
| 62 | /// Boolean | ||
| 63 | Bool, | ||
| 64 | /// JS BigInt | ||
| 65 | BigInt, | ||
| 66 | /// JS String | ||
| 67 | String, | ||
| 68 | /// A union of multiple possible types. Might be removed | ||
| 69 | Union(Vec<Type>), | ||
| 70 | } | ||
| 71 | |||
| 72 | impl std::fmt::Display for Type { | ||
| 73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 74 | match self { | ||
| 75 | Type::Infer => write!(f, "infered type"), | ||
| 76 | Type::Any => write!(f, "opaque js value"), | ||
| 77 | Type::NumberLiteral(_) => write!(f, "numeric literal"), | ||
| 78 | Type::Int => write!(f, "int"), | ||
| 79 | Type::Float => write!(f, "float"), | ||
| 80 | Type::BigInt => write!(f, "bigint"), | ||
| 81 | Type::Bool => write!(f, "bool"), | ||
| 82 | Type::String => write!(f, "string"), | ||
| 83 | Type::NoReturn => write!(f, "noreturn"), | ||
| 84 | Type::Union(of) => { | ||
| 85 | for (i, t) in of.iter().enumerate() { | ||
| 86 | if i != 0 { | ||
| 87 | write!(f, " | ")?; | ||
| 88 | } | ||
| 89 | write!(f, "{}", t)?; | ||
| 90 | } | ||
| 91 | Ok(()) | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | #[derive(Debug, Clone)] | ||
| 98 | pub struct Module { | ||
| 99 | pub value: Vec<Ast>, | ||
| 100 | } | ||
| 101 | |||
| 102 | #[derive(Debug, Clone)] | ||
| 103 | pub struct Block { | ||
| 104 | pub value: Vec<Ast>, | ||
| 105 | pub implicit_return: bool, | ||
| 106 | } | ||
| 107 | |||
| 108 | #[derive(Debug, Clone)] | ||
| 109 | pub struct Argument { | ||
| 110 | pub name: String, | ||
| 111 | pub return_type: Type, | ||
| 112 | } | ||
| 113 | |||
| 114 | #[derive(Debug, Clone)] | ||
| 115 | pub enum Ast { | ||
| 116 | Declaration { | ||
| 117 | name: String, | ||
| 118 | decl_type: Type, | ||
| 119 | value: Box<Ast>, | ||
| 120 | is_constant: bool, | ||
| 121 | }, | ||
| 122 | Operation { | ||
| 123 | operator: Operator, | ||
| 124 | left: Box<Ast>, | ||
| 125 | right: Box<Ast>, | ||
| 126 | }, | ||
| 127 | Function { | ||
| 128 | name: String, | ||
| 129 | arguments: Vec<Argument>, | ||
| 130 | return_type: Type, | ||
| 131 | block: Block, | ||
| 132 | }, | ||
| 133 | FunctionCall { | ||
| 134 | /// Identifier or BuiltIn | ||
| 135 | name: Box<Ast>, | ||
| 136 | arguments: Vec<Ast>, | ||
| 137 | }, | ||
| 138 | If { | ||
| 139 | condition: Box<Ast>, | ||
| 140 | value: Block, | ||
| 141 | else_value: Option<Block>, | ||
| 142 | }, | ||
| 143 | Return(Box<Ast>), | ||
| 144 | Public(Box<Ast>), | ||
| 145 | Block(Block), | ||
| 146 | Number(f64), | ||
| 147 | Bool(bool), | ||
| 148 | String(String), | ||
| 149 | Identifier(String), | ||
| 150 | BuiltIn(String), | ||
| 151 | Comment(String), | ||
| 152 | DocComment(String), | ||
| 153 | } | ||
crates/ast/types.rs created+208| ... | @@ -0,0 +1,208 @@ | ||
| 1 | use miette::{Diagnostic, Result}; | ||
| 2 | use thiserror::Error; | ||
| 3 | |||
| 4 | /// A value that is known at compile-time | ||
| 5 | #[derive(Debug, Clone, PartialEq)] | ||
| 6 | pub enum Literal { | ||
| 7 | Number(f64), | ||
| 8 | Bool(bool), | ||
| 9 | String(String), | ||
| 10 | Void, | ||
| 11 | } | ||
| 12 | |||
| 13 | impl std::fmt::Display for Literal { | ||
| 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 15 | match self { | ||
| 16 | Literal::Number(value) => write!(f, "{}", value), | ||
| 17 | Literal::Bool(value) => write!(f, "{}", value), | ||
| 18 | Literal::String(value) => write!(f, "{}", value), | ||
| 19 | Literal::Void => write!(f, "void"), | ||
| 20 | } | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | #[derive(Debug, Clone, PartialEq)] | ||
| 25 | pub enum Type { | ||
| 26 | /// Variables without an explicit type are assigned this until | ||
| 27 | /// the type inferencer figures out what this is. There is not a | ||
| 28 | /// keyword for this. | ||
| 29 | Infer, | ||
| 30 | /// The type assigned to literals. This allows integer literals | ||
| 31 | /// to coerce to integer or float depending on the context, but | ||
| 32 | /// prevents implicit casting of runtime values. | ||
| 33 | Literal(Literal), | ||
| 34 | /// Return value of `@raw`. All type checks are disabled. | ||
| 35 | Any, | ||
| 36 | /// The type of `void`. This is the return type of functions | ||
| 37 | /// that do not return a value. | ||
| 38 | Void, | ||
| 39 | /// noreturn is the type of 'return', aka a value that never exists. | ||
| 40 | /// unlike void, this includes functions that do not return at all. | ||
| 41 | NoReturn, | ||
| 42 | /// 32 signed integer | ||
| 43 | Int, | ||
| 44 | /// 64 signed JS float | ||
| 45 | Float, | ||
| 46 | /// Boolean | ||
| 47 | Bool, | ||
| 48 | /// JS BigInt | ||
| 49 | BigInt, | ||
| 50 | /// JS String | ||
| 51 | String, | ||
| 52 | /// A union of multiple possible types. Might be removed | ||
| 53 | Union(Vec<Type>), | ||
| 54 | } | ||
| 55 | |||
| 56 | pub trait TypeCoerceable { | ||
| 57 | fn to_type(&self) -> Option<Type>; | ||
| 58 | } | ||
| 59 | |||
| 60 | impl TypeCoerceable for Literal { | ||
| 61 | fn to_type(&self) -> Option<Type> { | ||
| 62 | Some(match self { | ||
| 63 | Literal::Number(_) => Type::Float, | ||
| 64 | Literal::Bool(_) => Type::Bool, | ||
| 65 | Literal::String(_) => Type::String, | ||
| 66 | Literal::Void => Type::Void, | ||
| 67 | }) | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | impl TypeCoerceable for Type { | ||
| 72 | fn to_type(&self) -> Option<Type> { | ||
| 73 | Some(self.clone()) | ||
| 74 | } | ||
| 75 | } | ||
| 76 | |||
| 77 | impl std::fmt::Display for Type { | ||
| 78 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 79 | match self { | ||
| 80 | Type::Infer => write!(f, "infered type"), | ||
| 81 | Type::Any => write!(f, "opaque js value"), | ||
| 82 | Type::Literal(value) => write!(f, "{}", value), | ||
| 83 | Type::Int => write!(f, "int"), | ||
| 84 | Type::Float => write!(f, "float"), | ||
| 85 | Type::BigInt => write!(f, "bigint"), | ||
| 86 | Type::Bool => write!(f, "bool"), | ||
| 87 | Type::String => write!(f, "string"), | ||
| 88 | Type::NoReturn => write!(f, "noreturn"), | ||
| 89 | Type::Void => write!(f, "void"), | ||
| 90 | Type::Union(of) => { | ||
| 91 | for (i, t) in of.iter().enumerate() { | ||
| 92 | if i != 0 { | ||
| 93 | write!(f, " | ")?; | ||
| 94 | } | ||
| 95 | write!(f, "{}", t)?; | ||
| 96 | } | ||
| 97 | Ok(()) | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | pub fn merge_types(a: Type, b: Type) -> Result<Type> { | ||
| 104 | match a { | ||
| 105 | Type::Infer => return Ok(b), | ||
| 106 | Type::Any => return Ok(Type::Any), | ||
| 107 | _ => {} | ||
| 108 | } | ||
| 109 | |||
| 110 | match b { | ||
| 111 | Type::Infer => return Ok(a), | ||
| 112 | Type::Any => return Ok(Type::Any), | ||
| 113 | _ => {} | ||
| 114 | } | ||
| 115 | |||
| 116 | if a == b { | ||
| 117 | return Ok(a); | ||
| 118 | } | ||
| 119 | |||
| 120 | Err(TypeError::new(format!("TODO: Implement type merging for {} and {}", a, b)).into()) | ||
| 121 | } | ||
| 122 | |||
| 123 | // pub fn coerce_type(target: Type, source: Type) -> Result<Type> { | ||
| 124 | // match source { | ||
| 125 | // Type::Any => return Ok(target), | ||
| 126 | // _ => {} | ||
| 127 | // } | ||
| 128 | // match target { | ||
| 129 | // Type::Infer => Ok(source), | ||
| 130 | // Type::Any => Ok(Type::Any), | ||
| 131 | |||
| 132 | // Type::Literal(_) => unreachable!(), | ||
| 133 | |||
| 134 | // Type::Int => match source { | ||
| 135 | // Type::Int => Ok(Type::Int), | ||
| 136 | // Type::Float => Ok(Type::Int), | ||
| 137 | // Type::Literal(value) => { | ||
| 138 | // if value.fract() != 0.0 { | ||
| 139 | // Err( | ||
| 140 | // TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | ||
| 141 | // .into(), | ||
| 142 | // ) | ||
| 143 | // } else if value > i32::MAX.into() || value < i32::MIN.into() { | ||
| 144 | // Err(TypeError::new(format!( | ||
| 145 | // "Numeric Literal '{}' is out of range for an integer ({}..{})", | ||
| 146 | // value, | ||
| 147 | // i32::MIN, | ||
| 148 | // i32::MAX | ||
| 149 | // )) | ||
| 150 | // .into()) | ||
| 151 | // } else { | ||
| 152 | // Ok(Type::Int) | ||
| 153 | // } | ||
| 154 | // } | ||
| 155 | // t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | ||
| 156 | // }, | ||
| 157 | |||
| 158 | // Type::BigInt => match source { | ||
| 159 | // Type::Int => Ok(Type::Int), | ||
| 160 | // Type::Float => Ok(Type::Int), | ||
| 161 | // Type::NumberLiteral(value) => { | ||
| 162 | // if value.fract() != 0.0 { | ||
| 163 | // Err( | ||
| 164 | // TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | ||
| 165 | // .into(), | ||
| 166 | // ) | ||
| 167 | // } else { | ||
| 168 | // Ok(Type::BigInt) | ||
| 169 | // } | ||
| 170 | // } | ||
| 171 | // t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | ||
| 172 | // }, | ||
| 173 | |||
| 174 | // Type::Float => match source { | ||
| 175 | // Type::Int => Ok(Type::Float), | ||
| 176 | // Type::Float => Ok(Type::Float), | ||
| 177 | // Type::NumberLiteral(value) => { | ||
| 178 | // if value.fract() == 0.0 && value <= f64::MAX.into() && value >= f64::MIN.into() { | ||
| 179 | // Ok(Type::Float) | ||
| 180 | // } else { | ||
| 181 | // Err(TypeError::new(format!("Numeric Literal")).into()) | ||
| 182 | // } | ||
| 183 | // } | ||
| 184 | // _ => Err(TypeError::new(format!("Cannot coerce int to float")).into()), | ||
| 185 | // }, | ||
| 186 | |||
| 187 | // other => { | ||
| 188 | // if other == source { | ||
| 189 | // Ok(other) | ||
| 190 | // } else { | ||
| 191 | // Err(TypeError::new(format!("Cannot coerce {} to {}", source, other)).into()) | ||
| 192 | // } | ||
| 193 | // } | ||
| 194 | // } | ||
| 195 | // } | ||
| 196 | |||
| 197 | #[derive(Error, Diagnostic, Debug)] | ||
| 198 | #[error("TypeError")] | ||
| 199 | pub struct TypeError { | ||
| 200 | #[help] | ||
| 201 | error: String, | ||
| 202 | } | ||
| 203 | |||
| 204 | impl TypeError { | ||
| 205 | pub fn new(error: String) -> Self { | ||
| 206 | Self { error } | ||
| 207 | } | ||
| 208 | } | ||
crates/checker/Cargo.toml+1-2| ... | @@ -10,5 +10,4 @@ miette.workspace = true | ... | @@ -10,5 +10,4 @@ miette.workspace = true |
| 10 | thiserror.workspace = true | 10 | thiserror.workspace = true |
| 11 | num.workspace = true | 11 | num.workspace = true |
| 12 | 12 | ||
| 13 | ast = { path = "../ast" } | 13 | ast = { path = "../ast" } |
| 14 | util = { path = "../util" } | ||
| \ No newline at end of file | |||
crates/checker/src/ast_type_coersion.rs created+54| ... | @@ -0,0 +1,54 @@ | ||
| 1 | // use ast::{BinaryOperator, ExprData, Type}; | ||
| 2 | |||
| 3 | // impl TypeCoerceable for ExprData { | ||
| 4 | // fn to_type(&self) -> Option<Type> { | ||
| 5 | // match self { | ||
| 6 | // ExprData::Block(block) => { | ||
| 7 | // if (block.has_implicit_return) { | ||
| 8 | // block.statements.last().unwrap().to_type() | ||
| 9 | // } else { | ||
| 10 | // Some(Type::Void) | ||
| 11 | // } | ||
| 12 | // } | ||
| 13 | // ExprData::Binary { op, left, right } => { | ||
| 14 | // // TODO: this likely has to do further type checking | ||
| 15 | // let left_type = left.to_type(); | ||
| 16 | // match op { | ||
| 17 | // BinaryOperator::Add => left_type, | ||
| 18 | // BinaryOperator::Sub => left_type, | ||
| 19 | // BinaryOperator::Mul => left_type, | ||
| 20 | // BinaryOperator::Div => left_type, | ||
| 21 | // BinaryOperator::Pow => left_type, | ||
| 22 | // BinaryOperator::Eq => Some(Type::Bool), | ||
| 23 | // BinaryOperator::Gt => Some(Type::Bool), | ||
| 24 | // BinaryOperator::Lt => Some(Type::Bool), | ||
| 25 | // BinaryOperator::Gte => Some(Type::Bool), | ||
| 26 | // BinaryOperator::Lte => Some(Type::Bool), | ||
| 27 | // BinaryOperator::Lshift => Some(Type::Int), | ||
| 28 | // BinaryOperator::Rshift => Some(Type::Int), | ||
| 29 | // BinaryOperator::BitAnd => Some(Type::Int), | ||
| 30 | // BinaryOperator::BitOr => Some(Type::Int), | ||
| 31 | // BinaryOperator::BitXor => Some(Type::Int), | ||
| 32 | // BinaryOperator::Mod => Some(Type::Int), | ||
| 33 | // BinaryOperator::NotEq => Some(Type::Int), | ||
| 34 | |||
| 35 | // BinaryOperator::And => Some(Type::Bool), | ||
| 36 | // BinaryOperator::Or => Some(Type::Bool), | ||
| 37 | // } | ||
| 38 | // } | ||
| 39 | // ExprData::Unary { op, value } => todo!(), | ||
| 40 | // ExprData::Function { | ||
| 41 | // name, | ||
| 42 | // params, | ||
| 43 | // return_type, | ||
| 44 | // block, | ||
| 45 | // } => Some(return_type), | ||
| 46 | // ExprData::Call { base, arguments } => None, // oh shit. yeah this trait isn't implementable | ||
| 47 | // ExprData::Builtin { id, arguments } => todo!(), | ||
| 48 | // ExprData::If(_) => todo!(), | ||
| 49 | // ExprData::Return(_) => todo!(), | ||
| 50 | // ExprData::Literal(_) => todo!(), | ||
| 51 | // ExprData::Identifier(_) => todo!(), | ||
| 52 | // } | ||
| 53 | // } | ||
| 54 | // } | ||
crates/checker/src/lib.rs+96-203| ... | @@ -1,203 +1,96 @@ | ... | @@ -1,203 +1,96 @@ |
| 1 | use ast::{Ast, Module, Type}; | 1 | // use ast::{ Module, Type}; |
| 2 | use miette::Result; | 2 | // use miette::Result; |
| 3 | 3 | ||
| 4 | use miette::Diagnostic; | 4 | // mod ast_type_coersion; |
| 5 | use thiserror::Error; | 5 | |
| 6 | 6 | // pub fn check_module(block: &Module) -> Result<()> { | |
| 7 | #[derive(Error, Diagnostic, Debug)] | 7 | // for statement in &block.statements { |
| 8 | #[error("TypeError")] | 8 | // check_expression(statement)?; |
| 9 | pub struct TypeError { | 9 | // } |
| 10 | #[help] | 10 | |
| 11 | error: String, | 11 | // Ok(()) |
| 12 | } | 12 | // } |
| 13 | 13 | ||
| 14 | impl TypeError { | 14 | // pub fn resolve_type(expr: &Ast) -> Result<Type> { |
| 15 | pub fn new(error: String) -> Self { | 15 | // match expr { |
| 16 | Self { error } | 16 | // Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), |
| 17 | } | 17 | // Ast::Declaration { |
| 18 | } | 18 | // name, |
| 19 | 19 | // decl_type, | |
| 20 | pub fn check_module(block: &Module) -> Result<()> { | 20 | // value, |
| 21 | for statement in &block.value { | 21 | // is_constant, |
| 22 | check_expression(statement)?; | 22 | // } => Ok(Type::NoReturn), |
| 23 | } | 23 | // Ast::Binary { |
| 24 | 24 | // operator, | |
| 25 | Ok(()) | 25 | // left, |
| 26 | } | 26 | // right, |
| 27 | 27 | // } => merge_types(resolve_type(left)?, resolve_type(right)?), | |
| 28 | pub fn resolve_type(expr: &Ast) -> Result<Type> { | 28 | // Ast::Function { |
| 29 | match expr { | 29 | // name, |
| 30 | Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | 30 | // arguments, |
| 31 | Ast::Declaration { | 31 | // return_type, |
| 32 | name, | 32 | // block, |
| 33 | decl_type, | 33 | // } => todo!(), |
| 34 | value, | 34 | // Ast::FunctionCall { name, arguments } => todo!(), |
| 35 | is_constant, | 35 | // Ast::If { |
| 36 | } => Ok(Type::NoReturn), | 36 | // condition, |
| 37 | Ast::Operation { | 37 | // value, |
| 38 | operator, | 38 | // else_value, |
| 39 | left, | 39 | // } => todo!(), |
| 40 | right, | 40 | // Ast::Return(_) => Ok(Type::NoReturn), |
| 41 | } => merge_types(resolve_type(left)?, resolve_type(right)?), | 41 | // Ast::Public(_) => todo!(), // TODO: remove Public |
| 42 | Ast::Function { | 42 | // Ast::Block(_) => todo!(), |
| 43 | name, | 43 | // Ast::Bool(_) => Ok(Type::Bool), |
| 44 | arguments, | 44 | // Ast::String(_) => Ok(Type::String), |
| 45 | return_type, | 45 | // Ast::Identifier(_) => todo!(), |
| 46 | block, | 46 | // Ast::BuiltIn(_) => todo!(), |
| 47 | } => todo!(), | 47 | |
| 48 | Ast::FunctionCall { name, arguments } => todo!(), | 48 | // Ast::Comment(_) => unreachable!(), |
| 49 | Ast::If { | 49 | // Ast::DocComment(_) => unreachable!(), |
| 50 | condition, | 50 | // } |
| 51 | value, | 51 | // } |
| 52 | else_value, | 52 | |
| 53 | } => todo!(), | 53 | // pub fn check_expression(expr: &Ast) -> Result<()> { |
| 54 | Ast::Return(_) => Ok(Type::NoReturn), | 54 | // panic!() |
| 55 | Ast::Public(_) => todo!(), // TODO: remove Public | 55 | // match expr { |
| 56 | Ast::Block(_) => todo!(), | 56 | // Ast::Declaration { |
| 57 | Ast::Bool(_) => Ok(Type::Bool), | 57 | // name, |
| 58 | Ast::String(_) => Ok(Type::String), | 58 | // decl_type: return_type, |
| 59 | Ast::Identifier(_) => todo!(), | 59 | // value, |
| 60 | Ast::BuiltIn(_) => todo!(), | 60 | // is_constant, |
| 61 | 61 | // } => { | |
| 62 | Ast::Comment(_) => unreachable!(), | 62 | // check_expression(value)?; |
| 63 | Ast::DocComment(_) => unreachable!(), | 63 | // // coerce_type(return_type.clone(), resolve_type(value)?)?; |
| 64 | } | 64 | // } |
| 65 | } | 65 | // _ => {} |
| 66 | 66 | // // Ast::Operation { | |
| 67 | pub fn merge_types(a: Type, b: Type) -> Result<Type> { | 67 | // // operator, |
| 68 | match a { | 68 | // // left, |
| 69 | Type::Infer => return Ok(b), | 69 | // // right, |
| 70 | Type::Any => return Ok(Type::Any), | 70 | // // } => Ok(()), |
| 71 | _ => {} | 71 | // // Ast::Function { |
| 72 | } | 72 | // // name, |
| 73 | 73 | // // arguments, | |
| 74 | match b { | 74 | // // return_type, |
| 75 | Type::Infer => return Ok(a), | 75 | // // block, |
| 76 | Type::Any => return Ok(Type::Any), | 76 | // // } => Ok(()), |
| 77 | _ => {} | 77 | // // Ast::FunctionCall { name, arguments } => Ok(()), |
| 78 | } | 78 | // // Ast::If { |
| 79 | 79 | // // condition, | |
| 80 | if a == b { | 80 | // // value, |
| 81 | return Ok(a); | 81 | // // else_value, |
| 82 | } | 82 | // // } => Ok(()), |
| 83 | 83 | // // Ast::Return(_) => Ok(()), | |
| 84 | Err(TypeError::new(format!("Cannot merge {} and {}", a, b)).into()) | 84 | // // Ast::Public(_) => Ok(()), |
| 85 | } | 85 | // // Ast::Block(_) => Ok(()), |
| 86 | 86 | // // Ast::Number(_) => Ok(()), | |
| 87 | pub fn coerce_type(target: Type, source: Type) -> Result<Type> { | 87 | // // Ast::Bool(_) => Ok(()), |
| 88 | match source { | 88 | // // Ast::String(_) => Ok(()), |
| 89 | Type::Any => return Ok(target), | 89 | // // Ast::Identifier(_) => Ok(()), |
| 90 | _ => {} | 90 | // // Ast::BuiltIn(_) => Ok(()), |
| 91 | } | 91 | // // Ast::Comment(_) => Ok(()), |
| 92 | match target { | 92 | // // Ast::DocComment(_) => Ok(()), |
| 93 | Type::Infer => Ok(source), | 93 | // } |
| 94 | Type::Any => Ok(Type::Any), | 94 | |
| 95 | 95 | // Ok(()) | |
| 96 | Type::NumberLiteral(_) => unreachable!(), | 96 | // } |
| 97 | |||
| 98 | Type::Int => match source { | ||
| 99 | Type::Int => Ok(Type::Int), | ||
| 100 | Type::Float => Ok(Type::Int), | ||
| 101 | Type::NumberLiteral(value) => { | ||
| 102 | if value.fract() != 0.0 { | ||
| 103 | Err( | ||
| 104 | TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | ||
| 105 | .into(), | ||
| 106 | ) | ||
| 107 | } else if value > i32::MAX.into() || value < i32::MIN.into() { | ||
| 108 | Err(TypeError::new(format!( | ||
| 109 | "Numeric Literal '{}' is out of range for an integer ({}..{})", | ||
| 110 | value, | ||
| 111 | i32::MIN, | ||
| 112 | i32::MAX | ||
| 113 | )) | ||
| 114 | .into()) | ||
| 115 | } else { | ||
| 116 | Ok(Type::Int) | ||
| 117 | } | ||
| 118 | } | ||
| 119 | t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | ||
| 120 | }, | ||
| 121 | |||
| 122 | Type::BigInt => match source { | ||
| 123 | Type::Int => Ok(Type::Int), | ||
| 124 | Type::Float => Ok(Type::Int), | ||
| 125 | Type::NumberLiteral(value) => { | ||
| 126 | if value.fract() != 0.0 { | ||
| 127 | Err( | ||
| 128 | TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | ||
| 129 | .into(), | ||
| 130 | ) | ||
| 131 | } else { | ||
| 132 | Ok(Type::BigInt) | ||
| 133 | } | ||
| 134 | } | ||
| 135 | t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | ||
| 136 | }, | ||
| 137 | |||
| 138 | Type::Float => match source { | ||
| 139 | Type::Int => Ok(Type::Float), | ||
| 140 | Type::Float => Ok(Type::Float), | ||
| 141 | Type::NumberLiteral(value) => { | ||
| 142 | if value.fract() == 0.0 && value <= f64::MAX.into() && value >= f64::MIN.into() { | ||
| 143 | Ok(Type::Float) | ||
| 144 | } else { | ||
| 145 | Err(TypeError::new(format!("Numeric Literal")).into()) | ||
| 146 | } | ||
| 147 | } | ||
| 148 | _ => Err(TypeError::new(format!("Cannot coerce int to float")).into()), | ||
| 149 | }, | ||
| 150 | |||
| 151 | other => { | ||
| 152 | if other == source { | ||
| 153 | Ok(other) | ||
| 154 | } else { | ||
| 155 | Err(TypeError::new(format!("Cannot coerce {} to {}", source, other)).into()) | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | pub fn check_expression(expr: &Ast) -> Result<()> { | ||
| 162 | match expr { | ||
| 163 | Ast::Declaration { | ||
| 164 | name, | ||
| 165 | decl_type: return_type, | ||
| 166 | value, | ||
| 167 | is_constant, | ||
| 168 | } => { | ||
| 169 | check_expression(value)?; | ||
| 170 | coerce_type(return_type.clone(), resolve_type(value)?)?; | ||
| 171 | } | ||
| 172 | _ => {} | ||
| 173 | // Ast::Operation { | ||
| 174 | // operator, | ||
| 175 | // left, | ||
| 176 | // right, | ||
| 177 | // } => Ok(()), | ||
| 178 | // Ast::Function { | ||
| 179 | // name, | ||
| 180 | // arguments, | ||
| 181 | // return_type, | ||
| 182 | // block, | ||
| 183 | // } => Ok(()), | ||
| 184 | // Ast::FunctionCall { name, arguments } => Ok(()), | ||
| 185 | // Ast::If { | ||
| 186 | // condition, | ||
| 187 | // value, | ||
| 188 | // else_value, | ||
| 189 | // } => Ok(()), | ||
| 190 | // Ast::Return(_) => Ok(()), | ||
| 191 | // Ast::Public(_) => Ok(()), | ||
| 192 | // Ast::Block(_) => Ok(()), | ||
| 193 | // Ast::Number(_) => Ok(()), | ||
| 194 | // Ast::Bool(_) => Ok(()), | ||
| 195 | // Ast::String(_) => Ok(()), | ||
| 196 | // Ast::Identifier(_) => Ok(()), | ||
| 197 | // Ast::BuiltIn(_) => Ok(()), | ||
| 198 | // Ast::Comment(_) => Ok(()), | ||
| 199 | // Ast::DocComment(_) => Ok(()), | ||
| 200 | } | ||
| 201 | |||
| 202 | Ok(()) | ||
| 203 | } |
crates/cli/Cargo.toml+1| ... | @@ -14,6 +14,7 @@ thiserror.workspace = true | ... | @@ -14,6 +14,7 @@ thiserror.workspace = true |
| 14 | 14 | ||
| 15 | tokenizer = { path = "../tokenizer" } | 15 | tokenizer = { path = "../tokenizer" } |
| 16 | ast = { path = "../ast" } | 16 | ast = { path = "../ast" } |
| 17 | parser = { path = "../parser" } | ||
| 17 | js_ast = { path = "../js_ast" } | 18 | js_ast = { path = "../js_ast" } |
| 18 | js_printer = { path = "../js_printer" } | 19 | js_printer = { path = "../js_printer" } |
| 19 | checker = { path = "../checker" } | 20 | checker = { path = "../checker" } |
crates/cli/src/main.rs+18-4| ... | @@ -1,10 +1,8 @@ | ... | @@ -1,10 +1,8 @@ |
| 1 | use std::{io::Write, path::PathBuf}; | 1 | use std::{io::Write, path::PathBuf}; |
| 2 | 2 | ||
| 3 | use checker; | ||
| 4 | use clap::{Parser, Subcommand}; | 3 | use clap::{Parser, Subcommand}; |
| 5 | use miette::Result; | 4 | use miette::Result; |
| 6 | 5 | ||
| 7 | use ast; | ||
| 8 | use js_printer; | 6 | use js_printer; |
| 9 | use tokenizer; | 7 | use tokenizer; |
| 10 | 8 | ||
| ... | @@ -40,6 +38,9 @@ enum Commands { | ... | @@ -40,6 +38,9 @@ enum Commands { |
| 40 | #[arg(short, long, default_value = "deno")] | 38 | #[arg(short, long, default_value = "deno")] |
| 41 | target: String, | 39 | target: String, |
| 42 | }, | 40 | }, |
| 41 | Astdump { | ||
| 42 | file: PathBuf, | ||
| 43 | }, | ||
| 43 | } | 44 | } |
| 44 | 45 | ||
| 45 | fn get_runtime(runtime: &String) -> String { | 46 | fn get_runtime(runtime: &String) -> String { |
| ... | @@ -53,9 +54,9 @@ pub fn transpile(content: String) -> Result<String> { | ... | @@ -53,9 +54,9 @@ pub fn transpile(content: String) -> Result<String> { |
| 53 | // 1. Tokenize the input | 54 | // 1. Tokenize the input |
| 54 | let tokens = tokenizer::tokenize(content)?; | 55 | let tokens = tokenizer::tokenize(content)?; |
| 55 | // 2. Parse the tokens into an AST | 56 | // 2. Parse the tokens into an AST |
| 56 | let blight_ast = ast::parse(tokens)?; | 57 | let blight_ast = parser::parse(tokens)?; |
| 57 | // 3. Type check the AST | 58 | // 3. Type check the AST |
| 58 | checker::check_module(&blight_ast)?; | 59 | // checker::check_module(&blight_ast)?; |
| 59 | // 4. Optimize the AST | 60 | // 4. Optimize the AST |
| 60 | // optimize(tree.clone())?; | 61 | // optimize(tree.clone())?; |
| 61 | // 5. Translate the AST into javascript | 62 | // 5. Translate the AST into javascript |
| ... | @@ -66,6 +67,14 @@ pub fn transpile(content: String) -> Result<String> { | ... | @@ -66,6 +67,14 @@ pub fn transpile(content: String) -> Result<String> { |
| 66 | return Ok(javascript); | 67 | return Ok(javascript); |
| 67 | } | 68 | } |
| 68 | 69 | ||
| 70 | pub fn astdump(content: String) -> Result<String> { | ||
| 71 | // 1. Tokenize the input | ||
| 72 | let tokens = tokenizer::tokenize(content)?; | ||
| 73 | // 2. Parse the tokens into an AST | ||
| 74 | let blight_ast = parser::parse(tokens)?; | ||
| 75 | return Ok(format!("{:#?}", blight_ast)); | ||
| 76 | } | ||
| 77 | |||
| 69 | fn main() -> Result<()> { | 78 | fn main() -> Result<()> { |
| 70 | let cli = Cli::parse(); | 79 | let cli = Cli::parse(); |
| 71 | 80 | ||
| ... | @@ -145,6 +154,11 @@ fn main() -> Result<()> { | ... | @@ -145,6 +154,11 @@ fn main() -> Result<()> { |
| 145 | .wait() | 154 | .wait() |
| 146 | .unwrap(); | 155 | .unwrap(); |
| 147 | } | 156 | } |
| 157 | Commands::Astdump { file } => { | ||
| 158 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | ||
| 159 | let result = astdump(contents.clone())?; | ||
| 160 | println!("{}", result); | ||
| 161 | } | ||
| 148 | } | 162 | } |
| 149 | 163 | ||
| 150 | Ok(()) | 164 | Ok(()) |
crates/js_ast/Cargo.toml-1| ... | @@ -11,4 +11,3 @@ thiserror.workspace = true | ... | @@ -11,4 +11,3 @@ thiserror.workspace = true |
| 11 | num.workspace = true | 11 | num.workspace = true |
| 12 | 12 | ||
| 13 | ast = { path = "../ast" } | 13 | ast = { path = "../ast" } |
| 14 | util = { path = "../util" } |
crates/js_ast/src/lib.rs+190-194| ... | @@ -1,9 +1,8 @@ | ... | @@ -1,9 +1,8 @@ |
| 1 | use std::collections::HashMap; | 1 | use std::collections::HashMap; |
| 2 | 2 | ||
| 3 | use ast::{Expr, ExprData, Literal, Module, Statement}; | ||
| 3 | use miette::Result; | 4 | use miette::Result; |
| 4 | 5 | ||
| 5 | use ast::{Ast, Module}; | ||
| 6 | |||
| 7 | mod types; | 6 | mod types; |
| 8 | pub use types::{JsExpression, JsStatement}; | 7 | pub use types::{JsExpression, JsStatement}; |
| 9 | 8 | ||
| ... | @@ -99,68 +98,69 @@ impl TranslateContext { | ... | @@ -99,68 +98,69 @@ impl TranslateContext { |
| 99 | // here, we recursively expand out expressions into statements | 98 | // here, we recursively expand out expressions into statements |
| 100 | // we then store the result of all the expressions in a variable | 99 | // we then store the result of all the expressions in a variable |
| 101 | // and then return the name of that variable | 100 | // and then return the name of that variable |
| 102 | fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression { | 101 | fn get_expression_handle(ast: &Expr, ctx: &mut TranslateContext) -> JsExpression { |
| 103 | match ast { | 102 | match ast.data.clone() { |
| 104 | Ast::Number(value) => JsExpression::Number(*value), | 103 | ExprData::Literal(Literal::String(value)) => JsExpression::String(value), |
| 105 | Ast::String(value) => JsExpression::String(value.clone()), | 104 | ExprData::Literal(Literal::Number(value)) => JsExpression::Number(value), |
| 106 | Ast::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), | 105 | ExprData::Literal(Literal::Void) => JsExpression::Identifier("null".to_string()), |
| 107 | Ast::Bool(value) => JsExpression::Bool(*value), | 106 | ExprData::Literal(Literal::Bool(value)) => JsExpression::Identifier(if value { |
| 108 | Ast::FunctionCall { name, arguments } => { | 107 | "true".to_string() |
| 108 | } else { | ||
| 109 | "false".to_string() | ||
| 110 | }), | ||
| 111 | |||
| 112 | ExprData::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), | ||
| 113 | ExprData::Call { base, arguments } => { | ||
| 109 | let mut handles = Vec::new(); | 114 | let mut handles = Vec::new(); |
| 110 | for argument in arguments { | 115 | for argument in arguments { |
| 111 | handles.push(get_expression_handle(argument, ctx)); | 116 | handles.push(get_expression_handle(&argument, ctx)); |
| 112 | } | ||
| 113 | match *name.clone() { | ||
| 114 | Ast::Identifier(name) => ctx.push_internal_variable(JsExpression::FunctionCall { | ||
| 115 | name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | ||
| 116 | arguments: handles, | ||
| 117 | }), | ||
| 118 | Ast::BuiltIn(name) => match name.as_str() { | ||
| 119 | "@raw" => { | ||
| 120 | if handles.len() != 1 { | ||
| 121 | panic!("@raw expects exactly one argument"); | ||
| 122 | } | ||
| 123 | match handles.get(0).unwrap() { | ||
| 124 | JsExpression::String(value) => { | ||
| 125 | return JsExpression::Raw(value.clone()); | ||
| 126 | } | ||
| 127 | _ => panic!("@raw expects a string argument"), | ||
| 128 | } | ||
| 129 | } | ||
| 130 | _ => panic!("Unknown built-in function {}", name), | ||
| 131 | }, | ||
| 132 | _ => panic!("Function name must be an identifier"), | ||
| 133 | } | 117 | } |
| 118 | let base = get_expression_handle(&base, ctx); | ||
| 119 | ctx.push_internal_variable(JsExpression::Call { | ||
| 120 | base: Box::new(base), | ||
| 121 | arguments: handles, | ||
| 122 | }) | ||
| 134 | } | 123 | } |
| 135 | Ast::Operation { | 124 | ExprData::Builtin { id, arguments } => match id { |
| 136 | operator, | 125 | ast::BuiltinId::Raw => { |
| 137 | left, | 126 | if arguments.len() != 1 { |
| 138 | right, | 127 | panic!("@raw expects exactly one argument"); |
| 139 | } => { | 128 | } |
| 140 | let left_handle = get_expression_handle(left, ctx); | 129 | match arguments.get(0).unwrap() { |
| 141 | let right_handle = get_expression_handle(right, ctx); | 130 | Expr { |
| 142 | JsExpression::Operation { | 131 | data: ExprData::Literal(Literal::String(value)), |
| 143 | operator: operator.clone(), | 132 | .. |
| 133 | } => JsExpression::Raw(value.clone()), | ||
| 134 | _ => panic!("@raw expects a string literal"), | ||
| 135 | } | ||
| 136 | } | ||
| 137 | }, | ||
| 138 | ExprData::Binary { op, left, right } => { | ||
| 139 | let left_handle = get_expression_handle(&left, ctx); | ||
| 140 | let right_handle = get_expression_handle(&right, ctx); | ||
| 141 | JsExpression::Binary { | ||
| 142 | operator: op.clone(), | ||
| 144 | left: Box::new(left_handle), | 143 | left: Box::new(left_handle), |
| 145 | right: Box::new(right_handle), | 144 | right: Box::new(right_handle), |
| 146 | } | 145 | } |
| 147 | } | 146 | } |
| 148 | Ast::Block(block) => { | 147 | ExprData::Block(block) => { |
| 149 | if !block.implicit_return { | 148 | if !block.has_implicit_return { |
| 150 | panic!("Expression blocks must have an implicit return"); | 149 | panic!("Expression blocks must have an implicit return"); |
| 151 | } | 150 | } |
| 152 | ctx.push_context(); | 151 | ctx.push_context(); |
| 153 | for (i, statement) in block.value.iter().enumerate() { | 152 | for (i, statement) in block.statements.iter().enumerate() { |
| 154 | if i == block.value.len() - 1 { | 153 | if i == block.statements.len() - 1 { |
| 155 | break; | 154 | break; |
| 156 | } else { | 155 | } else { |
| 157 | translate_statement(statement, ctx); | 156 | translate_statement(statement, ctx); |
| 158 | } | 157 | } |
| 159 | } | 158 | } |
| 160 | let result = get_expression_handle(block.value.last().unwrap(), ctx); | 159 | todo!() |
| 161 | let value = ctx.pop_context(); | 160 | // let result = get_expression_handle(block.statements.last().unwrap(), ctx); |
| 162 | value.iter().for_each(|x| ctx.push(x.clone())); | 161 | // let value = ctx.pop_context(); |
| 163 | result | 162 | // value.iter().for_each(|x| ctx.push(x.clone())); |
| 163 | // result | ||
| 164 | } | 164 | } |
| 165 | _ => { | 165 | _ => { |
| 166 | panic!("unimplemented: {:?}", ast); | 166 | panic!("unimplemented: {:?}", ast); |
| ... | @@ -168,168 +168,164 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression | ... | @@ -168,168 +168,164 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression |
| 168 | } | 168 | } |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | 171 | fn translate_statement(ast: &Statement, ctx: &mut TranslateContext) { |
| 172 | match ast { | 172 | match ast { |
| 173 | Ast::Declaration { | 173 | Statement::Decl(decl) => { |
| 174 | name, | 174 | let handle = get_expression_handle(&decl.init, ctx); |
| 175 | decl_type: _, | 175 | let rename = ctx.declare_variable(decl.name.clone()); |
| 176 | value, | ||
| 177 | is_constant, | ||
| 178 | } => { | ||
| 179 | let handle = get_expression_handle(value, ctx); | ||
| 180 | let rename = ctx.declare_variable(name.clone()); | ||
| 181 | ctx.push(JsStatement::Declaration { | 176 | ctx.push(JsStatement::Declaration { |
| 182 | name: rename, | 177 | name: rename, |
| 183 | value: handle, | 178 | value: handle, |
| 184 | is_constant: is_constant.clone(), | 179 | is_constant: decl.is_constant.clone(), |
| 185 | }); | 180 | }); |
| 186 | } | 181 | } |
| 187 | Ast::Function { | 182 | Statement::Expr(_) => todo!(), |
| 188 | name, | 183 | // Ast::Function { |
| 189 | arguments, | 184 | // name, |
| 190 | return_type: _, | 185 | // arguments, |
| 191 | block, | 186 | // return_type: _, |
| 192 | } => { | 187 | // block, |
| 193 | let rename = ctx.declare_variable(name.clone()); | 188 | // } => { |
| 194 | let arguments = arguments | 189 | // let rename = ctx.declare_variable(name.clone()); |
| 195 | .iter() | 190 | // let arguments = arguments |
| 196 | .map(|x| ctx.declare_variable(x.name.clone())) | 191 | // .iter() |
| 197 | .collect(); | 192 | // .map(|x| ctx.declare_variable(x.name.clone())) |
| 193 | // .collect(); | ||
| 198 | 194 | ||
| 199 | ctx.push_context(); | 195 | // ctx.push_context(); |
| 200 | for (i, statement) in block.value.iter().enumerate() { | 196 | // for (i, statement) in block.value.iter().enumerate() { |
| 201 | if block.implicit_return && i == block.value.len() - 1 { | 197 | // if block.implicit_return && i == block.value.len() - 1 { |
| 202 | let handle = get_expression_handle(statement, ctx); | 198 | // let handle = get_expression_handle(statement, ctx); |
| 203 | ctx.push(JsStatement::Return(handle)); | 199 | // ctx.push(JsStatement::Return(handle)); |
| 204 | } else { | 200 | // } else { |
| 205 | translate_statement(statement, ctx); | 201 | // translate_statement(statement, ctx); |
| 206 | } | 202 | // } |
| 207 | } | 203 | // } |
| 208 | let value = ctx.pop_context(); | 204 | // let value = ctx.pop_context(); |
| 209 | 205 | ||
| 210 | ctx.push(JsStatement::Function { | 206 | // ctx.push(JsStatement::Function { |
| 211 | name: rename, | 207 | // name: rename, |
| 212 | arguments, | 208 | // arguments, |
| 213 | value, | 209 | // value, |
| 214 | }); | 210 | // }); |
| 215 | } | 211 | // } |
| 216 | Ast::Public(value) => { | 212 | // Ast::Public(value) => { |
| 217 | translate_statement(value, ctx); | 213 | // translate_statement(value, ctx); |
| 218 | // I'm sorry, I just don't know how to get the original name properly... my bad | 214 | // // I'm sorry, I just don't know how to get the original name properly... my bad |
| 219 | match ctx.peek() { | 215 | // match ctx.peek() { |
| 220 | JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { | 216 | // JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { |
| 221 | let original_name = name.split("$").next().unwrap().to_string(); | 217 | // let original_name = name.split("$").next().unwrap().to_string(); |
| 222 | ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { | 218 | // ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { |
| 223 | name: original_name, | 219 | // name: original_name, |
| 224 | value: JsExpression::Identifier(name.clone()), | 220 | // value: JsExpression::Identifier(name.clone()), |
| 225 | is_constant: false, | 221 | // is_constant: true, |
| 226 | }))); | 222 | // }))); |
| 227 | } | 223 | // } |
| 228 | _ => { | 224 | // _ => { |
| 229 | panic!("Cannot make non-function public"); | 225 | // panic!("Cannot make non-function public"); |
| 230 | } | 226 | // } |
| 231 | } | 227 | // } |
| 232 | } | 228 | // } |
| 233 | Ast::Return(value) => { | 229 | // Ast::Return(value) => { |
| 234 | let handle = get_expression_handle(value, ctx); | 230 | // let handle = get_expression_handle(value, ctx); |
| 235 | ctx.push(JsStatement::Return(handle)); | 231 | // ctx.push(JsStatement::Return(handle)); |
| 236 | } | 232 | // } |
| 237 | Ast::Block(block) => { | 233 | // Ast::Block(block) => { |
| 238 | ctx.push_context(); | 234 | // ctx.push_context(); |
| 239 | for (i, statement) in block.value.iter().enumerate() { | 235 | // for (i, statement) in block.value.iter().enumerate() { |
| 240 | if block.implicit_return && i == block.value.len() - 1 { | 236 | // if block.implicit_return && i == block.value.len() - 1 { |
| 241 | let handle = get_expression_handle(statement, ctx); | 237 | // let handle = get_expression_handle(statement, ctx); |
| 242 | ctx.push(JsStatement::Return(handle)); | 238 | // ctx.push(JsStatement::Return(handle)); |
| 243 | } else { | 239 | // } else { |
| 244 | translate_statement(statement, ctx); | 240 | // translate_statement(statement, ctx); |
| 245 | } | 241 | // } |
| 246 | } | 242 | // } |
| 247 | let value = ctx.pop_context(); | 243 | // let value = ctx.pop_context(); |
| 248 | value.iter().for_each(|x| ctx.push(x.clone())); | 244 | // value.iter().for_each(|x| ctx.push(x.clone())); |
| 249 | } | 245 | // } |
| 250 | Ast::If { | 246 | // Ast::If { |
| 251 | condition, | 247 | // condition, |
| 252 | value: block, | 248 | // value: block, |
| 253 | else_value, | 249 | // else_value, |
| 254 | } => { | 250 | // } => { |
| 255 | let handle = get_expression_handle(condition, ctx); | 251 | // let handle = get_expression_handle(condition, ctx); |
| 256 | 252 | ||
| 257 | ctx.push_context(); | 253 | // ctx.push_context(); |
| 258 | for (i, statement) in block.value.iter().enumerate() { | 254 | // for (i, statement) in block.value.iter().enumerate() { |
| 259 | if block.implicit_return && i == block.value.len() - 1 { | 255 | // if block.implicit_return && i == block.value.len() - 1 { |
| 260 | let handle = get_expression_handle(statement, ctx); | 256 | // let handle = get_expression_handle(statement, ctx); |
| 261 | ctx.push(JsStatement::Return(handle)); | 257 | // ctx.push(JsStatement::Return(handle)); |
| 262 | } else { | 258 | // } else { |
| 263 | translate_statement(statement, ctx); | 259 | // translate_statement(statement, ctx); |
| 264 | } | 260 | // } |
| 265 | } | 261 | // } |
| 266 | let value = ctx.pop_context(); | 262 | // let value = ctx.pop_context(); |
| 267 | let else_value = match else_value { | 263 | // let else_value = match else_value { |
| 268 | Some(block) => { | 264 | // Some(block) => { |
| 269 | ctx.push_context(); | 265 | // ctx.push_context(); |
| 270 | for (i, statement) in block.value.iter().enumerate() { | 266 | // for (i, statement) in block.value.iter().enumerate() { |
| 271 | if block.implicit_return && i == block.value.len() - 1 { | 267 | // if block.implicit_return && i == block.value.len() - 1 { |
| 272 | let handle = get_expression_handle(statement, ctx); | 268 | // let handle = get_expression_handle(statement, ctx); |
| 273 | ctx.push(JsStatement::Return(handle)); | 269 | // ctx.push(JsStatement::Return(handle)); |
| 274 | } else { | 270 | // } else { |
| 275 | translate_statement(statement, ctx); | 271 | // translate_statement(statement, ctx); |
| 276 | } | 272 | // } |
| 277 | } | 273 | // } |
| 278 | Some(ctx.pop_context()) | 274 | // Some(ctx.pop_context()) |
| 279 | } | 275 | // } |
| 280 | None => None, | 276 | // None => None, |
| 281 | }; | 277 | // }; |
| 282 | 278 | ||
| 283 | ctx.push(JsStatement::If { | 279 | // ctx.push(JsStatement::If { |
| 284 | condition: handle, | 280 | // condition: handle, |
| 285 | value, | 281 | // value, |
| 286 | else_value, | 282 | // else_value, |
| 287 | }); | 283 | // }); |
| 288 | } | 284 | // } |
| 289 | Ast::FunctionCall { name, arguments } => { | 285 | // Ast::FunctionCall { name, arguments } => { |
| 290 | let mut handles = Vec::new(); | 286 | // let mut handles = Vec::new(); |
| 291 | for argument in arguments { | 287 | // for argument in arguments { |
| 292 | handles.push(get_expression_handle(argument, ctx)); | 288 | // handles.push(get_expression_handle(argument, ctx)); |
| 293 | } | 289 | // } |
| 294 | match *name.clone() { | 290 | // match *name.clone() { |
| 295 | Ast::Identifier(name) => { | 291 | // Ast::Identifier(name) => { |
| 296 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | 292 | // ctx.push(JsStatement::Expression(JsExpression::Call { |
| 297 | name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | 293 | // name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), |
| 298 | arguments: handles, | 294 | // arguments: handles, |
| 299 | })); | 295 | // })); |
| 300 | } | 296 | // } |
| 301 | Ast::BuiltIn(name) => match name.as_str() { | 297 | // Ast::BuiltIn(name) => match name.as_str() { |
| 302 | "@raw" => { | 298 | // "@raw" => { |
| 303 | if handles.len() != 1 { | 299 | // if handles.len() != 1 { |
| 304 | panic!("@raw expects exactly one argument"); | 300 | // panic!("@raw expects exactly one argument"); |
| 305 | } | 301 | // } |
| 306 | match handles.get(0).unwrap() { | 302 | // match handles.get(0).unwrap() { |
| 307 | JsExpression::String(value) => { | 303 | // JsExpression::String(value) => { |
| 308 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | 304 | // ctx.push(JsStatement::Expression(JsExpression::Call { |
| 309 | name: Box::new(JsExpression::Raw(value.clone())), | 305 | // name: Box::new(JsExpression::Raw(value.clone())), |
| 310 | arguments: handles, | 306 | // arguments: handles, |
| 311 | })); | 307 | // })); |
| 312 | } | 308 | // } |
| 313 | _ => panic!("@raw expects a string argument"), | 309 | // _ => panic!("@raw expects a string argument"), |
| 314 | } | 310 | // } |
| 315 | } | 311 | // } |
| 316 | _ => panic!("Unknown built-in function {}", name), | 312 | // _ => panic!("Unknown built-in function {}", name), |
| 317 | }, | 313 | // }, |
| 318 | _ => panic!("Function name must be an identifier"), | 314 | // _ => panic!("Function name must be an identifier"), |
| 319 | } | 315 | // } |
| 320 | } | 316 | // } |
| 321 | Ast::Comment(_) | Ast::DocComment(_) => {} | 317 | // Ast::Comment(_) | Ast::DocComment(_) => {} |
| 322 | _ => { | 318 | // _ => { |
| 323 | panic!("unimplemented: {:?}", ast); | 319 | // panic!("unimplemented: {:?}", ast); |
| 324 | } | 320 | // } |
| 325 | } | 321 | } |
| 326 | } | 322 | } |
| 327 | 323 | ||
| 328 | pub fn translate(module: &Module) -> Result<Vec<JsStatement>> { | 324 | pub fn translate(module: &Module) -> Result<Vec<JsStatement>> { |
| 329 | let mut ctx = TranslateContext::new(); | 325 | let mut ctx = TranslateContext::new(); |
| 330 | 326 | ||
| 331 | for ast in module.value.iter() { | 327 | for ast in module.statements.iter() { |
| 332 | translate_statement(&ast, &mut ctx); | 328 | translate_statement(&ast.clone().to_statement(), &mut ctx); |
| 333 | } | 329 | } |
| 334 | 330 | ||
| 335 | Ok(ctx.pop_context()) | 331 | Ok(ctx.pop_context()) |
crates/js_ast/src/types.rs+5-6| ... | @@ -1,8 +1,7 @@ | ... | @@ -1,8 +1,7 @@ |
| 1 | use ast::BinaryOperator; | ||
| 1 | use num::bigint::BigInt; | 2 | use num::bigint::BigInt; |
| 2 | use std::collections::HashMap; | 3 | use std::collections::HashMap; |
| 3 | 4 | ||
| 4 | use util::Operator; | ||
| 5 | |||
| 6 | #[derive(Debug, PartialEq, Clone)] | 5 | #[derive(Debug, PartialEq, Clone)] |
| 7 | pub enum JsExpression { | 6 | pub enum JsExpression { |
| 8 | Number(f64), | 7 | Number(f64), |
| ... | @@ -13,8 +12,8 @@ pub enum JsExpression { | ... | @@ -13,8 +12,8 @@ pub enum JsExpression { |
| 13 | Raw(String), | 12 | Raw(String), |
| 14 | Array(Vec<JsExpression>), | 13 | Array(Vec<JsExpression>), |
| 15 | Object(HashMap<String, JsExpression>), | 14 | Object(HashMap<String, JsExpression>), |
| 16 | Operation { | 15 | Binary { |
| 17 | operator: Operator, | 16 | operator: BinaryOperator, |
| 18 | left: Box<JsExpression>, | 17 | left: Box<JsExpression>, |
| 19 | right: Box<JsExpression>, | 18 | right: Box<JsExpression>, |
| 20 | }, | 19 | }, |
| ... | @@ -22,8 +21,8 @@ pub enum JsExpression { | ... | @@ -22,8 +21,8 @@ pub enum JsExpression { |
| 22 | arguments: Vec<String>, | 21 | arguments: Vec<String>, |
| 23 | value: Vec<JsStatement>, | 22 | value: Vec<JsStatement>, |
| 24 | }, | 23 | }, |
| 25 | FunctionCall { | 24 | Call { |
| 26 | name: Box<JsExpression>, | 25 | base: Box<JsExpression>, |
| 27 | arguments: Vec<JsExpression>, | 26 | arguments: Vec<JsExpression>, |
| 28 | }, | 27 | }, |
| 29 | } | 28 | } |
crates/js_printer/Cargo.toml-1| ... | @@ -11,4 +11,3 @@ thiserror.workspace = true | ... | @@ -11,4 +11,3 @@ thiserror.workspace = true |
| 11 | num.workspace = true | 11 | num.workspace = true |
| 12 | 12 | ||
| 13 | js_ast = { path = "../js_ast" } | 13 | js_ast = { path = "../js_ast" } |
| 14 | util = { path = "../util" } |
crates/js_printer/src/print_expression.rs+10-11| ... | @@ -41,7 +41,7 @@ pub fn print_expression(statement: &JsExpression) -> String { | ... | @@ -41,7 +41,7 @@ pub fn print_expression(statement: &JsExpression) -> String { |
| 41 | .collect::<Vec<String>>() | 41 | .collect::<Vec<String>>() |
| 42 | .join(",") | 42 | .join(",") |
| 43 | ), | 43 | ), |
| 44 | JsExpression::Operation { | 44 | JsExpression::Binary { |
| 45 | operator, | 45 | operator, |
| 46 | left, | 46 | left, |
| 47 | right, | 47 | right, |
| ... | @@ -60,9 +60,9 @@ pub fn print_expression(statement: &JsExpression) -> String { | ... | @@ -60,9 +60,9 @@ pub fn print_expression(statement: &JsExpression) -> String { |
| 60 | .collect::<Vec<String>>() | 60 | .collect::<Vec<String>>() |
| 61 | .join("") | 61 | .join("") |
| 62 | ), | 62 | ), |
| 63 | JsExpression::FunctionCall { name, arguments } => format!( | 63 | JsExpression::Call { base, arguments } => format!( |
| 64 | "{}({})", | 64 | "{}({})", |
| 65 | match *name.clone() { | 65 | match *base.clone() { |
| 66 | JsExpression::Identifier(value) => value.clone(), | 66 | JsExpression::Identifier(value) => value.clone(), |
| 67 | JsExpression::Raw(value) => value.clone(), | 67 | JsExpression::Raw(value) => value.clone(), |
| 68 | _ => panic!("Function call name must be an identifier"), | 68 | _ => panic!("Function call name must be an identifier"), |
| ... | @@ -83,7 +83,6 @@ mod tests { | ... | @@ -83,7 +83,6 @@ mod tests { |
| 83 | use js_ast::JsStatement; | 83 | use js_ast::JsStatement; |
| 84 | use num::{bigint::Sign, BigInt}; | 84 | use num::{bigint::Sign, BigInt}; |
| 85 | use std::collections::HashMap; | 85 | use std::collections::HashMap; |
| 86 | use util::Operator; | ||
| 87 | 86 | ||
| 88 | #[test] | 87 | #[test] |
| 89 | fn number() { | 88 | fn number() { |
| ... | @@ -184,16 +183,16 @@ mod tests { | ... | @@ -184,16 +183,16 @@ mod tests { |
| 184 | #[test] | 183 | #[test] |
| 185 | fn operation() { | 184 | fn operation() { |
| 186 | assert_eq!( | 185 | assert_eq!( |
| 187 | print_expression(&JsExpression::Operation { | 186 | print_expression(&JsExpression::Binary { |
| 188 | operator: Operator::Add, | 187 | operator: AnyOperator::Add, |
| 189 | left: Box::new(JsExpression::Number(1.0)), | 188 | left: Box::new(JsExpression::Number(1.0)), |
| 190 | right: Box::new(JsExpression::Number(2.0)), | 189 | right: Box::new(JsExpression::Number(2.0)), |
| 191 | }), | 190 | }), |
| 192 | "1+2" | 191 | "1+2" |
| 193 | ); | 192 | ); |
| 194 | assert_eq!( | 193 | assert_eq!( |
| 195 | print_expression(&JsExpression::Operation { | 194 | print_expression(&JsExpression::Binary { |
| 196 | operator: Operator::Subtract, | 195 | operator: AnyOperator::Sub, |
| 197 | left: Box::new(JsExpression::Number(1.0)), | 196 | left: Box::new(JsExpression::Number(1.0)), |
| 198 | right: Box::new(JsExpression::Number(2.0)), | 197 | right: Box::new(JsExpression::Number(2.0)), |
| 199 | }), | 198 | }), |
| ... | @@ -242,21 +241,21 @@ mod tests { | ... | @@ -242,21 +241,21 @@ mod tests { |
| 242 | #[test] | 241 | #[test] |
| 243 | fn function_call() { | 242 | fn function_call() { |
| 244 | assert_eq!( | 243 | assert_eq!( |
| 245 | print_expression(&JsExpression::FunctionCall { | 244 | print_expression(&JsExpression::Call { |
| 246 | name: Box::new(JsExpression::Identifier("hello".to_string())), | 245 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 247 | arguments: vec![], | 246 | arguments: vec![], |
| 248 | }), | 247 | }), |
| 249 | "hello()" | 248 | "hello()" |
| 250 | ); | 249 | ); |
| 251 | assert_eq!( | 250 | assert_eq!( |
| 252 | print_expression(&JsExpression::FunctionCall { | 251 | print_expression(&JsExpression::Call { |
| 253 | name: Box::new(JsExpression::Identifier("hello".to_string())), | 252 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 254 | arguments: vec![JsExpression::Number(1.0)], | 253 | arguments: vec![JsExpression::Number(1.0)], |
| 255 | }), | 254 | }), |
| 256 | "hello(1)" | 255 | "hello(1)" |
| 257 | ); | 256 | ); |
| 258 | assert_eq!( | 257 | assert_eq!( |
| 259 | print_expression(&JsExpression::FunctionCall { | 258 | print_expression(&JsExpression::Call { |
| 260 | name: Box::new(JsExpression::Identifier("hello".to_string())), | 259 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 261 | arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], | 260 | arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], |
| 262 | }), | 261 | }), |
crates/parser/Cargo.toml created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | [package] | ||
| 2 | name = "parser" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | path = "./parser.rs" | ||
| 8 | |||
| 9 | [dependencies] | ||
| 10 | miette.workspace = true | ||
| 11 | thiserror.workspace = true | ||
| 12 | num.workspace = true | ||
| 13 | |||
| 14 | ast = { path = "../ast" } | ||
| 15 | tokenizer = { path = "../tokenizer" } | ||
crates/parser/parse_block.rs created+64| ... | @@ -0,0 +1,64 @@ | ||
| 1 | use crate::{parse_statement::parse_statement, Parser}; | ||
| 2 | use ast::Block; | ||
| 3 | use miette::Result; | ||
| 4 | use tokenizer::Token; | ||
| 5 | |||
| 6 | /// Precondition: The first token exists and is '{' | ||
| 7 | pub fn parse_block(p: &mut Parser) -> Result<Block> { | ||
| 8 | match p.next() { | ||
| 9 | Some(Token::OpenBrace) => (), | ||
| 10 | _ => unreachable!(), | ||
| 11 | } | ||
| 12 | |||
| 13 | let mut statements = Vec::new(); | ||
| 14 | |||
| 15 | let was_top_level = p.is_top_level; | ||
| 16 | p.is_top_level = false; | ||
| 17 | |||
| 18 | loop { | ||
| 19 | let statement = match parse_statement(p) { | ||
| 20 | Ok(statement) => statement, | ||
| 21 | Err(err) => { | ||
| 22 | p.is_top_level = was_top_level; | ||
| 23 | return Err(err); | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | statements.push(statement.clone()); | ||
| 27 | |||
| 28 | match p.next() { | ||
| 29 | Some(Token::Semicolon) => loop { | ||
| 30 | match p.peek() { | ||
| 31 | Some(Token::ClosedBrace) => { | ||
| 32 | p.next(); | ||
| 33 | p.is_top_level = was_top_level; | ||
| 34 | return Ok(Block { | ||
| 35 | statements, | ||
| 36 | has_implicit_return: false, | ||
| 37 | }); | ||
| 38 | } | ||
| 39 | _ => continue, | ||
| 40 | } | ||
| 41 | }, | ||
| 42 | Some(Token::ClosedBrace) => { | ||
| 43 | if statement.requires_semicolon() { | ||
| 44 | p.is_top_level = was_top_level; | ||
| 45 | return Err(p | ||
| 46 | .error("Expected semicolon after this statement".to_string()) | ||
| 47 | .into()); | ||
| 48 | } | ||
| 49 | |||
| 50 | p.is_top_level = was_top_level; | ||
| 51 | return Ok(Block { | ||
| 52 | statements, | ||
| 53 | has_implicit_return: true, | ||
| 54 | }); | ||
| 55 | } | ||
| 56 | _ => { | ||
| 57 | p.is_top_level = was_top_level; | ||
| 58 | return Err(p | ||
| 59 | .error("Expected semicolon or '}' after statement".to_string()) | ||
| 60 | .into()); | ||
| 61 | } | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | } | ||
crates/parser/parse_call_arguments.rs created+31| ... | @@ -0,0 +1,31 @@ | ||
| 1 | use ast::Expr; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::{parse_expr::parse_expr, Parser}; | ||
| 6 | |||
| 7 | /// Precondition: The '(' was already consumed | ||
| 8 | pub fn parse_call_arguments(p: &mut Parser) -> Result<Vec<Expr>> { | ||
| 9 | let mut args = Vec::new(); | ||
| 10 | |||
| 11 | loop { | ||
| 12 | let arg = parse_expr(p)?; | ||
| 13 | args.push(arg); | ||
| 14 | |||
| 15 | match p.peek() { | ||
| 16 | Some(Token::Comma) => { | ||
| 17 | _ = p.next(); | ||
| 18 | continue; | ||
| 19 | } | ||
| 20 | Some(Token::ClosedParen) => { | ||
| 21 | _ = p.next(); | ||
| 22 | return Ok(args); | ||
| 23 | } | ||
| 24 | _ => { | ||
| 25 | return Err(p | ||
| 26 | .error("Expected ',' or ')' after argument".to_string()) | ||
| 27 | .into()) | ||
| 28 | } | ||
| 29 | } | ||
| 30 | } | ||
| 31 | } | ||
crates/parser/parse_declaration.rs created+46| ... | @@ -0,0 +1,46 @@ | ||
| 1 | use ast::{Decl, Type}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_expr::parse_expr; | ||
| 6 | use crate::parse_type::parse_type; | ||
| 7 | use crate::Parser; | ||
| 8 | |||
| 9 | /// Precondition: The first token exists and is either 'var' or 'let' | ||
| 10 | pub fn parse_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | ||
| 11 | let loc = p.loc(); | ||
| 12 | let is_constant = match p.next() { | ||
| 13 | Some(Token::Var) => false, | ||
| 14 | Some(Token::Let) => true, | ||
| 15 | _ => unreachable!(), | ||
| 16 | }; | ||
| 17 | let name = match p.next() { | ||
| 18 | Some(Token::Identifier(name)) => name, | ||
| 19 | Some(tok) => return Err(p.error(format!("Unexpected {}", tok)).into()), | ||
| 20 | None => return Err(p.error(format!("Unexpected end of file")).into()), | ||
| 21 | }; | ||
| 22 | let return_type = match p.peek() { | ||
| 23 | Some(Token::Colon) => { | ||
| 24 | p.next(); | ||
| 25 | parse_type(p)? | ||
| 26 | } | ||
| 27 | _ => Type::Infer, | ||
| 28 | }; | ||
| 29 | let init = match p.next() { | ||
| 30 | Some(Token::Equal) => parse_expr(p)?, | ||
| 31 | _ => { | ||
| 32 | return Err(p | ||
| 33 | .error("Variable must have an initializer".to_string()) | ||
| 34 | .into()) | ||
| 35 | } | ||
| 36 | }; | ||
| 37 | Ok(Decl { | ||
| 38 | name: name.clone(), | ||
| 39 | decl_type: return_type, | ||
| 40 | init, | ||
| 41 | is_constant, | ||
| 42 | is_pub, | ||
| 43 | comment: None, | ||
| 44 | loc, | ||
| 45 | }) | ||
| 46 | } | ||
crates/parser/parse_expr.rs created+161| ... | @@ -0,0 +1,161 @@ | ||
| 1 | use ast::{BinaryOperator, Expr, ExprData, Literal}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::{ | ||
| 6 | parse_call_arguments::parse_call_arguments, parse_expr_basic::parse_expr_basic, Parser, | ||
| 7 | }; | ||
| 8 | |||
| 9 | /// Derived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table | ||
| 10 | |||
| 11 | #[derive(PartialEq, PartialOrd)] | ||
| 12 | enum Level { | ||
| 13 | Prefix, // +, -, !, ~ | ||
| 14 | Exponentiation, // ** | ||
| 15 | Multiplicative, // *, /, % | ||
| 16 | Additive, // +, - | ||
| 17 | BitwiseShift, // <<, >>, >>> | ||
| 18 | Relational, // <, <=, >, >= | ||
| 19 | Equality, // ==, != | ||
| 20 | BitwiseAnd, // & | ||
| 21 | BitwiseXor, // ^ | ||
| 22 | BitwiseOr, // | | ||
| 23 | LogicalAnd, // && | ||
| 24 | LogicalOr, // ||, ?? | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn get_postfix_op_level(token: &Token) -> Option<Level> { | ||
| 28 | match token { | ||
| 29 | Token::StarStar => Some(Level::Exponentiation), | ||
| 30 | Token::Star | Token::Slash | Token::Percent => Some(Level::Multiplicative), | ||
| 31 | Token::Plus | Token::Minus => Some(Level::Additive), | ||
| 32 | Token::LtLt | Token::GtGt | Token::GtGtGt => Some(Level::BitwiseShift), | ||
| 33 | Token::Gt | Token::GtEqual | Token::Lt | Token::LtEqual => Some(Level::Relational), | ||
| 34 | Token::EqualEqual | Token::BangEqual => Some(Level::Equality), | ||
| 35 | Token::And => Some(Level::BitwiseAnd), | ||
| 36 | Token::Caret => Some(Level::BitwiseXor), | ||
| 37 | Token::Or => Some(Level::BitwiseOr), | ||
| 38 | Token::AndAnd => Some(Level::LogicalAnd), | ||
| 39 | Token::OrOr => Some(Level::LogicalOr), | ||
| 40 | _ => None, | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | pub fn parse_expr(p: &mut Parser) -> Result<Expr> { | ||
| 45 | let mut expr = parse_expr_basic(p)?; | ||
| 46 | |||
| 47 | // Edge case for return. This has already parsed all the way to the end of the expression | ||
| 48 | if let ExprData::Return(_) = expr.data { | ||
| 49 | return Ok(expr); | ||
| 50 | } | ||
| 51 | |||
| 52 | // Parse function calls and field accesses | ||
| 53 | loop { | ||
| 54 | match p.peek() { | ||
| 55 | Some(Token::Dot) => { | ||
| 56 | let loc = p.loc(); | ||
| 57 | _ = p.next(); | ||
| 58 | let field = match p.peek() { | ||
| 59 | Some(Token::Identifier(name)) => { | ||
| 60 | let loc = p.loc(); | ||
| 61 | p.next(); | ||
| 62 | Expr::new(ExprData::Literal(Literal::String(name)), loc) | ||
| 63 | } | ||
| 64 | _ => { | ||
| 65 | return Err(p.error("Expected identifier after dot".to_string()).into()); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | expr = Expr::new( | ||
| 69 | ExprData::Field { | ||
| 70 | base: Box::new(expr), | ||
| 71 | field: Box::new(field), | ||
| 72 | }, | ||
| 73 | loc, | ||
| 74 | ); | ||
| 75 | } | ||
| 76 | Some(Token::OpenParen) => { | ||
| 77 | let loc = p.loc(); | ||
| 78 | _ = p.next(); | ||
| 79 | let arguments = parse_call_arguments(p)?; | ||
| 80 | expr = Expr::new( | ||
| 81 | ExprData::Call { | ||
| 82 | base: Box::new(expr), | ||
| 83 | arguments, | ||
| 84 | }, | ||
| 85 | loc, | ||
| 86 | ); | ||
| 87 | } | ||
| 88 | Some(Token::OpenBracket) => { | ||
| 89 | let loc = p.loc(); | ||
| 90 | _ = p.next(); | ||
| 91 | let index = parse_expr(p)?; | ||
| 92 | if let Some(Token::ClosedBracket) = p.next() { | ||
| 93 | expr = Expr::new( | ||
| 94 | ExprData::Field { | ||
| 95 | base: Box::new(expr), | ||
| 96 | field: Box::new(index), | ||
| 97 | }, | ||
| 98 | loc, | ||
| 99 | ); | ||
| 100 | } else { | ||
| 101 | return Err(p | ||
| 102 | .error("Expected closing bracket after index".to_string()) | ||
| 103 | .into()); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | _ => break, | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | // Parse operators | ||
| 111 | loop { | ||
| 112 | if let Some(token) = p.peek() { | ||
| 113 | if let Some(level) = get_postfix_op_level(&token) { | ||
| 114 | _ = p.next(); | ||
| 115 | expr = parse_expr_binary( | ||
| 116 | p, | ||
| 117 | BinaryOperator::from_token(&token).unwrap(), | ||
| 118 | expr.clone(), | ||
| 119 | level, | ||
| 120 | )?; | ||
| 121 | continue; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | break; | ||
| 125 | } | ||
| 126 | |||
| 127 | Ok(expr) | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Prerequisite: `p.peek()` is an operator | ||
| 131 | fn parse_expr_binary(p: &mut Parser, op: BinaryOperator, left: Expr, level: Level) -> Result<Expr> { | ||
| 132 | let loc = p.loc(); | ||
| 133 | let mut right = parse_expr_basic(p)?; | ||
| 134 | |||
| 135 | loop { | ||
| 136 | if let Some(token) = p.peek() { | ||
| 137 | if let Some(next_level) = get_postfix_op_level(&token) { | ||
| 138 | if next_level <= level { | ||
| 139 | _ = p.next(); | ||
| 140 | right = parse_expr_binary( | ||
| 141 | p, | ||
| 142 | BinaryOperator::from_token(&token).unwrap(), | ||
| 143 | right, | ||
| 144 | next_level, | ||
| 145 | )?; | ||
| 146 | continue; | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | break; | ||
| 151 | } | ||
| 152 | |||
| 153 | Ok(Expr::new( | ||
| 154 | ExprData::Binary { | ||
| 155 | op, | ||
| 156 | left: Box::new(left), | ||
| 157 | right: Box::new(right), | ||
| 158 | }, | ||
| 159 | loc, | ||
| 160 | )) | ||
| 161 | } | ||
crates/parser/parse_expr_basic.rs created+110| ... | @@ -0,0 +1,110 @@ | ||
| 1 | use ast::{BuiltinId, Expr, ExprData, Literal, UnaryOperator}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::{ | ||
| 6 | parse_block::parse_block, parse_call_arguments::parse_call_arguments, parse_expr::parse_expr, | ||
| 7 | parse_expr_if::parse_expr_if, Parser, | ||
| 8 | }; | ||
| 9 | |||
| 10 | pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ||
| 11 | let loc = p.loc(); | ||
| 12 | match p.peek() { | ||
| 13 | Some(Token::Number(number)) => { | ||
| 14 | _ = p.next(); | ||
| 15 | Ok(Expr::new( | ||
| 16 | ExprData::Literal(Literal::Number(number.clone())), | ||
| 17 | loc, | ||
| 18 | )) | ||
| 19 | } | ||
| 20 | Some(Token::String(string)) => { | ||
| 21 | _ = p.next(); | ||
| 22 | Ok(Expr::new( | ||
| 23 | ExprData::Literal(Literal::String(string.clone())), | ||
| 24 | loc, | ||
| 25 | )) | ||
| 26 | } | ||
| 27 | Some(Token::True) => { | ||
| 28 | _ = p.next(); | ||
| 29 | Ok(Expr::new(ExprData::Literal(Literal::Bool(true)), loc)) | ||
| 30 | } | ||
| 31 | Some(Token::False) => { | ||
| 32 | _ = p.next(); | ||
| 33 | Ok(Expr::new(ExprData::Literal(Literal::Bool(false)), loc)) | ||
| 34 | } | ||
| 35 | Some(Token::Void) => { | ||
| 36 | _ = p.next(); | ||
| 37 | Ok(Expr::new(ExprData::Literal(Literal::Void), loc)) | ||
| 38 | } | ||
| 39 | Some(Token::Identifier(name)) => { | ||
| 40 | let loc = p.loc(); | ||
| 41 | _ = p.next(); | ||
| 42 | if name.starts_with("@") { | ||
| 43 | if let Some(id) = BuiltinId::from_str(name.as_str()) { | ||
| 44 | if let Some(Token::OpenParen) = p.next() { | ||
| 45 | let arguments = parse_call_arguments(p)?; | ||
| 46 | return Ok(Expr::new(ExprData::Builtin { id, arguments }, loc)); | ||
| 47 | } else { | ||
| 48 | return Err(p | ||
| 49 | .error_at(format!("Built-in function '{}' must be called", name), loc) | ||
| 50 | .into()); | ||
| 51 | } | ||
| 52 | } else { | ||
| 53 | return Err(p | ||
| 54 | .error_at(format!("Non-existant built-in: '{}'", name), loc) | ||
| 55 | .into()); | ||
| 56 | } | ||
| 57 | } else { | ||
| 58 | Ok(Expr::new(ExprData::Identifier(name), loc)) | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | Some(Token::Return) => { | ||
| 63 | if p.is_top_level { | ||
| 64 | return Err(p | ||
| 65 | .error("'return' cannot be used at top level".to_string()) | ||
| 66 | .into()); | ||
| 67 | } else { | ||
| 68 | _ = p.next(); | ||
| 69 | |||
| 70 | return Ok(Expr::new(ExprData::Return(Box::new(parse_expr(p)?)), loc)); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | Some(Token::If) => Ok(Expr::new(ExprData::If(parse_expr_if(p)?), loc)), | ||
| 75 | |||
| 76 | Some(Token::OpenBrace) => Ok(Expr::new(ExprData::Block(parse_block(p)?), loc)), | ||
| 77 | |||
| 78 | Some(Token::OpenParen) => { | ||
| 79 | _ = p.next(); | ||
| 80 | let expr = parse_expr_basic(p)?; | ||
| 81 | match p.next() { | ||
| 82 | Some(Token::ClosedParen) => (), | ||
| 83 | Some(token) => { | ||
| 84 | return Err(p.error(format!("Expected ')', but got {:?}", token)).into()) | ||
| 85 | } | ||
| 86 | None => return Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 87 | } | ||
| 88 | Ok(expr) | ||
| 89 | } | ||
| 90 | |||
| 91 | Some(t @ Token::Bang) | Some(t @ Token::Minus) | Some(t @ Token::Tilde) => { | ||
| 92 | _ = p.next(); | ||
| 93 | Ok(Expr::new( | ||
| 94 | ExprData::Unary { | ||
| 95 | op: match t { | ||
| 96 | Token::Bang => UnaryOperator::Not, | ||
| 97 | Token::Minus => UnaryOperator::Neg, | ||
| 98 | Token::Tilde => UnaryOperator::BitNot, | ||
| 99 | _ => unreachable!(), | ||
| 100 | }, | ||
| 101 | value: Box::new(parse_expr_basic(p)?), | ||
| 102 | }, | ||
| 103 | loc, | ||
| 104 | )) | ||
| 105 | } | ||
| 106 | |||
| 107 | Some(t) => Err(p.error(format!("Unexpected '{}'", t)).into()), | ||
| 108 | None => Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 109 | } | ||
| 110 | } | ||
crates/parser/parse_expr_if.rs created+61| ... | @@ -0,0 +1,61 @@ | ||
| 1 | use ast::{BlockWithLoc, If}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::{parse_block::parse_block, parse_expr::parse_expr, Parser}; | ||
| 6 | |||
| 7 | /// Precondition: the first token exists and is 'if' | ||
| 8 | pub fn parse_expr_if(p: &mut Parser) -> Result<If> { | ||
| 9 | match p.next() { | ||
| 10 | Some(Token::If) => (), | ||
| 11 | _ => unreachable!(), | ||
| 12 | } | ||
| 13 | |||
| 14 | match p.peek() { | ||
| 15 | Some(Token::OpenBrace) => { | ||
| 16 | return Err(p | ||
| 17 | .error( | ||
| 18 | "To use a block as the condition for an if statement, wrap it in parentheses" | ||
| 19 | .to_string(), | ||
| 20 | ) | ||
| 21 | .into()); | ||
| 22 | } | ||
| 23 | _ => (), | ||
| 24 | } | ||
| 25 | |||
| 26 | let condition = Box::new(parse_expr(p)?); | ||
| 27 | |||
| 28 | let yes = match p.peek() { | ||
| 29 | Some(Token::OpenBrace) => { | ||
| 30 | let loc = p.loc(); | ||
| 31 | BlockWithLoc { | ||
| 32 | block: parse_block(p)?, | ||
| 33 | loc, | ||
| 34 | } | ||
| 35 | } | ||
| 36 | _ => { | ||
| 37 | return Err(p | ||
| 38 | .error("Expected a block after condition".to_string()) | ||
| 39 | .into()) | ||
| 40 | } | ||
| 41 | }; | ||
| 42 | |||
| 43 | let no = match p.peek() { | ||
| 44 | Some(Token::Else) => { | ||
| 45 | _ = p.next(); | ||
| 46 | match p.peek() { | ||
| 47 | Some(Token::OpenBrace) => { | ||
| 48 | let loc = p.loc(); | ||
| 49 | Some(BlockWithLoc { | ||
| 50 | block: parse_block(p)?, | ||
| 51 | loc, | ||
| 52 | }) | ||
| 53 | } | ||
| 54 | _ => return Err(p.error("Expected a block after else".to_string()).into()), | ||
| 55 | } | ||
| 56 | } | ||
| 57 | _ => None, | ||
| 58 | }; | ||
| 59 | |||
| 60 | Ok(If { condition, yes, no }) | ||
| 61 | } | ||
crates/parser/parse_fn_declaration.rs created+111| ... | @@ -0,0 +1,111 @@ | ||
| 1 | use ast::{Decl, Expr, ExprData, Type}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_block::parse_block; | ||
| 6 | use crate::parse_param::parse_param; | ||
| 7 | use crate::parse_type::parse_type; | ||
| 8 | use crate::Parser; | ||
| 9 | |||
| 10 | /// Precondition: | ||
| 11 | pub fn parse_fn_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | ||
| 12 | let loc = p.loc(); | ||
| 13 | match p.next() { | ||
| 14 | Some(Token::Fn) => (), | ||
| 15 | _ => unreachable!(), | ||
| 16 | } | ||
| 17 | |||
| 18 | let name = match p.next() { | ||
| 19 | Some(Token::Identifier(name)) => name, | ||
| 20 | _ => return Err(p.error("Expected identifier after 'fn'".to_string()).into()), | ||
| 21 | }; | ||
| 22 | |||
| 23 | match p.peek() { | ||
| 24 | Some(Token::OpenParen) => p.next(), | ||
| 25 | _ => { | ||
| 26 | return Err(p | ||
| 27 | .error("Expected '(' after function name".to_string()) | ||
| 28 | .into()) | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | |||
| 32 | let mut params = Vec::new(); | ||
| 33 | |||
| 34 | loop { | ||
| 35 | match p.peek() { | ||
| 36 | Some(Token::ClosedParen) => { | ||
| 37 | _ = p.next(); | ||
| 38 | break; | ||
| 39 | } | ||
| 40 | Some(Token::Identifier(_)) => { | ||
| 41 | params.push(parse_param(p)?); | ||
| 42 | |||
| 43 | match p.peek() { | ||
| 44 | Some(Token::Comma) => { | ||
| 45 | p.next(); | ||
| 46 | if let Some(Token::ClosedParen) = p.peek() { | ||
| 47 | p.next(); | ||
| 48 | break; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | Some(Token::ClosedParen) => break, | ||
| 52 | _ => { | ||
| 53 | return Err(p | ||
| 54 | .error("Expected ',' or ')' after identifier".to_string()) | ||
| 55 | .into()) | ||
| 56 | } | ||
| 57 | }; | ||
| 58 | } | ||
| 59 | _ => { | ||
| 60 | return Err(p | ||
| 61 | .error("Expected ')' or identifier after '('".to_string()) | ||
| 62 | .into()) | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | match p.peek() { | ||
| 67 | Some(Token::Comma) => p.next(), | ||
| 68 | Some(Token::ClosedParen) => { | ||
| 69 | _ = p.next(); | ||
| 70 | break; | ||
| 71 | } | ||
| 72 | _ => { | ||
| 73 | return Err(p | ||
| 74 | .error("Expected ',' or ')' after identifier".to_string()) | ||
| 75 | .into()) | ||
| 76 | } | ||
| 77 | }; | ||
| 78 | } | ||
| 79 | |||
| 80 | let return_type = match p.peek() { | ||
| 81 | Some(Token::RightArrow) => { | ||
| 82 | _ = p.next(); | ||
| 83 | parse_type(p)? | ||
| 84 | } | ||
| 85 | _ => Type::Infer, | ||
| 86 | }; | ||
| 87 | |||
| 88 | if let Some(Token::OpenBrace) = p.peek() { | ||
| 89 | let block_loc = p.loc(); | ||
| 90 | let block = parse_block(p)?; | ||
| 91 | let function_body = ExprData::Fn { | ||
| 92 | name: name.clone(), | ||
| 93 | params, | ||
| 94 | return_type, | ||
| 95 | block, | ||
| 96 | }; | ||
| 97 | Ok(Decl { | ||
| 98 | name, | ||
| 99 | decl_type: Type::Any, | ||
| 100 | init: Expr::new(function_body, block_loc), | ||
| 101 | is_constant: true, | ||
| 102 | is_pub, | ||
| 103 | comment: None, | ||
| 104 | loc: loc, | ||
| 105 | }) | ||
| 106 | } else { | ||
| 107 | return Err(p | ||
| 108 | .error("Expected a block to follow function declaration".to_string()) | ||
| 109 | .into()); | ||
| 110 | } | ||
| 111 | } | ||
crates/parser/parse_module.rs created+33| ... | @@ -0,0 +1,33 @@ | ||
| 1 | use crate::{parse_top_level_statement::parse_top_level_statement, Parser}; | ||
| 2 | use ast::Module; | ||
| 3 | use miette::Result; | ||
| 4 | use tokenizer::Token; | ||
| 5 | |||
| 6 | pub fn parse_module(tokens: &mut Parser) -> Result<Module> { | ||
| 7 | let mut statements = Vec::new(); | ||
| 8 | |||
| 9 | loop { | ||
| 10 | let statement = parse_top_level_statement(tokens)?; | ||
| 11 | |||
| 12 | match tokens.peek() { | ||
| 13 | Some(Token::Semicolon) => { | ||
| 14 | while let Some(Token::Semicolon) = tokens.peek() { | ||
| 15 | tokens.next(); | ||
| 16 | } | ||
| 17 | } | ||
| 18 | _ => { | ||
| 19 | if statement.requires_semicolon() { | ||
| 20 | return Err(tokens | ||
| 21 | .error("Expected semicolon after statement".to_string()) | ||
| 22 | .into()); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | |||
| 27 | statements.push(statement); | ||
| 28 | |||
| 29 | if tokens.empty() { | ||
| 30 | return Ok(Module { statements }); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
crates/parser/parse_param.rs created+30| ... | @@ -0,0 +1,30 @@ | ||
| 1 | use ast::Param; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::{parse_type::parse_type, Parser}; | ||
| 6 | |||
| 7 | /// Precondition: A token exists and is an identifier | ||
| 8 | pub fn parse_param(p: &mut Parser) -> Result<Param> { | ||
| 9 | let name = match p.next() { | ||
| 10 | Some(Token::Identifier(name)) => name.clone(), | ||
| 11 | _ => unreachable!(), | ||
| 12 | }; | ||
| 13 | |||
| 14 | let param_type = match p.peek() { | ||
| 15 | Some(Token::Colon) => { | ||
| 16 | _ = p.next(); | ||
| 17 | parse_type(p)? | ||
| 18 | } | ||
| 19 | _ => { | ||
| 20 | return Err(p | ||
| 21 | .error("Function parameters require type annotations".to_string()) | ||
| 22 | .into()) | ||
| 23 | } | ||
| 24 | }; | ||
| 25 | |||
| 26 | Ok(Param { | ||
| 27 | name: name.clone(), | ||
| 28 | param_type, | ||
| 29 | }) | ||
| 30 | } | ||
crates/parser/parse_statement.rs created+28| ... | @@ -0,0 +1,28 @@ | ||
| 1 | use ast::Statement; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_declaration::parse_declaration; | ||
| 6 | use crate::parse_expr::parse_expr; | ||
| 7 | use crate::parse_fn_declaration::parse_fn_declaration; | ||
| 8 | use crate::Parser; | ||
| 9 | |||
| 10 | pub fn parse_statement(p: &mut Parser) -> Result<Statement> { | ||
| 11 | match p.peek() { | ||
| 12 | Some(Token::Pub) => { | ||
| 13 | _ = p.next(); | ||
| 14 | match p.peek() { | ||
| 15 | Some(Token::Var) | Some(Token::Let) => { | ||
| 16 | Ok(Statement::Decl(parse_declaration(p, true)?)) | ||
| 17 | } | ||
| 18 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(p, true)?)), | ||
| 19 | Some(_) => Err(p.error("Expected a declaration".to_string()).into()), | ||
| 20 | None => Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 21 | } | ||
| 22 | } | ||
| 23 | Some(Token::Var) | Some(Token::Let) => Ok(Statement::Decl(parse_declaration(p, false)?)), | ||
| 24 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(p, false)?)), | ||
| 25 | Some(_) => Ok(Statement::Expr(parse_expr(p)?)), | ||
| 26 | None => Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 27 | } | ||
| 28 | } | ||
crates/parser/parse_top_level_statement.rs created+10| ... | @@ -0,0 +1,10 @@ | ||
| 1 | use ast::TopLevelStatement; | ||
| 2 | use miette::Result; | ||
| 3 | |||
| 4 | use crate::{parse_statement::parse_statement, Parser}; | ||
| 5 | |||
| 6 | /// Precondition: the next token exists | ||
| 7 | pub fn parse_top_level_statement(p: &mut Parser) -> Result<TopLevelStatement> { | ||
| 8 | // TODO: we will eventually have real statements that are exclusive to top-level | ||
| 9 | Ok(parse_statement(p)?.to_top_level()) | ||
| 10 | } | ||
crates/parser/parse_type.rs created+26| ... | @@ -0,0 +1,26 @@ | ||
| 1 | use ast::Type; | ||
| 2 | use miette::Result; | ||
| 3 | |||
| 4 | use tokenizer::Token; | ||
| 5 | |||
| 6 | use crate::Parser; | ||
| 7 | |||
| 8 | pub fn parse_type(p: &mut Parser) -> Result<Type> { | ||
| 9 | match p.next() { | ||
| 10 | Some(Token::Identifier(id)) => match id.as_str() { | ||
| 11 | "int" => Ok(Type::Int), | ||
| 12 | "float" => Ok(Type::Float), | ||
| 13 | "bigint" => Ok(Type::BigInt), | ||
| 14 | "bool" => Ok(Type::Bool), | ||
| 15 | "string" => Ok(Type::String), | ||
| 16 | invalid_type => Err(p | ||
| 17 | .error(format!( | ||
| 18 | "Invalid type '{}' (TODO: add custom type support)", | ||
| 19 | invalid_type | ||
| 20 | )) | ||
| 21 | .into()), | ||
| 22 | }, | ||
| 23 | Some(unexpected) => Err(p.error(format!("Expected type, got {}", unexpected)).into()), | ||
| 24 | None => Err(p.error("Expected type, got EOF".to_string()).into()), | ||
| 25 | } | ||
| 26 | } | ||
crates/parser/parser.rs created+104| ... | @@ -0,0 +1,104 @@ | ||
| 1 | mod parse_block; | ||
| 2 | mod parse_call_arguments; | ||
| 3 | mod parse_declaration; | ||
| 4 | mod parse_expr; | ||
| 5 | mod parse_expr_basic; | ||
| 6 | mod parse_expr_if; | ||
| 7 | mod parse_fn_declaration; | ||
| 8 | mod parse_module; | ||
| 9 | mod parse_param; | ||
| 10 | mod parse_statement; | ||
| 11 | mod parse_top_level_statement; | ||
| 12 | mod parse_type; | ||
| 13 | |||
| 14 | use tokenizer::{Loc, Token}; | ||
| 15 | |||
| 16 | use miette::Diagnostic; | ||
| 17 | use thiserror::Error; | ||
| 18 | |||
| 19 | use ast::Module; | ||
| 20 | use miette::Result; | ||
| 21 | use parse_module::parse_module; | ||
| 22 | use tokenizer::tokenize; | ||
| 23 | |||
| 24 | pub fn parse(tokens: Vec<Token>) -> Result<Module> { | ||
| 25 | let mut p = Parser::new(tokens); | ||
| 26 | parse_module(&mut p) | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn parse_from_string(string: String) -> Result<Module> { | ||
| 30 | let tokens = tokenize(string)?; | ||
| 31 | parse(tokens) | ||
| 32 | } | ||
| 33 | |||
| 34 | #[derive(Error, Diagnostic, Debug)] | ||
| 35 | #[error("ParseError")] | ||
| 36 | pub struct ParseError { | ||
| 37 | #[help] | ||
| 38 | error: String, | ||
| 39 | loc: Option<Loc>, | ||
| 40 | } | ||
| 41 | |||
| 42 | impl ParseError { | ||
| 43 | pub fn new(error: String, loc: Option<Loc>) -> ParseError { | ||
| 44 | ParseError { error, loc } | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | #[derive(Debug)] | ||
| 49 | pub struct Parser { | ||
| 50 | pub tokens: Vec<Token>, | ||
| 51 | pub cursor: usize, | ||
| 52 | |||
| 53 | /// Whether or not the parser is at the top level | ||
| 54 | pub is_top_level: bool, | ||
| 55 | } | ||
| 56 | |||
| 57 | impl Parser { | ||
| 58 | pub fn new(tokens: Vec<Token>) -> Parser { | ||
| 59 | Parser { | ||
| 60 | tokens, | ||
| 61 | cursor: 0, | ||
| 62 | is_top_level: true, | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | pub fn empty(&self) -> bool { | ||
| 67 | self.cursor == self.tokens.len() | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn back(&mut self) { | ||
| 71 | self.cursor -= 1; | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn next(&mut self) -> Option<Token> { | ||
| 75 | if self.cursor == self.tokens.len() { | ||
| 76 | return None; | ||
| 77 | } | ||
| 78 | |||
| 79 | let token = self.tokens[self.cursor].clone(); | ||
| 80 | self.cursor += 1; | ||
| 81 | Some(token) | ||
| 82 | } | ||
| 83 | |||
| 84 | pub fn peek(&self) -> Option<Token> { | ||
| 85 | if self.cursor == self.tokens.len() { | ||
| 86 | return None; | ||
| 87 | } | ||
| 88 | |||
| 89 | Some(self.tokens[self.cursor].clone()) | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn error(&self, error: String) -> ParseError { | ||
| 93 | // TODO(@lino-levan): Add location information | ||
| 94 | ParseError::new(error, None) | ||
| 95 | } | ||
| 96 | |||
| 97 | fn loc(&self) -> Loc { | ||
| 98 | return Loc {}; | ||
| 99 | } | ||
| 100 | |||
| 101 | fn error_at(&self, error: String, loc: Loc) -> ParseError { | ||
| 102 | ParseError::new(error, Some(loc)) | ||
| 103 | } | ||
| 104 | } | ||
crates/tokenizer/Cargo.toml-2| ... | @@ -8,5 +8,3 @@ edition = "2021" | ... | @@ -8,5 +8,3 @@ edition = "2021" |
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | miette.workspace = true | 9 | miette.workspace = true |
| 10 | thiserror.workspace = true | 10 | thiserror.workspace = true |
| 11 | |||
| 12 | util = { path = "../util" } |
crates/tokenizer/src/lib.rs+150-64| ... | @@ -1,7 +1,8 @@ | ... | @@ -1,7 +1,8 @@ |
| 1 | use miette::{Diagnostic, Result}; | 1 | use miette::{Diagnostic, Result}; |
| 2 | use thiserror::Error; | 2 | use thiserror::Error; |
| 3 | 3 | ||
| 4 | use util::Operator; | 4 | #[derive(Debug, Clone)] |
| 5 | pub struct Loc {} | ||
| 5 | 6 | ||
| 6 | #[derive(Error, Diagnostic, Debug)] | 7 | #[derive(Error, Diagnostic, Debug)] |
| 7 | #[error("UnknownCharacter")] | 8 | #[error("UnknownCharacter")] |
| ... | @@ -16,69 +17,118 @@ pub enum Token { | ... | @@ -16,69 +17,118 @@ pub enum Token { |
| 16 | Identifier(String), | 17 | Identifier(String), |
| 17 | Number(f64), | 18 | Number(f64), |
| 18 | String(String), | 19 | String(String), |
| 19 | BuiltIn(String), | ||
| 20 | // Comments | 20 | // Comments |
| 21 | Comment(String), | 21 | Comment(String), |
| 22 | DocComment(String), | 22 | DocComment(String), |
| 23 | // Keywords | 23 | // Keywords |
| 24 | True, | 24 | Else, |
| 25 | False, | 25 | False, |
| 26 | Let, | 26 | Fn, |
| 27 | Var, | ||
| 28 | If, | 27 | If, |
| 29 | Else, | 28 | Let, |
| 30 | Return, | ||
| 31 | Pub, | 29 | Pub, |
| 32 | Function, | 30 | Return, |
| 33 | RightArrow, | 31 | True, |
| 34 | Comma, | 32 | Var, |
| 35 | OpenParanthesis, | 33 | Void, |
| 36 | ClosedParanthesis, | 34 | // Symbols |
| 37 | OpenBracket, | 35 | And, // & |
| 38 | ClosedBracket, | 36 | AndAnd, // && |
| 39 | Colon, | 37 | Bang, // ! |
| 40 | Equals, | 38 | BangEqual, // != |
| 41 | Semicolon, | 39 | Caret, // ^ |
| 42 | // Operators | 40 | ClosedBrace, // } |
| 43 | Operator(Operator), | 41 | ClosedBracket, // ] |
| 42 | ClosedParen, // ) | ||
| 43 | Colon, // : | ||
| 44 | Comma, // , | ||
| 45 | Equal, // = | ||
| 46 | EqualEqual, // == | ||
| 47 | Gt, // > | ||
| 48 | GtEqual, // >= | ||
| 49 | GtGt, // >> | ||
| 50 | GtGtGt, // >>> | ||
| 51 | Lt, // < | ||
| 52 | LtEqual, // <= | ||
| 53 | LtLt, // << | ||
| 54 | Minus, // - | ||
| 55 | OpenBrace, // { | ||
| 56 | OpenBracket, // [ | ||
| 57 | OpenParen, // ( | ||
| 58 | Or, // | | ||
| 59 | OrOr, // || | ||
| 60 | Percent, // % | ||
| 61 | Plus, // + | ||
| 62 | RightArrow, // -> | ||
| 63 | Semicolon, // ; | ||
| 64 | Slash, // / | ||
| 65 | Star, // * | ||
| 66 | StarStar, // ** | ||
| 67 | Tilde, // ~ | ||
| 68 | Dot, // . | ||
| 44 | } | 69 | } |
| 45 | 70 | ||
| 46 | impl std::fmt::Display for Token { | 71 | impl std::fmt::Display for Token { |
| 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 48 | let token = match self { | 73 | let token = match self { |
| 49 | Token::Identifier(_) => "an identifier".to_string(), | 74 | Token::Identifier(_) => "an identifier".to_string(), |
| 75 | |||
| 50 | Token::Number(n) => n.to_string(), | 76 | Token::Number(n) => n.to_string(), |
| 51 | Token::String(s) => format!("\"{}\"", s), | 77 | Token::String(s) => format!("\"{}\"", s), |
| 52 | Token::BuiltIn(_) => "a builtin identifier".to_string(), | ||
| 53 | Token::Comment(_) => "a comment".to_string(), | 78 | Token::Comment(_) => "a comment".to_string(), |
| 54 | Token::DocComment(_) => "a documentation comment".to_string(), | 79 | Token::DocComment(_) => "a documentation comment".to_string(), |
| 55 | Token::True => "true".to_string(), | 80 | |
| 81 | Token::Else => "else".to_string(), | ||
| 56 | Token::False => "false".to_string(), | 82 | Token::False => "false".to_string(), |
| 57 | Token::Let => "let".to_string(), | 83 | Token::Fn => "fn".to_string(), |
| 58 | Token::Var => "var".to_string(), | ||
| 59 | Token::If => "if".to_string(), | 84 | Token::If => "if".to_string(), |
| 60 | Token::Else => "else".to_string(), | 85 | Token::Let => "let".to_string(), |
| 61 | Token::Return => "return".to_string(), | ||
| 62 | Token::Pub => "pub".to_string(), | 86 | Token::Pub => "pub".to_string(), |
| 63 | Token::Function => "fn".to_string(), | 87 | Token::Return => "return".to_string(), |
| 64 | Token::RightArrow => "->".to_string(), | 88 | Token::True => "true".to_string(), |
| 65 | Token::Comma => ",".to_string(), | 89 | Token::Var => "var".to_string(), |
| 66 | Token::OpenParanthesis => "(".to_string(), | 90 | Token::Void => "void".to_string(), |
| 67 | Token::ClosedParanthesis => ")".to_string(), | 91 | |
| 68 | Token::OpenBracket => "{".to_string(), | 92 | Token::And => "&".to_string(), |
| 69 | Token::ClosedBracket => "}".to_string(), | 93 | Token::AndAnd => "&&".to_string(), |
| 94 | Token::Bang => "!".to_string(), | ||
| 95 | Token::BangEqual => "!=".to_string(), | ||
| 96 | Token::Caret => "^".to_string(), | ||
| 97 | Token::ClosedBrace => "}".to_string(), | ||
| 98 | Token::ClosedBracket => "]".to_string(), | ||
| 99 | Token::ClosedParen => ")".to_string(), | ||
| 70 | Token::Colon => ":".to_string(), | 100 | Token::Colon => ":".to_string(), |
| 71 | Token::Equals => "=".to_string(), | 101 | Token::Comma => ",".to_string(), |
| 102 | Token::Equal => "=".to_string(), | ||
| 103 | Token::EqualEqual => "==".to_string(), | ||
| 104 | Token::Gt => ">".to_string(), | ||
| 105 | Token::GtEqual => ">=".to_string(), | ||
| 106 | Token::GtGt => ">>".to_string(), | ||
| 107 | Token::Lt => "<".to_string(), | ||
| 108 | Token::LtEqual => "<=".to_string(), | ||
| 109 | Token::LtLt => "<<".to_string(), | ||
| 110 | Token::Minus => "-".to_string(), | ||
| 111 | Token::OpenBrace => "{".to_string(), | ||
| 112 | Token::OpenBracket => "[".to_string(), | ||
| 113 | Token::OpenParen => "(".to_string(), | ||
| 114 | Token::Or => "|".to_string(), | ||
| 115 | Token::OrOr => "||".to_string(), | ||
| 116 | Token::Percent => "%".to_string(), | ||
| 117 | Token::Plus => "+".to_string(), | ||
| 118 | Token::RightArrow => "->".to_string(), | ||
| 72 | Token::Semicolon => ";".to_string(), | 119 | Token::Semicolon => ";".to_string(), |
| 73 | Token::Operator(o) => o.clone().to_string(), | 120 | Token::Slash => "/".to_string(), |
| 121 | Token::Star => "*".to_string(), | ||
| 122 | Token::StarStar => "**".to_string(), | ||
| 123 | Token::Tilde => "~".to_string(), | ||
| 124 | Token::Dot => ".".to_string(), | ||
| 125 | Token::GtGtGt => ">>>".to_string(), | ||
| 74 | }; | 126 | }; |
| 75 | 127 | ||
| 76 | write!(f, "{}", token) | 128 | write!(f, "{}", token) |
| 77 | } | 129 | } |
| 78 | } | 130 | } |
| 79 | 131 | ||
| 80 | |||
| 81 | |||
| 82 | pub fn tokenize(content: String) -> Result<Vec<Token>> { | 132 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 83 | let mut tokens: Vec<Token> = Vec::new(); | 133 | let mut tokens: Vec<Token> = Vec::new(); |
| 84 | let mut chars = content.chars().enumerate().peekable(); | 134 | let mut chars = content.chars().enumerate().peekable(); |
| ... | @@ -105,22 +155,17 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -105,22 +155,17 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 105 | } | 155 | } |
| 106 | 156 | ||
| 107 | match current_token.as_str() { | 157 | match current_token.as_str() { |
| 108 | "var" => tokens.push(Token::Var), | ||
| 109 | "let" => tokens.push(Token::Let), | ||
| 110 | "if" => tokens.push(Token::If), | ||
| 111 | "else" => tokens.push(Token::Else), | 158 | "else" => tokens.push(Token::Else), |
| 159 | "false" => tokens.push(Token::False), | ||
| 160 | "fn" => tokens.push(Token::Fn), | ||
| 161 | "if" => tokens.push(Token::If), | ||
| 162 | "let" => tokens.push(Token::Let), | ||
| 112 | "pub" => tokens.push(Token::Pub), | 163 | "pub" => tokens.push(Token::Pub), |
| 113 | "fn" => tokens.push(Token::Function), | ||
| 114 | "return" => tokens.push(Token::Return), | 164 | "return" => tokens.push(Token::Return), |
| 115 | "true" => tokens.push(Token::True), | 165 | "true" => tokens.push(Token::True), |
| 116 | "false" => tokens.push(Token::False), | 166 | "var" => tokens.push(Token::Var), |
| 117 | _ => { | 167 | "void" => tokens.push(Token::Void), |
| 118 | if current_token.starts_with('@') { | 168 | _ => tokens.push(Token::Identifier(current_token)), |
| 119 | tokens.push(Token::BuiltIn(current_token)); | ||
| 120 | } else { | ||
| 121 | tokens.push(Token::Identifier(current_token)) | ||
| 122 | } | ||
| 123 | } | ||
| 124 | } | 169 | } |
| 125 | } | 170 | } |
| 126 | '"' => { | 171 | '"' => { |
| ... | @@ -169,39 +214,53 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -169,39 +214,53 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 169 | '=' => match chars.peek() { | 214 | '=' => match chars.peek() { |
| 170 | Some((_, '=')) => { | 215 | Some((_, '=')) => { |
| 171 | chars.next(); | 216 | chars.next(); |
| 172 | tokens.push(Token::Operator(Operator::Equals)); | 217 | tokens.push(Token::EqualEqual); |
| 173 | } | 218 | } |
| 174 | _ => tokens.push(Token::Equals), | 219 | _ => tokens.push(Token::Equal), |
| 175 | }, | 220 | }, |
| 176 | ';' => tokens.push(Token::Semicolon), | 221 | ';' => tokens.push(Token::Semicolon), |
| 177 | '>' => match chars.peek() { | 222 | '>' => match chars.peek() { |
| 178 | Some((_, '=')) => { | 223 | Some((_, '=')) => { |
| 179 | chars.next(); | 224 | chars.next(); |
| 180 | tokens.push(Token::Operator(Operator::GreaterThanOrEquals)); | 225 | tokens.push(Token::GtEqual); |
| 181 | } | 226 | } |
| 182 | _ => tokens.push(Token::Operator(Operator::GreaterThan)), | 227 | Some((_, '>')) => { |
| 228 | chars.next(); | ||
| 229 | match chars.peek() { | ||
| 230 | Some((_, '>')) => { | ||
| 231 | chars.next(); | ||
| 232 | tokens.push(Token::GtGtGt); | ||
| 233 | } | ||
| 234 | _ => tokens.push(Token::GtGt), | ||
| 235 | } | ||
| 236 | } | ||
| 237 | _ => tokens.push(Token::Gt), | ||
| 183 | }, | 238 | }, |
| 184 | '<' => match chars.peek() { | 239 | '<' => match chars.peek() { |
| 185 | Some((_, '=')) => { | 240 | Some((_, '=')) => { |
| 186 | chars.next(); | 241 | chars.next(); |
| 187 | tokens.push(Token::Operator(Operator::LessThanOrEquals)); | 242 | tokens.push(Token::LtEqual); |
| 243 | } | ||
| 244 | Some((_, '<')) => { | ||
| 245 | chars.next(); | ||
| 246 | tokens.push(Token::LtLt); | ||
| 188 | } | 247 | } |
| 189 | _ => tokens.push(Token::Operator(Operator::LessThan)), | 248 | _ => tokens.push(Token::Lt), |
| 190 | }, | 249 | }, |
| 191 | '+' => tokens.push(Token::Operator(Operator::Add)), | 250 | '+' => tokens.push(Token::Plus), |
| 192 | '-' => match chars.peek() { | 251 | '-' => match chars.peek() { |
| 193 | Some((_, '>')) => { | 252 | Some((_, '>')) => { |
| 194 | chars.next(); | 253 | chars.next(); |
| 195 | tokens.push(Token::RightArrow); | 254 | tokens.push(Token::RightArrow); |
| 196 | } | 255 | } |
| 197 | _ => tokens.push(Token::Operator(Operator::Subtract)), | 256 | _ => tokens.push(Token::Minus), |
| 198 | }, | 257 | }, |
| 199 | '*' => match chars.peek() { | 258 | '*' => match chars.peek() { |
| 200 | Some((_, '*')) => { | 259 | Some((_, '*')) => { |
| 201 | chars.next(); | 260 | chars.next(); |
| 202 | tokens.push(Token::Operator(Operator::Power)); | 261 | tokens.push(Token::StarStar); |
| 203 | } | 262 | } |
| 204 | _ => tokens.push(Token::Operator(Operator::Multiply)), | 263 | _ => tokens.push(Token::Star), |
| 205 | }, | 264 | }, |
| 206 | '/' => { | 265 | '/' => { |
| 207 | match chars.peek() { | 266 | match chars.peek() { |
| ... | @@ -239,18 +298,45 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -239,18 +298,45 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 239 | tokens.push(Token::Comment(comment)); | 298 | tokens.push(Token::Comment(comment)); |
| 240 | } | 299 | } |
| 241 | } | 300 | } |
| 242 | _ => tokens.push(Token::Operator(Operator::Divide)), | 301 | _ => tokens.push(Token::Slash), |
| 243 | } | 302 | } |
| 244 | } | 303 | } |
| 245 | ',' => tokens.push(Token::Comma), | 304 | ',' => tokens.push(Token::Comma), |
| 246 | '(' => tokens.push(Token::OpenParanthesis), | 305 | '(' => tokens.push(Token::OpenParen), |
| 247 | ')' => tokens.push(Token::ClosedParanthesis), | 306 | ')' => tokens.push(Token::ClosedParen), |
| 248 | '{' => tokens.push(Token::OpenBracket), | 307 | '{' => tokens.push(Token::OpenBrace), |
| 249 | '}' => tokens.push(Token::ClosedBracket), | 308 | '}' => tokens.push(Token::ClosedBrace), |
| 309 | '[' => tokens.push(Token::OpenBracket), | ||
| 310 | ']' => tokens.push(Token::ClosedBracket), | ||
| 311 | '&' => match chars.peek() { | ||
| 312 | Some((_, '&')) => { | ||
| 313 | chars.next(); | ||
| 314 | tokens.push(Token::AndAnd); | ||
| 315 | } | ||
| 316 | _ => tokens.push(Token::And), | ||
| 317 | }, | ||
| 318 | '|' => match chars.peek() { | ||
| 319 | Some((_, '|')) => { | ||
| 320 | chars.next(); | ||
| 321 | tokens.push(Token::OrOr); | ||
| 322 | } | ||
| 323 | _ => tokens.push(Token::Or), | ||
| 324 | }, | ||
| 325 | '!' => match chars.peek() { | ||
| 326 | Some((_, '=')) => { | ||
| 327 | chars.next(); | ||
| 328 | tokens.push(Token::BangEqual); | ||
| 329 | } | ||
| 330 | _ => tokens.push(Token::Bang), | ||
| 331 | }, | ||
| 332 | '^' => tokens.push(Token::Caret), | ||
| 333 | '%' => tokens.push(Token::Percent), | ||
| 334 | '~' => tokens.push(Token::Tilde), | ||
| 335 | '.' => tokens.push(Token::Dot), | ||
| 250 | s if s.is_whitespace() => (), | 336 | s if s.is_whitespace() => (), |
| 251 | _ => { | 337 | _ => { |
| 252 | return Err(UnknownCharacter { | 338 | return Err(UnknownCharacter { |
| 253 | error: format!("Unsupported character: {}", c), | 339 | error: format!("Unexpected '{}' while tokenizing", c), |
| 254 | } | 340 | } |
| 255 | .into()) | 341 | .into()) |
| 256 | } | 342 | } |
crates/util/Cargo.toml deleted-8| ... | @@ -1,8 +0,0 @@ | ||
| 1 | [package] | ||
| 2 | name = "util" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | |||
| 8 | [dependencies] | ||
crates/util/src/lib.rs deleted-30| ... | @@ -1,30 +0,0 @@ | ||
| 1 | #[derive(Debug, Clone, PartialEq)] | ||
| 2 | pub enum Operator { | ||
| 3 | Add, | ||
| 4 | Subtract, | ||
| 5 | Multiply, | ||
| 6 | Divide, | ||
| 7 | Power, | ||
| 8 | Equals, | ||
| 9 | GreaterThan, | ||
| 10 | LessThan, | ||
| 11 | GreaterThanOrEquals, | ||
| 12 | LessThanOrEquals, | ||
| 13 | } | ||
| 14 | |||
| 15 | impl Operator { | ||
| 16 | pub fn to_string(self) -> String { | ||
| 17 | String::from(match self { | ||
| 18 | Self::Add => "+", | ||
| 19 | Self::Subtract => "-", | ||
| 20 | Self::Divide => "/", | ||
| 21 | Self::Multiply => "*", | ||
| 22 | Self::Power => "**", | ||
| 23 | Self::Equals => "===", | ||
| 24 | Self::GreaterThan => ">", | ||
| 25 | Self::LessThan => "<", | ||
| 26 | Self::GreaterThanOrEquals => ">=", | ||
| 27 | Self::LessThanOrEquals => "<=", | ||
| 28 | }) | ||
| 29 | } | ||
| 30 | } | ||
examples/basics/control.blight+1-17| ... | @@ -1,17 +1 @@ | ... | @@ -1,17 +1 @@ |
| 1 | let print = @raw("console.log"); | 1 | let x: bool = 1 * 3 + 2 / 2 == 4 + 2; |
| 2 | |||
| 3 | let x: bool = true; | ||
| 4 | |||
| 5 | if x { | ||
| 6 | print("x is true!"); | ||
| 7 | } | ||
| 8 | |||
| 9 | let y: int = 3; | ||
| 10 | |||
| 11 | if y == 1 { | ||
| 12 | print("y is 1!"); | ||
| 13 | } else if y == 2 { | ||
| 14 | print("y is 2!"); | ||
| 15 | } else { | ||
| 16 | print("y is not 1 or 2!"); | ||
| 17 | } |