44 files changed, 1941 insertions(+), 1245 deletions(-)
Cargo.lock+13-9| ... | ... | @@ -70,9 +70,9 @@ name = "ast" |
| 70 | 70 | version = "0.1.0" |
| 71 | 71 | dependencies = [ |
| 72 | 72 | "miette", |
| 73 | "num", | |
| 73 | 74 | "thiserror", |
| 74 | 75 | "tokenizer", |
| 75 | "util", | |
| 76 | 76 | ] |
| 77 | 77 | |
| 78 | 78 | [[package]] |
| ... | ... | @@ -134,7 +134,6 @@ dependencies = [ |
| 134 | 134 | "miette", |
| 135 | 135 | "num", |
| 136 | 136 | "thiserror", |
| 137 | "util", | |
| 138 | 137 | ] |
| 139 | 138 | |
| 140 | 139 | [[package]] |
| ... | ... | @@ -187,6 +186,7 @@ dependencies = [ |
| 187 | 186 | "js_ast", |
| 188 | 187 | "js_printer", |
| 189 | 188 | "miette", |
| 189 | "parser", | |
| 190 | 190 | "thiserror", |
| 191 | 191 | "tokenizer", |
| 192 | 192 | ] |
| ... | ... | @@ -250,7 +250,6 @@ dependencies = [ |
| 250 | 250 | "miette", |
| 251 | 251 | "num", |
| 252 | 252 | "thiserror", |
| 253 | "util", | |
| 254 | 253 | ] |
| 255 | 254 | |
| 256 | 255 | [[package]] |
| ... | ... | @@ -261,7 +260,6 @@ dependencies = [ |
| 261 | 260 | "miette", |
| 262 | 261 | "num", |
| 263 | 262 | "thiserror", |
| 264 | "util", | |
| 265 | 263 | ] |
| 266 | 264 | |
| 267 | 265 | [[package]] |
| ... | ... | @@ -420,6 +418,17 @@ version = "3.5.0" |
| 420 | 418 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 421 | 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 | 432 | [[package]] |
| 424 | 433 | name = "proc-macro2" |
| 425 | 434 | version = "1.0.76" |
| ... | ... | @@ -555,7 +564,6 @@ version = "0.1.0" |
| 555 | 564 | dependencies = [ |
| 556 | 565 | "miette", |
| 557 | 566 | "thiserror", |
| 558 | "util", | |
| 559 | 567 | ] |
| 560 | 568 | |
| 561 | 569 | [[package]] |
| ... | ... | @@ -582,10 +590,6 @@ version = "0.2.1" |
| 582 | 590 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 583 | 591 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" |
| 584 | 592 | |
| 585 | [[package]] | |
| 586 | name = "util" | |
| 587 | version = "0.1.0" | |
| 588 | ||
| 589 | 593 | [[package]] |
| 590 | 594 | name = "winapi" |
| 591 | 595 | version = "0.3.9" |
Cargo.toml+10-2| ... | ... | @@ -3,12 +3,20 @@ version = "0.1.0" |
| 3 | 3 | name = "blight" |
| 4 | 4 | edition = "2023" |
| 5 | 5 | license = "MIT" |
| 6 | authors = ["Lino Le Van"] | |
| 6 | authors = ["Lino Le Van", "Dave Caruso"] | |
| 7 | 7 | repository = "https://github.com/lino-levan/blight" |
| 8 | 8 | |
| 9 | 9 | [workspace] |
| 10 | 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 | 21 | [workspace.dependencies] |
| 14 | 22 | clap = { version = "4.4.12", features = ["derive"] } |
crates/ast/Cargo.toml+2-1| ... | ... | @@ -4,10 +4,11 @@ version = "0.1.0" |
| 4 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| 7 | path = "./lib.rs" | |
| 7 | 8 | |
| 8 | 9 | [dependencies] |
| 9 | 10 | miette.workspace = true |
| 10 | 11 | thiserror.workspace = true |
| 12 | num.workspace = true | |
| 11 | 13 | |
| 12 | 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 | 10 | thiserror.workspace = true |
| 11 | 11 | num.workspace = true |
| 12 | 12 | |
| 13 | ast = { path = "../ast" } | |
| 14 | util = { path = "../util" } | |
| 13 | ast = { path = "../ast" } | |
| \ 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 | use ast::{Ast, Module, Type}; | |
| 2 | use miette::Result; | |
| 3 | ||
| 4 | use miette::Diagnostic; | |
| 5 | use thiserror::Error; | |
| 6 | ||
| 7 | #[derive(Error, Diagnostic, Debug)] | |
| 8 | #[error("TypeError")] | |
| 9 | pub struct TypeError { | |
| 10 | #[help] | |
| 11 | error: String, | |
| 12 | } | |
| 13 | ||
| 14 | impl TypeError { | |
| 15 | pub fn new(error: String) -> Self { | |
| 16 | Self { error } | |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | pub fn check_module(block: &Module) -> Result<()> { | |
| 21 | for statement in &block.value { | |
| 22 | check_expression(statement)?; | |
| 23 | } | |
| 24 | ||
| 25 | Ok(()) | |
| 26 | } | |
| 27 | ||
| 28 | pub fn resolve_type(expr: &Ast) -> Result<Type> { | |
| 29 | match expr { | |
| 30 | Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | |
| 31 | Ast::Declaration { | |
| 32 | name, | |
| 33 | decl_type, | |
| 34 | value, | |
| 35 | is_constant, | |
| 36 | } => Ok(Type::NoReturn), | |
| 37 | Ast::Operation { | |
| 38 | operator, | |
| 39 | left, | |
| 40 | right, | |
| 41 | } => merge_types(resolve_type(left)?, resolve_type(right)?), | |
| 42 | Ast::Function { | |
| 43 | name, | |
| 44 | arguments, | |
| 45 | return_type, | |
| 46 | block, | |
| 47 | } => todo!(), | |
| 48 | Ast::FunctionCall { name, arguments } => todo!(), | |
| 49 | Ast::If { | |
| 50 | condition, | |
| 51 | value, | |
| 52 | else_value, | |
| 53 | } => todo!(), | |
| 54 | Ast::Return(_) => Ok(Type::NoReturn), | |
| 55 | Ast::Public(_) => todo!(), // TODO: remove Public | |
| 56 | Ast::Block(_) => todo!(), | |
| 57 | Ast::Bool(_) => Ok(Type::Bool), | |
| 58 | Ast::String(_) => Ok(Type::String), | |
| 59 | Ast::Identifier(_) => todo!(), | |
| 60 | Ast::BuiltIn(_) => todo!(), | |
| 61 | ||
| 62 | Ast::Comment(_) => unreachable!(), | |
| 63 | Ast::DocComment(_) => unreachable!(), | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | pub fn merge_types(a: Type, b: Type) -> Result<Type> { | |
| 68 | match a { | |
| 69 | Type::Infer => return Ok(b), | |
| 70 | Type::Any => return Ok(Type::Any), | |
| 71 | _ => {} | |
| 72 | } | |
| 73 | ||
| 74 | match b { | |
| 75 | Type::Infer => return Ok(a), | |
| 76 | Type::Any => return Ok(Type::Any), | |
| 77 | _ => {} | |
| 78 | } | |
| 79 | ||
| 80 | if a == b { | |
| 81 | return Ok(a); | |
| 82 | } | |
| 83 | ||
| 84 | Err(TypeError::new(format!("Cannot merge {} and {}", a, b)).into()) | |
| 85 | } | |
| 86 | ||
| 87 | pub fn coerce_type(target: Type, source: Type) -> Result<Type> { | |
| 88 | match source { | |
| 89 | Type::Any => return Ok(target), | |
| 90 | _ => {} | |
| 91 | } | |
| 92 | match target { | |
| 93 | Type::Infer => Ok(source), | |
| 94 | Type::Any => Ok(Type::Any), | |
| 95 | ||
| 96 | Type::NumberLiteral(_) => unreachable!(), | |
| 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 | } | |
| 1 | // use ast::{ Module, Type}; | |
| 2 | // use miette::Result; | |
| 3 | ||
| 4 | // mod ast_type_coersion; | |
| 5 | ||
| 6 | // pub fn check_module(block: &Module) -> Result<()> { | |
| 7 | // for statement in &block.statements { | |
| 8 | // check_expression(statement)?; | |
| 9 | // } | |
| 10 | ||
| 11 | // Ok(()) | |
| 12 | // } | |
| 13 | ||
| 14 | // pub fn resolve_type(expr: &Ast) -> Result<Type> { | |
| 15 | // match expr { | |
| 16 | // Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | |
| 17 | // Ast::Declaration { | |
| 18 | // name, | |
| 19 | // decl_type, | |
| 20 | // value, | |
| 21 | // is_constant, | |
| 22 | // } => Ok(Type::NoReturn), | |
| 23 | // Ast::Binary { | |
| 24 | // operator, | |
| 25 | // left, | |
| 26 | // right, | |
| 27 | // } => merge_types(resolve_type(left)?, resolve_type(right)?), | |
| 28 | // Ast::Function { | |
| 29 | // name, | |
| 30 | // arguments, | |
| 31 | // return_type, | |
| 32 | // block, | |
| 33 | // } => todo!(), | |
| 34 | // Ast::FunctionCall { name, arguments } => todo!(), | |
| 35 | // Ast::If { | |
| 36 | // condition, | |
| 37 | // value, | |
| 38 | // else_value, | |
| 39 | // } => todo!(), | |
| 40 | // Ast::Return(_) => Ok(Type::NoReturn), | |
| 41 | // Ast::Public(_) => todo!(), // TODO: remove Public | |
| 42 | // Ast::Block(_) => todo!(), | |
| 43 | // Ast::Bool(_) => Ok(Type::Bool), | |
| 44 | // Ast::String(_) => Ok(Type::String), | |
| 45 | // Ast::Identifier(_) => todo!(), | |
| 46 | // Ast::BuiltIn(_) => todo!(), | |
| 47 | ||
| 48 | // Ast::Comment(_) => unreachable!(), | |
| 49 | // Ast::DocComment(_) => unreachable!(), | |
| 50 | // } | |
| 51 | // } | |
| 52 | ||
| 53 | // pub fn check_expression(expr: &Ast) -> Result<()> { | |
| 54 | // panic!() | |
| 55 | // match expr { | |
| 56 | // Ast::Declaration { | |
| 57 | // name, | |
| 58 | // decl_type: return_type, | |
| 59 | // value, | |
| 60 | // is_constant, | |
| 61 | // } => { | |
| 62 | // check_expression(value)?; | |
| 63 | // // coerce_type(return_type.clone(), resolve_type(value)?)?; | |
| 64 | // } | |
| 65 | // _ => {} | |
| 66 | // // Ast::Operation { | |
| 67 | // // operator, | |
| 68 | // // left, | |
| 69 | // // right, | |
| 70 | // // } => Ok(()), | |
| 71 | // // Ast::Function { | |
| 72 | // // name, | |
| 73 | // // arguments, | |
| 74 | // // return_type, | |
| 75 | // // block, | |
| 76 | // // } => Ok(()), | |
| 77 | // // Ast::FunctionCall { name, arguments } => Ok(()), | |
| 78 | // // Ast::If { | |
| 79 | // // condition, | |
| 80 | // // value, | |
| 81 | // // else_value, | |
| 82 | // // } => Ok(()), | |
| 83 | // // Ast::Return(_) => Ok(()), | |
| 84 | // // Ast::Public(_) => Ok(()), | |
| 85 | // // Ast::Block(_) => Ok(()), | |
| 86 | // // Ast::Number(_) => Ok(()), | |
| 87 | // // Ast::Bool(_) => Ok(()), | |
| 88 | // // Ast::String(_) => Ok(()), | |
| 89 | // // Ast::Identifier(_) => Ok(()), | |
| 90 | // // Ast::BuiltIn(_) => Ok(()), | |
| 91 | // // Ast::Comment(_) => Ok(()), | |
| 92 | // // Ast::DocComment(_) => Ok(()), | |
| 93 | // } | |
| 94 | ||
| 95 | // Ok(()) | |
| 96 | // } |
crates/cli/Cargo.toml+1| ... | ... | @@ -14,6 +14,7 @@ thiserror.workspace = true |
| 14 | 14 | |
| 15 | 15 | tokenizer = { path = "../tokenizer" } |
| 16 | 16 | ast = { path = "../ast" } |
| 17 | parser = { path = "../parser" } | |
| 17 | 18 | js_ast = { path = "../js_ast" } |
| 18 | 19 | js_printer = { path = "../js_printer" } |
| 19 | 20 | checker = { path = "../checker" } |
crates/cli/src/main.rs+18-4| ... | ... | @@ -1,10 +1,8 @@ |
| 1 | 1 | use std::{io::Write, path::PathBuf}; |
| 2 | 2 | |
| 3 | use checker; | |
| 4 | 3 | use clap::{Parser, Subcommand}; |
| 5 | 4 | use miette::Result; |
| 6 | 5 | |
| 7 | use ast; | |
| 8 | 6 | use js_printer; |
| 9 | 7 | use tokenizer; |
| 10 | 8 | |
| ... | ... | @@ -40,6 +38,9 @@ enum Commands { |
| 40 | 38 | #[arg(short, long, default_value = "deno")] |
| 41 | 39 | target: String, |
| 42 | 40 | }, |
| 41 | Astdump { | |
| 42 | file: PathBuf, | |
| 43 | }, | |
| 43 | 44 | } |
| 44 | 45 | |
| 45 | 46 | fn get_runtime(runtime: &String) -> String { |
| ... | ... | @@ -53,9 +54,9 @@ pub fn transpile(content: String) -> Result<String> { |
| 53 | 54 | // 1. Tokenize the input |
| 54 | 55 | let tokens = tokenizer::tokenize(content)?; |
| 55 | 56 | // 2. Parse the tokens into an AST |
| 56 | let blight_ast = ast::parse(tokens)?; | |
| 57 | let blight_ast = parser::parse(tokens)?; | |
| 57 | 58 | // 3. Type check the AST |
| 58 | checker::check_module(&blight_ast)?; | |
| 59 | // checker::check_module(&blight_ast)?; | |
| 59 | 60 | // 4. Optimize the AST |
| 60 | 61 | // optimize(tree.clone())?; |
| 61 | 62 | // 5. Translate the AST into javascript |
| ... | ... | @@ -66,6 +67,14 @@ pub fn transpile(content: String) -> Result<String> { |
| 66 | 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 | 78 | fn main() -> Result<()> { |
| 70 | 79 | let cli = Cli::parse(); |
| 71 | 80 | |
| ... | ... | @@ -145,6 +154,11 @@ fn main() -> Result<()> { |
| 145 | 154 | .wait() |
| 146 | 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 | 164 | Ok(()) |
crates/js_ast/Cargo.toml-1| ... | ... | @@ -11,4 +11,3 @@ thiserror.workspace = true |
| 11 | 11 | num.workspace = true |
| 12 | 12 | |
| 13 | 13 | ast = { path = "../ast" } |
| 14 | util = { path = "../util" } |
crates/js_ast/src/lib.rs+190-194| ... | ... | @@ -1,9 +1,8 @@ |
| 1 | 1 | use std::collections::HashMap; |
| 2 | 2 | |
| 3 | use ast::{Expr, ExprData, Literal, Module, Statement}; | |
| 3 | 4 | use miette::Result; |
| 4 | 5 | |
| 5 | use ast::{Ast, Module}; | |
| 6 | ||
| 7 | 6 | mod types; |
| 8 | 7 | pub use types::{JsExpression, JsStatement}; |
| 9 | 8 | |
| ... | ... | @@ -99,68 +98,69 @@ impl TranslateContext { |
| 99 | 98 | // here, we recursively expand out expressions into statements |
| 100 | 99 | // we then store the result of all the expressions in a variable |
| 101 | 100 | // and then return the name of that variable |
| 102 | fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression { | |
| 103 | match ast { | |
| 104 | Ast::Number(value) => JsExpression::Number(*value), | |
| 105 | Ast::String(value) => JsExpression::String(value.clone()), | |
| 106 | Ast::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), | |
| 107 | Ast::Bool(value) => JsExpression::Bool(*value), | |
| 108 | Ast::FunctionCall { name, arguments } => { | |
| 101 | fn get_expression_handle(ast: &Expr, ctx: &mut TranslateContext) -> JsExpression { | |
| 102 | match ast.data.clone() { | |
| 103 | ExprData::Literal(Literal::String(value)) => JsExpression::String(value), | |
| 104 | ExprData::Literal(Literal::Number(value)) => JsExpression::Number(value), | |
| 105 | ExprData::Literal(Literal::Void) => JsExpression::Identifier("null".to_string()), | |
| 106 | ExprData::Literal(Literal::Bool(value)) => JsExpression::Identifier(if value { | |
| 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 | 114 | let mut handles = Vec::new(); |
| 110 | 115 | for argument in arguments { |
| 111 | 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"), | |
| 116 | handles.push(get_expression_handle(&argument, ctx)); | |
| 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 { | |
| 136 | operator, | |
| 137 | left, | |
| 138 | right, | |
| 139 | } => { | |
| 140 | let left_handle = get_expression_handle(left, ctx); | |
| 141 | let right_handle = get_expression_handle(right, ctx); | |
| 142 | JsExpression::Operation { | |
| 143 | operator: operator.clone(), | |
| 124 | ExprData::Builtin { id, arguments } => match id { | |
| 125 | ast::BuiltinId::Raw => { | |
| 126 | if arguments.len() != 1 { | |
| 127 | panic!("@raw expects exactly one argument"); | |
| 128 | } | |
| 129 | match arguments.get(0).unwrap() { | |
| 130 | Expr { | |
| 131 | data: ExprData::Literal(Literal::String(value)), | |
| 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 | 143 | left: Box::new(left_handle), |
| 145 | 144 | right: Box::new(right_handle), |
| 146 | 145 | } |
| 147 | 146 | } |
| 148 | Ast::Block(block) => { | |
| 149 | if !block.implicit_return { | |
| 147 | ExprData::Block(block) => { | |
| 148 | if !block.has_implicit_return { | |
| 150 | 149 | panic!("Expression blocks must have an implicit return"); |
| 151 | 150 | } |
| 152 | 151 | ctx.push_context(); |
| 153 | for (i, statement) in block.value.iter().enumerate() { | |
| 154 | if i == block.value.len() - 1 { | |
| 152 | for (i, statement) in block.statements.iter().enumerate() { | |
| 153 | if i == block.statements.len() - 1 { | |
| 155 | 154 | break; |
| 156 | 155 | } else { |
| 157 | 156 | translate_statement(statement, ctx); |
| 158 | 157 | } |
| 159 | 158 | } |
| 160 | let result = get_expression_handle(block.value.last().unwrap(), ctx); | |
| 161 | let value = ctx.pop_context(); | |
| 162 | value.iter().for_each(|x| ctx.push(x.clone())); | |
| 163 | result | |
| 159 | todo!() | |
| 160 | // let result = get_expression_handle(block.statements.last().unwrap(), ctx); | |
| 161 | // let value = ctx.pop_context(); | |
| 162 | // value.iter().for_each(|x| ctx.push(x.clone())); | |
| 163 | // result | |
| 164 | 164 | } |
| 165 | 165 | _ => { |
| 166 | 166 | panic!("unimplemented: {:?}", ast); |
| ... | ... | @@ -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 | 172 | match ast { |
| 173 | Ast::Declaration { | |
| 174 | name, | |
| 175 | decl_type: _, | |
| 176 | value, | |
| 177 | is_constant, | |
| 178 | } => { | |
| 179 | let handle = get_expression_handle(value, ctx); | |
| 180 | let rename = ctx.declare_variable(name.clone()); | |
| 173 | Statement::Decl(decl) => { | |
| 174 | let handle = get_expression_handle(&decl.init, ctx); | |
| 175 | let rename = ctx.declare_variable(decl.name.clone()); | |
| 181 | 176 | ctx.push(JsStatement::Declaration { |
| 182 | 177 | name: rename, |
| 183 | 178 | value: handle, |
| 184 | is_constant: is_constant.clone(), | |
| 179 | is_constant: decl.is_constant.clone(), | |
| 185 | 180 | }); |
| 186 | 181 | } |
| 187 | Ast::Function { | |
| 188 | name, | |
| 189 | arguments, | |
| 190 | return_type: _, | |
| 191 | block, | |
| 192 | } => { | |
| 193 | let rename = ctx.declare_variable(name.clone()); | |
| 194 | let arguments = arguments | |
| 195 | .iter() | |
| 196 | .map(|x| ctx.declare_variable(x.name.clone())) | |
| 197 | .collect(); | |
| 182 | Statement::Expr(_) => todo!(), | |
| 183 | // Ast::Function { | |
| 184 | // name, | |
| 185 | // arguments, | |
| 186 | // return_type: _, | |
| 187 | // block, | |
| 188 | // } => { | |
| 189 | // let rename = ctx.declare_variable(name.clone()); | |
| 190 | // let arguments = arguments | |
| 191 | // .iter() | |
| 192 | // .map(|x| ctx.declare_variable(x.name.clone())) | |
| 193 | // .collect(); | |
| 198 | 194 | |
| 199 | ctx.push_context(); | |
| 200 | for (i, statement) in block.value.iter().enumerate() { | |
| 201 | if block.implicit_return && i == block.value.len() - 1 { | |
| 202 | let handle = get_expression_handle(statement, ctx); | |
| 203 | ctx.push(JsStatement::Return(handle)); | |
| 204 | } else { | |
| 205 | translate_statement(statement, ctx); | |
| 206 | } | |
| 207 | } | |
| 208 | let value = ctx.pop_context(); | |
| 195 | // ctx.push_context(); | |
| 196 | // for (i, statement) in block.value.iter().enumerate() { | |
| 197 | // if block.implicit_return && i == block.value.len() - 1 { | |
| 198 | // let handle = get_expression_handle(statement, ctx); | |
| 199 | // ctx.push(JsStatement::Return(handle)); | |
| 200 | // } else { | |
| 201 | // translate_statement(statement, ctx); | |
| 202 | // } | |
| 203 | // } | |
| 204 | // let value = ctx.pop_context(); | |
| 209 | 205 | |
| 210 | ctx.push(JsStatement::Function { | |
| 211 | name: rename, | |
| 212 | arguments, | |
| 213 | value, | |
| 214 | }); | |
| 215 | } | |
| 216 | Ast::Public(value) => { | |
| 217 | translate_statement(value, ctx); | |
| 218 | // I'm sorry, I just don't know how to get the original name properly... my bad | |
| 219 | match ctx.peek() { | |
| 220 | JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { | |
| 221 | let original_name = name.split("$").next().unwrap().to_string(); | |
| 222 | ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { | |
| 223 | name: original_name, | |
| 224 | value: JsExpression::Identifier(name.clone()), | |
| 225 | is_constant: false, | |
| 226 | }))); | |
| 227 | } | |
| 228 | _ => { | |
| 229 | panic!("Cannot make non-function public"); | |
| 230 | } | |
| 231 | } | |
| 232 | } | |
| 233 | Ast::Return(value) => { | |
| 234 | let handle = get_expression_handle(value, ctx); | |
| 235 | ctx.push(JsStatement::Return(handle)); | |
| 236 | } | |
| 237 | Ast::Block(block) => { | |
| 238 | ctx.push_context(); | |
| 239 | for (i, statement) in block.value.iter().enumerate() { | |
| 240 | if block.implicit_return && i == block.value.len() - 1 { | |
| 241 | let handle = get_expression_handle(statement, ctx); | |
| 242 | ctx.push(JsStatement::Return(handle)); | |
| 243 | } else { | |
| 244 | translate_statement(statement, ctx); | |
| 245 | } | |
| 246 | } | |
| 247 | let value = ctx.pop_context(); | |
| 248 | value.iter().for_each(|x| ctx.push(x.clone())); | |
| 249 | } | |
| 250 | Ast::If { | |
| 251 | condition, | |
| 252 | value: block, | |
| 253 | else_value, | |
| 254 | } => { | |
| 255 | let handle = get_expression_handle(condition, ctx); | |
| 206 | // ctx.push(JsStatement::Function { | |
| 207 | // name: rename, | |
| 208 | // arguments, | |
| 209 | // value, | |
| 210 | // }); | |
| 211 | // } | |
| 212 | // Ast::Public(value) => { | |
| 213 | // translate_statement(value, ctx); | |
| 214 | // // I'm sorry, I just don't know how to get the original name properly... my bad | |
| 215 | // match ctx.peek() { | |
| 216 | // JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { | |
| 217 | // let original_name = name.split("$").next().unwrap().to_string(); | |
| 218 | // ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { | |
| 219 | // name: original_name, | |
| 220 | // value: JsExpression::Identifier(name.clone()), | |
| 221 | // is_constant: true, | |
| 222 | // }))); | |
| 223 | // } | |
| 224 | // _ => { | |
| 225 | // panic!("Cannot make non-function public"); | |
| 226 | // } | |
| 227 | // } | |
| 228 | // } | |
| 229 | // Ast::Return(value) => { | |
| 230 | // let handle = get_expression_handle(value, ctx); | |
| 231 | // ctx.push(JsStatement::Return(handle)); | |
| 232 | // } | |
| 233 | // Ast::Block(block) => { | |
| 234 | // ctx.push_context(); | |
| 235 | // for (i, statement) in block.value.iter().enumerate() { | |
| 236 | // if block.implicit_return && i == block.value.len() - 1 { | |
| 237 | // let handle = get_expression_handle(statement, ctx); | |
| 238 | // ctx.push(JsStatement::Return(handle)); | |
| 239 | // } else { | |
| 240 | // translate_statement(statement, ctx); | |
| 241 | // } | |
| 242 | // } | |
| 243 | // let value = ctx.pop_context(); | |
| 244 | // value.iter().for_each(|x| ctx.push(x.clone())); | |
| 245 | // } | |
| 246 | // Ast::If { | |
| 247 | // condition, | |
| 248 | // value: block, | |
| 249 | // else_value, | |
| 250 | // } => { | |
| 251 | // let handle = get_expression_handle(condition, ctx); | |
| 256 | 252 | |
| 257 | ctx.push_context(); | |
| 258 | for (i, statement) in block.value.iter().enumerate() { | |
| 259 | if block.implicit_return && i == block.value.len() - 1 { | |
| 260 | let handle = get_expression_handle(statement, ctx); | |
| 261 | ctx.push(JsStatement::Return(handle)); | |
| 262 | } else { | |
| 263 | translate_statement(statement, ctx); | |
| 264 | } | |
| 265 | } | |
| 266 | let value = ctx.pop_context(); | |
| 267 | let else_value = match else_value { | |
| 268 | Some(block) => { | |
| 269 | ctx.push_context(); | |
| 270 | for (i, statement) in block.value.iter().enumerate() { | |
| 271 | if block.implicit_return && i == block.value.len() - 1 { | |
| 272 | let handle = get_expression_handle(statement, ctx); | |
| 273 | ctx.push(JsStatement::Return(handle)); | |
| 274 | } else { | |
| 275 | translate_statement(statement, ctx); | |
| 276 | } | |
| 277 | } | |
| 278 | Some(ctx.pop_context()) | |
| 279 | } | |
| 280 | None => None, | |
| 281 | }; | |
| 253 | // ctx.push_context(); | |
| 254 | // for (i, statement) in block.value.iter().enumerate() { | |
| 255 | // if block.implicit_return && i == block.value.len() - 1 { | |
| 256 | // let handle = get_expression_handle(statement, ctx); | |
| 257 | // ctx.push(JsStatement::Return(handle)); | |
| 258 | // } else { | |
| 259 | // translate_statement(statement, ctx); | |
| 260 | // } | |
| 261 | // } | |
| 262 | // let value = ctx.pop_context(); | |
| 263 | // let else_value = match else_value { | |
| 264 | // Some(block) => { | |
| 265 | // ctx.push_context(); | |
| 266 | // for (i, statement) in block.value.iter().enumerate() { | |
| 267 | // if block.implicit_return && i == block.value.len() - 1 { | |
| 268 | // let handle = get_expression_handle(statement, ctx); | |
| 269 | // ctx.push(JsStatement::Return(handle)); | |
| 270 | // } else { | |
| 271 | // translate_statement(statement, ctx); | |
| 272 | // } | |
| 273 | // } | |
| 274 | // Some(ctx.pop_context()) | |
| 275 | // } | |
| 276 | // None => None, | |
| 277 | // }; | |
| 282 | 278 | |
| 283 | ctx.push(JsStatement::If { | |
| 284 | condition: handle, | |
| 285 | value, | |
| 286 | else_value, | |
| 287 | }); | |
| 288 | } | |
| 289 | Ast::FunctionCall { name, arguments } => { | |
| 290 | let mut handles = Vec::new(); | |
| 291 | for argument in arguments { | |
| 292 | handles.push(get_expression_handle(argument, ctx)); | |
| 293 | } | |
| 294 | match *name.clone() { | |
| 295 | Ast::Identifier(name) => { | |
| 296 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | |
| 297 | name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | |
| 298 | arguments: handles, | |
| 299 | })); | |
| 300 | } | |
| 301 | Ast::BuiltIn(name) => match name.as_str() { | |
| 302 | "@raw" => { | |
| 303 | if handles.len() != 1 { | |
| 304 | panic!("@raw expects exactly one argument"); | |
| 305 | } | |
| 306 | match handles.get(0).unwrap() { | |
| 307 | JsExpression::String(value) => { | |
| 308 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | |
| 309 | name: Box::new(JsExpression::Raw(value.clone())), | |
| 310 | arguments: handles, | |
| 311 | })); | |
| 312 | } | |
| 313 | _ => panic!("@raw expects a string argument"), | |
| 314 | } | |
| 315 | } | |
| 316 | _ => panic!("Unknown built-in function {}", name), | |
| 317 | }, | |
| 318 | _ => panic!("Function name must be an identifier"), | |
| 319 | } | |
| 320 | } | |
| 321 | Ast::Comment(_) | Ast::DocComment(_) => {} | |
| 322 | _ => { | |
| 323 | panic!("unimplemented: {:?}", ast); | |
| 324 | } | |
| 279 | // ctx.push(JsStatement::If { | |
| 280 | // condition: handle, | |
| 281 | // value, | |
| 282 | // else_value, | |
| 283 | // }); | |
| 284 | // } | |
| 285 | // Ast::FunctionCall { name, arguments } => { | |
| 286 | // let mut handles = Vec::new(); | |
| 287 | // for argument in arguments { | |
| 288 | // handles.push(get_expression_handle(argument, ctx)); | |
| 289 | // } | |
| 290 | // match *name.clone() { | |
| 291 | // Ast::Identifier(name) => { | |
| 292 | // ctx.push(JsStatement::Expression(JsExpression::Call { | |
| 293 | // name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | |
| 294 | // arguments: handles, | |
| 295 | // })); | |
| 296 | // } | |
| 297 | // Ast::BuiltIn(name) => match name.as_str() { | |
| 298 | // "@raw" => { | |
| 299 | // if handles.len() != 1 { | |
| 300 | // panic!("@raw expects exactly one argument"); | |
| 301 | // } | |
| 302 | // match handles.get(0).unwrap() { | |
| 303 | // JsExpression::String(value) => { | |
| 304 | // ctx.push(JsStatement::Expression(JsExpression::Call { | |
| 305 | // name: Box::new(JsExpression::Raw(value.clone())), | |
| 306 | // arguments: handles, | |
| 307 | // })); | |
| 308 | // } | |
| 309 | // _ => panic!("@raw expects a string argument"), | |
| 310 | // } | |
| 311 | // } | |
| 312 | // _ => panic!("Unknown built-in function {}", name), | |
| 313 | // }, | |
| 314 | // _ => panic!("Function name must be an identifier"), | |
| 315 | // } | |
| 316 | // } | |
| 317 | // Ast::Comment(_) | Ast::DocComment(_) => {} | |
| 318 | // _ => { | |
| 319 | // panic!("unimplemented: {:?}", ast); | |
| 320 | // } | |
| 325 | 321 | } |
| 326 | 322 | } |
| 327 | 323 | |
| 328 | 324 | pub fn translate(module: &Module) -> Result<Vec<JsStatement>> { |
| 329 | 325 | let mut ctx = TranslateContext::new(); |
| 330 | 326 | |
| 331 | for ast in module.value.iter() { | |
| 332 | translate_statement(&ast, &mut ctx); | |
| 327 | for ast in module.statements.iter() { | |
| 328 | translate_statement(&ast.clone().to_statement(), &mut ctx); | |
| 333 | 329 | } |
| 334 | 330 | |
| 335 | 331 | Ok(ctx.pop_context()) |
crates/js_ast/src/types.rs+5-6| ... | ... | @@ -1,8 +1,7 @@ |
| 1 | use ast::BinaryOperator; | |
| 1 | 2 | use num::bigint::BigInt; |
| 2 | 3 | use std::collections::HashMap; |
| 3 | 4 | |
| 4 | use util::Operator; | |
| 5 | ||
| 6 | 5 | #[derive(Debug, PartialEq, Clone)] |
| 7 | 6 | pub enum JsExpression { |
| 8 | 7 | Number(f64), |
| ... | ... | @@ -13,8 +12,8 @@ pub enum JsExpression { |
| 13 | 12 | Raw(String), |
| 14 | 13 | Array(Vec<JsExpression>), |
| 15 | 14 | Object(HashMap<String, JsExpression>), |
| 16 | Operation { | |
| 17 | operator: Operator, | |
| 15 | Binary { | |
| 16 | operator: BinaryOperator, | |
| 18 | 17 | left: Box<JsExpression>, |
| 19 | 18 | right: Box<JsExpression>, |
| 20 | 19 | }, |
| ... | ... | @@ -22,8 +21,8 @@ pub enum JsExpression { |
| 22 | 21 | arguments: Vec<String>, |
| 23 | 22 | value: Vec<JsStatement>, |
| 24 | 23 | }, |
| 25 | FunctionCall { | |
| 26 | name: Box<JsExpression>, | |
| 24 | Call { | |
| 25 | base: Box<JsExpression>, | |
| 27 | 26 | arguments: Vec<JsExpression>, |
| 28 | 27 | }, |
| 29 | 28 | } |
crates/js_printer/Cargo.toml-1| ... | ... | @@ -11,4 +11,3 @@ thiserror.workspace = true |
| 11 | 11 | num.workspace = true |
| 12 | 12 | |
| 13 | 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 | 41 | .collect::<Vec<String>>() |
| 42 | 42 | .join(",") |
| 43 | 43 | ), |
| 44 | JsExpression::Operation { | |
| 44 | JsExpression::Binary { | |
| 45 | 45 | operator, |
| 46 | 46 | left, |
| 47 | 47 | right, |
| ... | ... | @@ -60,9 +60,9 @@ pub fn print_expression(statement: &JsExpression) -> String { |
| 60 | 60 | .collect::<Vec<String>>() |
| 61 | 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 | 66 | JsExpression::Identifier(value) => value.clone(), |
| 67 | 67 | JsExpression::Raw(value) => value.clone(), |
| 68 | 68 | _ => panic!("Function call name must be an identifier"), |
| ... | ... | @@ -83,7 +83,6 @@ mod tests { |
| 83 | 83 | use js_ast::JsStatement; |
| 84 | 84 | use num::{bigint::Sign, BigInt}; |
| 85 | 85 | use std::collections::HashMap; |
| 86 | use util::Operator; | |
| 87 | 86 | |
| 88 | 87 | #[test] |
| 89 | 88 | fn number() { |
| ... | ... | @@ -184,16 +183,16 @@ mod tests { |
| 184 | 183 | #[test] |
| 185 | 184 | fn operation() { |
| 186 | 185 | assert_eq!( |
| 187 | print_expression(&JsExpression::Operation { | |
| 188 | operator: Operator::Add, | |
| 186 | print_expression(&JsExpression::Binary { | |
| 187 | operator: AnyOperator::Add, | |
| 189 | 188 | left: Box::new(JsExpression::Number(1.0)), |
| 190 | 189 | right: Box::new(JsExpression::Number(2.0)), |
| 191 | 190 | }), |
| 192 | 191 | "1+2" |
| 193 | 192 | ); |
| 194 | 193 | assert_eq!( |
| 195 | print_expression(&JsExpression::Operation { | |
| 196 | operator: Operator::Subtract, | |
| 194 | print_expression(&JsExpression::Binary { | |
| 195 | operator: AnyOperator::Sub, | |
| 197 | 196 | left: Box::new(JsExpression::Number(1.0)), |
| 198 | 197 | right: Box::new(JsExpression::Number(2.0)), |
| 199 | 198 | }), |
| ... | ... | @@ -242,21 +241,21 @@ mod tests { |
| 242 | 241 | #[test] |
| 243 | 242 | fn function_call() { |
| 244 | 243 | assert_eq!( |
| 245 | print_expression(&JsExpression::FunctionCall { | |
| 244 | print_expression(&JsExpression::Call { | |
| 246 | 245 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 247 | 246 | arguments: vec![], |
| 248 | 247 | }), |
| 249 | 248 | "hello()" |
| 250 | 249 | ); |
| 251 | 250 | assert_eq!( |
| 252 | print_expression(&JsExpression::FunctionCall { | |
| 251 | print_expression(&JsExpression::Call { | |
| 253 | 252 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 254 | 253 | arguments: vec![JsExpression::Number(1.0)], |
| 255 | 254 | }), |
| 256 | 255 | "hello(1)" |
| 257 | 256 | ); |
| 258 | 257 | assert_eq!( |
| 259 | print_expression(&JsExpression::FunctionCall { | |
| 258 | print_expression(&JsExpression::Call { | |
| 260 | 259 | name: Box::new(JsExpression::Identifier("hello".to_string())), |
| 261 | 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 | 8 | [dependencies] |
| 9 | 9 | miette.workspace = true |
| 10 | 10 | thiserror.workspace = true |
| 11 | ||
| 12 | util = { path = "../util" } |
crates/tokenizer/src/lib.rs+150-64| ... | ... | @@ -1,7 +1,8 @@ |
| 1 | 1 | use miette::{Diagnostic, Result}; |
| 2 | 2 | use thiserror::Error; |
| 3 | 3 | |
| 4 | use util::Operator; | |
| 4 | #[derive(Debug, Clone)] | |
| 5 | pub struct Loc {} | |
| 5 | 6 | |
| 6 | 7 | #[derive(Error, Diagnostic, Debug)] |
| 7 | 8 | #[error("UnknownCharacter")] |
| ... | ... | @@ -16,69 +17,118 @@ pub enum Token { |
| 16 | 17 | Identifier(String), |
| 17 | 18 | Number(f64), |
| 18 | 19 | String(String), |
| 19 | BuiltIn(String), | |
| 20 | 20 | // Comments |
| 21 | 21 | Comment(String), |
| 22 | 22 | DocComment(String), |
| 23 | 23 | // Keywords |
| 24 | True, | |
| 24 | Else, | |
| 25 | 25 | False, |
| 26 | Let, | |
| 27 | Var, | |
| 26 | Fn, | |
| 28 | 27 | If, |
| 29 | Else, | |
| 30 | Return, | |
| 28 | Let, | |
| 31 | 29 | Pub, |
| 32 | Function, | |
| 33 | RightArrow, | |
| 34 | Comma, | |
| 35 | OpenParanthesis, | |
| 36 | ClosedParanthesis, | |
| 37 | OpenBracket, | |
| 38 | ClosedBracket, | |
| 39 | Colon, | |
| 40 | Equals, | |
| 41 | Semicolon, | |
| 42 | // Operators | |
| 43 | Operator(Operator), | |
| 30 | Return, | |
| 31 | True, | |
| 32 | Var, | |
| 33 | Void, | |
| 34 | // Symbols | |
| 35 | And, // & | |
| 36 | AndAnd, // && | |
| 37 | Bang, // ! | |
| 38 | BangEqual, // != | |
| 39 | Caret, // ^ | |
| 40 | ClosedBrace, // } | |
| 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 | 71 | impl std::fmt::Display for Token { |
| 47 | 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 48 | 73 | let token = match self { |
| 49 | 74 | Token::Identifier(_) => "an identifier".to_string(), |
| 75 | ||
| 50 | 76 | Token::Number(n) => n.to_string(), |
| 51 | 77 | Token::String(s) => format!("\"{}\"", s), |
| 52 | Token::BuiltIn(_) => "a builtin identifier".to_string(), | |
| 53 | 78 | Token::Comment(_) => "a comment".to_string(), |
| 54 | 79 | Token::DocComment(_) => "a documentation comment".to_string(), |
| 55 | Token::True => "true".to_string(), | |
| 80 | ||
| 81 | Token::Else => "else".to_string(), | |
| 56 | 82 | Token::False => "false".to_string(), |
| 57 | Token::Let => "let".to_string(), | |
| 58 | Token::Var => "var".to_string(), | |
| 83 | Token::Fn => "fn".to_string(), | |
| 59 | 84 | Token::If => "if".to_string(), |
| 60 | Token::Else => "else".to_string(), | |
| 61 | Token::Return => "return".to_string(), | |
| 85 | Token::Let => "let".to_string(), | |
| 62 | 86 | Token::Pub => "pub".to_string(), |
| 63 | Token::Function => "fn".to_string(), | |
| 64 | Token::RightArrow => "->".to_string(), | |
| 65 | Token::Comma => ",".to_string(), | |
| 66 | Token::OpenParanthesis => "(".to_string(), | |
| 67 | Token::ClosedParanthesis => ")".to_string(), | |
| 68 | Token::OpenBracket => "{".to_string(), | |
| 69 | Token::ClosedBracket => "}".to_string(), | |
| 87 | Token::Return => "return".to_string(), | |
| 88 | Token::True => "true".to_string(), | |
| 89 | Token::Var => "var".to_string(), | |
| 90 | Token::Void => "void".to_string(), | |
| 91 | ||
| 92 | Token::And => "&".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 | 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 | 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 | 128 | write!(f, "{}", token) |
| 77 | 129 | } |
| 78 | 130 | } |
| 79 | 131 | |
| 80 | ||
| 81 | ||
| 82 | 132 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 83 | 133 | let mut tokens: Vec<Token> = Vec::new(); |
| 84 | 134 | let mut chars = content.chars().enumerate().peekable(); |
| ... | ... | @@ -105,22 +155,17 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 105 | 155 | } |
| 106 | 156 | |
| 107 | 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 | 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 | 163 | "pub" => tokens.push(Token::Pub), |
| 113 | "fn" => tokens.push(Token::Function), | |
| 114 | 164 | "return" => tokens.push(Token::Return), |
| 115 | 165 | "true" => tokens.push(Token::True), |
| 116 | "false" => tokens.push(Token::False), | |
| 117 | _ => { | |
| 118 | if current_token.starts_with('@') { | |
| 119 | tokens.push(Token::BuiltIn(current_token)); | |
| 120 | } else { | |
| 121 | tokens.push(Token::Identifier(current_token)) | |
| 122 | } | |
| 123 | } | |
| 166 | "var" => tokens.push(Token::Var), | |
| 167 | "void" => tokens.push(Token::Void), | |
| 168 | _ => tokens.push(Token::Identifier(current_token)), | |
| 124 | 169 | } |
| 125 | 170 | } |
| 126 | 171 | '"' => { |
| ... | ... | @@ -169,39 +214,53 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 169 | 214 | '=' => match chars.peek() { |
| 170 | 215 | Some((_, '=')) => { |
| 171 | 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 | 221 | ';' => tokens.push(Token::Semicolon), |
| 177 | 222 | '>' => match chars.peek() { |
| 178 | 223 | Some((_, '=')) => { |
| 179 | 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 | 239 | '<' => match chars.peek() { |
| 185 | 240 | Some((_, '=')) => { |
| 186 | 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 | 251 | '-' => match chars.peek() { |
| 193 | 252 | Some((_, '>')) => { |
| 194 | 253 | chars.next(); |
| 195 | 254 | tokens.push(Token::RightArrow); |
| 196 | 255 | } |
| 197 | _ => tokens.push(Token::Operator(Operator::Subtract)), | |
| 256 | _ => tokens.push(Token::Minus), | |
| 198 | 257 | }, |
| 199 | 258 | '*' => match chars.peek() { |
| 200 | 259 | Some((_, '*')) => { |
| 201 | 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 | 266 | match chars.peek() { |
| ... | ... | @@ -239,18 +298,45 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 239 | 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 | 304 | ',' => tokens.push(Token::Comma), |
| 246 | '(' => tokens.push(Token::OpenParanthesis), | |
| 247 | ')' => tokens.push(Token::ClosedParanthesis), | |
| 248 | '{' => tokens.push(Token::OpenBracket), | |
| 249 | '}' => tokens.push(Token::ClosedBracket), | |
| 305 | '(' => tokens.push(Token::OpenParen), | |
| 306 | ')' => tokens.push(Token::ClosedParen), | |
| 307 | '{' => tokens.push(Token::OpenBrace), | |
| 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 | 336 | s if s.is_whitespace() => (), |
| 251 | 337 | _ => { |
| 252 | 338 | return Err(UnknownCharacter { |
| 253 | error: format!("Unsupported character: {}", c), | |
| 339 | error: format!("Unexpected '{}' while tokenizing", c), | |
| 254 | 340 | } |
| 255 | 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 | let print = @raw("console.log"); | |
| 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 | } | |
| 1 | let x: bool = 1 * 3 + 2 / 2 == 4 + 2; |