| author | |
| committer | |
| log | 1aaca7fa9509918bee08db12d51f2aef670b69fc |
| tree | d78076620f5b9a3a1d0219d8dad16bab5350195d |
| parent | fc5de308ee56a46b94d38e1b6a46d38fc2f3466e |
| parent | cd0400a1a24ca1bd96fb883589958dabece01154 |
| signature | Signed by PGP key B5690EEEBB952194 |
feat: rewrite the project in rust, add operator precedence and type checking81 files changed, 4462 insertions(+), 2421 deletions(-)
.gitignore+1| ... | @@ -1 +1,2 @@ | ... | @@ -1 +1,2 @@ |
| 1 | target | 1 | target |
| 2 | *.js |
Cargo.lock+26-8| ... | @@ -70,9 +70,9 @@ name = "ast" | ... | @@ -70,9 +70,9 @@ name = "ast" |
| 70 | version = "0.1.0" | 70 | version = "0.1.0" |
| 71 | dependencies = [ | 71 | dependencies = [ |
| 72 | "miette", | 72 | "miette", |
| 73 | "num", | ||
| 73 | "thiserror", | 74 | "thiserror", |
| 74 | "tokenizer", | 75 | "tokenizer", |
| 75 | "util", | ||
| 76 | ] | 76 | ] |
| 77 | 77 | ||
| 78 | [[package]] | 78 | [[package]] |
| ... | @@ -175,6 +175,8 @@ dependencies = [ | ... | @@ -175,6 +175,8 @@ dependencies = [ |
| 175 | "js_ast", | 175 | "js_ast", |
| 176 | "js_printer", | 176 | "js_printer", |
| 177 | "miette", | 177 | "miette", |
| 178 | "parser", | ||
| 179 | "sema", | ||
| 178 | "thiserror", | 180 | "thiserror", |
| 179 | "tokenizer", | 181 | "tokenizer", |
| 180 | ] | 182 | ] |
| ... | @@ -237,8 +239,9 @@ dependencies = [ | ... | @@ -237,8 +239,9 @@ dependencies = [ |
| 237 | "ast", | 239 | "ast", |
| 238 | "miette", | 240 | "miette", |
| 239 | "num", | 241 | "num", |
| 242 | "parser", | ||
| 240 | "thiserror", | 243 | "thiserror", |
| 241 | "util", | 244 | "tokenizer", |
| 242 | ] | 245 | ] |
| 243 | 246 | ||
| 244 | [[package]] | 247 | [[package]] |
| ... | @@ -249,7 +252,6 @@ dependencies = [ | ... | @@ -249,7 +252,6 @@ dependencies = [ |
| 249 | "miette", | 252 | "miette", |
| 250 | "num", | 253 | "num", |
| 251 | "thiserror", | 254 | "thiserror", |
| 252 | "util", | ||
| 253 | ] | 255 | ] |
| 254 | 256 | ||
| 255 | [[package]] | 257 | [[package]] |
| ... | @@ -408,6 +410,17 @@ version = "3.5.0" | ... | @@ -408,6 +410,17 @@ version = "3.5.0" |
| 408 | source = "registry+https://github.com/rust-lang/crates.io-index" | 410 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 409 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" | 411 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" |
| 410 | 412 | ||
| 413 | [[package]] | ||
| 414 | name = "parser" | ||
| 415 | version = "0.1.0" | ||
| 416 | dependencies = [ | ||
| 417 | "ast", | ||
| 418 | "miette", | ||
| 419 | "num", | ||
| 420 | "thiserror", | ||
| 421 | "tokenizer", | ||
| 422 | ] | ||
| 423 | |||
| 411 | [[package]] | 424 | [[package]] |
| 412 | name = "proc-macro2" | 425 | name = "proc-macro2" |
| 413 | version = "1.0.76" | 426 | version = "1.0.76" |
| ... | @@ -445,6 +458,16 @@ dependencies = [ | ... | @@ -445,6 +458,16 @@ dependencies = [ |
| 445 | "windows-sys", | 458 | "windows-sys", |
| 446 | ] | 459 | ] |
| 447 | 460 | ||
| 461 | [[package]] | ||
| 462 | name = "sema" | ||
| 463 | version = "0.1.0" | ||
| 464 | dependencies = [ | ||
| 465 | "ast", | ||
| 466 | "miette", | ||
| 467 | "num", | ||
| 468 | "thiserror", | ||
| 469 | ] | ||
| 470 | |||
| 448 | [[package]] | 471 | [[package]] |
| 449 | name = "smawk" | 472 | name = "smawk" |
| 450 | version = "0.3.2" | 473 | version = "0.3.2" |
| ... | @@ -543,7 +566,6 @@ version = "0.1.0" | ... | @@ -543,7 +566,6 @@ version = "0.1.0" |
| 543 | dependencies = [ | 566 | dependencies = [ |
| 544 | "miette", | 567 | "miette", |
| 545 | "thiserror", | 568 | "thiserror", |
| 546 | "util", | ||
| 547 | ] | 569 | ] |
| 548 | 570 | ||
| 549 | [[package]] | 571 | [[package]] |
| ... | @@ -570,10 +592,6 @@ version = "0.2.1" | ... | @@ -570,10 +592,6 @@ version = "0.2.1" |
| 570 | source = "registry+https://github.com/rust-lang/crates.io-index" | 592 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 571 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | 593 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" |
| 572 | 594 | ||
| 573 | [[package]] | ||
| 574 | name = "util" | ||
| 575 | version = "0.1.0" | ||
| 576 | |||
| 577 | [[package]] | 595 | [[package]] |
| 578 | name = "winapi" | 596 | name = "winapi" |
| 579 | version = "0.3.9" | 597 | version = "0.3.9" |
Cargo.toml+10-3| ... | @@ -1,14 +1,21 @@ | ... | @@ -1,14 +1,21 @@ |
| 1 | [workspace.package] | 1 | [workspace.package] |
| 2 | version = "0.1.0" | 2 | version = "0.1.0" |
| 3 | name = "blight" | ||
| 4 | edition = "2023" | 3 | edition = "2023" |
| 5 | license = "MIT" | 4 | license = "MIT" |
| 6 | authors = ["Lino Le Van"] | 5 | authors = ["Lino Le Van", "Dave Caruso"] |
| 7 | repository = "https://github.com/lino-levan/blight" | 6 | repository = "https://github.com/lino-levan/blight" |
| 8 | 7 | ||
| 9 | [workspace] | 8 | [workspace] |
| 10 | resolver = "2" | 9 | resolver = "2" |
| 11 | members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crates/js_ast", "crates/js_printer"] | 10 | members = [ |
| 11 | "crates/ast", | ||
| 12 | "crates/sema", | ||
| 13 | "crates/cli", | ||
| 14 | "crates/js_ast", | ||
| 15 | "crates/js_printer", | ||
| 16 | "crates/parser", | ||
| 17 | "crates/tokenizer", | ||
| 18 | ] | ||
| 12 | 19 | ||
| 13 | [workspace.dependencies] | 20 | [workspace.dependencies] |
| 14 | clap = { version = "4.4.12", features = ["derive"] } | 21 | clap = { version = "4.4.12", features = ["derive"] } |
crates/ast/Cargo.toml+2-1| ... | @@ -4,10 +4,11 @@ version = "0.1.0" | ... | @@ -4,10 +4,11 @@ version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
| 7 | path = "./lib.rs" | ||
| 7 | 8 | ||
| 8 | [dependencies] | 9 | [dependencies] |
| 9 | miette.workspace = true | 10 | miette.workspace = true |
| 10 | thiserror.workspace = true | 11 | thiserror.workspace = true |
| 12 | num.workspace = true | ||
| 11 | 13 | ||
| 12 | tokenizer = { path = "../tokenizer" } | 14 | tokenizer = { path = "../tokenizer" } |
| 13 | util = { path = "../util" } |
crates/ast/ast.rs created+247| ... | @@ -0,0 +1,247 @@ | ||
| 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 comment: Option<DocComment>, | ||
| 16 | pub statements: Vec<TopLevelStatement>, | ||
| 17 | } | ||
| 18 | |||
| 19 | #[derive(Debug, Clone)] | ||
| 20 | pub struct Block { | ||
| 21 | pub statements: Vec<Statement>, | ||
| 22 | pub has_implicit_return: bool, | ||
| 23 | } | ||
| 24 | |||
| 25 | #[derive(Debug, Clone)] | ||
| 26 | pub struct Param { | ||
| 27 | pub name: String, | ||
| 28 | pub param_type: Type, | ||
| 29 | } | ||
| 30 | |||
| 31 | #[derive(Debug, Clone)] | ||
| 32 | |||
| 33 | pub struct VarDecl { | ||
| 34 | pub name: String, | ||
| 35 | pub data_type: Type, | ||
| 36 | pub init: Expr, | ||
| 37 | pub is_constant: bool, | ||
| 38 | pub is_pub: bool, | ||
| 39 | pub loc: Loc, | ||
| 40 | pub comment: Option<DocComment>, | ||
| 41 | } | ||
| 42 | |||
| 43 | #[derive(Debug, Clone)] | ||
| 44 | pub struct FnDecl { | ||
| 45 | pub name: String, | ||
| 46 | pub return_type: Type, | ||
| 47 | pub params: Vec<Param>, | ||
| 48 | pub body: Expr, | ||
| 49 | pub is_pub: bool, | ||
| 50 | pub loc: Loc, | ||
| 51 | pub comment: Option<DocComment>, | ||
| 52 | } | ||
| 53 | |||
| 54 | #[derive(Debug, Clone)] | ||
| 55 | pub struct Expr { | ||
| 56 | pub data: ExprData, | ||
| 57 | pub loc: Loc, | ||
| 58 | pub comment: Option<Comment>, | ||
| 59 | } | ||
| 60 | |||
| 61 | impl Expr { | ||
| 62 | pub fn new(data: ExprData, loc: Loc, comment: Option<Comment>) -> Self { | ||
| 63 | Self { data, loc, comment } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | #[derive(Debug, Clone)] | ||
| 68 | pub struct DocComment { | ||
| 69 | pub data: String, | ||
| 70 | pub loc: Loc, | ||
| 71 | } | ||
| 72 | |||
| 73 | #[derive(Debug, PartialEq, Clone)] | ||
| 74 | pub struct Comment { | ||
| 75 | pub data: String, | ||
| 76 | } | ||
| 77 | |||
| 78 | impl Comment { | ||
| 79 | pub fn format(&self) -> String { | ||
| 80 | let mut lines = self.data.lines(); | ||
| 81 | let first_line = lines.next().unwrap(); | ||
| 82 | let mut result = format!("// {}", first_line); | ||
| 83 | for line in lines { | ||
| 84 | result.push_str(&format!("\n// {}", line)); | ||
| 85 | } | ||
| 86 | result | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn to_doc_comment(self, loc: Loc) -> DocComment { | ||
| 90 | DocComment { | ||
| 91 | data: self.data, | ||
| 92 | loc, | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | impl Into<Comment> for DocComment { | ||
| 98 | fn into(self) -> Comment { | ||
| 99 | Comment { data: self.data } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | impl DocComment { | ||
| 104 | pub fn format(&self) -> String { | ||
| 105 | let mut lines = self.data.lines(); | ||
| 106 | let first_line = lines.next().unwrap(); | ||
| 107 | let mut result = format!("/// {}", first_line); | ||
| 108 | for line in lines { | ||
| 109 | result.push_str(&format!("\n/// {}", line)); | ||
| 110 | } | ||
| 111 | result | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | #[derive(Debug, Clone)] | ||
| 116 | pub struct BlockWithLoc { | ||
| 117 | pub block: Block, | ||
| 118 | pub loc: Loc, | ||
| 119 | } | ||
| 120 | |||
| 121 | #[derive(Debug, Clone)] | ||
| 122 | pub struct If { | ||
| 123 | pub condition: Box<Expr>, | ||
| 124 | pub yes: BlockWithLoc, | ||
| 125 | pub no: Option<BlockOrIf>, | ||
| 126 | } | ||
| 127 | |||
| 128 | #[derive(Debug, Clone)] | ||
| 129 | pub enum BlockOrIf { | ||
| 130 | Block(BlockWithLoc), | ||
| 131 | If(Box<If>), | ||
| 132 | } | ||
| 133 | |||
| 134 | #[derive(Debug, Clone)] | ||
| 135 | pub enum ExprData { | ||
| 136 | Block(Block), | ||
| 137 | Binary { | ||
| 138 | op: BinaryOperator, | ||
| 139 | left: Box<Expr>, | ||
| 140 | right: Box<Expr>, | ||
| 141 | }, | ||
| 142 | Unary { | ||
| 143 | op: UnaryOperator, | ||
| 144 | value: Box<Expr>, | ||
| 145 | }, | ||
| 146 | Fn { | ||
| 147 | name: String, | ||
| 148 | params: Vec<Param>, | ||
| 149 | return_type: Type, | ||
| 150 | body: Box<Expr>, | ||
| 151 | }, | ||
| 152 | Call { | ||
| 153 | base: Box<Expr>, | ||
| 154 | arguments: Vec<Expr>, | ||
| 155 | }, | ||
| 156 | Field { | ||
| 157 | base: Box<Expr>, | ||
| 158 | field: Box<Expr>, | ||
| 159 | }, | ||
| 160 | Builtin { | ||
| 161 | id: BuiltinId, | ||
| 162 | arguments: Vec<Expr>, | ||
| 163 | }, | ||
| 164 | If(If), | ||
| 165 | Return(Box<Expr>), | ||
| 166 | Literal(Literal), | ||
| 167 | Identifier(String), | ||
| 168 | } | ||
| 169 | |||
| 170 | #[derive(Debug, Clone)] | ||
| 171 | pub enum Statement { | ||
| 172 | Expr(Expr), | ||
| 173 | VarDecl(VarDecl), | ||
| 174 | FnDecl(FnDecl), | ||
| 175 | Comment(Comment), | ||
| 176 | } | ||
| 177 | |||
| 178 | #[derive(Debug, Clone)] | ||
| 179 | pub enum TopLevelStatement { | ||
| 180 | Expr(Expr), | ||
| 181 | VarDecl(VarDecl), | ||
| 182 | FnDecl(FnDecl), | ||
| 183 | Comment(Comment), | ||
| 184 | } | ||
| 185 | |||
| 186 | impl BuiltinId { | ||
| 187 | pub fn display_name(&self) -> &'static str { | ||
| 188 | match self { | ||
| 189 | Self::Raw => "@raw", | ||
| 190 | } | ||
| 191 | } | ||
| 192 | |||
| 193 | pub fn from_str(name: &str) -> Option<Self> { | ||
| 194 | match name { | ||
| 195 | "@raw" => Some(Self::Raw), | ||
| 196 | _ => None, | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | impl Statement { | ||
| 202 | pub fn to_top_level(self) -> TopLevelStatement { | ||
| 203 | match self { | ||
| 204 | Statement::Expr(expr) => TopLevelStatement::Expr(expr), | ||
| 205 | Statement::VarDecl(decl) => TopLevelStatement::VarDecl(decl), | ||
| 206 | Statement::FnDecl(decl) => TopLevelStatement::FnDecl(decl), | ||
| 207 | Statement::Comment(comment) => TopLevelStatement::Comment(comment), | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | /// This is stupid function im sorry | ||
| 212 | pub fn require_semicolon(&self, semicolon_hint: bool) -> bool { | ||
| 213 | match &self { | ||
| 214 | Statement::Expr(_) => !semicolon_hint, | ||
| 215 | Statement::VarDecl(_) => true, | ||
| 216 | _ => false, | ||
| 217 | } | ||
| 218 | } | ||
| 219 | |||
| 220 | pub fn is_expression(&self) -> bool { | ||
| 221 | match &self { | ||
| 222 | Statement::Expr(_) => true, | ||
| 223 | _ => false, | ||
| 224 | } | ||
| 225 | } | ||
| 226 | } | ||
| 227 | |||
| 228 | impl TopLevelStatement { | ||
| 229 | /// This is stupid function im sorry | ||
| 230 | pub fn require_semicolon(&self, earlier_hint: bool) -> bool { | ||
| 231 | match &self { | ||
| 232 | TopLevelStatement::Expr(_) => !earlier_hint, | ||
| 233 | TopLevelStatement::VarDecl(_) => true, | ||
| 234 | TopLevelStatement::FnDecl(_) => false, | ||
| 235 | _ => false, | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | pub fn to_statement(self) -> Option<Statement> { | ||
| 240 | match self { | ||
| 241 | TopLevelStatement::Expr(expr) => Some(Statement::Expr(expr)), | ||
| 242 | TopLevelStatement::VarDecl(decl) => Some(Statement::VarDecl(decl)), | ||
| 243 | TopLevelStatement::FnDecl(decl) => Some(Statement::FnDecl(decl)), | ||
| 244 | TopLevelStatement::Comment(comment) => Some(Statement::Comment(comment)), | ||
| 245 | } | ||
| 246 | } | ||
| 247 | } | ||
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+315| ... | @@ -0,0 +1,315 @@ | ||
| 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 | Assign, | ||
| 29 | AssignAdd, | ||
| 30 | AssignSub, | ||
| 31 | AssignMul, | ||
| 32 | AssignDiv, | ||
| 33 | AssignPow, | ||
| 34 | AssignLshift, | ||
| 35 | AssignRshift, | ||
| 36 | AssignRshiftUnsigned, | ||
| 37 | AssignBitAnd, | ||
| 38 | AssignBitOr, | ||
| 39 | AssignBitXor, | ||
| 40 | AssignMod, | ||
| 41 | AssignAnd, | ||
| 42 | AssignOr, | ||
| 43 | } | ||
| 44 | |||
| 45 | #[derive(Debug, Clone, PartialEq)] | ||
| 46 | pub enum UnaryOperator { | ||
| 47 | Neg, | ||
| 48 | Not, | ||
| 49 | BitNot, | ||
| 50 | } | ||
| 51 | |||
| 52 | impl BinaryOperator { | ||
| 53 | pub fn to_str(&self) -> &'static str { | ||
| 54 | match self { | ||
| 55 | Self::Add => "+", | ||
| 56 | Self::Sub => "-", | ||
| 57 | Self::Div => "/", | ||
| 58 | Self::Mul => "*", | ||
| 59 | Self::Pow => "**", | ||
| 60 | Self::Eq => "==", | ||
| 61 | Self::Gt => ">", | ||
| 62 | Self::Lt => "<", | ||
| 63 | Self::Gte => ">=", | ||
| 64 | Self::Lte => "<=", | ||
| 65 | Self::Lshift => "<<", | ||
| 66 | Self::Rshift => ">>", | ||
| 67 | Self::RshiftUnsigned => ">>>", | ||
| 68 | Self::BitAnd => "&", | ||
| 69 | Self::BitOr => "|", | ||
| 70 | Self::BitXor => "^", | ||
| 71 | Self::Mod => "%", | ||
| 72 | Self::NotEq => "!=", | ||
| 73 | Self::And => "&&", | ||
| 74 | Self::Or => "||", | ||
| 75 | Self::AssignAdd => "+=", | ||
| 76 | Self::AssignSub => "-=", | ||
| 77 | Self::AssignDiv => "/=", | ||
| 78 | Self::AssignMul => "*=", | ||
| 79 | Self::AssignPow => "**=", | ||
| 80 | Self::Assign => "=", | ||
| 81 | Self::AssignLshift => "<<=", | ||
| 82 | Self::AssignRshift => ">>=", | ||
| 83 | Self::AssignRshiftUnsigned => ">>>=", | ||
| 84 | Self::AssignBitAnd => "&=", | ||
| 85 | Self::AssignBitOr => "|=", | ||
| 86 | Self::AssignBitXor => "^=", | ||
| 87 | Self::AssignMod => "%=", | ||
| 88 | Self::AssignAnd => "&&=", | ||
| 89 | Self::AssignOr => "||=", | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn to_string(&self) -> String { | ||
| 94 | String::from(self.to_str()) | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn from_token(token: &Token) -> Option<BinaryOperator> { | ||
| 98 | match token { | ||
| 99 | Token::And => Some(Self::BitAnd), | ||
| 100 | Token::AndAnd => Some(Self::And), | ||
| 101 | Token::BangEqual => Some(Self::NotEq), | ||
| 102 | Token::Caret => Some(Self::BitXor), | ||
| 103 | Token::EqualEqual => Some(Self::Eq), | ||
| 104 | Token::Gt => Some(Self::Gt), | ||
| 105 | Token::GtEqual => Some(Self::Gte), | ||
| 106 | Token::GtGt => Some(Self::Rshift), | ||
| 107 | Token::GtGtGt => Some(Self::RshiftUnsigned), | ||
| 108 | Token::Lt => Some(Self::Lt), | ||
| 109 | Token::LtEqual => Some(Self::Lte), | ||
| 110 | Token::LtLt => Some(Self::Lshift), | ||
| 111 | Token::Minus => Some(Self::Sub), | ||
| 112 | Token::Or => Some(Self::BitOr), | ||
| 113 | Token::OrOr => Some(Self::Or), | ||
| 114 | Token::Percent => Some(Self::Mod), | ||
| 115 | Token::Plus => Some(Self::Add), | ||
| 116 | Token::Slash => Some(Self::Div), | ||
| 117 | Token::Star => Some(Self::Mul), | ||
| 118 | Token::StarStar => Some(Self::Pow), | ||
| 119 | _ => None, | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | pub fn level(&self) -> OperatorLevel { | ||
| 124 | OperatorLevel::from(self) | ||
| 125 | } | ||
| 126 | |||
| 127 | /// Error case of this means 'types do not allow this operation' | ||
| 128 | pub fn perform(&self, left: Literal, right: Literal) -> Result<Literal, ()> { | ||
| 129 | match self { | ||
| 130 | BinaryOperator::Eq => Ok(Literal::Bool(left == right)), | ||
| 131 | BinaryOperator::NotEq => Ok(Literal::Bool(left != right)), | ||
| 132 | |||
| 133 | BinaryOperator::Or | BinaryOperator::And => match (left, right) { | ||
| 134 | (Literal::Bool(a), Literal::Bool(b)) => Ok(Literal::Bool(match self { | ||
| 135 | BinaryOperator::Or => a || b, | ||
| 136 | BinaryOperator::And => a && b, | ||
| 137 | _ => unreachable!(), | ||
| 138 | })), | ||
| 139 | _ => Err(()), | ||
| 140 | }, | ||
| 141 | |||
| 142 | BinaryOperator::Add | ||
| 143 | | BinaryOperator::Sub | ||
| 144 | | BinaryOperator::Mul | ||
| 145 | | BinaryOperator::Div | ||
| 146 | | BinaryOperator::Pow | ||
| 147 | | BinaryOperator::Gt | ||
| 148 | | BinaryOperator::Lt | ||
| 149 | | BinaryOperator::Gte | ||
| 150 | | BinaryOperator::Lte | ||
| 151 | | BinaryOperator::Lshift | ||
| 152 | | BinaryOperator::Rshift | ||
| 153 | | BinaryOperator::RshiftUnsigned | ||
| 154 | | BinaryOperator::BitAnd | ||
| 155 | | BinaryOperator::BitOr | ||
| 156 | | BinaryOperator::BitXor | ||
| 157 | | BinaryOperator::Mod => match (left, right) { | ||
| 158 | (Literal::Number(a), Literal::Number(b)) => Ok(match self { | ||
| 159 | BinaryOperator::Add => Literal::Number(a + b), | ||
| 160 | BinaryOperator::Sub => Literal::Number(a - b), | ||
| 161 | BinaryOperator::Mul => Literal::Number(a * b), | ||
| 162 | BinaryOperator::Div => Literal::Number(a / b), | ||
| 163 | BinaryOperator::Pow => Literal::Number(a.powf(b)), | ||
| 164 | BinaryOperator::Gt => Literal::Bool(a > b), | ||
| 165 | BinaryOperator::Lt => Literal::Bool(a < b), | ||
| 166 | BinaryOperator::Gte => Literal::Bool(a >= b), | ||
| 167 | BinaryOperator::Lte => Literal::Bool(a <= b), | ||
| 168 | BinaryOperator::Lshift => Literal::Number(((a as i32) << (b as i32)).into()), | ||
| 169 | BinaryOperator::Rshift => Literal::Number(((a as i32) >> (b as i32)).into()), | ||
| 170 | BinaryOperator::RshiftUnsigned => { | ||
| 171 | Literal::Number(((a as u32) >> (b as u32)).into()) | ||
| 172 | } | ||
| 173 | BinaryOperator::BitAnd => Literal::Number(((a as i32) & (b as i32)).into()), | ||
| 174 | BinaryOperator::BitOr => Literal::Number(((a as i32) | (b as i32)).into()), | ||
| 175 | BinaryOperator::BitXor => Literal::Number(((a as i32) ^ (b as i32)).into()), | ||
| 176 | BinaryOperator::Mod => Literal::Number(a % b), | ||
| 177 | _ => unreachable!(), | ||
| 178 | }), | ||
| 179 | _ => Err(()), | ||
| 180 | }, | ||
| 181 | |||
| 182 | // Impossible condition doing assign-add to a literal | ||
| 183 | _ => Err(()), | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | impl UnaryOperator { | ||
| 189 | pub fn to_str(&self) -> &'static str { | ||
| 190 | match self { | ||
| 191 | Self::Neg => "-", | ||
| 192 | Self::Not => "!", | ||
| 193 | Self::BitNot => "~", | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | pub fn to_string(&self) -> String { | ||
| 198 | String::from(self.to_str()) | ||
| 199 | } | ||
| 200 | |||
| 201 | pub fn perform(self, value: Literal) -> Result<Literal, ()> { | ||
| 202 | match self { | ||
| 203 | UnaryOperator::Neg => match value { | ||
| 204 | Literal::Number(a) => Ok(Literal::Number(-a)), | ||
| 205 | _ => Err(()), | ||
| 206 | }, | ||
| 207 | UnaryOperator::Not => match value { | ||
| 208 | Literal::Bool(a) => Ok(Literal::Bool(!a)), | ||
| 209 | _ => Err(()), | ||
| 210 | }, | ||
| 211 | UnaryOperator::BitNot => match value { | ||
| 212 | Literal::Number(a) => Ok(Literal::Number((!(a as i32)).into())), | ||
| 213 | _ => Err(()), | ||
| 214 | }, | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | /// Taken from from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table | ||
| 220 | /// | ||
| 221 | /// Blight doesn't have all of these operators, but the | ||
| 222 | /// printer uses it to determine where to put parens. | ||
| 223 | #[derive(PartialEq, PartialOrd, Debug)] | ||
| 224 | pub enum OperatorLevel { | ||
| 225 | Accessor, // e. e[] e() | ||
| 226 | New, // new | ||
| 227 | Postfix, // expr++ expr-- | ||
| 228 | Prefix, // ++e, --e, !e, ~e, +e, -e, typeof, void, delete | ||
| 229 | |||
| 230 | // these are the binary operators | ||
| 231 | Exponentiation, // ** | ||
| 232 | Multiplicative, // *, /, % | ||
| 233 | Additive, // +, - | ||
| 234 | BitwiseShift, // <<, >>, >>> | ||
| 235 | Relational, // <, <=, >, >= | ||
| 236 | Equality, // ==, != | ||
| 237 | BitwiseAnd, // & | ||
| 238 | BitwiseXor, // ^ | ||
| 239 | BitwiseOr, // | | ||
| 240 | LogicalAnd, // && | ||
| 241 | LogicalOr, // ||, ?? | ||
| 242 | |||
| 243 | AssignmentAndOther, // =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |= | ||
| 244 | Comma, // , | ||
| 245 | } | ||
| 246 | |||
| 247 | pub enum Associativity { | ||
| 248 | LeftToRight, | ||
| 249 | RightToLeft, | ||
| 250 | } | ||
| 251 | |||
| 252 | impl OperatorLevel { | ||
| 253 | pub fn associativity(&self) -> Associativity { | ||
| 254 | use Associativity::*; | ||
| 255 | use OperatorLevel::*; | ||
| 256 | |||
| 257 | match self { | ||
| 258 | Accessor => LeftToRight, | ||
| 259 | Exponentiation => RightToLeft, | ||
| 260 | Multiplicative => LeftToRight, | ||
| 261 | Additive => LeftToRight, | ||
| 262 | BitwiseShift => LeftToRight, | ||
| 263 | Relational => LeftToRight, | ||
| 264 | Equality => LeftToRight, | ||
| 265 | BitwiseAnd => LeftToRight, | ||
| 266 | BitwiseXor => LeftToRight, | ||
| 267 | BitwiseOr => LeftToRight, | ||
| 268 | LogicalAnd => LeftToRight, | ||
| 269 | LogicalOr => LeftToRight, | ||
| 270 | AssignmentAndOther => RightToLeft, | ||
| 271 | Comma => LeftToRight, | ||
| 272 | |||
| 273 | // These do not make sense to have an associativity | ||
| 274 | // You will likely not run into this if you do | ||
| 275 | // BinaryOperator::level(o).associativity() | ||
| 276 | New | Postfix | Prefix => unreachable!("{:?} does not have an associativity", self), | ||
| 277 | } | ||
| 278 | } | ||
| 279 | } | ||
| 280 | |||
| 281 | impl From<&BinaryOperator> for OperatorLevel { | ||
| 282 | fn from(operator: &BinaryOperator) -> Self { | ||
| 283 | use BinaryOperator::*; | ||
| 284 | use OperatorLevel::*; | ||
| 285 | |||
| 286 | match operator { | ||
| 287 | Add | Sub => Additive, | ||
| 288 | Mul | Div | Mod => Multiplicative, | ||
| 289 | Pow => Exponentiation, | ||
| 290 | Gt | Lt | Gte | Lte => Relational, | ||
| 291 | Lshift | Rshift | RshiftUnsigned => BitwiseShift, | ||
| 292 | BitAnd => BitwiseAnd, | ||
| 293 | BitOr => BitwiseOr, | ||
| 294 | BitXor => BitwiseXor, | ||
| 295 | Eq | NotEq => Equality, | ||
| 296 | And => LogicalAnd, | ||
| 297 | Or => LogicalOr, | ||
| 298 | |||
| 299 | Assign | AssignAdd | AssignSub | AssignMul | AssignDiv | AssignPow | AssignLshift | ||
| 300 | | AssignRshift | AssignRshiftUnsigned | AssignBitAnd | AssignBitOr | AssignBitXor | ||
| 301 | | AssignMod | AssignAnd | AssignOr => AssignmentAndOther, | ||
| 302 | } | ||
| 303 | } | ||
| 304 | } | ||
| 305 | |||
| 306 | impl From<UnaryOperator> for OperatorLevel { | ||
| 307 | fn from(operator: UnaryOperator) -> Self { | ||
| 308 | use OperatorLevel::*; | ||
| 309 | use UnaryOperator::*; | ||
| 310 | |||
| 311 | match operator { | ||
| 312 | Neg | Not | BitNot => Prefix, | ||
| 313 | } | ||
| 314 | } | ||
| 315 | } | ||
crates/ast/src/error.rs deleted-17| ... | @@ -1,17 +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 | } | ||
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, Type}; | ||
| 17 | |||
| 18 | pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { | ||
| 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(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-261| ... | @@ -1,261 +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_expression::parse_expression; | ||
| 8 | use super::parse_function_call::parse_function_call; | ||
| 9 | use super::parse_type::parse_type; | ||
| 10 | use super::types::{Argument, Ast, Block, ParserContext}; | ||
| 11 | |||
| 12 | /// Precondition: There is at least one token in the iterator | ||
| 13 | pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ||
| 14 | let token = tokens.next().unwrap(); | ||
| 15 | match token { | ||
| 16 | Token::BuiltIn(name) => { | ||
| 17 | match tokens.peek() { | ||
| 18 | Some(Token::OpenParanthesis) => { | ||
| 19 | // Okay, so we messed up. We shouldn't have consumed that token. | ||
| 20 | tokens.back(); | ||
| 21 | parse_function_call(tokens) | ||
| 22 | } | ||
| 23 | _ => Ok(Ast::BuiltIn(name)), | ||
| 24 | } | ||
| 25 | } | ||
| 26 | Token::OpenBracket => { | ||
| 27 | // FRICK, we have to backtrack | ||
| 28 | tokens.back(); | ||
| 29 | Ok(Ast::Block(parse_block(tokens)?)) | ||
| 30 | } | ||
| 31 | Token::Pub => { | ||
| 32 | match tokens.peek() { | ||
| 33 | Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (), | ||
| 34 | _ => return Err(AstError::new("Expected let, var, or fn after pub").into()), | ||
| 35 | } | ||
| 36 | |||
| 37 | let inner = parse_statement(tokens)?; | ||
| 38 | |||
| 39 | return Ok(Ast::Public(Box::new(inner))); | ||
| 40 | } | ||
| 41 | Token::Let => { | ||
| 42 | let name = match tokens.next() { | ||
| 43 | Some(Token::Identifier(name)) => name, | ||
| 44 | _ => return Err(AstError::new("Expected identifier after var").into()), | ||
| 45 | }; | ||
| 46 | let return_type = match tokens.next() { | ||
| 47 | Some(Token::Colon) => parse_type(tokens)?, | ||
| 48 | _ => return Err(AstError::new("Expected colon after identifier").into()), | ||
| 49 | }; | ||
| 50 | let value = match tokens.next() { | ||
| 51 | Some(Token::Equals) => parse_expression(tokens)?, | ||
| 52 | _ => return Err(AstError::new("Expected equals after identifier").into()), | ||
| 53 | }; | ||
| 54 | match tokens.next() { | ||
| 55 | Some(Token::Semicolon) => (), | ||
| 56 | _ => { | ||
| 57 | return Err( | ||
| 58 | AstError::new("Expected semicolon after constant declaration").into(), | ||
| 59 | ) | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | Ok(Ast::ConstantDeclaration { | ||
| 63 | name: name.clone(), | ||
| 64 | return_type, | ||
| 65 | value: Box::new(value), | ||
| 66 | }) | ||
| 67 | } | ||
| 68 | Token::Var => { | ||
| 69 | let name = match tokens.next() { | ||
| 70 | Some(Token::Identifier(name)) => name, | ||
| 71 | _ => return Err(AstError::new("Expected identifier after var").into()), | ||
| 72 | }; | ||
| 73 | let return_type = match tokens.next() { | ||
| 74 | Some(Token::Colon) => parse_type(tokens)?, | ||
| 75 | _ => return Err(AstError::new("Expected colon after identifier").into()), | ||
| 76 | }; | ||
| 77 | let value = match tokens.next() { | ||
| 78 | Some(Token::Equals) => parse_expression(tokens)?, | ||
| 79 | _ => return Err(AstError::new("Expected equals after identifier").into()), | ||
| 80 | }; | ||
| 81 | match tokens.next() { | ||
| 82 | Some(Token::Semicolon) => (), | ||
| 83 | _ => { | ||
| 84 | return Err( | ||
| 85 | AstError::new("Expected semicolon after variable declaration").into(), | ||
| 86 | ) | ||
| 87 | } | ||
| 88 | }; | ||
| 89 | Ok(Ast::VariableDeclaration { | ||
| 90 | name: name.clone(), | ||
| 91 | return_type, | ||
| 92 | value: Box::new(value), | ||
| 93 | }) | ||
| 94 | } | ||
| 95 | Token::Return => { | ||
| 96 | let expression = parse_expression(tokens)?; | ||
| 97 | |||
| 98 | match tokens.next() { | ||
| 99 | Some(Token::Semicolon) => (), | ||
| 100 | _ => return Err(AstError::new("Expected semicolon after return statement").into()), | ||
| 101 | }; | ||
| 102 | |||
| 103 | Ok(Ast::Return(Box::new(expression))) | ||
| 104 | } | ||
| 105 | Token::Function => { | ||
| 106 | let name = match tokens.next() { | ||
| 107 | Some(Token::Identifier(name)) => name.clone(), | ||
| 108 | _ => return Err(AstError::new("Expected identifier after fn").into()), | ||
| 109 | }; | ||
| 110 | |||
| 111 | match tokens.next() { | ||
| 112 | Some(Token::OpenParanthesis) => (), | ||
| 113 | _ => { | ||
| 114 | return Err(AstError::new( | ||
| 115 | "Expected open paranthesis after identifier in function", | ||
| 116 | ) | ||
| 117 | .into()) | ||
| 118 | } | ||
| 119 | }; | ||
| 120 | |||
| 121 | let mut arguments = Vec::new(); | ||
| 122 | |||
| 123 | loop { | ||
| 124 | match tokens.peek() { | ||
| 125 | Some(Token::ClosedParanthesis) => { | ||
| 126 | tokens.next(); | ||
| 127 | break; | ||
| 128 | } | ||
| 129 | _ => (), | ||
| 130 | } | ||
| 131 | |||
| 132 | let argument_name = match tokens.next() { | ||
| 133 | Some(Token::Identifier(name)) => name, | ||
| 134 | _ => { | ||
| 135 | return Err(AstError::new( | ||
| 136 | "Expected identifier for argument name in function", | ||
| 137 | ) | ||
| 138 | .into()) | ||
| 139 | } | ||
| 140 | }; | ||
| 141 | |||
| 142 | let return_type = match tokens.next() { | ||
| 143 | Some(Token::Colon) => parse_type(tokens)?, | ||
| 144 | _ => return Err(AstError::new("Expected colon after identifier").into()), | ||
| 145 | }; | ||
| 146 | |||
| 147 | match tokens.peek() { | ||
| 148 | Some(Token::Comma) => { | ||
| 149 | tokens.next(); | ||
| 150 | } | ||
| 151 | _ => (), | ||
| 152 | } | ||
| 153 | |||
| 154 | arguments.push(Argument { | ||
| 155 | name: argument_name.clone(), | ||
| 156 | return_type, | ||
| 157 | }) | ||
| 158 | } | ||
| 159 | |||
| 160 | let return_type = match tokens.next() { | ||
| 161 | Some(Token::RightArrow) => parse_type(tokens)?, | ||
| 162 | _ => { | ||
| 163 | return Err(AstError::new( | ||
| 164 | "Expected right arrow after argument list in function", | ||
| 165 | ) | ||
| 166 | .into()) | ||
| 167 | } | ||
| 168 | }; | ||
| 169 | |||
| 170 | match tokens.peek() { | ||
| 171 | Some(Token::OpenBracket) => (), | ||
| 172 | _ => { | ||
| 173 | return Err(AstError::new( | ||
| 174 | "Expected open bracket after type in function declaration", | ||
| 175 | ) | ||
| 176 | .into()) | ||
| 177 | } | ||
| 178 | }; | ||
| 179 | |||
| 180 | let block = parse_block(tokens)?; | ||
| 181 | |||
| 182 | return Ok(Ast::Function { | ||
| 183 | name, | ||
| 184 | arguments, | ||
| 185 | return_type, | ||
| 186 | block, | ||
| 187 | }); | ||
| 188 | } | ||
| 189 | Token::If => { | ||
| 190 | let condition = parse_expression(tokens)?; | ||
| 191 | |||
| 192 | let value = parse_block(tokens)?; | ||
| 193 | |||
| 194 | let mut else_value: Option<Block> = None; | ||
| 195 | |||
| 196 | match tokens.peek() { | ||
| 197 | Some(Token::Else) => { | ||
| 198 | tokens.next(); | ||
| 199 | match tokens.peek() { | ||
| 200 | Some(Token::If) => { | ||
| 201 | let else_if = parse_statement(tokens)?; | ||
| 202 | else_value = Some(Block { | ||
| 203 | value: vec![else_if], | ||
| 204 | implicit_return: false, | ||
| 205 | }); | ||
| 206 | } | ||
| 207 | Some(Token::OpenBracket) => { | ||
| 208 | else_value = Some(parse_block(tokens)?); | ||
| 209 | } | ||
| 210 | _ => { | ||
| 211 | return Err(AstError::new( | ||
| 212 | "Expected if or open bracket after else in if statement", | ||
| 213 | ) | ||
| 214 | .into()) | ||
| 215 | } | ||
| 216 | } | ||
| 217 | } | ||
| 218 | _ => (), | ||
| 219 | } | ||
| 220 | |||
| 221 | return Ok(Ast::If { | ||
| 222 | condition: Box::new(condition), | ||
| 223 | value, | ||
| 224 | else_value, | ||
| 225 | }); | ||
| 226 | } | ||
| 227 | Token::Identifier(_) => match tokens.peek() { | ||
| 228 | Some(Token::OpenParanthesis) => { | ||
| 229 | // We need to give the identifier back to the parser | ||
| 230 | tokens.back(); | ||
| 231 | let result = parse_function_call(tokens); | ||
| 232 | match tokens.next() { | ||
| 233 | Some(Token::Semicolon) => (), | ||
| 234 | _ => { | ||
| 235 | return Err(AstError::new( | ||
| 236 | "Expected semicolon after function call statement", | ||
| 237 | ) | ||
| 238 | .into()) | ||
| 239 | } | ||
| 240 | }; | ||
| 241 | result | ||
| 242 | } | ||
| 243 | _ => Err(AstError::new("Unexpected identifier").into()), | ||
| 244 | }, | ||
| 245 | Token::Comment(comment) => Ok(Ast::Comment(comment.clone())), | ||
| 246 | Token::DocComment(comment) => { | ||
| 247 | let mut concatted_comments = comment.clone(); | ||
| 248 | loop { | ||
| 249 | match tokens.peek() { | ||
| 250 | Some(Token::DocComment(comment)) => { | ||
| 251 | concatted_comments.push_str(format!("\n{}", comment).as_str()); | ||
| 252 | tokens.next(); | ||
| 253 | } | ||
| 254 | _ => break, | ||
| 255 | } | ||
| 256 | } | ||
| 257 | Ok(Ast::DocComment(concatted_comments)) | ||
| 258 | } | ||
| 259 | _ => return Err(AstError::new("Unexpected token").into()), | ||
| 260 | } | ||
| 261 | } | ||
crates/ast/src/parse_type.rs deleted-21| ... | @@ -1,21 +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 | "any" => Ok(Type::Any), | ||
| 17 | _ => return Err(AstError::new("Expected type").into()), | ||
| 18 | }, | ||
| 19 | _ => return Err(AstError::new("Expected type").into()), | ||
| 20 | } | ||
| 21 | } | ||
crates/ast/src/types.rs deleted-109| ... | @@ -1,109 +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)] | ||
| 45 | pub enum Type { | ||
| 46 | Int, | ||
| 47 | Float, | ||
| 48 | Bigint, | ||
| 49 | Bool, | ||
| 50 | String, | ||
| 51 | Any, | ||
| 52 | } | ||
| 53 | |||
| 54 | #[derive(Debug, Clone)] | ||
| 55 | pub struct Block { | ||
| 56 | pub value: Vec<Ast>, | ||
| 57 | pub implicit_return: bool, | ||
| 58 | } | ||
| 59 | |||
| 60 | #[derive(Debug, Clone)] | ||
| 61 | pub struct Argument { | ||
| 62 | pub name: String, | ||
| 63 | pub return_type: Type, | ||
| 64 | } | ||
| 65 | |||
| 66 | #[derive(Debug, Clone)] | ||
| 67 | pub enum Ast { | ||
| 68 | ConstantDeclaration { | ||
| 69 | name: String, | ||
| 70 | return_type: Type, | ||
| 71 | value: Box<Ast>, | ||
| 72 | }, | ||
| 73 | VariableDeclaration { | ||
| 74 | name: String, | ||
| 75 | return_type: Type, | ||
| 76 | value: Box<Ast>, | ||
| 77 | }, | ||
| 78 | Operation { | ||
| 79 | operator: Operator, | ||
| 80 | left: Box<Ast>, | ||
| 81 | right: Box<Ast>, | ||
| 82 | }, | ||
| 83 | Function { | ||
| 84 | name: String, | ||
| 85 | arguments: Vec<Argument>, | ||
| 86 | return_type: Type, | ||
| 87 | block: Block, | ||
| 88 | }, | ||
| 89 | FunctionCall { | ||
| 90 | /// Identifier or BuiltIn | ||
| 91 | name: Box<Ast>, | ||
| 92 | arguments: Vec<Ast>, | ||
| 93 | }, | ||
| 94 | If { | ||
| 95 | condition: Box<Ast>, | ||
| 96 | value: Block, | ||
| 97 | else_value: Option<Block>, | ||
| 98 | }, | ||
| 99 | Return(Box<Ast>), | ||
| 100 | Public(Box<Ast>), | ||
| 101 | Block(Block), | ||
| 102 | Number(f64), | ||
| 103 | Bool(bool), | ||
| 104 | String(String), | ||
| 105 | Identifier(String), | ||
| 106 | BuiltIn(String), | ||
| 107 | Comment(String), | ||
| 108 | DocComment(String), | ||
| 109 | } | ||
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/cli/Cargo.lock deleted-532| ... | @@ -1,532 +0,0 @@ | ||
| 1 | # This file is automatically @generated by Cargo. | ||
| 2 | # It is not intended for manual editing. | ||
| 3 | version = 3 | ||
| 4 | |||
| 5 | [[package]] | ||
| 6 | name = "addr2line" | ||
| 7 | version = "0.21.0" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" | ||
| 10 | dependencies = [ | ||
| 11 | "gimli", | ||
| 12 | ] | ||
| 13 | |||
| 14 | [[package]] | ||
| 15 | name = "adler" | ||
| 16 | version = "1.0.2" | ||
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" | ||
| 19 | |||
| 20 | [[package]] | ||
| 21 | name = "anstream" | ||
| 22 | version = "0.6.5" | ||
| 23 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 24 | checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" | ||
| 25 | dependencies = [ | ||
| 26 | "anstyle", | ||
| 27 | "anstyle-parse", | ||
| 28 | "anstyle-query", | ||
| 29 | "anstyle-wincon", | ||
| 30 | "colorchoice", | ||
| 31 | "utf8parse", | ||
| 32 | ] | ||
| 33 | |||
| 34 | [[package]] | ||
| 35 | name = "anstyle" | ||
| 36 | version = "1.0.4" | ||
| 37 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 38 | checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" | ||
| 39 | |||
| 40 | [[package]] | ||
| 41 | name = "anstyle-parse" | ||
| 42 | version = "0.2.3" | ||
| 43 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 44 | checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" | ||
| 45 | dependencies = [ | ||
| 46 | "utf8parse", | ||
| 47 | ] | ||
| 48 | |||
| 49 | [[package]] | ||
| 50 | name = "anstyle-query" | ||
| 51 | version = "1.0.2" | ||
| 52 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 53 | checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" | ||
| 54 | dependencies = [ | ||
| 55 | "windows-sys", | ||
| 56 | ] | ||
| 57 | |||
| 58 | [[package]] | ||
| 59 | name = "anstyle-wincon" | ||
| 60 | version = "3.0.2" | ||
| 61 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 62 | checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" | ||
| 63 | dependencies = [ | ||
| 64 | "anstyle", | ||
| 65 | "windows-sys", | ||
| 66 | ] | ||
| 67 | |||
| 68 | [[package]] | ||
| 69 | name = "backtrace" | ||
| 70 | version = "0.3.69" | ||
| 71 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 72 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" | ||
| 73 | dependencies = [ | ||
| 74 | "addr2line", | ||
| 75 | "cc", | ||
| 76 | "cfg-if", | ||
| 77 | "libc", | ||
| 78 | "miniz_oxide", | ||
| 79 | "object", | ||
| 80 | "rustc-demangle", | ||
| 81 | ] | ||
| 82 | |||
| 83 | [[package]] | ||
| 84 | name = "backtrace-ext" | ||
| 85 | version = "0.2.1" | ||
| 86 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 87 | checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" | ||
| 88 | dependencies = [ | ||
| 89 | "backtrace", | ||
| 90 | ] | ||
| 91 | |||
| 92 | [[package]] | ||
| 93 | name = "bitflags" | ||
| 94 | version = "2.4.1" | ||
| 95 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 96 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" | ||
| 97 | |||
| 98 | [[package]] | ||
| 99 | name = "blight" | ||
| 100 | version = "0.1.0" | ||
| 101 | dependencies = [ | ||
| 102 | "clap", | ||
| 103 | "miette", | ||
| 104 | "thiserror", | ||
| 105 | ] | ||
| 106 | |||
| 107 | [[package]] | ||
| 108 | name = "cc" | ||
| 109 | version = "1.0.83" | ||
| 110 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 111 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" | ||
| 112 | dependencies = [ | ||
| 113 | "libc", | ||
| 114 | ] | ||
| 115 | |||
| 116 | [[package]] | ||
| 117 | name = "cfg-if" | ||
| 118 | version = "1.0.0" | ||
| 119 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 120 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | ||
| 121 | |||
| 122 | [[package]] | ||
| 123 | name = "clap" | ||
| 124 | version = "4.4.12" | ||
| 125 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 126 | checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d" | ||
| 127 | dependencies = [ | ||
| 128 | "clap_builder", | ||
| 129 | "clap_derive", | ||
| 130 | ] | ||
| 131 | |||
| 132 | [[package]] | ||
| 133 | name = "clap_builder" | ||
| 134 | version = "4.4.12" | ||
| 135 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 136 | checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9" | ||
| 137 | dependencies = [ | ||
| 138 | "anstream", | ||
| 139 | "anstyle", | ||
| 140 | "clap_lex", | ||
| 141 | "strsim", | ||
| 142 | ] | ||
| 143 | |||
| 144 | [[package]] | ||
| 145 | name = "clap_derive" | ||
| 146 | version = "4.4.7" | ||
| 147 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 148 | checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" | ||
| 149 | dependencies = [ | ||
| 150 | "heck", | ||
| 151 | "proc-macro2", | ||
| 152 | "quote", | ||
| 153 | "syn", | ||
| 154 | ] | ||
| 155 | |||
| 156 | [[package]] | ||
| 157 | name = "clap_lex" | ||
| 158 | version = "0.6.0" | ||
| 159 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 160 | checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" | ||
| 161 | |||
| 162 | [[package]] | ||
| 163 | name = "colorchoice" | ||
| 164 | version = "1.0.0" | ||
| 165 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 166 | checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" | ||
| 167 | |||
| 168 | [[package]] | ||
| 169 | name = "errno" | ||
| 170 | version = "0.3.8" | ||
| 171 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 172 | checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" | ||
| 173 | dependencies = [ | ||
| 174 | "libc", | ||
| 175 | "windows-sys", | ||
| 176 | ] | ||
| 177 | |||
| 178 | [[package]] | ||
| 179 | name = "gimli" | ||
| 180 | version = "0.28.1" | ||
| 181 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 182 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" | ||
| 183 | |||
| 184 | [[package]] | ||
| 185 | name = "heck" | ||
| 186 | version = "0.4.1" | ||
| 187 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 188 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" | ||
| 189 | |||
| 190 | [[package]] | ||
| 191 | name = "hermit-abi" | ||
| 192 | version = "0.3.3" | ||
| 193 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 194 | checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" | ||
| 195 | |||
| 196 | [[package]] | ||
| 197 | name = "is-terminal" | ||
| 198 | version = "0.4.10" | ||
| 199 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 200 | checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" | ||
| 201 | dependencies = [ | ||
| 202 | "hermit-abi", | ||
| 203 | "rustix", | ||
| 204 | "windows-sys", | ||
| 205 | ] | ||
| 206 | |||
| 207 | [[package]] | ||
| 208 | name = "is_ci" | ||
| 209 | version = "1.1.1" | ||
| 210 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 211 | checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" | ||
| 212 | |||
| 213 | [[package]] | ||
| 214 | name = "libc" | ||
| 215 | version = "0.2.151" | ||
| 216 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 217 | checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" | ||
| 218 | |||
| 219 | [[package]] | ||
| 220 | name = "linux-raw-sys" | ||
| 221 | version = "0.4.12" | ||
| 222 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 223 | checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" | ||
| 224 | |||
| 225 | [[package]] | ||
| 226 | name = "memchr" | ||
| 227 | version = "2.7.1" | ||
| 228 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 229 | checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" | ||
| 230 | |||
| 231 | [[package]] | ||
| 232 | name = "miette" | ||
| 233 | version = "5.10.0" | ||
| 234 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 235 | checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e" | ||
| 236 | dependencies = [ | ||
| 237 | "backtrace", | ||
| 238 | "backtrace-ext", | ||
| 239 | "is-terminal", | ||
| 240 | "miette-derive", | ||
| 241 | "once_cell", | ||
| 242 | "owo-colors", | ||
| 243 | "supports-color", | ||
| 244 | "supports-hyperlinks", | ||
| 245 | "supports-unicode", | ||
| 246 | "terminal_size", | ||
| 247 | "textwrap", | ||
| 248 | "thiserror", | ||
| 249 | "unicode-width", | ||
| 250 | ] | ||
| 251 | |||
| 252 | [[package]] | ||
| 253 | name = "miette-derive" | ||
| 254 | version = "5.10.0" | ||
| 255 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 256 | checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c" | ||
| 257 | dependencies = [ | ||
| 258 | "proc-macro2", | ||
| 259 | "quote", | ||
| 260 | "syn", | ||
| 261 | ] | ||
| 262 | |||
| 263 | [[package]] | ||
| 264 | name = "miniz_oxide" | ||
| 265 | version = "0.7.1" | ||
| 266 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 267 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" | ||
| 268 | dependencies = [ | ||
| 269 | "adler", | ||
| 270 | ] | ||
| 271 | |||
| 272 | [[package]] | ||
| 273 | name = "object" | ||
| 274 | version = "0.32.2" | ||
| 275 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 276 | checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" | ||
| 277 | dependencies = [ | ||
| 278 | "memchr", | ||
| 279 | ] | ||
| 280 | |||
| 281 | [[package]] | ||
| 282 | name = "once_cell" | ||
| 283 | version = "1.19.0" | ||
| 284 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 285 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" | ||
| 286 | |||
| 287 | [[package]] | ||
| 288 | name = "owo-colors" | ||
| 289 | version = "3.5.0" | ||
| 290 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 291 | checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" | ||
| 292 | |||
| 293 | [[package]] | ||
| 294 | name = "proc-macro2" | ||
| 295 | version = "1.0.73" | ||
| 296 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 297 | checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1" | ||
| 298 | dependencies = [ | ||
| 299 | "unicode-ident", | ||
| 300 | ] | ||
| 301 | |||
| 302 | [[package]] | ||
| 303 | name = "quote" | ||
| 304 | version = "1.0.34" | ||
| 305 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 306 | checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a" | ||
| 307 | dependencies = [ | ||
| 308 | "proc-macro2", | ||
| 309 | ] | ||
| 310 | |||
| 311 | [[package]] | ||
| 312 | name = "rustc-demangle" | ||
| 313 | version = "0.1.23" | ||
| 314 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 315 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" | ||
| 316 | |||
| 317 | [[package]] | ||
| 318 | name = "rustix" | ||
| 319 | version = "0.38.28" | ||
| 320 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 321 | checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" | ||
| 322 | dependencies = [ | ||
| 323 | "bitflags", | ||
| 324 | "errno", | ||
| 325 | "libc", | ||
| 326 | "linux-raw-sys", | ||
| 327 | "windows-sys", | ||
| 328 | ] | ||
| 329 | |||
| 330 | [[package]] | ||
| 331 | name = "smawk" | ||
| 332 | version = "0.3.2" | ||
| 333 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 334 | checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" | ||
| 335 | |||
| 336 | [[package]] | ||
| 337 | name = "strsim" | ||
| 338 | version = "0.10.0" | ||
| 339 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 340 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" | ||
| 341 | |||
| 342 | [[package]] | ||
| 343 | name = "supports-color" | ||
| 344 | version = "2.1.0" | ||
| 345 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 346 | checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" | ||
| 347 | dependencies = [ | ||
| 348 | "is-terminal", | ||
| 349 | "is_ci", | ||
| 350 | ] | ||
| 351 | |||
| 352 | [[package]] | ||
| 353 | name = "supports-hyperlinks" | ||
| 354 | version = "2.1.0" | ||
| 355 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 356 | checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d" | ||
| 357 | dependencies = [ | ||
| 358 | "is-terminal", | ||
| 359 | ] | ||
| 360 | |||
| 361 | [[package]] | ||
| 362 | name = "supports-unicode" | ||
| 363 | version = "2.0.0" | ||
| 364 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 365 | checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7" | ||
| 366 | dependencies = [ | ||
| 367 | "is-terminal", | ||
| 368 | ] | ||
| 369 | |||
| 370 | [[package]] | ||
| 371 | name = "syn" | ||
| 372 | version = "2.0.44" | ||
| 373 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 374 | checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d" | ||
| 375 | dependencies = [ | ||
| 376 | "proc-macro2", | ||
| 377 | "quote", | ||
| 378 | "unicode-ident", | ||
| 379 | ] | ||
| 380 | |||
| 381 | [[package]] | ||
| 382 | name = "terminal_size" | ||
| 383 | version = "0.1.17" | ||
| 384 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 385 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" | ||
| 386 | dependencies = [ | ||
| 387 | "libc", | ||
| 388 | "winapi", | ||
| 389 | ] | ||
| 390 | |||
| 391 | [[package]] | ||
| 392 | name = "textwrap" | ||
| 393 | version = "0.15.2" | ||
| 394 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 395 | checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" | ||
| 396 | dependencies = [ | ||
| 397 | "smawk", | ||
| 398 | "unicode-linebreak", | ||
| 399 | "unicode-width", | ||
| 400 | ] | ||
| 401 | |||
| 402 | [[package]] | ||
| 403 | name = "thiserror" | ||
| 404 | version = "1.0.53" | ||
| 405 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 406 | checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09" | ||
| 407 | dependencies = [ | ||
| 408 | "thiserror-impl", | ||
| 409 | ] | ||
| 410 | |||
| 411 | [[package]] | ||
| 412 | name = "thiserror-impl" | ||
| 413 | version = "1.0.53" | ||
| 414 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 415 | checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19" | ||
| 416 | dependencies = [ | ||
| 417 | "proc-macro2", | ||
| 418 | "quote", | ||
| 419 | "syn", | ||
| 420 | ] | ||
| 421 | |||
| 422 | [[package]] | ||
| 423 | name = "unicode-ident" | ||
| 424 | version = "1.0.12" | ||
| 425 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 426 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" | ||
| 427 | |||
| 428 | [[package]] | ||
| 429 | name = "unicode-linebreak" | ||
| 430 | version = "0.1.5" | ||
| 431 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 432 | checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" | ||
| 433 | |||
| 434 | [[package]] | ||
| 435 | name = "unicode-width" | ||
| 436 | version = "0.1.11" | ||
| 437 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 438 | checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" | ||
| 439 | |||
| 440 | [[package]] | ||
| 441 | name = "utf8parse" | ||
| 442 | version = "0.2.1" | ||
| 443 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 444 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" | ||
| 445 | |||
| 446 | [[package]] | ||
| 447 | name = "winapi" | ||
| 448 | version = "0.3.9" | ||
| 449 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 450 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" | ||
| 451 | dependencies = [ | ||
| 452 | "winapi-i686-pc-windows-gnu", | ||
| 453 | "winapi-x86_64-pc-windows-gnu", | ||
| 454 | ] | ||
| 455 | |||
| 456 | [[package]] | ||
| 457 | name = "winapi-i686-pc-windows-gnu" | ||
| 458 | version = "0.4.0" | ||
| 459 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 460 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||
| 461 | |||
| 462 | [[package]] | ||
| 463 | name = "winapi-x86_64-pc-windows-gnu" | ||
| 464 | version = "0.4.0" | ||
| 465 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 466 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" | ||
| 467 | |||
| 468 | [[package]] | ||
| 469 | name = "windows-sys" | ||
| 470 | version = "0.52.0" | ||
| 471 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 472 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||
| 473 | dependencies = [ | ||
| 474 | "windows-targets", | ||
| 475 | ] | ||
| 476 | |||
| 477 | [[package]] | ||
| 478 | name = "windows-targets" | ||
| 479 | version = "0.52.0" | ||
| 480 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 481 | checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" | ||
| 482 | dependencies = [ | ||
| 483 | "windows_aarch64_gnullvm", | ||
| 484 | "windows_aarch64_msvc", | ||
| 485 | "windows_i686_gnu", | ||
| 486 | "windows_i686_msvc", | ||
| 487 | "windows_x86_64_gnu", | ||
| 488 | "windows_x86_64_gnullvm", | ||
| 489 | "windows_x86_64_msvc", | ||
| 490 | ] | ||
| 491 | |||
| 492 | [[package]] | ||
| 493 | name = "windows_aarch64_gnullvm" | ||
| 494 | version = "0.52.0" | ||
| 495 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 496 | checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" | ||
| 497 | |||
| 498 | [[package]] | ||
| 499 | name = "windows_aarch64_msvc" | ||
| 500 | version = "0.52.0" | ||
| 501 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 502 | checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" | ||
| 503 | |||
| 504 | [[package]] | ||
| 505 | name = "windows_i686_gnu" | ||
| 506 | version = "0.52.0" | ||
| 507 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 508 | checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" | ||
| 509 | |||
| 510 | [[package]] | ||
| 511 | name = "windows_i686_msvc" | ||
| 512 | version = "0.52.0" | ||
| 513 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 514 | checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" | ||
| 515 | |||
| 516 | [[package]] | ||
| 517 | name = "windows_x86_64_gnu" | ||
| 518 | version = "0.52.0" | ||
| 519 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 520 | checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" | ||
| 521 | |||
| 522 | [[package]] | ||
| 523 | name = "windows_x86_64_gnullvm" | ||
| 524 | version = "0.52.0" | ||
| 525 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 526 | checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" | ||
| 527 | |||
| 528 | [[package]] | ||
| 529 | name = "windows_x86_64_msvc" | ||
| 530 | version = "0.52.0" | ||
| 531 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 532 | checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" | ||
crates/cli/Cargo.toml+3-1| ... | @@ -5,7 +5,7 @@ edition = "2021" | ... | @@ -5,7 +5,7 @@ edition = "2021" |
| 5 | 5 | ||
| 6 | [[bin]] | 6 | [[bin]] |
| 7 | name = "blight" | 7 | name = "blight" |
| 8 | path = "./src/main.rs" | 8 | path = "./main.rs" |
| 9 | 9 | ||
| 10 | [dependencies] | 10 | [dependencies] |
| 11 | clap.workspace = true | 11 | clap.workspace = true |
| ... | @@ -14,5 +14,7 @@ thiserror.workspace = true | ... | @@ -14,5 +14,7 @@ thiserror.workspace = true |
| 14 | 14 | ||
| 15 | tokenizer = { path = "../tokenizer" } | 15 | tokenizer = { path = "../tokenizer" } |
| 16 | ast = { path = "../ast" } | 16 | ast = { path = "../ast" } |
| 17 | parser = { path = "../parser" } | ||
| 17 | js_ast = { path = "../js_ast" } | 18 | js_ast = { path = "../js_ast" } |
| 18 | js_printer = { path = "../js_printer" } | 19 | js_printer = { path = "../js_printer" } |
| 20 | sema = { path = "../sema" } |
crates/cli/main.rs created+172| ... | @@ -0,0 +1,172 @@ | ||
| 1 | use std::{io::Write, path::PathBuf}; | ||
| 2 | |||
| 3 | use clap::{Parser, Subcommand}; | ||
| 4 | use miette::{diagnostic, Result}; | ||
| 5 | |||
| 6 | use js_printer; | ||
| 7 | use tokenizer; | ||
| 8 | |||
| 9 | #[derive(Parser)] | ||
| 10 | #[command(author, version, about, long_about = None)] | ||
| 11 | struct Cli { | ||
| 12 | /// Turn debugging information on | ||
| 13 | #[arg(short, long, action = clap::ArgAction::Count)] | ||
| 14 | debug: u8, | ||
| 15 | |||
| 16 | #[command(subcommand)] | ||
| 17 | command: Commands, | ||
| 18 | } | ||
| 19 | |||
| 20 | #[derive(Subcommand)] | ||
| 21 | enum Commands { | ||
| 22 | /// Compiles a file into javascript | ||
| 23 | Compile { | ||
| 24 | file: PathBuf, | ||
| 25 | // Whether to output to stdout instead of a file | ||
| 26 | #[arg(long, default_value_t = false)] | ||
| 27 | stdout: bool, | ||
| 28 | /// What javascript runtime to compile to | ||
| 29 | #[arg(short, long, default_value = "deno")] | ||
| 30 | target: String, | ||
| 31 | /// Format the output javascript? | ||
| 32 | #[arg(long, default_value_t = true)] | ||
| 33 | format: bool, | ||
| 34 | }, | ||
| 35 | Run { | ||
| 36 | file: PathBuf, | ||
| 37 | /// What javascript runtime to compile to and use | ||
| 38 | #[arg(short, long, default_value = "deno")] | ||
| 39 | target: String, | ||
| 40 | }, | ||
| 41 | Astdump { | ||
| 42 | file: PathBuf, | ||
| 43 | }, | ||
| 44 | } | ||
| 45 | |||
| 46 | fn get_runtime(runtime: &String) -> String { | ||
| 47 | match std::env::var("USER") { | ||
| 48 | Ok(user) if user == "dave" => String::from("bun"), | ||
| 49 | _ => runtime.clone(), | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn transpile(content: String, writer: &mut dyn Write) -> Result<()> { | ||
| 54 | // 1. Tokenize the input | ||
| 55 | let tokens = tokenizer::tokenize(content)?; | ||
| 56 | // 2. Parse the tokens into an AST | ||
| 57 | let mut blight_ast = parser::parse(tokens)?; | ||
| 58 | // 3. Semantic analysis. | ||
| 59 | sema::analyze_module(&mut blight_ast)?; | ||
| 60 | // 4. Optimize the AST | ||
| 61 | // optimize::optimize_module(&mut blight_ast); | ||
| 62 | // 5. Translate the AST into javascript | ||
| 63 | let js_ast = js_ast::blight_to_js(blight_ast); | ||
| 64 | // 6. Print the JS AST | ||
| 65 | // TODO(@paperdave): use std::io::Write instead of a string | ||
| 66 | js_printer::Print::print(&js_ast, writer) | ||
| 67 | .map_err(|e| diagnostic!("Error while printing: {}", e))?; | ||
| 68 | Ok(()) | ||
| 69 | } | ||
| 70 | |||
| 71 | pub fn transpile_to_string(content: String) -> Result<String> { | ||
| 72 | let mut buffer = Vec::new(); | ||
| 73 | transpile(content, &mut buffer)?; | ||
| 74 | Ok(String::from_utf8(buffer).unwrap()) | ||
| 75 | } | ||
| 76 | |||
| 77 | pub fn astdump(content: String) -> Result<String> { | ||
| 78 | // 1. Tokenize the input | ||
| 79 | let tokens = tokenizer::tokenize(content)?; | ||
| 80 | // 2. Parse the tokens into an AST | ||
| 81 | let blight_ast = parser::parse(tokens)?; | ||
| 82 | return Ok(format!("{:#?}", blight_ast)); | ||
| 83 | } | ||
| 84 | |||
| 85 | fn main() -> Result<()> { | ||
| 86 | let cli = Cli::parse(); | ||
| 87 | |||
| 88 | match cli.command { | ||
| 89 | Commands::Compile { | ||
| 90 | file, | ||
| 91 | stdout, | ||
| 92 | target: _, | ||
| 93 | format, | ||
| 94 | } => { | ||
| 95 | let contents = match std::fs::read_to_string(file.clone()) { | ||
| 96 | Ok(contents) => contents, | ||
| 97 | Err(e) => { | ||
| 98 | eprintln!("Failed to read {}: {}", file.display(), e); | ||
| 99 | std::process::exit(1); | ||
| 100 | } | ||
| 101 | }; | ||
| 102 | |||
| 103 | let mut result = transpile_to_string(contents.clone())?; | ||
| 104 | |||
| 105 | if format { | ||
| 106 | let mut formatter = std::process::Command::new("deno") | ||
| 107 | .stdin(std::process::Stdio::piped()) | ||
| 108 | .stdout(std::process::Stdio::piped()) | ||
| 109 | .stderr(std::process::Stdio::inherit()) | ||
| 110 | .arg("fmt") | ||
| 111 | .arg("-") | ||
| 112 | .spawn() | ||
| 113 | .expect("Failed to execute 'deno fmt' on output"); | ||
| 114 | let mut stdin = formatter.stdin.take().expect("Failed to open stdin"); | ||
| 115 | stdin | ||
| 116 | .write_all(result.as_bytes()) | ||
| 117 | .expect("Failed to write to stdin"); | ||
| 118 | stdin.flush().expect("Failed to flush stdin"); | ||
| 119 | drop(stdin); | ||
| 120 | let out = formatter | ||
| 121 | .wait_with_output() | ||
| 122 | .expect("Failed to execute 'deno fmt' on output"); | ||
| 123 | result = String::from_utf8(out.stdout).unwrap(); | ||
| 124 | } | ||
| 125 | |||
| 126 | if stdout { | ||
| 127 | println!("{}", result); | ||
| 128 | } else { | ||
| 129 | std::fs::write(file.with_extension("js"), result).unwrap(); | ||
| 130 | } | ||
| 131 | } | ||
| 132 | Commands::Run { file, target } => { | ||
| 133 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | ||
| 134 | let result = transpile_to_string(contents.clone())?; | ||
| 135 | let mut temp_file = std::env::temp_dir(); | ||
| 136 | temp_file.push("temp.js"); | ||
| 137 | std::fs::write(temp_file.clone(), result).unwrap(); | ||
| 138 | |||
| 139 | let rt = get_runtime(&target); | ||
| 140 | |||
| 141 | match rt.as_str() { | ||
| 142 | "deno" => std::process::Command::new("deno") | ||
| 143 | .arg("run") | ||
| 144 | .arg("-A") | ||
| 145 | .arg(temp_file) | ||
| 146 | .spawn() | ||
| 147 | .expect("Failed to execute process"), | ||
| 148 | "node" => std::process::Command::new("node") | ||
| 149 | .arg(temp_file) | ||
| 150 | .spawn() | ||
| 151 | .expect("Failed to execute process"), | ||
| 152 | "bun" => std::process::Command::new("bun") | ||
| 153 | .arg("run") | ||
| 154 | .arg(temp_file) | ||
| 155 | .spawn() | ||
| 156 | .expect("Failed to execute process"), | ||
| 157 | _ => { | ||
| 158 | panic!("Unknown runtime: {}", target); | ||
| 159 | } | ||
| 160 | } | ||
| 161 | .wait() | ||
| 162 | .unwrap(); | ||
| 163 | } | ||
| 164 | Commands::Astdump { file } => { | ||
| 165 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | ||
| 166 | let result = astdump(contents.clone())?; | ||
| 167 | println!("{}", result); | ||
| 168 | } | ||
| 169 | } | ||
| 170 | |||
| 171 | Ok(()) | ||
| 172 | } | ||
crates/cli/src/main.rs deleted-118| ... | @@ -1,118 +0,0 @@ | ||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | use clap::{Parser, Subcommand}; | ||
| 4 | use miette::Result; | ||
| 5 | |||
| 6 | use ast; | ||
| 7 | use js_printer; | ||
| 8 | use tokenizer; | ||
| 9 | |||
| 10 | #[derive(Parser)] | ||
| 11 | #[command(author, version, about, long_about = None)] | ||
| 12 | struct Cli { | ||
| 13 | /// Turn debugging information on | ||
| 14 | #[arg(short, long, action = clap::ArgAction::Count)] | ||
| 15 | debug: u8, | ||
| 16 | |||
| 17 | #[command(subcommand)] | ||
| 18 | command: Commands, | ||
| 19 | } | ||
| 20 | |||
| 21 | #[derive(Subcommand)] | ||
| 22 | enum Commands { | ||
| 23 | /// Compiles a file into javascript | ||
| 24 | Compile { | ||
| 25 | file: PathBuf, | ||
| 26 | // Whether to output to stdout instead of a file | ||
| 27 | #[arg(long, default_value_t = false)] | ||
| 28 | stdout: bool, | ||
| 29 | /// What javascript runtime to compile to | ||
| 30 | #[arg(short, long, default_value = "deno")] | ||
| 31 | target: String, | ||
| 32 | }, | ||
| 33 | Run { | ||
| 34 | file: PathBuf, | ||
| 35 | /// What javascript runtime to compile to and use | ||
| 36 | #[arg(short, long, default_value = "deno")] | ||
| 37 | target: String, | ||
| 38 | }, | ||
| 39 | } | ||
| 40 | |||
| 41 | fn get_runtime(runtime: &String) -> String { | ||
| 42 | match std::env::var("USER") { | ||
| 43 | Ok(user) if user == "dave" => String::from("bun"), | ||
| 44 | _ => runtime.clone(), | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | pub fn transpile(content: String) -> Result<String> { | ||
| 49 | // 1. Tokenize the input | ||
| 50 | let tokens = tokenizer::tokenize(content)?; | ||
| 51 | // 2. Parse the tokens into an AST | ||
| 52 | let blight_ast = ast::parse(tokens)?; | ||
| 53 | // 3. Type check the AST | ||
| 54 | // type_check(tree.clone())?; | ||
| 55 | // 4. Optimize the AST | ||
| 56 | // optimize(tree.clone())?; | ||
| 57 | // 5. Translate the AST into javascript | ||
| 58 | let js_ast = js_ast::translate(blight_ast)?; | ||
| 59 | // 6. Print the JS AST | ||
| 60 | let javascript = js_printer::print(js_ast); | ||
| 61 | // 7. Return the javascript | ||
| 62 | return Ok(javascript); | ||
| 63 | } | ||
| 64 | |||
| 65 | fn main() -> Result<()> { | ||
| 66 | let cli = Cli::parse(); | ||
| 67 | |||
| 68 | match cli.command { | ||
| 69 | Commands::Compile { | ||
| 70 | file, | ||
| 71 | stdout, | ||
| 72 | target: _, | ||
| 73 | } => { | ||
| 74 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | ||
| 75 | let result = transpile(contents.clone())?; | ||
| 76 | |||
| 77 | if stdout { | ||
| 78 | println!("{}", result); | ||
| 79 | } else { | ||
| 80 | std::fs::write(file.with_extension("js"), result).unwrap(); | ||
| 81 | } | ||
| 82 | } | ||
| 83 | Commands::Run { file, target } => { | ||
| 84 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | ||
| 85 | let result = transpile(contents.clone())?; | ||
| 86 | let mut temp_file = std::env::temp_dir(); | ||
| 87 | temp_file.push("temp.js"); | ||
| 88 | std::fs::write(temp_file.clone(), result).unwrap(); | ||
| 89 | |||
| 90 | let rt = get_runtime(&target); | ||
| 91 | |||
| 92 | match rt.as_str() { | ||
| 93 | "deno" => std::process::Command::new("deno") | ||
| 94 | .arg("run") | ||
| 95 | .arg("-A") | ||
| 96 | .arg(temp_file) | ||
| 97 | .spawn() | ||
| 98 | .expect("Failed to execute process"), | ||
| 99 | "node" => std::process::Command::new("node") | ||
| 100 | .arg(temp_file) | ||
| 101 | .spawn() | ||
| 102 | .expect("Failed to execute process"), | ||
| 103 | "bun" => std::process::Command::new("bun") | ||
| 104 | .arg("run") | ||
| 105 | .arg(temp_file) | ||
| 106 | .spawn() | ||
| 107 | .expect("Failed to execute process"), | ||
| 108 | _ => { | ||
| 109 | panic!("Unknown runtime: {}", target); | ||
| 110 | } | ||
| 111 | } | ||
| 112 | .wait() | ||
| 113 | .unwrap(); | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | Ok(()) | ||
| 118 | } | ||
crates/js_ast/Cargo.toml+3-1| ... | @@ -4,6 +4,7 @@ version = "0.1.0" | ... | @@ -4,6 +4,7 @@ version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
| 7 | path = "./lib.rs" | ||
| 7 | 8 | ||
| 8 | [dependencies] | 9 | [dependencies] |
| 9 | miette.workspace = true | 10 | miette.workspace = true |
| ... | @@ -11,4 +12,5 @@ thiserror.workspace = true | ... | @@ -11,4 +12,5 @@ thiserror.workspace = true |
| 11 | num.workspace = true | 12 | num.workspace = true |
| 12 | 13 | ||
| 13 | ast = { path = "../ast" } | 14 | ast = { path = "../ast" } |
| 14 | util = { path = "../util" } | 15 | tokenizer = { path = "../tokenizer" } # TODO: move Loc out of the tokenizer |
| 16 | parser = { path = "../parser" } # TODO: move Loc out of the tokenizer |
crates/js_ast/convert/block_convert_context.rs created+244| ... | @@ -0,0 +1,244 @@ | ||
| 1 | use crate::js_ast::*; | ||
| 2 | use ast::*; | ||
| 3 | use std::collections::{HashMap, HashSet}; | ||
| 4 | use std::vec; | ||
| 5 | use tokenizer::Loc; | ||
| 6 | |||
| 7 | use super::tree_shake_statement_list; | ||
| 8 | |||
| 9 | /// A block convert context keeps track of state while converting non-valid JS expressions to JS | ||
| 10 | pub struct BlockConvertContext { | ||
| 11 | /// Statements in this block context | ||
| 12 | /// This may be the merged contents of multiple blocks | ||
| 13 | statements: Vec<JsStatement>, | ||
| 14 | ids: HashSet<String>, | ||
| 15 | next_id: u32, | ||
| 16 | scopes: Vec<VirtualScope>, | ||
| 17 | /// If the last item in `statements` is actually valid | ||
| 18 | has_last_statement: bool, | ||
| 19 | } | ||
| 20 | |||
| 21 | struct VirtualScope { | ||
| 22 | renames: HashMap<String, String>, | ||
| 23 | } | ||
| 24 | |||
| 25 | pub struct TempId { | ||
| 26 | unmapped_name: String, | ||
| 27 | } | ||
| 28 | |||
| 29 | pub struct StatementHandle { | ||
| 30 | index: Option<u32>, | ||
| 31 | } | ||
| 32 | |||
| 33 | impl StatementHandle { | ||
| 34 | pub fn delete(self, ctx: &mut BlockConvertContext) { | ||
| 35 | if let Some(index) = self.index { | ||
| 36 | ctx.statements.remove(index as usize); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | impl TempId { | ||
| 42 | pub fn get(&self, hoist_context: &BlockConvertContext) -> String { | ||
| 43 | hoist_context.remap_identifier(&self.unmapped_name) | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | impl BlockConvertContext { | ||
| 48 | pub fn new() -> Self { | ||
| 49 | Self { | ||
| 50 | statements: Vec::new(), | ||
| 51 | ids: HashSet::new(), | ||
| 52 | next_id: 0, | ||
| 53 | scopes: vec![VirtualScope { | ||
| 54 | renames: HashMap::new(), | ||
| 55 | }], | ||
| 56 | has_last_statement: false, | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | pub fn new_child(&self) -> Self { | ||
| 61 | Self { | ||
| 62 | statements: Vec::new(), | ||
| 63 | ids: self.ids.clone(), | ||
| 64 | next_id: 0, | ||
| 65 | scopes: vec![VirtualScope { | ||
| 66 | renames: HashMap::new(), | ||
| 67 | }], | ||
| 68 | has_last_statement: false, | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | /// A virtual scope is a scope that exists in blight syntax | ||
| 73 | /// but may or may not exist in blight. With these it is important to | ||
| 74 | pub fn push_virtual_scope(&mut self) { | ||
| 75 | self.scopes.push(VirtualScope { | ||
| 76 | renames: HashMap::new(), | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | |||
| 80 | /// See `push_virtual_scope` for more information on what this is. | ||
| 81 | pub fn pop_virtual_scope(&mut self) { | ||
| 82 | self.scopes.pop(); | ||
| 83 | debug_assert!(self.scopes.len() > 0); | ||
| 84 | } | ||
| 85 | |||
| 86 | pub fn generate_temp_id(&mut self, basename: &str) -> TempId { | ||
| 87 | self.next_id += 1; | ||
| 88 | loop { | ||
| 89 | let name = format!("{}{}", basename, self.next_id); | ||
| 90 | if !self.ids.contains(&name) { | ||
| 91 | self.ids.insert(name.clone()); | ||
| 92 | return TempId { | ||
| 93 | unmapped_name: name, | ||
| 94 | }; | ||
| 95 | } | ||
| 96 | self.next_id += 1; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | |||
| 100 | /// This does some extra optimizations: | ||
| 101 | /// - `let a = b;` will not be emitted but instad write a variable alias | ||
| 102 | pub fn add_statement(&mut self, statement: JsStatement) -> StatementHandle { | ||
| 103 | match statement.data { | ||
| 104 | JsStatementData::Declaration(JSDeclaration { | ||
| 105 | name, | ||
| 106 | init: | ||
| 107 | Some(JsExpr { | ||
| 108 | data: JsExprData::Identifier(init_name), | ||
| 109 | .. | ||
| 110 | }), | ||
| 111 | is_constant: true, | ||
| 112 | }) => { | ||
| 113 | self.scopes | ||
| 114 | .last_mut() | ||
| 115 | .unwrap() | ||
| 116 | .renames | ||
| 117 | .insert(name, init_name); | ||
| 118 | self.has_last_statement = false; | ||
| 119 | return StatementHandle { index: None }; | ||
| 120 | } | ||
| 121 | _ => {} | ||
| 122 | } | ||
| 123 | self.statements.push(statement); | ||
| 124 | self.has_last_statement = true; | ||
| 125 | StatementHandle { | ||
| 126 | index: Some(self.statements.len() as u32 - 1), | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Get the last statement if it exists. | ||
| 131 | /// If the statement was optimized away, this returns None | ||
| 132 | pub fn last_statement(&self) -> Option<&JsStatement> { | ||
| 133 | if self.has_last_statement { | ||
| 134 | Some(self.statements.last().unwrap()) | ||
| 135 | } else { | ||
| 136 | None | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | pub fn generate_id(&mut self, basename: &String) -> TempId { | ||
| 141 | let mut id = 0u32; | ||
| 142 | loop { | ||
| 143 | let name = match id { | ||
| 144 | 0 => basename.clone(), | ||
| 145 | _ => format!("{}{}", basename, id), | ||
| 146 | }; | ||
| 147 | if !self.ids.contains(&name) { | ||
| 148 | self.ids.insert(name.clone()); | ||
| 149 | return TempId { | ||
| 150 | unmapped_name: name, | ||
| 151 | }; | ||
| 152 | } | ||
| 153 | id += 1; | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | /// Given a blight identifier, remap it to a new identifier. | ||
| 158 | pub fn remap_identifier(&self, name: &String) -> String { | ||
| 159 | for scope in self.scopes.iter().rev() { | ||
| 160 | if let Some(remapped) = scope.renames.get(name) { | ||
| 161 | return remapped.clone(); | ||
| 162 | } | ||
| 163 | } | ||
| 164 | name.clone() | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Declare an identifier in the current scope. | ||
| 168 | pub fn declare_identifier(&mut self, name: String) -> TempId { | ||
| 169 | let remapped = self.generate_id(&name); | ||
| 170 | let scope = self.scopes.last_mut().unwrap(); | ||
| 171 | self.ids.insert(remapped.unmapped_name.clone()); | ||
| 172 | scope.renames.insert(name, remapped.unmapped_name.clone()); | ||
| 173 | remapped | ||
| 174 | } | ||
| 175 | |||
| 176 | pub fn take_last_statement(&mut self) -> Option<JsStatement> { | ||
| 177 | if !self.has_last_statement { | ||
| 178 | return None; | ||
| 179 | } | ||
| 180 | self.has_last_statement = false; | ||
| 181 | Some(self.statements.pop().unwrap()) | ||
| 182 | } | ||
| 183 | |||
| 184 | pub fn take_last_statement_if_expr(&mut self) -> Option<JsExpr> { | ||
| 185 | if !self.has_last_statement { | ||
| 186 | return None; | ||
| 187 | } | ||
| 188 | match self.statements.last().unwrap().data { | ||
| 189 | JsStatementData::Expression(_) => { | ||
| 190 | self.has_last_statement = false; | ||
| 191 | match self.statements.pop().unwrap().data { | ||
| 192 | JsStatementData::Expression(expr) => Some(expr), | ||
| 193 | _ => unreachable!(), | ||
| 194 | } | ||
| 195 | } | ||
| 196 | _ => return None, | ||
| 197 | } | ||
| 198 | } | ||
| 199 | } | ||
| 200 | |||
| 201 | pub fn new_js_assignment_statement(expr: JsExpr, target: String, loc: Loc) -> JsStatement { | ||
| 202 | JsStatement::new( | ||
| 203 | JsStatementData::Expression(new_js_assignment_expr(expr, target, loc.clone())), | ||
| 204 | loc, | ||
| 205 | None, | ||
| 206 | ) | ||
| 207 | } | ||
| 208 | |||
| 209 | pub fn new_js_assignment_expr(expr: JsExpr, target: String, loc: Loc) -> JsExpr { | ||
| 210 | JsExpr::new( | ||
| 211 | JsExprData::Binary { | ||
| 212 | op: BinaryOperator::Assign, | ||
| 213 | left: Box::new(JsExpr::new( | ||
| 214 | JsExprData::Identifier(target), | ||
| 215 | loc.clone(), | ||
| 216 | None, | ||
| 217 | )), | ||
| 218 | right: Box::new(expr), | ||
| 219 | }, | ||
| 220 | loc, | ||
| 221 | None, | ||
| 222 | ) | ||
| 223 | } | ||
| 224 | |||
| 225 | impl Into<JsBlock> for BlockConvertContext { | ||
| 226 | fn into(self) -> JsBlock { | ||
| 227 | JsBlock::new(self.into(), Loc::EMPTY) | ||
| 228 | } | ||
| 229 | } | ||
| 230 | |||
| 231 | impl Into<JsModule> for BlockConvertContext { | ||
| 232 | fn into(self) -> JsModule { | ||
| 233 | JsModule { | ||
| 234 | statements: self.into(), | ||
| 235 | } | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | impl Into<Vec<JsStatement>> for BlockConvertContext { | ||
| 240 | fn into(mut self) -> Vec<JsStatement> { | ||
| 241 | tree_shake_statement_list(&mut self.statements); | ||
| 242 | self.statements | ||
| 243 | } | ||
| 244 | } | ||
crates/js_ast/convert/convert_valid_block.rs created+37| ... | @@ -0,0 +1,37 @@ | ||
| 1 | use super::convert_valid_statement_to_expr; | ||
| 2 | use super::BlockConvertContext; | ||
| 3 | use super::IsValidJSExpression; | ||
| 4 | use crate::js_ast::*; | ||
| 5 | use ast::*; | ||
| 6 | use tokenizer::Loc; | ||
| 7 | |||
| 8 | pub fn convert_valid_block(ctx: &mut BlockConvertContext, block: Block, loc: Loc) -> JsExpr { | ||
| 9 | debug_assert!( | ||
| 10 | block.is_valid_js_expression(), | ||
| 11 | "must call convert_valid_block with valid js expression" | ||
| 12 | ); | ||
| 13 | |||
| 14 | let len = block.statements.len(); | ||
| 15 | let loc = loc; | ||
| 16 | let block_expr = match len { | ||
| 17 | 0 => JsExpr::new(JsExprData::Null, loc.clone(), None), | ||
| 18 | // a block expression with a single expression is just that expression | ||
| 19 | 1 => convert_valid_statement_to_expr(ctx, block.statements.into_iter().next().unwrap()), | ||
| 20 | // Since blocks are not valid expressions in JS, we create a | ||
| 21 | // comma expression from a block with more than one statement. | ||
| 22 | 2.. => { | ||
| 23 | let mut items = Vec::new(); | ||
| 24 | for statement in block.statements.into_iter() { | ||
| 25 | items.push(convert_valid_statement_to_expr(ctx, statement)); | ||
| 26 | } | ||
| 27 | JsExpr::new(JsExprData::Comma { items }, loc.clone(), None) | ||
| 28 | } | ||
| 29 | }; | ||
| 30 | |||
| 31 | // a block without implicit return is to be type void, so we wrap in a void expression | ||
| 32 | if !block.has_implicit_return && len > 0 { | ||
| 33 | JsExpr::new(JsExprData::Void(Box::new(block_expr)), loc, None) | ||
| 34 | } else { | ||
| 35 | block_expr | ||
| 36 | } | ||
| 37 | } | ||
crates/js_ast/convert/convert_valid_expr.rs created+115| ... | @@ -0,0 +1,115 @@ | ||
| 1 | use super::convert_valid_block; | ||
| 2 | use super::BlockConvertContext; | ||
| 3 | use crate::convert::convert_valid_if; | ||
| 4 | use crate::convert::process_function::process_function_to_arrow; | ||
| 5 | use crate::convert::IsValidJSExpression; | ||
| 6 | use crate::js_ast::*; | ||
| 7 | use ast::*; | ||
| 8 | |||
| 9 | /// Translate a blight ast expression that is a valid JS expression, to a JS expression | ||
| 10 | /// Precondition: it is a valid JS expression, check this with `is_valid_js_expression` | ||
| 11 | pub fn convert_valid_expr(ctx: &mut BlockConvertContext, expr: Expr) -> JsExpr { | ||
| 12 | // Assert may be skipped in release builds as all failures are | ||
| 13 | // annotated with a release-checked panic | ||
| 14 | debug_assert!( | ||
| 15 | expr.is_valid_js_expression(), | ||
| 16 | "must call convert_valid_expr with valid js expression (use hoist instead)" | ||
| 17 | ); | ||
| 18 | |||
| 19 | let loc = expr.loc; | ||
| 20 | let comment = expr.comment; | ||
| 21 | |||
| 22 | match expr.data { | ||
| 23 | ExprData::Identifier(str) => JsExpr::new( | ||
| 24 | JsExprData::Identifier(ctx.remap_identifier(&str)), | ||
| 25 | loc, | ||
| 26 | comment, | ||
| 27 | ), | ||
| 28 | |||
| 29 | ExprData::Literal(Literal::Bool(value)) => { | ||
| 30 | JsExpr::new(JsExprData::Bool(value), loc, comment) | ||
| 31 | } | ||
| 32 | ExprData::Literal(Literal::Number(value)) => { | ||
| 33 | JsExpr::new(JsExprData::Number(value), loc, comment) | ||
| 34 | } | ||
| 35 | ExprData::Literal(Literal::String(value)) => { | ||
| 36 | JsExpr::new(JsExprData::String(value), loc, comment) | ||
| 37 | } | ||
| 38 | ExprData::Literal(Literal::Void) => JsExpr::new(JsExprData::Null, loc, comment), | ||
| 39 | |||
| 40 | ExprData::Block(block) => convert_valid_block(ctx, block, loc), | ||
| 41 | |||
| 42 | ExprData::Binary { op, left, right } => { | ||
| 43 | let left = convert_valid_expr(ctx, *left); | ||
| 44 | let right = convert_valid_expr(ctx, *right); | ||
| 45 | JsExpr::new( | ||
| 46 | JsExprData::Binary { | ||
| 47 | op, | ||
| 48 | left: Box::new(left), | ||
| 49 | right: Box::new(right), | ||
| 50 | }, | ||
| 51 | loc, | ||
| 52 | comment, | ||
| 53 | ) | ||
| 54 | } | ||
| 55 | ExprData::Unary { op, value } => { | ||
| 56 | let value = convert_valid_expr(ctx, *value); | ||
| 57 | JsExpr::new( | ||
| 58 | JsExprData::Unary { | ||
| 59 | op, | ||
| 60 | value: Box::new(value), | ||
| 61 | }, | ||
| 62 | loc, | ||
| 63 | comment, | ||
| 64 | ) | ||
| 65 | } | ||
| 66 | ExprData::Fn { body, params, .. } => { | ||
| 67 | process_function_to_arrow(ctx, *body, params, loc, comment) | ||
| 68 | } | ||
| 69 | ExprData::Builtin { id, arguments } => match id { | ||
| 70 | BuiltinId::Raw => { | ||
| 71 | let first_arg = arguments.iter().next().unwrap(); | ||
| 72 | // TODO(@paperdave): this string clone is stupid | ||
| 73 | let str = match first_arg.data.clone() { | ||
| 74 | ExprData::Literal(Literal::String(s)) => s, | ||
| 75 | _ => panic!("raw expects a string"), | ||
| 76 | }; | ||
| 77 | JsExpr::new(JsExprData::Raw(str), loc, comment) | ||
| 78 | } | ||
| 79 | }, | ||
| 80 | ExprData::If(expr) => convert_valid_if(ctx, expr, loc, comment), | ||
| 81 | |||
| 82 | ExprData::Call { base, arguments } => { | ||
| 83 | let base = convert_valid_expr(ctx, *base); | ||
| 84 | let arguments = arguments | ||
| 85 | .into_iter() | ||
| 86 | .map(|x| convert_valid_expr(ctx, x)) | ||
| 87 | .collect(); | ||
| 88 | JsExpr::new( | ||
| 89 | JsExprData::Call { | ||
| 90 | base: Box::new(base), | ||
| 91 | arguments, | ||
| 92 | }, | ||
| 93 | loc, | ||
| 94 | comment, | ||
| 95 | ) | ||
| 96 | } | ||
| 97 | |||
| 98 | ExprData::Field { base, field } => JsExpr::new( | ||
| 99 | match field.data { | ||
| 100 | ExprData::Literal(Literal::String(name)) => JsExprData::Dot { | ||
| 101 | base: Box::new(convert_valid_expr(ctx, *base)), | ||
| 102 | name, | ||
| 103 | }, | ||
| 104 | _ => JsExprData::Index { | ||
| 105 | base: Box::new(convert_valid_expr(ctx, *base)), | ||
| 106 | index: Box::new(convert_valid_expr(ctx, *field)), | ||
| 107 | }, | ||
| 108 | }, | ||
| 109 | loc, | ||
| 110 | comment, | ||
| 111 | ), | ||
| 112 | |||
| 113 | ExprData::Return(_) => unreachable!("convert_valid_expr called on return"), | ||
| 114 | } | ||
| 115 | } | ||
crates/js_ast/convert/convert_valid_if.rs created+38| ... | @@ -0,0 +1,38 @@ | ||
| 1 | use super::convert_valid_block; | ||
| 2 | use super::convert_valid_expr; | ||
| 3 | use super::BlockConvertContext; | ||
| 4 | use crate::convert::IsValidJSExpression; | ||
| 5 | use crate::js_ast::*; | ||
| 6 | use ast::*; | ||
| 7 | use tokenizer::Loc; | ||
| 8 | |||
| 9 | /// Convert a valid blight if statement to a JS expression | ||
| 10 | pub fn convert_valid_if( | ||
| 11 | ctx: &mut BlockConvertContext, | ||
| 12 | expr: If, | ||
| 13 | loc: Loc, | ||
| 14 | comment: Option<Comment>, | ||
| 15 | ) -> JsExpr { | ||
| 16 | debug_assert!( | ||
| 17 | expr.is_valid_js_expression(), | ||
| 18 | "convert_valid_if on an invalid expression (use hoist instead)" | ||
| 19 | ); | ||
| 20 | let condition = convert_valid_expr(ctx, *expr.condition); | ||
| 21 | |||
| 22 | let yes = convert_valid_block(ctx, expr.yes.block, expr.yes.loc); | ||
| 23 | let no = match expr.no { | ||
| 24 | Some(BlockOrIf::Block(b)) => Some(convert_valid_block(ctx, b.block, b.loc)), | ||
| 25 | Some(BlockOrIf::If(else_if)) => Some(convert_valid_if(ctx, *else_if, Loc::EMPTY, None)), | ||
| 26 | None => None, | ||
| 27 | }; | ||
| 28 | |||
| 29 | JsExpr::new( | ||
| 30 | JsExprData::Ternary { | ||
| 31 | condition: Box::new(condition), | ||
| 32 | yes: Box::new(yes), | ||
| 33 | no: no.map(Box::new), | ||
| 34 | }, | ||
| 35 | loc, | ||
| 36 | comment, | ||
| 37 | ) | ||
| 38 | } | ||
crates/js_ast/convert/convert_valid_statement.rs created+32| ... | @@ -0,0 +1,32 @@ | ||
| 1 | use super::convert_valid_expr; | ||
| 2 | use super::is_valid_js_expression::*; | ||
| 3 | use super::BlockConvertContext; | ||
| 4 | use crate::js_ast::*; | ||
| 5 | use ast::*; | ||
| 6 | |||
| 7 | /// Translate a blight ast statement that is a valid JS expression, to a JS expression | ||
| 8 | /// Precondition: it is a valid JS expression, check this with `is_valid_js_expression` | ||
| 9 | pub fn convert_valid_statement_to_expr( | ||
| 10 | ctx: &mut BlockConvertContext, | ||
| 11 | statement: Statement, | ||
| 12 | ) -> JsExpr { | ||
| 13 | debug_assert!( | ||
| 14 | statement.is_valid_js_expression(), | ||
| 15 | "convert_valid_statement_to_expr on an invalid expression (use hoist instead)" | ||
| 16 | ); | ||
| 17 | |||
| 18 | match statement { | ||
| 19 | ast::Statement::Expr(expr) => convert_valid_expr(ctx, expr), | ||
| 20 | _ => unreachable!("attempt to convert statement to js without hoisting"), | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | /// Translate a blight ast statement that is a valid JS expression, to a JS statement | ||
| 25 | /// Precondition: it is a valid JS expression, check this with `is_valid_js_expression` | ||
| 26 | /// | ||
| 27 | /// TODO(@paperdave): Replace this with Into<JsStatement> for JsExpr | ||
| 28 | pub fn convert_valid_statement(ctx: &mut BlockConvertContext, statement: Statement) -> JsStatement { | ||
| 29 | let expr = convert_valid_statement_to_expr(ctx, statement); | ||
| 30 | let loc = expr.loc.clone(); | ||
| 31 | JsStatement::new(JsStatementData::Expression(expr), loc, None) | ||
| 32 | } | ||
crates/js_ast/convert/hoist_block_to_expr.rs created+78| ... | @@ -0,0 +1,78 @@ | ||
| 1 | use super::is_valid_js_expression::*; | ||
| 2 | use super::process_statement; | ||
| 3 | use super::BlockConvertContext; | ||
| 4 | use crate::js_ast::*; | ||
| 5 | use ast::*; | ||
| 6 | use tokenizer::Loc; | ||
| 7 | |||
| 8 | /// This block hoist is used when we have a block in an expression context. | ||
| 9 | /// | ||
| 10 | /// All the expressions in the block are lifted above the currently | ||
| 11 | /// processed expression, and then a temporary variable is returned. | ||
| 12 | /// (or the last expression might get inlined as the return value) | ||
| 13 | /// | ||
| 14 | /// A "virtual scope" is created for this block, which handles renaming | ||
| 15 | /// situations like: | ||
| 16 | /// | ||
| 17 | /// let x = 1; | ||
| 18 | /// { | ||
| 19 | /// let x = 1; | ||
| 20 | /// } | ||
| 21 | /// call(x) | ||
| 22 | /// | ||
| 23 | /// The output JS of the above would be: | ||
| 24 | /// | ||
| 25 | /// const x = 1; | ||
| 26 | /// const x2 = 1; | ||
| 27 | /// call(x) | ||
| 28 | /// | ||
| 29 | /// For expressions that have branches, use `hoist_block_to_js_block` | ||
| 30 | pub fn hoist_block_to_expr( | ||
| 31 | ctx: &mut BlockConvertContext, | ||
| 32 | block: Block, | ||
| 33 | loc: Loc, | ||
| 34 | comment: Option<Comment>, | ||
| 35 | ) -> Option<JsExpr> { | ||
| 36 | debug_assert!( | ||
| 37 | !block.is_valid_js_expression(), | ||
| 38 | "hoist_block on a valid expression, should call convert_valid_block instead" | ||
| 39 | ); | ||
| 40 | |||
| 41 | let temporary = ctx.generate_temp_id("_block"); | ||
| 42 | |||
| 43 | ctx.push_virtual_scope(); | ||
| 44 | |||
| 45 | for statement in block.statements { | ||
| 46 | process_statement(ctx, statement); | ||
| 47 | } | ||
| 48 | |||
| 49 | ctx.pop_virtual_scope(); | ||
| 50 | |||
| 51 | // If the last statement is an expression, we need | ||
| 52 | // to assign to the temporary variable. | ||
| 53 | let result = match ctx.last_statement().map(|x| &x.data) { | ||
| 54 | Some(JsStatementData::Expression(_)) if block.has_implicit_return => { | ||
| 55 | match ctx.take_last_statement().unwrap().data { | ||
| 56 | JsStatementData::Expression(expr) => expr, | ||
| 57 | _ => unreachable!(), | ||
| 58 | } | ||
| 59 | } | ||
| 60 | _ => JsExpr::new(JsExprData::Null, loc.clone(), None), | ||
| 61 | }; | ||
| 62 | |||
| 63 | ctx.add_statement(JsStatement::new( | ||
| 64 | JsStatementData::Declaration(JSDeclaration { | ||
| 65 | name: temporary.get(&ctx), | ||
| 66 | init: Some(result), | ||
| 67 | is_constant: false, | ||
| 68 | }), | ||
| 69 | loc.clone(), | ||
| 70 | comment, | ||
| 71 | )); | ||
| 72 | |||
| 73 | Some(JsExpr::new( | ||
| 74 | JsExprData::Identifier(temporary.get(&ctx)), | ||
| 75 | loc, | ||
| 76 | None, | ||
| 77 | )) | ||
| 78 | } | ||
crates/js_ast/convert/hoist_block_to_js_block.rs created+68| ... | @@ -0,0 +1,68 @@ | ||
| 1 | use crate::js_ast::*; | ||
| 2 | use ast::*; | ||
| 3 | use tokenizer::Loc; | ||
| 4 | |||
| 5 | use super::*; | ||
| 6 | |||
| 7 | /// This block hoist is used when we actually need to create a JsBlock, which | ||
| 8 | /// is needed if there is an invalid expression within a branch of code. | ||
| 9 | /// | ||
| 10 | /// For example, `if` has two branches, the 'yes' and 'no' paths. | ||
| 11 | /// When we cannot lower it as a ternary, we have to create two JsBlocks | ||
| 12 | /// | ||
| 13 | /// The `target` param is a shared temporary variable that will be assigned | ||
| 14 | /// to the result of the block. | ||
| 15 | /// | ||
| 16 | /// Since blocks can be void or noreturn, this target is not always | ||
| 17 | /// assigned to. The `did_assign` flag is used to indicate that. | ||
| 18 | /// | ||
| 19 | /// For expressions that do not have branches, use `hoist_block_to_expr` | ||
| 20 | /// | ||
| 21 | /// TODO(@paperdave): move `did_assign` into the return value | ||
| 22 | pub fn hoist_block_to_js_block( | ||
| 23 | ctx: &mut BlockConvertContext, | ||
| 24 | block: Block, | ||
| 25 | loc: Loc, | ||
| 26 | target: &TempId, | ||
| 27 | did_assign: &mut bool, | ||
| 28 | ) -> JsBlock { | ||
| 29 | // A non-block will become a block with a single expression: | ||
| 30 | // _temp = expr; | ||
| 31 | // | ||
| 32 | // We will also apply this transformation to blocks that are valid | ||
| 33 | // expressions, because that is more concise. | ||
| 34 | if block.is_valid_js_expression() { | ||
| 35 | return JsBlock::new( | ||
| 36 | vec![new_js_assignment_statement( | ||
| 37 | convert_valid_block(ctx, block, loc.clone()), | ||
| 38 | target.get(ctx), | ||
| 39 | loc.clone(), | ||
| 40 | )], | ||
| 41 | loc, | ||
| 42 | ); | ||
| 43 | } | ||
| 44 | |||
| 45 | let mut child = ctx.new_child(); | ||
| 46 | |||
| 47 | for statement in block.statements { | ||
| 48 | if let None = process_statement(&mut child, statement) { | ||
| 49 | return child.into(); | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | // If the last statement is an expression, we need | ||
| 54 | // to assign to the temporary variable. | ||
| 55 | match child.take_last_statement_if_expr() { | ||
| 56 | Some(expr) => { | ||
| 57 | *did_assign = true; | ||
| 58 | child.add_statement(new_js_assignment_statement( | ||
| 59 | expr, | ||
| 60 | target.get(ctx), | ||
| 61 | loc.clone(), | ||
| 62 | )); | ||
| 63 | } | ||
| 64 | _ => {} | ||
| 65 | } | ||
| 66 | |||
| 67 | child.into() | ||
| 68 | } | ||
crates/js_ast/convert/hoist_expr.rs created+132| ... | @@ -0,0 +1,132 @@ | ||
| 1 | use super::convert_valid_expr; | ||
| 2 | use super::hoist_block_to_expr; | ||
| 3 | use super::hoist_if_expr; | ||
| 4 | use super::is_valid_js_expression::*; | ||
| 5 | use super::BlockConvertContext; | ||
| 6 | // use super::HasSideEffects; | ||
| 7 | use crate::js_ast::*; | ||
| 8 | use crate::util::is_js_identifier; | ||
| 9 | use ast::*; | ||
| 10 | |||
| 11 | /// Pushes a decl with a generated identifier to the hoisted result | ||
| 12 | /// and returns an identifier expression for it. | ||
| 13 | pub fn hoist_expr_if_needed(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> { | ||
| 14 | // if !expr.has_side_effects() { | ||
| 15 | // return Some(ctx.hoist_expression_contents(expr)?); | ||
| 16 | // } | ||
| 17 | |||
| 18 | let init = hoist_expr_contents(ctx, expr)?; | ||
| 19 | |||
| 20 | let id = ctx.generate_temp_id("_"); | ||
| 21 | let loc = init.loc.clone(); | ||
| 22 | |||
| 23 | ctx.add_statement(JsStatement::new( | ||
| 24 | JsStatementData::Declaration(JSDeclaration { | ||
| 25 | name: id.get(&ctx), | ||
| 26 | init: Some(init), | ||
| 27 | is_constant: true, | ||
| 28 | }), | ||
| 29 | loc.clone(), | ||
| 30 | None, | ||
| 31 | )); | ||
| 32 | |||
| 33 | Some(JsExpr::new(JsExprData::Identifier(id.get(&ctx)), loc, None)) | ||
| 34 | } | ||
| 35 | |||
| 36 | /// Returning `None` from this means that the expression is noreturn, and that | ||
| 37 | /// the rest of the statement never occurs and can be stripped. | ||
| 38 | pub fn hoist_expr_contents(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> { | ||
| 39 | if expr.is_valid_js_expression() { | ||
| 40 | return Some(convert_valid_expr(ctx, expr)); | ||
| 41 | } | ||
| 42 | |||
| 43 | match expr.data { | ||
| 44 | ExprData::Binary { op, left, right } => { | ||
| 45 | let left = hoist_expr_if_needed(ctx, *left)?; | ||
| 46 | let right = hoist_expr_if_needed(ctx, *right)?; | ||
| 47 | |||
| 48 | Some(JsExpr::new( | ||
| 49 | JsExprData::Binary { | ||
| 50 | op, | ||
| 51 | left: Box::new(left), | ||
| 52 | right: Box::new(right), | ||
| 53 | }, | ||
| 54 | expr.loc, | ||
| 55 | expr.comment, | ||
| 56 | )) | ||
| 57 | } | ||
| 58 | |||
| 59 | ExprData::Unary { op, value } => { | ||
| 60 | let value = hoist_expr_if_needed(ctx, *value)?; | ||
| 61 | Some(JsExpr::new( | ||
| 62 | JsExprData::Unary { | ||
| 63 | op, | ||
| 64 | value: Box::new(value), | ||
| 65 | }, | ||
| 66 | expr.loc, | ||
| 67 | expr.comment, | ||
| 68 | )) | ||
| 69 | } | ||
| 70 | |||
| 71 | ExprData::Field { base, field } => { | ||
| 72 | let base = hoist_expr_if_needed(ctx, *base)?; | ||
| 73 | let field = hoist_expr_if_needed(ctx, *field)?; | ||
| 74 | |||
| 75 | Some(JsExpr::new( | ||
| 76 | match field.data { | ||
| 77 | JsExprData::String(name) if is_js_identifier(name.as_str()) => { | ||
| 78 | JsExprData::Dot { | ||
| 79 | base: Box::new(base), | ||
| 80 | name, | ||
| 81 | } | ||
| 82 | } | ||
| 83 | _ => JsExprData::Index { | ||
| 84 | base: Box::new(base), | ||
| 85 | index: Box::new(field), | ||
| 86 | }, | ||
| 87 | }, | ||
| 88 | expr.loc, | ||
| 89 | expr.comment, | ||
| 90 | )) | ||
| 91 | } | ||
| 92 | |||
| 93 | ExprData::Block(block) => hoist_block_to_expr(ctx, block, expr.loc, expr.comment), | ||
| 94 | |||
| 95 | ExprData::If(_) => hoist_if_expr(ctx, expr), | ||
| 96 | |||
| 97 | ExprData::Return(expr) => { | ||
| 98 | let loc = expr.loc.clone(); | ||
| 99 | let return_value = hoist_expr_if_needed(ctx, *expr)?; | ||
| 100 | ctx.add_statement(JsStatement::new( | ||
| 101 | JsStatementData::Return(return_value), | ||
| 102 | loc, | ||
| 103 | None, | ||
| 104 | )); | ||
| 105 | None | ||
| 106 | } | ||
| 107 | |||
| 108 | ExprData::Call { base, arguments } => { | ||
| 109 | let base = hoist_expr_if_needed(ctx, *base)?; | ||
| 110 | let arguments = arguments | ||
| 111 | .into_iter() | ||
| 112 | .map(|x| hoist_expr_if_needed(ctx, x)) | ||
| 113 | .collect::<Option<Vec<_>>>()?; | ||
| 114 | |||
| 115 | Some(JsExpr::new( | ||
| 116 | JsExprData::Call { | ||
| 117 | base: Box::new(base), | ||
| 118 | arguments, | ||
| 119 | }, | ||
| 120 | expr.loc, | ||
| 121 | expr.comment, | ||
| 122 | )) | ||
| 123 | } | ||
| 124 | |||
| 125 | ExprData::Fn { .. } | ||
| 126 | | ExprData::Builtin { .. } | ||
| 127 | | ExprData::Literal(_) | ||
| 128 | | ExprData::Identifier(_) => { | ||
| 129 | unreachable!("attempt to hoist an expression that does not require it") | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
crates/js_ast/convert/hoist_if.rs created+113| ... | @@ -0,0 +1,113 @@ | ||
| 1 | use super::convert_valid_expr; | ||
| 2 | use super::hoist_block_to_js_block; | ||
| 3 | use super::hoist_expr_if_needed; | ||
| 4 | use super::BlockConvertContext; | ||
| 5 | use super::IsValidJSExpression; | ||
| 6 | use super::TempId; | ||
| 7 | use crate::js_ast::*; | ||
| 8 | use ast::*; | ||
| 9 | use tokenizer::Loc; | ||
| 10 | |||
| 11 | pub fn hoist_if_expr(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> { | ||
| 12 | let data = match expr.data { | ||
| 13 | ExprData::If(data) => data, | ||
| 14 | _ => unreachable!("hoist_if_expr called on non-if expression"), | ||
| 15 | }; | ||
| 16 | |||
| 17 | let temporary = ctx.generate_temp_id("_if"); | ||
| 18 | |||
| 19 | let decl = ctx.add_statement(JsStatement::new( | ||
| 20 | JsStatementData::Declaration(JSDeclaration { | ||
| 21 | name: temporary.get(&ctx), | ||
| 22 | init: None, | ||
| 23 | is_constant: false, | ||
| 24 | }), | ||
| 25 | expr.loc.clone(), | ||
| 26 | None, | ||
| 27 | )); | ||
| 28 | |||
| 29 | let condition = hoist_expr_if_needed(ctx, *data.condition)?; | ||
| 30 | let mut did_assign = false; | ||
| 31 | |||
| 32 | let js_if = hoist_if_branch( | ||
| 33 | ctx, | ||
| 34 | data.yes, | ||
| 35 | data.no, | ||
| 36 | condition, | ||
| 37 | &mut did_assign, | ||
| 38 | &temporary, | ||
| 39 | ); | ||
| 40 | |||
| 41 | ctx.add_statement(JsStatement::new( | ||
| 42 | JsStatementData::If(js_if), | ||
| 43 | expr.loc.clone(), | ||
| 44 | None, | ||
| 45 | )); | ||
| 46 | |||
| 47 | if did_assign { | ||
| 48 | Some(JsExpr::new( | ||
| 49 | JsExprData::Identifier(temporary.get(&ctx)), | ||
| 50 | expr.loc, | ||
| 51 | expr.comment, | ||
| 52 | )) | ||
| 53 | } else { | ||
| 54 | decl.delete(ctx); | ||
| 55 | Some(JsExpr::new(JsExprData::Null, expr.loc, expr.comment)) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | pub fn hoist_if_branch( | ||
| 60 | ctx: &mut BlockConvertContext, | ||
| 61 | yes: BlockWithLoc, | ||
| 62 | no: Option<BlockOrIf>, | ||
| 63 | condition: JsExpr, | ||
| 64 | did_assign: &mut bool, | ||
| 65 | temporary: &TempId, | ||
| 66 | ) -> JsIf { | ||
| 67 | let yes = hoist_block_to_js_block(ctx, yes.block, yes.loc, &temporary, did_assign); | ||
| 68 | |||
| 69 | let no = match no { | ||
| 70 | Some(BlockOrIf::Block(no)) => Some(JsElse::Block(hoist_block_to_js_block( | ||
| 71 | ctx, no.block, no.loc, &temporary, did_assign, | ||
| 72 | ))), | ||
| 73 | Some(BlockOrIf::If(else_if)) => { | ||
| 74 | // If the condition of this block has a side effect, we need to | ||
| 75 | // emit a block so that we can assign to the temporary variable. | ||
| 76 | // | ||
| 77 | // Otherwise, we can just emit the condition inline | ||
| 78 | if else_if.condition.is_valid_js_expression() { | ||
| 79 | let condition = convert_valid_expr(ctx, *else_if.condition.clone()); | ||
| 80 | Some(JsElse::If(Box::new(hoist_if_branch( | ||
| 81 | ctx, | ||
| 82 | else_if.yes, | ||
| 83 | else_if.no, | ||
| 84 | condition, | ||
| 85 | did_assign, | ||
| 86 | temporary, | ||
| 87 | )))) | ||
| 88 | } else { | ||
| 89 | let mut temp_block = ctx.new_child(); | ||
| 90 | let condition = | ||
| 91 | hoist_expr_if_needed(&mut temp_block, *else_if.condition.clone()).unwrap(); | ||
| 92 | |||
| 93 | temp_block.add_statement(JsStatement::new( | ||
| 94 | JsStatementData::If(hoist_if_branch( | ||
| 95 | ctx, | ||
| 96 | else_if.yes, | ||
| 97 | else_if.no, | ||
| 98 | condition, | ||
| 99 | did_assign, | ||
| 100 | temporary, | ||
| 101 | )), | ||
| 102 | Loc::EMPTY, | ||
| 103 | None, | ||
| 104 | )); | ||
| 105 | |||
| 106 | Some(JsElse::Block(temp_block.into())) | ||
| 107 | } | ||
| 108 | } | ||
| 109 | None => None, | ||
| 110 | }; | ||
| 111 | |||
| 112 | JsIf { condition, yes, no } | ||
| 113 | } | ||
crates/js_ast/convert/is_valid_js_expression.rs created+91| ... | @@ -0,0 +1,91 @@ | ||
| 1 | use ast::*; | ||
| 2 | |||
| 3 | /// "Valid" in this context means that the expression does not contain JS statements | ||
| 4 | /// Meaning conversion is a direct map from a blight as node into a JS one | ||
| 5 | /// | ||
| 6 | /// A valid expression should not go through any hoist logic, but if an expression | ||
| 7 | /// isn't valid, then it means that it must be split into multiple JS statements | ||
| 8 | /// inside of a block. | ||
| 9 | pub trait IsValidJSExpression { | ||
| 10 | /// Can this be lowered to a JS ast without hoisting items | ||
| 11 | /// ex: a `return` expression is not a valid JS expression | ||
| 12 | fn is_valid_js_expression(&self) -> bool; | ||
| 13 | } | ||
| 14 | |||
| 15 | impl IsValidJSExpression for ExprData { | ||
| 16 | fn is_valid_js_expression(&self) -> bool { | ||
| 17 | match self { | ||
| 18 | // A block containing just expressions can be an expression | ||
| 19 | ExprData::Block(block) => block.is_valid_js_expression(), | ||
| 20 | ExprData::If(expr) => expr.is_valid_js_expression(), | ||
| 21 | ExprData::Return(_) => false, | ||
| 22 | ExprData::Builtin { id, .. } => match id { | ||
| 23 | BuiltinId::Raw => true, | ||
| 24 | }, | ||
| 25 | ExprData::Binary { left, right, .. } => { | ||
| 26 | left.is_valid_js_expression() && right.is_valid_js_expression() | ||
| 27 | } | ||
| 28 | ExprData::Unary { value, .. } => value.is_valid_js_expression(), | ||
| 29 | ExprData::Fn { .. } => true, | ||
| 30 | ExprData::Call { base, arguments } => { | ||
| 31 | base.is_valid_js_expression() | ||
| 32 | && arguments | ||
| 33 | .iter() | ||
| 34 | .all(|argument| argument.is_valid_js_expression()) | ||
| 35 | } | ||
| 36 | ExprData::Field { base, field } => { | ||
| 37 | base.is_valid_js_expression() && field.is_valid_js_expression() | ||
| 38 | } | ||
| 39 | ExprData::Literal(_) | ExprData::Identifier(_) => true, | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | impl IsValidJSExpression for Block { | ||
| 45 | fn is_valid_js_expression(&self) -> bool { | ||
| 46 | self.statements | ||
| 47 | .iter() | ||
| 48 | .all(|statement| statement.is_valid_js_expression()) | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | impl IsValidJSExpression for Expr { | ||
| 53 | fn is_valid_js_expression(&self) -> bool { | ||
| 54 | self.data.is_valid_js_expression() | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | impl IsValidJSExpression for If { | ||
| 59 | fn is_valid_js_expression(&self) -> bool { | ||
| 60 | self.condition.is_valid_js_expression() | ||
| 61 | && self.yes.is_valid_js_expression() | ||
| 62 | && match &self.no { | ||
| 63 | Some(no) => no.is_valid_js_expression(), | ||
| 64 | None => true, | ||
| 65 | } | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | impl IsValidJSExpression for BlockOrIf { | ||
| 70 | fn is_valid_js_expression(&self) -> bool { | ||
| 71 | match self { | ||
| 72 | BlockOrIf::Block(block) => block.is_valid_js_expression(), | ||
| 73 | BlockOrIf::If(if_) => if_.is_valid_js_expression(), | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | impl IsValidJSExpression for BlockWithLoc { | ||
| 79 | fn is_valid_js_expression(&self) -> bool { | ||
| 80 | self.block.is_valid_js_expression() | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | impl IsValidJSExpression for Statement { | ||
| 85 | fn is_valid_js_expression(&self) -> bool { | ||
| 86 | match self { | ||
| 87 | Statement::Expr(expr) => expr.data.is_valid_js_expression(), | ||
| 88 | _ => false, | ||
| 89 | } | ||
| 90 | } | ||
| 91 | } | ||
crates/js_ast/convert/mod.rs created+42| ... | @@ -0,0 +1,42 @@ | ||
| 1 | mod block_convert_context; | ||
| 2 | mod convert_valid_block; | ||
| 3 | mod convert_valid_expr; | ||
| 4 | mod convert_valid_if; | ||
| 5 | mod convert_valid_statement; | ||
| 6 | mod hoist_block_to_expr; | ||
| 7 | mod hoist_block_to_js_block; | ||
| 8 | mod hoist_expr; | ||
| 9 | mod hoist_if; | ||
| 10 | mod is_valid_js_expression; | ||
| 11 | mod process_function; | ||
| 12 | mod process_statement; | ||
| 13 | mod side_effects; | ||
| 14 | mod tree_shake_statement_list; | ||
| 15 | |||
| 16 | pub use block_convert_context::*; | ||
| 17 | pub use convert_valid_block::*; | ||
| 18 | pub use convert_valid_expr::*; | ||
| 19 | pub use convert_valid_if::*; | ||
| 20 | pub use convert_valid_statement::*; | ||
| 21 | pub use hoist_block_to_expr::*; | ||
| 22 | pub use hoist_block_to_js_block::*; | ||
| 23 | pub use hoist_expr::*; | ||
| 24 | pub use hoist_if::*; | ||
| 25 | pub use is_valid_js_expression::*; | ||
| 26 | pub use process_function::*; | ||
| 27 | pub use process_statement::*; | ||
| 28 | // pub use side_effects::*; | ||
| 29 | pub use tree_shake_statement_list::*; | ||
| 30 | |||
| 31 | use crate::js_ast::JsModule; | ||
| 32 | use ast::Module; | ||
| 33 | |||
| 34 | pub fn blight_to_js(blight: Module) -> JsModule { | ||
| 35 | let mut root_context = BlockConvertContext::new(); | ||
| 36 | |||
| 37 | for statement in blight.statements { | ||
| 38 | process_top_level_statement(&mut root_context, statement).unwrap(); | ||
| 39 | } | ||
| 40 | |||
| 41 | root_context.into() | ||
| 42 | } | ||
crates/js_ast/convert/process_function.rs created+63| ... | @@ -0,0 +1,63 @@ | ||
| 1 | use super::convert_valid_expr; | ||
| 2 | use super::hoist_expr_if_needed; | ||
| 3 | use super::process_statement; | ||
| 4 | use super::BlockConvertContext; | ||
| 5 | use super::IsValidJSExpression; | ||
| 6 | use crate::js_ast::*; | ||
| 7 | use ast::*; | ||
| 8 | use tokenizer::Loc; | ||
| 9 | |||
| 10 | pub fn process_function_to_arrow( | ||
| 11 | ctx: &mut BlockConvertContext, | ||
| 12 | body: Expr, | ||
| 13 | params: Vec<Param>, | ||
| 14 | loc: Loc, | ||
| 15 | comment: Option<Comment>, | ||
| 16 | ) -> JsExpr { | ||
| 17 | // A block that is a valid expression will just become | ||
| 18 | // that expression. We can convert multiple expressions | ||
| 19 | // into a comma expression. | ||
| 20 | let body = if body.is_valid_js_expression() { | ||
| 21 | JsArrowBody::Expr(convert_valid_expr(ctx, body)) | ||
| 22 | } else { | ||
| 23 | let mut child = ctx.new_child(); | ||
| 24 | |||
| 25 | match body.data { | ||
| 26 | ExprData::Block(block) => { | ||
| 27 | for statement in block.statements { | ||
| 28 | if let None = process_statement(&mut child, statement) { | ||
| 29 | break; | ||
| 30 | }; | ||
| 31 | } | ||
| 32 | |||
| 33 | if let Some(expr) = child.take_last_statement_if_expr() { | ||
| 34 | child.add_statement(JsStatement::new( | ||
| 35 | JsStatementData::Return(expr), | ||
| 36 | loc.clone(), | ||
| 37 | None, | ||
| 38 | )); | ||
| 39 | } | ||
| 40 | } | ||
| 41 | _ => { | ||
| 42 | if let Some(expr) = hoist_expr_if_needed(&mut child, body) { | ||
| 43 | child.add_statement(JsStatement::new( | ||
| 44 | JsStatementData::Return(expr), | ||
| 45 | loc.clone(), | ||
| 46 | None, | ||
| 47 | )); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | JsArrowBody::Block(child.into()) | ||
| 53 | }; | ||
| 54 | |||
| 55 | JsExpr::new( | ||
| 56 | JsExprData::ArrowFunction { | ||
| 57 | params: params.into_iter().map(|x| x.name).collect(), | ||
| 58 | body: Box::new(body), | ||
| 59 | }, | ||
| 60 | loc.clone(), | ||
| 61 | comment.map(|x| x.into()), | ||
| 62 | ) | ||
| 63 | } | ||
crates/js_ast/convert/process_statement.rs created+95| ... | @@ -0,0 +1,95 @@ | ||
| 1 | use super::convert_valid_statement; | ||
| 2 | use super::hoist_expr_contents; | ||
| 3 | use super::process_function_to_arrow; | ||
| 4 | use super::BlockConvertContext; | ||
| 5 | use super::IsValidJSExpression; | ||
| 6 | use crate::js_ast::*; | ||
| 7 | use ast::*; | ||
| 8 | |||
| 9 | pub fn process_statement(ctx: &mut BlockConvertContext, statement: Statement) -> Option<()> { | ||
| 10 | if statement.is_valid_js_expression() { | ||
| 11 | let result = convert_valid_statement(ctx, statement); | ||
| 12 | ctx.add_statement(result); | ||
| 13 | return Some(()); | ||
| 14 | } | ||
| 15 | |||
| 16 | match statement { | ||
| 17 | Statement::VarDecl(decl) => { | ||
| 18 | // visit init before name, this way allows for: | ||
| 19 | // | ||
| 20 | // let a = a; | ||
| 21 | // | ||
| 22 | // and `a` will be looked up before it is re-declared. | ||
| 23 | // we will also return in the noreturn case | ||
| 24 | let init = hoist_expr_contents(ctx, decl.init)?; | ||
| 25 | |||
| 26 | let name = ctx.declare_identifier(decl.name); | ||
| 27 | |||
| 28 | ctx.add_statement(JsStatement::new( | ||
| 29 | JsStatementData::Declaration(JSDeclaration { | ||
| 30 | name: name.get(&ctx), | ||
| 31 | init: Some(init), | ||
| 32 | is_constant: decl.is_constant, | ||
| 33 | }), | ||
| 34 | decl.loc, | ||
| 35 | decl.comment.map(|x| x.into()), | ||
| 36 | )); | ||
| 37 | |||
| 38 | Some(()) | ||
| 39 | } | ||
| 40 | Statement::Expr(expr) => { | ||
| 41 | if let Some(expr) = hoist_expr_contents(ctx, expr) { | ||
| 42 | let loc = expr.loc.clone(); | ||
| 43 | ctx.add_statement(JsStatement::new( | ||
| 44 | JsStatementData::Expression(expr), | ||
| 45 | loc, | ||
| 46 | None, | ||
| 47 | )); | ||
| 48 | Some(()) | ||
| 49 | } else { | ||
| 50 | None | ||
| 51 | } | ||
| 52 | } | ||
| 53 | Statement::FnDecl(FnDecl { | ||
| 54 | name, | ||
| 55 | params, | ||
| 56 | body, | ||
| 57 | loc, | ||
| 58 | comment, | ||
| 59 | .. | ||
| 60 | }) => { | ||
| 61 | let name = ctx.declare_identifier(name); | ||
| 62 | |||
| 63 | let processed = Some(process_function_to_arrow( | ||
| 64 | ctx, | ||
| 65 | body, | ||
| 66 | params, | ||
| 67 | loc.clone(), | ||
| 68 | None, | ||
| 69 | )); | ||
| 70 | |||
| 71 | ctx.add_statement(JsStatement::new( | ||
| 72 | JsStatementData::Declaration(JSDeclaration { | ||
| 73 | name: name.get(&ctx), | ||
| 74 | init: processed, | ||
| 75 | is_constant: true, | ||
| 76 | }), | ||
| 77 | loc, | ||
| 78 | comment.map(|x| x.into()), | ||
| 79 | )); | ||
| 80 | |||
| 81 | Some(()) | ||
| 82 | } | ||
| 83 | Statement::Comment(_) => Some(()), | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | pub fn process_top_level_statement( | ||
| 88 | ctx: &mut BlockConvertContext, | ||
| 89 | statement: TopLevelStatement, | ||
| 90 | ) -> Option<()> { | ||
| 91 | match statement.to_statement() { | ||
| 92 | Some(stmt) => process_statement(ctx, stmt), | ||
| 93 | None => todo!("process_top_level_statement"), | ||
| 94 | } | ||
| 95 | } | ||
crates/js_ast/convert/side_effects.rs created+91| ... | @@ -0,0 +1,91 @@ | ||
| 1 | use ast::*; | ||
| 2 | |||
| 3 | pub trait HasSideEffects { | ||
| 4 | /// Can this item be removed from the AST without changing the program's behavior? | ||
| 5 | fn has_side_effects(&self) -> bool; | ||
| 6 | } | ||
| 7 | |||
| 8 | impl HasSideEffects for ExprData { | ||
| 9 | fn has_side_effects(&self) -> bool { | ||
| 10 | match self { | ||
| 11 | ExprData::Literal(_) => false, | ||
| 12 | ExprData::Identifier(_) => false, | ||
| 13 | |||
| 14 | ExprData::Block(block) => block.has_side_effects(), | ||
| 15 | ExprData::Binary { left, right, .. } => { | ||
| 16 | left.has_side_effects() || right.has_side_effects() | ||
| 17 | } | ||
| 18 | ExprData::Unary { value, .. } => value.has_side_effects(), | ||
| 19 | ExprData::If(ifexpr) => ifexpr.has_side_effects(), | ||
| 20 | |||
| 21 | // note: a function *declaration* has no side effects, but the call *can* | ||
| 22 | ExprData::Fn { .. } => false, | ||
| 23 | |||
| 24 | // TODO(@paperdave): implement detection of pure functions | ||
| 25 | ExprData::Call { base, arguments } => { | ||
| 26 | base.has_side_effects() || arguments.iter().any(|x| x.has_side_effects()) | ||
| 27 | } | ||
| 28 | |||
| 29 | // note: we assume field access has side effects, ie: a getter doesn't throw | ||
| 30 | ExprData::Field { base, field } => base.has_side_effects() || field.has_side_effects(), | ||
| 31 | |||
| 32 | // note: we assume the code in @raw always has side effects | ||
| 33 | ExprData::Builtin { id, .. } => match id { | ||
| 34 | BuiltinId::Raw => true, | ||
| 35 | }, | ||
| 36 | |||
| 37 | ExprData::Return(_) => true, | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | impl HasSideEffects for Statement { | ||
| 43 | fn has_side_effects(&self) -> bool { | ||
| 44 | match self { | ||
| 45 | Statement::Expr(expr) => expr.has_side_effects(), | ||
| 46 | Statement::VarDecl(decl) => decl.init.has_side_effects(), | ||
| 47 | Statement::Comment(_) => false, | ||
| 48 | Statement::FnDecl(_) => false, | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | impl HasSideEffects for Block { | ||
| 54 | fn has_side_effects(&self) -> bool { | ||
| 55 | self.statements | ||
| 56 | .iter() | ||
| 57 | .any(|statement| statement.has_side_effects()) | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | impl HasSideEffects for Expr { | ||
| 62 | fn has_side_effects(&self) -> bool { | ||
| 63 | self.data.has_side_effects() | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | impl HasSideEffects for If { | ||
| 68 | fn has_side_effects(&self) -> bool { | ||
| 69 | self.condition.has_side_effects() | ||
| 70 | || self.yes.has_side_effects() | ||
| 71 | || match &self.no { | ||
| 72 | Some(no) => no.has_side_effects(), | ||
| 73 | None => false, | ||
| 74 | } | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | impl HasSideEffects for BlockOrIf { | ||
| 79 | fn has_side_effects(&self) -> bool { | ||
| 80 | match self { | ||
| 81 | BlockOrIf::Block(block) => block.has_side_effects(), | ||
| 82 | BlockOrIf::If(if_) => if_.has_side_effects(), | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | impl HasSideEffects for BlockWithLoc { | ||
| 88 | fn has_side_effects(&self) -> bool { | ||
| 89 | self.block.has_side_effects() | ||
| 90 | } | ||
| 91 | } | ||
crates/js_ast/convert/tree_shake_statement_list.rs created+31| ... | @@ -0,0 +1,31 @@ | ||
| 1 | use crate::js_ast::*; | ||
| 2 | |||
| 3 | /// Removes statements that are not needed from a JS block. Very limited. | ||
| 4 | /// | ||
| 5 | /// BlockConvertContext will automatically execute this when converting | ||
| 6 | /// itself into a Statement list or other data structure. | ||
| 7 | /// | ||
| 8 | /// What this does: | ||
| 9 | /// - Remove discarded literals `null` -> `` | ||
| 10 | pub fn tree_shake_statement_list(list: &mut Vec<JsStatement>) { | ||
| 11 | let mut set_i = 0; | ||
| 12 | for i in 0..list.len() { | ||
| 13 | let stmt = list.get(i).unwrap(); | ||
| 14 | let should_keep = match &stmt.data { | ||
| 15 | JsStatementData::Expression(expr) => !expr.is_literal(), | ||
| 16 | JsStatementData::Return(_) => true, | ||
| 17 | JsStatementData::Export(_) => true, | ||
| 18 | JsStatementData::Declaration(_) => true, | ||
| 19 | JsStatementData::Function(_) => true, | ||
| 20 | JsStatementData::If(_) => true, | ||
| 21 | }; | ||
| 22 | |||
| 23 | if should_keep { | ||
| 24 | if set_i != i { | ||
| 25 | list.swap(i, set_i) | ||
| 26 | } | ||
| 27 | set_i += 1; | ||
| 28 | } | ||
| 29 | } | ||
| 30 | list.truncate(set_i) | ||
| 31 | } | ||
crates/js_ast/js_ast.rs created+190| ... | @@ -0,0 +1,190 @@ | ||
| 1 | use ast::{BinaryOperator, Comment, UnaryOperator}; | ||
| 2 | use num::bigint::BigInt; | ||
| 3 | use tokenizer::Loc; | ||
| 4 | |||
| 5 | #[derive(Debug, PartialEq, Clone)] | ||
| 6 | pub struct JsExpr { | ||
| 7 | pub data: JsExprData, | ||
| 8 | pub loc: Loc, | ||
| 9 | pub comment: Option<Comment>, | ||
| 10 | } | ||
| 11 | |||
| 12 | #[derive(Debug, PartialEq, Clone)] | ||
| 13 | pub struct JsKV { | ||
| 14 | pub key: JsExpr, | ||
| 15 | pub value: JsExpr, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[derive(Debug, PartialEq, Clone)] | ||
| 19 | pub enum JsExprData { | ||
| 20 | /// node emitted by the '@raw' building | ||
| 21 | Raw(String), | ||
| 22 | // Litearls | ||
| 23 | Number(f64), | ||
| 24 | BigInt(BigInt), | ||
| 25 | String(String), | ||
| 26 | Bool(bool), | ||
| 27 | Array(Vec<JsExpr>), | ||
| 28 | Object(Vec<JsKV>), | ||
| 29 | Null, | ||
| 30 | Undefined, | ||
| 31 | |||
| 32 | Identifier(String), | ||
| 33 | Unary { | ||
| 34 | op: UnaryOperator, | ||
| 35 | value: Box<JsExpr>, | ||
| 36 | }, | ||
| 37 | Binary { | ||
| 38 | op: BinaryOperator, | ||
| 39 | left: Box<JsExpr>, | ||
| 40 | right: Box<JsExpr>, | ||
| 41 | }, | ||
| 42 | Dot { | ||
| 43 | base: Box<JsExpr>, | ||
| 44 | name: String, | ||
| 45 | }, | ||
| 46 | Index { | ||
| 47 | base: Box<JsExpr>, | ||
| 48 | index: Box<JsExpr>, | ||
| 49 | }, | ||
| 50 | Call { | ||
| 51 | base: Box<JsExpr>, | ||
| 52 | arguments: Vec<JsExpr>, | ||
| 53 | }, | ||
| 54 | Function { | ||
| 55 | name: Option<String>, | ||
| 56 | params: Vec<String>, | ||
| 57 | body: Box<JsExpr>, | ||
| 58 | }, | ||
| 59 | ArrowFunction { | ||
| 60 | params: Vec<String>, | ||
| 61 | body: Box<JsArrowBody>, | ||
| 62 | }, | ||
| 63 | Ternary { | ||
| 64 | condition: Box<JsExpr>, | ||
| 65 | yes: Box<JsExpr>, | ||
| 66 | no: Option<Box<JsExpr>>, | ||
| 67 | }, | ||
| 68 | Comma { | ||
| 69 | items: Vec<JsExpr>, | ||
| 70 | }, | ||
| 71 | Void(Box<JsExpr>), | ||
| 72 | } | ||
| 73 | |||
| 74 | #[derive(Debug, PartialEq, Clone)] | ||
| 75 | pub struct JsStatement { | ||
| 76 | pub data: JsStatementData, | ||
| 77 | pub loc: Loc, | ||
| 78 | pub comment: Option<Comment>, | ||
| 79 | } | ||
| 80 | |||
| 81 | #[derive(Debug, PartialEq, Clone)] | ||
| 82 | pub struct JSFunctionDeclaration { | ||
| 83 | pub name: String, | ||
| 84 | pub params: Vec<String>, | ||
| 85 | pub block: JsBlock, | ||
| 86 | } | ||
| 87 | |||
| 88 | #[derive(Debug, PartialEq, Clone)] | ||
| 89 | pub struct JSDeclaration { | ||
| 90 | pub name: String, | ||
| 91 | pub init: Option<JsExpr>, | ||
| 92 | pub is_constant: bool, | ||
| 93 | } | ||
| 94 | |||
| 95 | #[derive(Debug, PartialEq, Clone)] | ||
| 96 | pub struct JsIf { | ||
| 97 | pub condition: JsExpr, | ||
| 98 | pub yes: JsBlock, | ||
| 99 | pub no: Option<JsElse>, | ||
| 100 | } | ||
| 101 | |||
| 102 | #[derive(Debug, PartialEq, Clone)] | ||
| 103 | pub enum JsElse { | ||
| 104 | Expr(JsExpr), | ||
| 105 | Block(JsBlock), | ||
| 106 | If(Box<JsIf>), | ||
| 107 | } | ||
| 108 | |||
| 109 | #[derive(Debug, PartialEq, Clone)] | ||
| 110 | pub enum JsStatementData { | ||
| 111 | Expression(JsExpr), | ||
| 112 | Return(JsExpr), | ||
| 113 | Export(JsExport), | ||
| 114 | Declaration(JSDeclaration), | ||
| 115 | Function(JSFunctionDeclaration), | ||
| 116 | If(JsIf), | ||
| 117 | } | ||
| 118 | |||
| 119 | #[derive(Debug, PartialEq, Clone)] | ||
| 120 | pub enum JsExport { | ||
| 121 | Named(Vec<JSNamedExport>), | ||
| 122 | Function(Box<JSFunctionDeclaration>), | ||
| 123 | Declaration(Box<JSDeclaration>), | ||
| 124 | Default(JsExpr), | ||
| 125 | } | ||
| 126 | |||
| 127 | #[derive(Debug, PartialEq, Clone)] | ||
| 128 | pub struct JSNamedExport { | ||
| 129 | pub identifier: String, | ||
| 130 | pub export_name: String, | ||
| 131 | } | ||
| 132 | |||
| 133 | #[derive(Debug, PartialEq, Clone)] | ||
| 134 | pub struct JsBlock { | ||
| 135 | pub statements: Vec<JsStatement>, | ||
| 136 | pub loc: Loc, | ||
| 137 | } | ||
| 138 | |||
| 139 | impl JsExpr { | ||
| 140 | pub fn new(data: JsExprData, loc: Loc, comment: Option<Comment>) -> Self { | ||
| 141 | Self { data, loc, comment } | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn is_literal(&self) -> bool { | ||
| 145 | match &self.data { | ||
| 146 | JsExprData::Number(_) => true, | ||
| 147 | JsExprData::BigInt(_) => true, | ||
| 148 | JsExprData::String(_) => true, | ||
| 149 | JsExprData::Bool(_) => true, | ||
| 150 | JsExprData::Null => true, | ||
| 151 | JsExprData::Undefined => true, | ||
| 152 | _ => false, | ||
| 153 | } | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | impl JsExprData { | ||
| 158 | /// Helper for testing | ||
| 159 | pub fn str(value: &str) -> Self { | ||
| 160 | Self::String(value.to_string()) | ||
| 161 | } | ||
| 162 | |||
| 163 | /// Helper for testing | ||
| 164 | pub fn str_id(value: &str) -> Self { | ||
| 165 | Self::Identifier(value.to_string()) | ||
| 166 | } | ||
| 167 | } | ||
| 168 | |||
| 169 | impl JsBlock { | ||
| 170 | pub fn new(statements: Vec<JsStatement>, loc: Loc) -> Self { | ||
| 171 | Self { statements, loc } | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | impl JsStatement { | ||
| 176 | pub fn new(data: JsStatementData, loc: Loc, comment: Option<Comment>) -> Self { | ||
| 177 | Self { data, loc, comment } | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | #[derive(Debug, PartialEq, Clone)] | ||
| 182 | pub enum JsArrowBody { | ||
| 183 | Block(JsBlock), | ||
| 184 | Expr(JsExpr), | ||
| 185 | } | ||
| 186 | |||
| 187 | #[derive(Debug, PartialEq, Clone)] | ||
| 188 | pub struct JsModule { | ||
| 189 | pub statements: Vec<JsStatement>, | ||
| 190 | } | ||
crates/js_ast/lib.rs created+7| ... | @@ -0,0 +1,7 @@ | ||
| 1 | mod convert; | ||
| 2 | mod js_ast; | ||
| 3 | mod util; | ||
| 4 | |||
| 5 | pub use convert::*; | ||
| 6 | pub use js_ast::*; | ||
| 7 | pub use util::*; | ||
crates/js_ast/src/lib.rs deleted-348| ... | @@ -1,348 +0,0 @@ | ||
| 1 | use std::collections::HashMap; | ||
| 2 | |||
| 3 | use miette::Result; | ||
| 4 | |||
| 5 | use ast::Ast; | ||
| 6 | |||
| 7 | mod types; | ||
| 8 | pub use types::{JsExpression, JsStatement}; | ||
| 9 | |||
| 10 | struct StackContext { | ||
| 11 | statements: Vec<JsStatement>, | ||
| 12 | /// Variables that are declared in this context. | ||
| 13 | /// The number is the number of times the variable has been declared. | ||
| 14 | /// This is used to allow for shadowing | ||
| 15 | variable_map: HashMap<String, u32>, | ||
| 16 | block: u32, | ||
| 17 | } | ||
| 18 | |||
| 19 | impl StackContext { | ||
| 20 | fn new(block: u32) -> Self { | ||
| 21 | StackContext { | ||
| 22 | statements: Vec::new(), | ||
| 23 | variable_map: HashMap::new(), | ||
| 24 | block, | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | struct TranslateContext { | ||
| 30 | context: Vec<StackContext>, | ||
| 31 | internal_counter: u32, | ||
| 32 | block_counter: u32, | ||
| 33 | } | ||
| 34 | |||
| 35 | impl TranslateContext { | ||
| 36 | fn new() -> Self { | ||
| 37 | TranslateContext { | ||
| 38 | context: vec![StackContext::new(0)], | ||
| 39 | internal_counter: 0, | ||
| 40 | block_counter: 0, | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | fn push_context(self: &mut Self) { | ||
| 45 | self.block_counter += 1; | ||
| 46 | self.context.push(StackContext::new(self.block_counter)); | ||
| 47 | } | ||
| 48 | |||
| 49 | fn pop_context(self: &mut Self) -> Vec<JsStatement> { | ||
| 50 | self.context.pop().unwrap().statements | ||
| 51 | } | ||
| 52 | |||
| 53 | fn push_internal_variable(self: &mut Self, value: JsExpression) -> JsExpression { | ||
| 54 | self.context | ||
| 55 | .last_mut() | ||
| 56 | .unwrap() | ||
| 57 | .statements | ||
| 58 | .push(JsStatement::VariableDeclaration { | ||
| 59 | name: format!("${}", self.internal_counter), | ||
| 60 | value, | ||
| 61 | }); | ||
| 62 | self.internal_counter += 1; | ||
| 63 | JsExpression::Identifier(format!("${}", self.internal_counter - 1)) | ||
| 64 | } | ||
| 65 | |||
| 66 | fn push(self: &mut Self, statement: JsStatement) { | ||
| 67 | self.context.last_mut().unwrap().statements.push(statement); | ||
| 68 | } | ||
| 69 | |||
| 70 | fn peek(self: &Self) -> &JsStatement { | ||
| 71 | self.context.last().unwrap().statements.last().unwrap() | ||
| 72 | } | ||
| 73 | |||
| 74 | fn declare_variable(self: &mut Self, name: String) -> String { | ||
| 75 | let context = self.context.last_mut().unwrap(); | ||
| 76 | if let Some(value) = context.variable_map.get_mut(name.as_str()) { | ||
| 77 | *value += 1; | ||
| 78 | format!("{}${}${}", name, value, context.block) | ||
| 79 | } else { | ||
| 80 | context.variable_map.insert(name.to_string(), 0); | ||
| 81 | format!("{}$0${}", name, context.block) | ||
| 82 | } | ||
| 83 | } | ||
| 84 | |||
| 85 | fn get_identifier(self: &Self, name: String) -> String { | ||
| 86 | for context in self.context.iter().rev() { | ||
| 87 | if let Some(value) = context.variable_map.get(name.as_str()) { | ||
| 88 | return format!("{}${}${}", name, value, context.block); | ||
| 89 | } | ||
| 90 | } | ||
| 91 | panic!("Variable {} not found", name); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | // basically, expressions are really hard to translate | ||
| 96 | // in blight, everything is an expression | ||
| 97 | // in js, very few things are expressions | ||
| 98 | // here, we recursively expand out expressions into statements | ||
| 99 | // we then store the result of all the expressions in a variable | ||
| 100 | // and then return the name of that variable | ||
| 101 | fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression { | ||
| 102 | match ast { | ||
| 103 | Ast::Number(value) => JsExpression::Number(*value), | ||
| 104 | Ast::String(value) => JsExpression::String(value.clone()), | ||
| 105 | Ast::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), | ||
| 106 | Ast::Bool(value) => JsExpression::Bool(*value), | ||
| 107 | Ast::FunctionCall { name, arguments } => { | ||
| 108 | let mut handles = Vec::new(); | ||
| 109 | for argument in arguments { | ||
| 110 | handles.push(get_expression_handle(argument, ctx)); | ||
| 111 | } | ||
| 112 | match *name.clone() { | ||
| 113 | Ast::Identifier(name) => ctx.push_internal_variable(JsExpression::FunctionCall { | ||
| 114 | name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | ||
| 115 | arguments: handles, | ||
| 116 | }), | ||
| 117 | Ast::BuiltIn(name) => match name.as_str() { | ||
| 118 | "@raw" => { | ||
| 119 | if handles.len() != 1 { | ||
| 120 | panic!("@raw expects exactly one argument"); | ||
| 121 | } | ||
| 122 | match handles.get(0).unwrap() { | ||
| 123 | JsExpression::String(value) => { | ||
| 124 | return JsExpression::Raw(value.clone()); | ||
| 125 | } | ||
| 126 | _ => panic!("@raw expects a string argument"), | ||
| 127 | } | ||
| 128 | } | ||
| 129 | _ => panic!("Unknown built-in function {}", name), | ||
| 130 | }, | ||
| 131 | _ => panic!("Function name must be an identifier"), | ||
| 132 | } | ||
| 133 | } | ||
| 134 | Ast::Operation { | ||
| 135 | operator, | ||
| 136 | left, | ||
| 137 | right, | ||
| 138 | } => { | ||
| 139 | let left_handle = get_expression_handle(left, ctx); | ||
| 140 | let right_handle = get_expression_handle(right, ctx); | ||
| 141 | JsExpression::Operation { | ||
| 142 | operator: operator.clone(), | ||
| 143 | left: Box::new(left_handle), | ||
| 144 | right: Box::new(right_handle), | ||
| 145 | } | ||
| 146 | } | ||
| 147 | Ast::Block(block) => { | ||
| 148 | if !block.implicit_return { | ||
| 149 | panic!("Expression blocks must have an implicit return"); | ||
| 150 | } | ||
| 151 | ctx.push_context(); | ||
| 152 | for (i, statement) in block.value.iter().enumerate() { | ||
| 153 | if i == block.value.len() - 1 { | ||
| 154 | break; | ||
| 155 | } else { | ||
| 156 | translate_statement(statement, ctx); | ||
| 157 | } | ||
| 158 | } | ||
| 159 | let result = get_expression_handle(block.value.last().unwrap(), ctx); | ||
| 160 | let value = ctx.pop_context(); | ||
| 161 | value.iter().for_each(|x| ctx.push(x.clone())); | ||
| 162 | result | ||
| 163 | } | ||
| 164 | _ => { | ||
| 165 | panic!("unimplemented: {:?}", ast); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | ||
| 171 | match ast { | ||
| 172 | Ast::ConstantDeclaration { | ||
| 173 | name, | ||
| 174 | return_type: _, | ||
| 175 | value, | ||
| 176 | } => { | ||
| 177 | let handle = get_expression_handle(value, ctx); | ||
| 178 | let rename = ctx.declare_variable(name.clone()); | ||
| 179 | ctx.push(JsStatement::ConstantDeclaration { | ||
| 180 | name: rename, | ||
| 181 | value: handle, | ||
| 182 | }); | ||
| 183 | } | ||
| 184 | Ast::VariableDeclaration { | ||
| 185 | name, | ||
| 186 | return_type: _, | ||
| 187 | value, | ||
| 188 | } => { | ||
| 189 | let handle = get_expression_handle(value, ctx); | ||
| 190 | let rename = ctx.declare_variable(name.clone()); | ||
| 191 | ctx.push(JsStatement::VariableDeclaration { | ||
| 192 | name: rename, | ||
| 193 | value: handle, | ||
| 194 | }); | ||
| 195 | } | ||
| 196 | Ast::Function { | ||
| 197 | name, | ||
| 198 | arguments, | ||
| 199 | return_type: _, | ||
| 200 | block, | ||
| 201 | } => { | ||
| 202 | let rename = ctx.declare_variable(name.clone()); | ||
| 203 | let arguments = arguments | ||
| 204 | .iter() | ||
| 205 | .map(|x| ctx.declare_variable(x.name.clone())) | ||
| 206 | .collect(); | ||
| 207 | |||
| 208 | ctx.push_context(); | ||
| 209 | for (i, statement) in block.value.iter().enumerate() { | ||
| 210 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 211 | let handle = get_expression_handle(statement, ctx); | ||
| 212 | ctx.push(JsStatement::Return(handle)); | ||
| 213 | } else { | ||
| 214 | translate_statement(statement, ctx); | ||
| 215 | } | ||
| 216 | } | ||
| 217 | let value = ctx.pop_context(); | ||
| 218 | |||
| 219 | ctx.push(JsStatement::Function { | ||
| 220 | name: rename, | ||
| 221 | arguments, | ||
| 222 | value, | ||
| 223 | }); | ||
| 224 | } | ||
| 225 | Ast::Public(value) => { | ||
| 226 | translate_statement(value, ctx); | ||
| 227 | // I'm sorry, I just don't know how to get the original name properly... my bad | ||
| 228 | match ctx.peek() { | ||
| 229 | JsStatement::Function { name, .. } | ||
| 230 | | JsStatement::VariableDeclaration { name, .. } | ||
| 231 | | JsStatement::ConstantDeclaration { name, .. } => { | ||
| 232 | let original_name = name.split("$").next().unwrap().to_string(); | ||
| 233 | ctx.push(JsStatement::Export(Box::new( | ||
| 234 | JsStatement::ConstantDeclaration { | ||
| 235 | name: original_name, | ||
| 236 | value: JsExpression::Identifier(name.clone()), | ||
| 237 | }, | ||
| 238 | ))); | ||
| 239 | } | ||
| 240 | _ => { | ||
| 241 | panic!("Cannot make non-function public"); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | } | ||
| 245 | Ast::Return(value) => { | ||
| 246 | let handle = get_expression_handle(value, ctx); | ||
| 247 | ctx.push(JsStatement::Return(handle)); | ||
| 248 | } | ||
| 249 | Ast::Block(block) => { | ||
| 250 | ctx.push_context(); | ||
| 251 | for (i, statement) in block.value.iter().enumerate() { | ||
| 252 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 253 | let handle = get_expression_handle(statement, ctx); | ||
| 254 | ctx.push(JsStatement::Return(handle)); | ||
| 255 | } else { | ||
| 256 | translate_statement(statement, ctx); | ||
| 257 | } | ||
| 258 | } | ||
| 259 | let value = ctx.pop_context(); | ||
| 260 | value.iter().for_each(|x| ctx.push(x.clone())); | ||
| 261 | } | ||
| 262 | Ast::If { | ||
| 263 | condition, | ||
| 264 | value: block, | ||
| 265 | else_value, | ||
| 266 | } => { | ||
| 267 | let handle = get_expression_handle(condition, ctx); | ||
| 268 | |||
| 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 | let value = ctx.pop_context(); | ||
| 279 | let else_value = match else_value { | ||
| 280 | Some(block) => { | ||
| 281 | ctx.push_context(); | ||
| 282 | for (i, statement) in block.value.iter().enumerate() { | ||
| 283 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 284 | let handle = get_expression_handle(statement, ctx); | ||
| 285 | ctx.push(JsStatement::Return(handle)); | ||
| 286 | } else { | ||
| 287 | translate_statement(statement, ctx); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | Some(ctx.pop_context()) | ||
| 291 | } | ||
| 292 | None => None, | ||
| 293 | }; | ||
| 294 | |||
| 295 | ctx.push(JsStatement::If { | ||
| 296 | condition: handle, | ||
| 297 | value, | ||
| 298 | else_value, | ||
| 299 | }); | ||
| 300 | } | ||
| 301 | Ast::FunctionCall { name, arguments } => { | ||
| 302 | let mut handles = Vec::new(); | ||
| 303 | for argument in arguments { | ||
| 304 | handles.push(get_expression_handle(argument, ctx)); | ||
| 305 | } | ||
| 306 | match *name.clone() { | ||
| 307 | Ast::Identifier(name) => { | ||
| 308 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | ||
| 309 | name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), | ||
| 310 | arguments: handles, | ||
| 311 | })); | ||
| 312 | } | ||
| 313 | Ast::BuiltIn(name) => match name.as_str() { | ||
| 314 | "@raw" => { | ||
| 315 | if handles.len() != 1 { | ||
| 316 | panic!("@raw expects exactly one argument"); | ||
| 317 | } | ||
| 318 | match handles.get(0).unwrap() { | ||
| 319 | JsExpression::String(value) => { | ||
| 320 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | ||
| 321 | name: Box::new(JsExpression::Raw(value.clone())), | ||
| 322 | arguments: handles, | ||
| 323 | })); | ||
| 324 | } | ||
| 325 | _ => panic!("@raw expects a string argument"), | ||
| 326 | } | ||
| 327 | } | ||
| 328 | _ => panic!("Unknown built-in function {}", name), | ||
| 329 | }, | ||
| 330 | _ => panic!("Function name must be an identifier"), | ||
| 331 | } | ||
| 332 | } | ||
| 333 | Ast::Comment(_) | Ast::DocComment(_) => {} | ||
| 334 | _ => { | ||
| 335 | panic!("unimplemented: {:?}", ast); | ||
| 336 | } | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 340 | pub fn translate(_ast: Vec<Ast>) -> Result<Vec<JsStatement>> { | ||
| 341 | let mut ctx = TranslateContext::new(); | ||
| 342 | |||
| 343 | for ast in _ast { | ||
| 344 | translate_statement(&ast, &mut ctx); | ||
| 345 | } | ||
| 346 | |||
| 347 | Ok(ctx.pop_context()) | ||
| 348 | } | ||
crates/js_ast/src/types.rs deleted-54| ... | @@ -1,54 +0,0 @@ | ||
| 1 | use num::bigint::BigInt; | ||
| 2 | use std::collections::HashMap; | ||
| 3 | |||
| 4 | use util::Operator; | ||
| 5 | |||
| 6 | #[derive(Debug, PartialEq, Clone)] | ||
| 7 | pub enum JsExpression { | ||
| 8 | Number(f64), | ||
| 9 | BigInt(BigInt), | ||
| 10 | String(String), | ||
| 11 | Bool(bool), | ||
| 12 | Identifier(String), | ||
| 13 | Raw(String), | ||
| 14 | Array(Vec<JsExpression>), | ||
| 15 | Object(HashMap<String, JsExpression>), | ||
| 16 | Operation { | ||
| 17 | operator: Operator, | ||
| 18 | left: Box<JsExpression>, | ||
| 19 | right: Box<JsExpression>, | ||
| 20 | }, | ||
| 21 | Function { | ||
| 22 | arguments: Vec<String>, | ||
| 23 | value: Vec<JsStatement>, | ||
| 24 | }, | ||
| 25 | FunctionCall { | ||
| 26 | name: Box<JsExpression>, | ||
| 27 | arguments: Vec<JsExpression>, | ||
| 28 | }, | ||
| 29 | } | ||
| 30 | |||
| 31 | #[derive(Debug, PartialEq, Clone)] | ||
| 32 | pub enum JsStatement { | ||
| 33 | Expression(JsExpression), | ||
| 34 | Export(Box<JsStatement>), | ||
| 35 | Return(JsExpression), | ||
| 36 | VariableDeclaration { | ||
| 37 | name: String, | ||
| 38 | value: JsExpression, | ||
| 39 | }, | ||
| 40 | ConstantDeclaration { | ||
| 41 | name: String, | ||
| 42 | value: JsExpression, | ||
| 43 | }, | ||
| 44 | Function { | ||
| 45 | name: String, | ||
| 46 | arguments: Vec<String>, | ||
| 47 | value: Vec<JsStatement>, | ||
| 48 | }, | ||
| 49 | If { | ||
| 50 | condition: JsExpression, | ||
| 51 | value: Vec<JsStatement>, | ||
| 52 | else_value: Option<Vec<JsStatement>>, | ||
| 53 | }, | ||
| 54 | } | ||
crates/js_ast/util.rs created+17| ... | @@ -0,0 +1,17 @@ | ||
| 1 | /// TODO(@paperdave): handle the rest of the identifiers | ||
| 2 | pub fn is_js_identifier(s: &str) -> bool { | ||
| 3 | if s.is_empty() { | ||
| 4 | return false; | ||
| 5 | } | ||
| 6 | let mut chars = s.chars(); | ||
| 7 | let first = chars.next().unwrap(); | ||
| 8 | if !first.is_ascii_alphabetic() && first != '_' { | ||
| 9 | return false; | ||
| 10 | } | ||
| 11 | for c in chars { | ||
| 12 | if !c.is_ascii_alphanumeric() && c != '_' { | ||
| 13 | return false; | ||
| 14 | } | ||
| 15 | } | ||
| 16 | true | ||
| 17 | } | ||
crates/js_printer/Cargo.toml+1-1| ... | @@ -4,6 +4,7 @@ version = "0.1.0" | ... | @@ -4,6 +4,7 @@ version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
| 7 | path = "./lib.rs" | ||
| 7 | 8 | ||
| 8 | [dependencies] | 9 | [dependencies] |
| 9 | miette.workspace = true | 10 | miette.workspace = true |
| ... | @@ -11,4 +12,3 @@ thiserror.workspace = true | ... | @@ -11,4 +12,3 @@ thiserror.workspace = true |
| 11 | num.workspace = true | 12 | num.workspace = true |
| 12 | 13 | ||
| 13 | js_ast = { path = "../js_ast" } | 14 | js_ast = { path = "../js_ast" } |
| 14 | util = { path = "../util" } |
crates/js_printer/lib.rs created+372| ... | @@ -0,0 +1,372 @@ | ||
| 1 | mod printer_tests; | ||
| 2 | |||
| 3 | use js_ast::*; | ||
| 4 | use std::io::{Result, Write}; | ||
| 5 | |||
| 6 | pub trait Print { | ||
| 7 | fn print(&self, writer: &mut dyn Write) -> Result<()>; | ||
| 8 | |||
| 9 | fn print_to_string(&self) -> String { | ||
| 10 | let mut buffer = Vec::new(); | ||
| 11 | self.print(&mut buffer).unwrap(); | ||
| 12 | String::from_utf8(buffer).unwrap() | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | // the printer: | ||
| 17 | |||
| 18 | fn print_module_clause_identifier(writer: &mut dyn Write, export_name: &str) -> Result<()> { | ||
| 19 | // TODO(@paperdave): Escape invalid identifiers | ||
| 20 | if js_ast::is_js_identifier(export_name) { | ||
| 21 | write!(writer, "{}", export_name) | ||
| 22 | } else { | ||
| 23 | todo!("call JSON.stringify on identifier") | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | fn print_js_string(writer: &mut dyn Write, string: &str) -> Result<()> { | ||
| 28 | write!(writer, "\"")?; | ||
| 29 | // TODO(@paperdave): Instead of writing each character, this should write | ||
| 30 | // chunks between the escapes instead of calling write! on each character individually. | ||
| 31 | for c in string.chars() { | ||
| 32 | match c { | ||
| 33 | '\n' => write!(writer, "\\n")?, | ||
| 34 | '\r' => write!(writer, "\\r")?, | ||
| 35 | '\t' => write!(writer, "\\t")?, | ||
| 36 | '"' => write!(writer, "\\\"")?, | ||
| 37 | '\\' => write!(writer, "\\\\")?, | ||
| 38 | '\u{0008}' => write!(writer, "\\b")?, | ||
| 39 | '\u{000C}' => write!(writer, "\\f")?, | ||
| 40 | '\u{0000}'..='\u{001F}' => write!(writer, "\\u{:04x}", c as u32)?, | ||
| 41 | // TODO(@paperdave): I think there are some larger code points that need to be escaped? | ||
| 42 | _ => write!(writer, "{}", c)?, | ||
| 43 | } | ||
| 44 | } | ||
| 45 | write!(writer, "\"") | ||
| 46 | } | ||
| 47 | |||
| 48 | impl Print for JsStatement { | ||
| 49 | fn print(&self, w: &mut dyn Write) -> Result<()> { | ||
| 50 | match &self.data { | ||
| 51 | js_ast::JsStatementData::Expression(expr) => { | ||
| 52 | expr.print(w)?; | ||
| 53 | write!(w, ";") | ||
| 54 | } | ||
| 55 | js_ast::JsStatementData::Return(expr) => { | ||
| 56 | write!(w, "return ")?; | ||
| 57 | expr.print(w) | ||
| 58 | } | ||
| 59 | js_ast::JsStatementData::Export(export) => export.print(w), | ||
| 60 | js_ast::JsStatementData::Declaration(decl) => decl.print(w), | ||
| 61 | js_ast::JsStatementData::Function(function) => function.print(w), | ||
| 62 | js_ast::JsStatementData::If(js_if) => js_if.print(w), | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | impl Print for JsExport { | ||
| 68 | fn print(&self, w: &mut dyn Write) -> Result<()> { | ||
| 69 | match self { | ||
| 70 | JsExport::Named(exports) => { | ||
| 71 | write!(w, "export{{")?; | ||
| 72 | for (i, export) in exports.iter().enumerate() { | ||
| 73 | if i > 0 { | ||
| 74 | write!(w, ",")?; | ||
| 75 | } | ||
| 76 | export.print(w)?; | ||
| 77 | } | ||
| 78 | Ok(()) | ||
| 79 | } | ||
| 80 | JsExport::Function(function) => { | ||
| 81 | write!(w, "export ")?; | ||
| 82 | function.print(w) | ||
| 83 | } | ||
| 84 | JsExport::Declaration(declaration) => { | ||
| 85 | write!(w, "export ")?; | ||
| 86 | declaration.print(w) | ||
| 87 | } | ||
| 88 | JsExport::Default(expr) => { | ||
| 89 | write!(w, "export default ")?; | ||
| 90 | expr.print(w) | ||
| 91 | } | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | impl Print for JSNamedExport { | ||
| 97 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 98 | if self.export_name == self.identifier { | ||
| 99 | write!(writer, "{}", self.identifier) | ||
| 100 | } else { | ||
| 101 | write!(writer, "{} as ", self.identifier)?; | ||
| 102 | print_module_clause_identifier(writer, &self.export_name) | ||
| 103 | } | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | impl Print for JSDeclaration { | ||
| 108 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 109 | write!( | ||
| 110 | writer, | ||
| 111 | "{} {}", | ||
| 112 | if self.is_constant { "const" } else { "let" }, | ||
| 113 | self.name, | ||
| 114 | )?; | ||
| 115 | if let Some(init) = &self.init { | ||
| 116 | write!(writer, " = ")?; | ||
| 117 | init.print(writer)?; | ||
| 118 | } | ||
| 119 | write!(writer, ";")?; | ||
| 120 | Ok(()) | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | impl Print for JsExpr { | ||
| 125 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 126 | self.data.print(writer) | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | impl Print for JsExprData { | ||
| 131 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 132 | match &self { | ||
| 133 | JsExprData::Raw(data) => write!(writer, "{}", data), | ||
| 134 | JsExprData::Number(number) => write!(writer, "{}", number), | ||
| 135 | JsExprData::BigInt(bigint) => write!(writer, "{}", bigint), | ||
| 136 | JsExprData::String(string) => print_js_string(writer, string.as_str()), | ||
| 137 | JsExprData::Bool(bool) => write!(writer, "{}", bool), | ||
| 138 | JsExprData::Null => write!(writer, "null"), | ||
| 139 | JsExprData::Undefined => write!(writer, "undefined"), | ||
| 140 | JsExprData::Identifier(str) => write!(writer, "{}", str), | ||
| 141 | JsExprData::Array(items) => { | ||
| 142 | write!(writer, "[")?; | ||
| 143 | for (i, item) in items.iter().enumerate() { | ||
| 144 | if i > 0 { | ||
| 145 | write!(writer, ",")?; | ||
| 146 | } | ||
| 147 | item.print(writer)?; | ||
| 148 | } | ||
| 149 | write!(writer, "]") | ||
| 150 | } | ||
| 151 | JsExprData::Object(items) => { | ||
| 152 | write!(writer, "{{")?; | ||
| 153 | for (i, kv) in items.iter().enumerate() { | ||
| 154 | if i > 0 { | ||
| 155 | write!(writer, ",")?; | ||
| 156 | } | ||
| 157 | |||
| 158 | kv.print(writer)?; | ||
| 159 | } | ||
| 160 | write!(writer, "}}") | ||
| 161 | } | ||
| 162 | JsExprData::Unary { op, value } => { | ||
| 163 | write!(writer, "{}", op.to_str())?; | ||
| 164 | value.print(writer) | ||
| 165 | } | ||
| 166 | JsExprData::Binary { op, left, right } => { | ||
| 167 | // TODO: figure out when we dont need to wrap | ||
| 168 | let wrap = true; | ||
| 169 | if wrap { | ||
| 170 | write!(writer, "(")?; | ||
| 171 | } | ||
| 172 | left.print(writer)?; | ||
| 173 | write!(writer, "{}", op.to_str())?; | ||
| 174 | right.print(writer)?; | ||
| 175 | if wrap { | ||
| 176 | write!(writer, ")")?; | ||
| 177 | } | ||
| 178 | Ok(()) | ||
| 179 | } | ||
| 180 | JsExprData::Dot { base, name } => { | ||
| 181 | base.print(writer)?; | ||
| 182 | write!(writer, ".{}", name) | ||
| 183 | } | ||
| 184 | JsExprData::Index { base, index } => { | ||
| 185 | base.print(writer)?; | ||
| 186 | write!(writer, "[")?; | ||
| 187 | index.print(writer)?; | ||
| 188 | write!(writer, "]") | ||
| 189 | } | ||
| 190 | JsExprData::Call { base, arguments } => { | ||
| 191 | base.print(writer)?; | ||
| 192 | write!(writer, "(")?; | ||
| 193 | for (i, argument) in arguments.iter().enumerate() { | ||
| 194 | if i > 0 { | ||
| 195 | write!(writer, ",")?; | ||
| 196 | } | ||
| 197 | argument.print(writer)?; | ||
| 198 | } | ||
| 199 | write!(writer, ")") | ||
| 200 | } | ||
| 201 | JsExprData::Function { body, name, params } => { | ||
| 202 | write!(writer, "function ")?; | ||
| 203 | if let Some(name) = name { | ||
| 204 | write!(writer, "{}", name)?; | ||
| 205 | } | ||
| 206 | write!(writer, "(")?; | ||
| 207 | for (i, param) in params.iter().enumerate() { | ||
| 208 | if i > 0 { | ||
| 209 | write!(writer, ",")?; | ||
| 210 | } | ||
| 211 | write!(writer, "{}", param)?; | ||
| 212 | } | ||
| 213 | write!(writer, ")")?; | ||
| 214 | body.print(writer) | ||
| 215 | } | ||
| 216 | JsExprData::ArrowFunction { body, params } => { | ||
| 217 | let print_parens = params.len() != 1; | ||
| 218 | if print_parens { | ||
| 219 | write!(writer, "(")?; | ||
| 220 | } | ||
| 221 | for (i, param) in params.iter().enumerate() { | ||
| 222 | if i > 0 { | ||
| 223 | write!(writer, ",")?; | ||
| 224 | } | ||
| 225 | write!(writer, "{}", param)?; | ||
| 226 | } | ||
| 227 | if print_parens { | ||
| 228 | write!(writer, ")")?; | ||
| 229 | } | ||
| 230 | write!(writer, "=>")?; | ||
| 231 | body.print(writer) | ||
| 232 | } | ||
| 233 | JsExprData::Ternary { condition, yes, no } => { | ||
| 234 | let wrap = false; | ||
| 235 | if wrap { | ||
| 236 | write!(writer, "(")?; | ||
| 237 | } | ||
| 238 | condition.print(writer)?; | ||
| 239 | write!(writer, "?")?; | ||
| 240 | yes.print(writer)?; | ||
| 241 | write!(writer, ":")?; | ||
| 242 | if let Some(no) = no { | ||
| 243 | no.print(writer)?; | ||
| 244 | } else { | ||
| 245 | write!(writer, "null")?; | ||
| 246 | } | ||
| 247 | if wrap { | ||
| 248 | write!(writer, ")")?; | ||
| 249 | } | ||
| 250 | Ok(()) | ||
| 251 | } | ||
| 252 | JsExprData::Comma { items } => { | ||
| 253 | let wrap = true; | ||
| 254 | if wrap { | ||
| 255 | write!(writer, "(")?; | ||
| 256 | } | ||
| 257 | for (i, item) in items.iter().enumerate() { | ||
| 258 | if i > 0 { | ||
| 259 | write!(writer, ",")?; | ||
| 260 | } | ||
| 261 | item.print(writer)?; | ||
| 262 | } | ||
| 263 | if wrap { | ||
| 264 | write!(writer, ")")?; | ||
| 265 | } | ||
| 266 | Ok(()) | ||
| 267 | } | ||
| 268 | JsExprData::Void(expr) => { | ||
| 269 | write!(writer, "void ")?; | ||
| 270 | expr.print(writer) | ||
| 271 | } | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | impl Print for JSFunctionDeclaration { | ||
| 277 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 278 | write!(writer, "function {}", self.name)?; | ||
| 279 | write!(writer, "(")?; | ||
| 280 | for (i, param) in self.params.iter().enumerate() { | ||
| 281 | if i > 0 { | ||
| 282 | write!(writer, ",")?; | ||
| 283 | } | ||
| 284 | write!(writer, "{}", param)?; | ||
| 285 | } | ||
| 286 | write!(writer, ")")?; | ||
| 287 | self.block.print(writer) | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | impl Print for JsBlock { | ||
| 292 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 293 | write!(writer, "{{")?; | ||
| 294 | for statement in self.statements.iter() { | ||
| 295 | statement.print(writer)?; | ||
| 296 | } | ||
| 297 | write!(writer, "}}") | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 301 | impl Print for JsIf { | ||
| 302 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 303 | write!(writer, "if(")?; | ||
| 304 | self.condition.print(writer)?; | ||
| 305 | write!(writer, ")")?; | ||
| 306 | self.yes.print(writer)?; | ||
| 307 | if let Some(no) = &self.no { | ||
| 308 | no.print(writer)?; | ||
| 309 | } | ||
| 310 | Ok(()) | ||
| 311 | } | ||
| 312 | } | ||
| 313 | |||
| 314 | impl Print for JsElse { | ||
| 315 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 316 | match self { | ||
| 317 | JsElse::If(js_if) => { | ||
| 318 | write!(writer, "else ")?; | ||
| 319 | js_if.print(writer) | ||
| 320 | } | ||
| 321 | JsElse::Block(block) => { | ||
| 322 | write!(writer, "else")?; | ||
| 323 | block.print(writer) | ||
| 324 | } | ||
| 325 | JsElse::Expr(expr) => { | ||
| 326 | write!(writer, "else ")?; | ||
| 327 | expr.print(writer) | ||
| 328 | } | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | impl Print for JsKV { | ||
| 334 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 335 | match &self.key.data { | ||
| 336 | JsExprData::String(string) => { | ||
| 337 | if is_js_identifier(string) { | ||
| 338 | write!(writer, "{}", string)?; | ||
| 339 | } else { | ||
| 340 | print_js_string(writer, string.as_str())?; | ||
| 341 | } | ||
| 342 | } | ||
| 343 | _ => { | ||
| 344 | write!(writer, "[")?; | ||
| 345 | self.key.print(writer)?; | ||
| 346 | write!(writer, "]")?; | ||
| 347 | } | ||
| 348 | } | ||
| 349 | write!(writer, ":")?; | ||
| 350 | self.key.print(writer)?; | ||
| 351 | write!(writer, ":")?; | ||
| 352 | self.value.print(writer) | ||
| 353 | } | ||
| 354 | } | ||
| 355 | |||
| 356 | impl Print for JsModule { | ||
| 357 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 358 | for statement in self.statements.iter() { | ||
| 359 | statement.print(writer)?; | ||
| 360 | } | ||
| 361 | Ok(()) | ||
| 362 | } | ||
| 363 | } | ||
| 364 | |||
| 365 | impl Print for JsArrowBody { | ||
| 366 | fn print(&self, writer: &mut dyn Write) -> Result<()> { | ||
| 367 | match self { | ||
| 368 | JsArrowBody::Block(block) => block.print(writer), | ||
| 369 | JsArrowBody::Expr(expr) => expr.print(writer), | ||
| 370 | } | ||
| 371 | } | ||
| 372 | } | ||
crates/js_printer/printer_tests.rs created+53| ... | @@ -0,0 +1,53 @@ | ||
| 1 | #![cfg(test)] | ||
| 2 | use crate::Print; | ||
| 3 | |||
| 4 | use js_ast::JsExprData as E; | ||
| 5 | use num::{bigint::Sign, BigInt}; | ||
| 6 | |||
| 7 | macro_rules! assert_printed { | ||
| 8 | ($value:expr, $expected:expr) => { | ||
| 9 | assert_eq!($value.print_to_string(), $expected); | ||
| 10 | }; | ||
| 11 | } | ||
| 12 | |||
| 13 | #[test] | ||
| 14 | fn number() { | ||
| 15 | assert_printed!(E::Number(1.0), "1"); | ||
| 16 | assert_printed!(E::Number(1.5), "1.5"); | ||
| 17 | assert_printed!(E::Number(1.5e10), "15000000000"); | ||
| 18 | assert_printed!(E::Number(1.5e-10), "0.00000000015"); | ||
| 19 | assert_printed!(E::Number(-1.0), "-1"); | ||
| 20 | assert_printed!(E::Number(-1.5), "-1.5"); | ||
| 21 | assert_printed!(E::Number(0f64), "0"); | ||
| 22 | } | ||
| 23 | |||
| 24 | #[test] | ||
| 25 | fn bigint() { | ||
| 26 | assert_printed!(E::BigInt(BigInt::new(Sign::Plus, vec![1])), "1n"); | ||
| 27 | assert_printed!( | ||
| 28 | E::BigInt(BigInt::new(Sign::Plus, vec![10000000])), | ||
| 29 | "10000000n" | ||
| 30 | ); | ||
| 31 | assert_printed!( | ||
| 32 | E::BigInt(BigInt::new(Sign::Minus, vec![10000000])), | ||
| 33 | "-10000000n" | ||
| 34 | ); | ||
| 35 | } | ||
| 36 | |||
| 37 | #[test] | ||
| 38 | fn boolean() { | ||
| 39 | assert_printed!(E::Bool(true), "true"); | ||
| 40 | assert_printed!(E::Bool(false), "false"); | ||
| 41 | } | ||
| 42 | |||
| 43 | #[test] | ||
| 44 | fn string() { | ||
| 45 | assert_printed!(E::str(""), "\"\""); | ||
| 46 | assert_printed!(E::str("hello"), "\"hello\""); | ||
| 47 | assert_printed!(E::str("hello\nworld"), "\"hello\\nworld\""); | ||
| 48 | } | ||
| 49 | |||
| 50 | #[test] | ||
| 51 | fn identifier() { | ||
| 52 | assert_printed!(E::str_id("hello"), "hello"); | ||
| 53 | } | ||
crates/js_printer/src/lib.rs deleted-16| ... | @@ -1,16 +0,0 @@ | ||
| 1 | mod print_expression; | ||
| 2 | mod print_statement; | ||
| 3 | |||
| 4 | use print_statement::print_statement; | ||
| 5 | |||
| 6 | use js_ast::JsStatement; | ||
| 7 | |||
| 8 | pub fn print(forest: Vec<JsStatement>) -> String { | ||
| 9 | let mut result = String::new(); | ||
| 10 | |||
| 11 | for tree in forest { | ||
| 12 | result.push_str(&print_statement(&tree)); | ||
| 13 | } | ||
| 14 | |||
| 15 | result | ||
| 16 | } | ||
crates/js_printer/src/print_expression.rs deleted-266| ... | @@ -1,266 +0,0 @@ | ||
| 1 | use js_ast::JsExpression; | ||
| 2 | |||
| 3 | use crate::print_statement::print_statement; | ||
| 4 | |||
| 5 | pub fn print_expression(statement: &JsExpression) -> String { | ||
| 6 | match statement { | ||
| 7 | JsExpression::Raw(value) => format!("{}", value), | ||
| 8 | JsExpression::Number(value) => format!("{}", value), | ||
| 9 | JsExpression::BigInt(value) => format!("{}n", value), | ||
| 10 | JsExpression::String(value) => { | ||
| 11 | let mut result = String::new(); | ||
| 12 | // TODO: This is an INCREDIBLY naive implementation of escaping. | ||
| 13 | // Also it's probably not even correct. | ||
| 14 | for char in value.chars() { | ||
| 15 | match char { | ||
| 16 | '\n' => result.push_str("\\n"), | ||
| 17 | '\r' => result.push_str("\\r"), | ||
| 18 | '\t' => result.push_str("\\t"), | ||
| 19 | '"' => result.push_str("\\\""), | ||
| 20 | '\\' => result.push_str("\\\\"), | ||
| 21 | _ => result.push(char), | ||
| 22 | } | ||
| 23 | } | ||
| 24 | format!("\"{}\"", result) | ||
| 25 | } | ||
| 26 | JsExpression::Bool(value) => format!("{}", value), | ||
| 27 | JsExpression::Identifier(value) => format!("{}", value), | ||
| 28 | JsExpression::Array(value) => format!( | ||
| 29 | "[{}]", | ||
| 30 | value | ||
| 31 | .iter() | ||
| 32 | .map(|x| print_expression(x)) | ||
| 33 | .collect::<Vec<String>>() | ||
| 34 | .join(",") | ||
| 35 | ), | ||
| 36 | JsExpression::Object(value) => format!( | ||
| 37 | "{{{}}}", | ||
| 38 | value | ||
| 39 | .iter() | ||
| 40 | .map(|(k, v)| format!("\"{}\":{}", k, print_expression(v))) | ||
| 41 | .collect::<Vec<String>>() | ||
| 42 | .join(",") | ||
| 43 | ), | ||
| 44 | JsExpression::Operation { | ||
| 45 | operator, | ||
| 46 | left, | ||
| 47 | right, | ||
| 48 | } => format!( | ||
| 49 | "{}{}{}", | ||
| 50 | print_expression(left), | ||
| 51 | operator.clone().to_string(), | ||
| 52 | print_expression(right) | ||
| 53 | ), | ||
| 54 | JsExpression::Function { arguments, value } => format!( | ||
| 55 | "({})=>{{{}}}", | ||
| 56 | arguments.join(","), | ||
| 57 | value | ||
| 58 | .iter() | ||
| 59 | .map(|x| format!("{}", print_statement(x))) | ||
| 60 | .collect::<Vec<String>>() | ||
| 61 | .join("") | ||
| 62 | ), | ||
| 63 | JsExpression::FunctionCall { name, arguments } => format!( | ||
| 64 | "{}({})", | ||
| 65 | match *name.clone() { | ||
| 66 | JsExpression::Identifier(value) => value.clone(), | ||
| 67 | JsExpression::Raw(value) => value.clone(), | ||
| 68 | _ => panic!("Function call name must be an identifier"), | ||
| 69 | }, | ||
| 70 | arguments | ||
| 71 | .iter() | ||
| 72 | .map(|x| print_expression(x)) | ||
| 73 | .collect::<Vec<String>>() | ||
| 74 | .join(",") | ||
| 75 | ), | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | #[cfg(test)] | ||
| 80 | mod tests { | ||
| 81 | use crate::print_expression::print_expression; | ||
| 82 | use js_ast::JsExpression; | ||
| 83 | use js_ast::JsStatement; | ||
| 84 | use num::{bigint::Sign, BigInt}; | ||
| 85 | use std::collections::HashMap; | ||
| 86 | use util::Operator; | ||
| 87 | |||
| 88 | #[test] | ||
| 89 | fn number() { | ||
| 90 | assert_eq!(print_expression(&JsExpression::Number(1.0)), "1"); | ||
| 91 | assert_eq!(print_expression(&JsExpression::Number(1.5)), "1.5"); | ||
| 92 | assert_eq!( | ||
| 93 | print_expression(&JsExpression::Number(1.5e10)), | ||
| 94 | "15000000000" | ||
| 95 | ); | ||
| 96 | assert_eq!( | ||
| 97 | print_expression(&JsExpression::Number(1.5e-10)), | ||
| 98 | "0.00000000015" | ||
| 99 | ); | ||
| 100 | } | ||
| 101 | |||
| 102 | #[test] | ||
| 103 | fn bigint() { | ||
| 104 | assert_eq!( | ||
| 105 | print_expression(&JsExpression::BigInt(BigInt::new(Sign::Plus, vec![1]))), | ||
| 106 | "1n" | ||
| 107 | ); | ||
| 108 | assert_eq!( | ||
| 109 | print_expression(&JsExpression::BigInt(BigInt::new( | ||
| 110 | Sign::Plus, | ||
| 111 | vec![10000000] | ||
| 112 | ))), | ||
| 113 | "10000000n" | ||
| 114 | ); | ||
| 115 | assert_eq!( | ||
| 116 | print_expression(&JsExpression::BigInt(BigInt::new( | ||
| 117 | Sign::Minus, | ||
| 118 | vec![10000000] | ||
| 119 | ))), | ||
| 120 | "-10000000n" | ||
| 121 | ); | ||
| 122 | } | ||
| 123 | |||
| 124 | #[test] | ||
| 125 | fn string() { | ||
| 126 | assert_eq!( | ||
| 127 | print_expression(&JsExpression::String("".to_string())), | ||
| 128 | "\"\"" | ||
| 129 | ); | ||
| 130 | assert_eq!( | ||
| 131 | print_expression(&JsExpression::String("hello".to_string())), | ||
| 132 | "\"hello\"" | ||
| 133 | ); | ||
| 134 | assert_eq!( | ||
| 135 | print_expression(&JsExpression::String("hello\nworld".to_string())), | ||
| 136 | "\"hello\\nworld\"" | ||
| 137 | ); | ||
| 138 | } | ||
| 139 | |||
| 140 | #[test] | ||
| 141 | fn bool() { | ||
| 142 | assert_eq!(print_expression(&JsExpression::Bool(true)), "true"); | ||
| 143 | assert_eq!(print_expression(&JsExpression::Bool(false)), "false"); | ||
| 144 | } | ||
| 145 | |||
| 146 | #[test] | ||
| 147 | fn identifier() { | ||
| 148 | assert_eq!( | ||
| 149 | print_expression(&JsExpression::Identifier("hello".to_string())), | ||
| 150 | "hello" | ||
| 151 | ); | ||
| 152 | } | ||
| 153 | |||
| 154 | #[test] | ||
| 155 | fn array() { | ||
| 156 | assert_eq!(print_expression(&JsExpression::Array(vec![])), "[]"); | ||
| 157 | assert_eq!( | ||
| 158 | print_expression(&JsExpression::Array(vec![JsExpression::Number(1.0)])), | ||
| 159 | "[1]" | ||
| 160 | ); | ||
| 161 | assert_eq!( | ||
| 162 | print_expression(&JsExpression::Array(vec![ | ||
| 163 | JsExpression::Number(1.0), | ||
| 164 | JsExpression::Number(2.0) | ||
| 165 | ])), | ||
| 166 | "[1,2]" | ||
| 167 | ); | ||
| 168 | } | ||
| 169 | |||
| 170 | #[test] | ||
| 171 | fn object() { | ||
| 172 | assert_eq!( | ||
| 173 | print_expression(&JsExpression::Object(HashMap::new())), | ||
| 174 | "{}" | ||
| 175 | ); | ||
| 176 | let mut map = HashMap::new(); | ||
| 177 | map.insert("hello".to_string(), JsExpression::Number(1.0)); | ||
| 178 | assert_eq!( | ||
| 179 | print_expression(&JsExpression::Object(map)), | ||
| 180 | "{\"hello\":1}" | ||
| 181 | ); | ||
| 182 | } | ||
| 183 | |||
| 184 | #[test] | ||
| 185 | fn operation() { | ||
| 186 | assert_eq!( | ||
| 187 | print_expression(&JsExpression::Operation { | ||
| 188 | operator: Operator::Add, | ||
| 189 | left: Box::new(JsExpression::Number(1.0)), | ||
| 190 | right: Box::new(JsExpression::Number(2.0)), | ||
| 191 | }), | ||
| 192 | "1+2" | ||
| 193 | ); | ||
| 194 | assert_eq!( | ||
| 195 | print_expression(&JsExpression::Operation { | ||
| 196 | operator: Operator::Subtract, | ||
| 197 | left: Box::new(JsExpression::Number(1.0)), | ||
| 198 | right: Box::new(JsExpression::Number(2.0)), | ||
| 199 | }), | ||
| 200 | "1-2" | ||
| 201 | ); | ||
| 202 | } | ||
| 203 | |||
| 204 | #[test] | ||
| 205 | fn function() { | ||
| 206 | use crate::print_expression::print_expression; | ||
| 207 | use js_ast::JsExpression; | ||
| 208 | |||
| 209 | assert_eq!( | ||
| 210 | print_expression(&JsExpression::Function { | ||
| 211 | arguments: vec![], | ||
| 212 | value: vec![], | ||
| 213 | }), | ||
| 214 | "()=>{}" | ||
| 215 | ); | ||
| 216 | assert_eq!( | ||
| 217 | print_expression(&JsExpression::Function { | ||
| 218 | arguments: vec!["a".to_string()], | ||
| 219 | value: vec![], | ||
| 220 | }), | ||
| 221 | "(a)=>{}" | ||
| 222 | ); | ||
| 223 | assert_eq!( | ||
| 224 | print_expression(&JsExpression::Function { | ||
| 225 | arguments: vec!["a".to_string(), "b".to_string()], | ||
| 226 | value: vec![], | ||
| 227 | }), | ||
| 228 | "(a,b)=>{}" | ||
| 229 | ); | ||
| 230 | assert_eq!( | ||
| 231 | print_expression(&JsExpression::Function { | ||
| 232 | arguments: vec!["a".to_string(), "b".to_string()], | ||
| 233 | value: vec![JsStatement::VariableDeclaration { | ||
| 234 | name: "a".to_string(), | ||
| 235 | value: JsExpression::Number(1.0) | ||
| 236 | }], | ||
| 237 | }), | ||
| 238 | "(a,b)=>{let a=1;}" | ||
| 239 | ); | ||
| 240 | } | ||
| 241 | |||
| 242 | #[test] | ||
| 243 | fn function_call() { | ||
| 244 | assert_eq!( | ||
| 245 | print_expression(&JsExpression::FunctionCall { | ||
| 246 | name: Box::new(JsExpression::Identifier("hello".to_string())), | ||
| 247 | arguments: vec![], | ||
| 248 | }), | ||
| 249 | "hello()" | ||
| 250 | ); | ||
| 251 | assert_eq!( | ||
| 252 | print_expression(&JsExpression::FunctionCall { | ||
| 253 | name: Box::new(JsExpression::Identifier("hello".to_string())), | ||
| 254 | arguments: vec![JsExpression::Number(1.0)], | ||
| 255 | }), | ||
| 256 | "hello(1)" | ||
| 257 | ); | ||
| 258 | assert_eq!( | ||
| 259 | print_expression(&JsExpression::FunctionCall { | ||
| 260 | name: Box::new(JsExpression::Identifier("hello".to_string())), | ||
| 261 | arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], | ||
| 262 | }), | ||
| 263 | "hello(1,2)" | ||
| 264 | ); | ||
| 265 | } | ||
| 266 | } | ||
crates/js_printer/src/print_statement.rs deleted-145| ... | @@ -1,145 +0,0 @@ | ||
| 1 | use js_ast::JsStatement; | ||
| 2 | |||
| 3 | use crate::print_expression::print_expression; | ||
| 4 | |||
| 5 | pub fn print_statement(statement: &JsStatement) -> String { | ||
| 6 | match statement { | ||
| 7 | JsStatement::Expression(value) => format!("{};", print_expression(value)), | ||
| 8 | JsStatement::Export(value) => format!("export {}", print_statement(value)), | ||
| 9 | JsStatement::Return(value) => format!("return {};", print_expression(value)), | ||
| 10 | JsStatement::VariableDeclaration { name, value } => { | ||
| 11 | format!("let {}={};", name, print_expression(value)) | ||
| 12 | } | ||
| 13 | JsStatement::ConstantDeclaration { name, value } => { | ||
| 14 | format!("const {}={};", name, print_expression(value)) | ||
| 15 | } | ||
| 16 | JsStatement::Function { | ||
| 17 | name, | ||
| 18 | arguments, | ||
| 19 | value, | ||
| 20 | } => format!( | ||
| 21 | "function {}({}){{{}}}", | ||
| 22 | name, | ||
| 23 | arguments.join(","), | ||
| 24 | value | ||
| 25 | .iter() | ||
| 26 | .map(|x| format!("{}", print_statement(x))) | ||
| 27 | .collect::<Vec<String>>() | ||
| 28 | .join("") | ||
| 29 | ), | ||
| 30 | JsStatement::If { | ||
| 31 | condition, | ||
| 32 | value, | ||
| 33 | else_value, | ||
| 34 | } => { | ||
| 35 | let else_value = match else_value { | ||
| 36 | Some(value) => format!( | ||
| 37 | "else{{{}}}", | ||
| 38 | value | ||
| 39 | .iter() | ||
| 40 | .map(|x| format!("{}", print_statement(x))) | ||
| 41 | .collect::<Vec<String>>() | ||
| 42 | .join("") | ||
| 43 | ), | ||
| 44 | None => "".to_string(), | ||
| 45 | }; | ||
| 46 | format!( | ||
| 47 | "if({}){{{}}}{}", | ||
| 48 | print_expression(condition), | ||
| 49 | value | ||
| 50 | .iter() | ||
| 51 | .map(|x| format!("{}", print_statement(x))) | ||
| 52 | .collect::<Vec<String>>() | ||
| 53 | .join(""), | ||
| 54 | else_value | ||
| 55 | ) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | #[cfg(test)] | ||
| 61 | mod tests { | ||
| 62 | use crate::print_statement::print_statement; | ||
| 63 | use js_ast::JsExpression; | ||
| 64 | use js_ast::JsStatement; | ||
| 65 | |||
| 66 | #[test] | ||
| 67 | fn variable_declaration() { | ||
| 68 | assert_eq!( | ||
| 69 | print_statement(&JsStatement::VariableDeclaration { | ||
| 70 | name: "x".to_string(), | ||
| 71 | value: JsExpression::Number(1.0), | ||
| 72 | }), | ||
| 73 | "let x=1;" | ||
| 74 | ); | ||
| 75 | } | ||
| 76 | |||
| 77 | #[test] | ||
| 78 | fn constant_declaration() { | ||
| 79 | assert_eq!( | ||
| 80 | print_statement(&JsStatement::ConstantDeclaration { | ||
| 81 | name: "x".to_string(), | ||
| 82 | value: JsExpression::Number(1.0), | ||
| 83 | }), | ||
| 84 | "const x=1;" | ||
| 85 | ); | ||
| 86 | } | ||
| 87 | |||
| 88 | #[test] | ||
| 89 | fn function_declaration() { | ||
| 90 | assert_eq!( | ||
| 91 | print_statement(&JsStatement::Function { | ||
| 92 | name: "x".to_string(), | ||
| 93 | arguments: vec!["y".to_string()], | ||
| 94 | value: vec![JsStatement::Expression(JsExpression::Number(1.0))], | ||
| 95 | }), | ||
| 96 | "function x(y){1;}" | ||
| 97 | ); | ||
| 98 | } | ||
| 99 | |||
| 100 | #[test] | ||
| 101 | fn expression() { | ||
| 102 | assert_eq!( | ||
| 103 | print_statement(&JsStatement::Expression(JsExpression::Number(1.0))), | ||
| 104 | "1;" | ||
| 105 | ); | ||
| 106 | } | ||
| 107 | |||
| 108 | #[test] | ||
| 109 | fn export() { | ||
| 110 | assert_eq!( | ||
| 111 | print_statement(&JsStatement::Export(Box::new(JsStatement::Expression( | ||
| 112 | JsExpression::Number(1.0) | ||
| 113 | )))), | ||
| 114 | "export 1;" | ||
| 115 | ); | ||
| 116 | } | ||
| 117 | |||
| 118 | #[test] | ||
| 119 | fn return_statement() { | ||
| 120 | assert_eq!( | ||
| 121 | print_statement(&JsStatement::Return(JsExpression::Number(1.0))), | ||
| 122 | "return 1;" | ||
| 123 | ); | ||
| 124 | } | ||
| 125 | |||
| 126 | #[test] | ||
| 127 | fn if_statement() { | ||
| 128 | assert_eq!( | ||
| 129 | print_statement(&JsStatement::If { | ||
| 130 | condition: JsExpression::Number(1.0), | ||
| 131 | value: vec![JsStatement::Expression(JsExpression::Number(2.0))], | ||
| 132 | else_value: None, | ||
| 133 | }), | ||
| 134 | "if(1){2;}" | ||
| 135 | ); | ||
| 136 | assert_eq!( | ||
| 137 | print_statement(&JsStatement::If { | ||
| 138 | condition: JsExpression::Number(1.0), | ||
| 139 | value: vec![JsStatement::Expression(JsExpression::Number(2.0))], | ||
| 140 | else_value: Some(vec![JsStatement::Expression(JsExpression::Number(3.0))]), | ||
| 141 | }), | ||
| 142 | "if(1){2;}else{3;}" | ||
| 143 | ); | ||
| 144 | } | ||
| 145 | } | ||
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 | 'outer: loop { | ||
| 16 | let is_statement_like_expression = p | ||
| 17 | .peek() | ||
| 18 | .map_or(false, |x| x.is_start_of_statement_like_expression()); | ||
| 19 | |||
| 20 | let statement = match parse_statement(p) { | ||
| 21 | Ok(statement) => statement, | ||
| 22 | Err(err) => { | ||
| 23 | return Err(err); | ||
| 24 | } | ||
| 25 | }; | ||
| 26 | statements.push(statement.clone()); | ||
| 27 | |||
| 28 | match p.peek() { | ||
| 29 | Some(Token::Semicolon) => loop { | ||
| 30 | p.next(); | ||
| 31 | match p.peek() { | ||
| 32 | Some(Token::ClosedBrace) => { | ||
| 33 | p.next(); | ||
| 34 | return Ok(Block { | ||
| 35 | statements, | ||
| 36 | has_implicit_return: false, | ||
| 37 | }); | ||
| 38 | } | ||
| 39 | _ => continue 'outer, | ||
| 40 | } | ||
| 41 | }, | ||
| 42 | Some(Token::ClosedBrace) => { | ||
| 43 | p.next(); | ||
| 44 | if !statement.is_expression() { | ||
| 45 | return Err(p | ||
| 46 | .error("Expected semicolon or '}' after statement".to_string()) | ||
| 47 | .into()); | ||
| 48 | } | ||
| 49 | |||
| 50 | return Ok(Block { | ||
| 51 | statements, | ||
| 52 | has_implicit_return: true, | ||
| 53 | }); | ||
| 54 | } | ||
| 55 | _ => { | ||
| 56 | if statement.require_semicolon(is_statement_like_expression) { | ||
| 57 | return Err(p | ||
| 58 | .error("Expected semicolon or '}' after statement".to_string()) | ||
| 59 | .into()); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | }; | ||
| 63 | } | ||
| 64 | } | ||
crates/parser/parse_call_arguments.rs created+36| ... | @@ -0,0 +1,36 @@ | ||
| 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 | if let Some(Token::ClosedParen) = p.peek() { | ||
| 12 | _ = p.next(); | ||
| 13 | return Ok(args); | ||
| 14 | } | ||
| 15 | |||
| 16 | loop { | ||
| 17 | let arg = parse_expr(p)?; | ||
| 18 | args.push(arg); | ||
| 19 | |||
| 20 | match p.peek() { | ||
| 21 | Some(Token::Comma) => { | ||
| 22 | _ = p.next(); | ||
| 23 | continue; | ||
| 24 | } | ||
| 25 | Some(Token::ClosedParen) => { | ||
| 26 | _ = p.next(); | ||
| 27 | return Ok(args); | ||
| 28 | } | ||
| 29 | _ => { | ||
| 30 | return Err(p | ||
| 31 | .error("Expected ',' or ')' after argument".to_string()) | ||
| 32 | .into()) | ||
| 33 | } | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
crates/parser/parse_declaration.rs created+50| ... | @@ -0,0 +1,50 @@ | ||
| 1 | use ast::{DocComment, Type, VarDecl}; | ||
| 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( | ||
| 11 | p: &mut Parser, | ||
| 12 | is_pub: bool, | ||
| 13 | comment: Option<DocComment>, | ||
| 14 | ) -> Result<VarDecl> { | ||
| 15 | let loc = p.loc(); | ||
| 16 | let is_constant = match p.next() { | ||
| 17 | Some(Token::Var) => false, | ||
| 18 | Some(Token::Let) => true, | ||
| 19 | _ => unreachable!(), | ||
| 20 | }; | ||
| 21 | let name = match p.next() { | ||
| 22 | Some(Token::Identifier(name)) => name, | ||
| 23 | Some(tok) => return Err(p.error(format!("Unexpected {}", tok)).into()), | ||
| 24 | None => return Err(p.error(format!("Unexpected end of file")).into()), | ||
| 25 | }; | ||
| 26 | let return_type = match p.peek() { | ||
| 27 | Some(Token::Colon) => { | ||
| 28 | p.next(); | ||
| 29 | parse_type(p)? | ||
| 30 | } | ||
| 31 | _ => Type::Infer, | ||
| 32 | }; | ||
| 33 | let init = match p.next() { | ||
| 34 | Some(Token::Equal) => parse_expr(p)?, | ||
| 35 | _ => { | ||
| 36 | return Err(p | ||
| 37 | .error("Variable must have an initializer".to_string()) | ||
| 38 | .into()) | ||
| 39 | } | ||
| 40 | }; | ||
| 41 | Ok(VarDecl { | ||
| 42 | name: name.clone(), | ||
| 43 | data_type: return_type, | ||
| 44 | init, | ||
| 45 | is_constant, | ||
| 46 | is_pub, | ||
| 47 | comment, | ||
| 48 | loc, | ||
| 49 | }) | ||
| 50 | } | ||
crates/parser/parse_doc_comment.rs created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | use ast::DocComment; | ||
| 2 | use tokenizer::Token; | ||
| 3 | |||
| 4 | use crate::Parser; | ||
| 5 | |||
| 6 | pub fn maybe_read_doc_comment(p: &mut Parser) -> Option<DocComment> { | ||
| 7 | match p.peek() { | ||
| 8 | Some(Token::DocComment(data)) => { | ||
| 9 | let loc = p.loc(); | ||
| 10 | p.next(); | ||
| 11 | Some(DocComment { data, loc }) | ||
| 12 | } | ||
| 13 | _ => None, | ||
| 14 | } | ||
| 15 | } | ||
crates/parser/parse_expr.rs created+99| ... | @@ -0,0 +1,99 @@ | ||
| 1 | use ast::Associativity::*; | ||
| 2 | use ast::{BinaryOperator, Expr, ExprData, OperatorLevel}; | ||
| 3 | use miette::Result; | ||
| 4 | |||
| 5 | use crate::parse_expr_basic::parse_expr_basic; | ||
| 6 | use crate::Parser; | ||
| 7 | |||
| 8 | pub fn parse_expr(p: &mut Parser) -> Result<Expr> { | ||
| 9 | let mut expr = parse_expr_basic(p)?; | ||
| 10 | |||
| 11 | // Edge case for return. This has already parsed all the way to the end of the expression | ||
| 12 | if let ExprData::Return(_) = expr.data { | ||
| 13 | return Ok(expr); | ||
| 14 | } | ||
| 15 | |||
| 16 | // Parse operators | ||
| 17 | loop { | ||
| 18 | if let Some(token) = p.peek() { | ||
| 19 | if let Some(level) = BinaryOperator::from_token(&token).map(|o| o.level()) { | ||
| 20 | _ = p.next(); | ||
| 21 | match level.associativity() { | ||
| 22 | RightToLeft => { | ||
| 23 | expr = parse_expr_binary( | ||
| 24 | p, | ||
| 25 | BinaryOperator::from_token(&token).unwrap(), | ||
| 26 | expr, | ||
| 27 | level, | ||
| 28 | )?; | ||
| 29 | } | ||
| 30 | LeftToRight => { | ||
| 31 | // TODO(@paperdave): TODO | ||
| 32 | expr = parse_expr_binary( | ||
| 33 | p, | ||
| 34 | BinaryOperator::from_token(&token).unwrap(), | ||
| 35 | expr, | ||
| 36 | level, | ||
| 37 | )?; | ||
| 38 | } | ||
| 39 | } | ||
| 40 | continue; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | break; | ||
| 44 | } | ||
| 45 | |||
| 46 | Ok(expr) | ||
| 47 | } | ||
| 48 | |||
| 49 | /// Prerequisite: `p.peek()` is an operator | ||
| 50 | fn parse_expr_binary( | ||
| 51 | p: &mut Parser, | ||
| 52 | op: BinaryOperator, | ||
| 53 | left: Expr, | ||
| 54 | level: OperatorLevel, | ||
| 55 | ) -> Result<Expr> { | ||
| 56 | let loc = p.loc(); | ||
| 57 | let mut right = parse_expr_basic(p)?; | ||
| 58 | |||
| 59 | loop { | ||
| 60 | if let Some(token) = p.peek() { | ||
| 61 | if let Some(new_level) = BinaryOperator::from_token(&token).map(|o| o.level()) { | ||
| 62 | if new_level <= level { | ||
| 63 | _ = p.next(); | ||
| 64 | match new_level.associativity() { | ||
| 65 | RightToLeft => { | ||
| 66 | right = parse_expr_binary( | ||
| 67 | p, | ||
| 68 | BinaryOperator::from_token(&token).unwrap(), | ||
| 69 | right, | ||
| 70 | new_level, | ||
| 71 | )?; | ||
| 72 | } | ||
| 73 | LeftToRight => { | ||
| 74 | // TODO(@paperdave): TODO | ||
| 75 | right = parse_expr_binary( | ||
| 76 | p, | ||
| 77 | BinaryOperator::from_token(&token).unwrap(), | ||
| 78 | right, | ||
| 79 | new_level, | ||
| 80 | )?; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | continue; | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | break; | ||
| 88 | } | ||
| 89 | |||
| 90 | Ok(Expr::new( | ||
| 91 | ExprData::Binary { | ||
| 92 | op, | ||
| 93 | left: Box::new(left), | ||
| 94 | right: Box::new(right), | ||
| 95 | }, | ||
| 96 | loc, | ||
| 97 | None, | ||
| 98 | )) | ||
| 99 | } | ||
crates/parser/parse_expr_basic.rs created+180| ... | @@ -0,0 +1,180 @@ | ||
| 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 | let comments = p.take_comments(); | ||
| 13 | let mut expr = match p.peek() { | ||
| 14 | Some(Token::Number(number)) => { | ||
| 15 | _ = p.next(); | ||
| 16 | Expr::new( | ||
| 17 | ExprData::Literal(Literal::Number(number.clone())), | ||
| 18 | loc, | ||
| 19 | comments, | ||
| 20 | ) | ||
| 21 | } | ||
| 22 | Some(Token::String(string)) => { | ||
| 23 | _ = p.next(); | ||
| 24 | Expr::new( | ||
| 25 | ExprData::Literal(Literal::String(string.clone())), | ||
| 26 | loc, | ||
| 27 | comments, | ||
| 28 | ) | ||
| 29 | } | ||
| 30 | Some(Token::True) => { | ||
| 31 | _ = p.next(); | ||
| 32 | Expr::new(ExprData::Literal(Literal::Bool(true)), loc, comments) | ||
| 33 | } | ||
| 34 | Some(Token::False) => { | ||
| 35 | _ = p.next(); | ||
| 36 | Expr::new(ExprData::Literal(Literal::Bool(false)), loc, comments) | ||
| 37 | } | ||
| 38 | Some(Token::Void) => { | ||
| 39 | _ = p.next(); | ||
| 40 | Expr::new(ExprData::Literal(Literal::Void), loc, comments) | ||
| 41 | } | ||
| 42 | Some(Token::Identifier(name)) => { | ||
| 43 | let loc = p.loc(); | ||
| 44 | _ = p.next(); | ||
| 45 | if name.starts_with("@") { | ||
| 46 | if let Some(id) = BuiltinId::from_str(name.as_str()) { | ||
| 47 | if let Some(Token::OpenParen) = p.next() { | ||
| 48 | let arguments = parse_call_arguments(p)?; | ||
| 49 | Expr::new(ExprData::Builtin { id, arguments }, loc, comments) | ||
| 50 | } else { | ||
| 51 | return Err(p | ||
| 52 | .error_at(format!("Built-in function '{}' must be called", name), loc) | ||
| 53 | .into()); | ||
| 54 | } | ||
| 55 | } else { | ||
| 56 | return Err(p | ||
| 57 | .error_at(format!("Non-existant built-in: '{}'", name), loc) | ||
| 58 | .into()); | ||
| 59 | } | ||
| 60 | } else { | ||
| 61 | Expr::new(ExprData::Identifier(name), loc, comments) | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | Some(Token::Return) => { | ||
| 66 | if p.allow_return { | ||
| 67 | return Err(p | ||
| 68 | .error("'return' cannot be used at top level".to_string()) | ||
| 69 | .into()); | ||
| 70 | } else { | ||
| 71 | _ = p.next(); | ||
| 72 | return Ok(Expr::new( | ||
| 73 | ExprData::Return(Box::new(parse_expr(p)?)), | ||
| 74 | loc, | ||
| 75 | comments, | ||
| 76 | )); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | Some(Token::If) => Expr::new(ExprData::If(parse_expr_if(p)?), loc, comments), | ||
| 81 | |||
| 82 | Some(Token::OpenBrace) => Expr::new(ExprData::Block(parse_block(p)?), loc, comments), | ||
| 83 | |||
| 84 | Some(Token::OpenParen) => { | ||
| 85 | _ = p.next(); | ||
| 86 | let expr = parse_expr(p)?; | ||
| 87 | match p.next() { | ||
| 88 | Some(Token::ClosedParen) => (), | ||
| 89 | Some(token) => { | ||
| 90 | return Err(p.error(format!("Expected ')', but got {:?}", token)).into()) | ||
| 91 | } | ||
| 92 | None => return Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 93 | } | ||
| 94 | expr | ||
| 95 | } | ||
| 96 | |||
| 97 | Some(t @ Token::Bang) | Some(t @ Token::Minus) | Some(t @ Token::Tilde) => { | ||
| 98 | _ = p.next(); | ||
| 99 | Expr::new( | ||
| 100 | ExprData::Unary { | ||
| 101 | op: match t { | ||
| 102 | Token::Bang => UnaryOperator::Not, | ||
| 103 | Token::Minus => UnaryOperator::Neg, | ||
| 104 | Token::Tilde => UnaryOperator::BitNot, | ||
| 105 | _ => unreachable!(), | ||
| 106 | }, | ||
| 107 | value: Box::new(parse_expr_basic(p)?), | ||
| 108 | }, | ||
| 109 | loc, | ||
| 110 | comments, | ||
| 111 | ) | ||
| 112 | } | ||
| 113 | |||
| 114 | Some(t) => return Err(p.error(format!("Unexpected '{}'", t)).into()), | ||
| 115 | None => return Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 116 | }; | ||
| 117 | |||
| 118 | // Parse function calls and field accesses | ||
| 119 | loop { | ||
| 120 | match p.peek() { | ||
| 121 | Some(Token::Dot) => { | ||
| 122 | let loc = p.loc(); | ||
| 123 | _ = p.next(); | ||
| 124 | let field = match p.peek() { | ||
| 125 | Some(Token::Identifier(name)) => { | ||
| 126 | let loc = p.loc(); | ||
| 127 | p.next(); | ||
| 128 | Expr::new(ExprData::Literal(Literal::String(name)), loc, None) | ||
| 129 | } | ||
| 130 | _ => { | ||
| 131 | return Err(p.error("Expected identifier after dot".to_string()).into()); | ||
| 132 | } | ||
| 133 | }; | ||
| 134 | expr = Expr::new( | ||
| 135 | ExprData::Field { | ||
| 136 | base: Box::new(expr), | ||
| 137 | field: Box::new(field), | ||
| 138 | }, | ||
| 139 | loc, | ||
| 140 | None, | ||
| 141 | ); | ||
| 142 | } | ||
| 143 | Some(Token::OpenParen) => { | ||
| 144 | let loc = p.loc(); | ||
| 145 | _ = p.next(); | ||
| 146 | let arguments = parse_call_arguments(p)?; | ||
| 147 | expr = Expr::new( | ||
| 148 | ExprData::Call { | ||
| 149 | base: Box::new(expr), | ||
| 150 | arguments, | ||
| 151 | }, | ||
| 152 | loc, | ||
| 153 | None, | ||
| 154 | ); | ||
| 155 | } | ||
| 156 | Some(Token::OpenBracket) => { | ||
| 157 | let loc = p.loc(); | ||
| 158 | _ = p.next(); | ||
| 159 | let index = parse_expr(p)?; | ||
| 160 | if let Some(Token::ClosedBracket) = p.next() { | ||
| 161 | expr = Expr::new( | ||
| 162 | ExprData::Field { | ||
| 163 | base: Box::new(expr), | ||
| 164 | field: Box::new(index), | ||
| 165 | }, | ||
| 166 | loc, | ||
| 167 | None, | ||
| 168 | ); | ||
| 169 | } else { | ||
| 170 | return Err(p | ||
| 171 | .error("Expected closing bracket after index".to_string()) | ||
| 172 | .into()); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | _ => break, | ||
| 176 | } | ||
| 177 | } | ||
| 178 | |||
| 179 | Ok(expr) | ||
| 180 | } | ||
crates/parser/parse_expr_if.rs created+70| ... | @@ -0,0 +1,70 @@ | ||
| 1 | use ast::{BlockOrIf, BlockWithLoc, If}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_block::parse_block; | ||
| 6 | use crate::parse_expr::parse_expr; | ||
| 7 | use crate::Parser; | ||
| 8 | |||
| 9 | /// Precondition: the first token exists and is 'if' | ||
| 10 | pub fn parse_expr_if(p: &mut Parser) -> Result<If> { | ||
| 11 | match p.next() { | ||
| 12 | Some(Token::If) => (), | ||
| 13 | _ => unreachable!(), | ||
| 14 | } | ||
| 15 | |||
| 16 | match p.peek() { | ||
| 17 | Some(Token::OpenBrace) => { | ||
| 18 | return Err(p | ||
| 19 | .error( | ||
| 20 | "To use a block as the condition for an if statement, wrap it in parentheses" | ||
| 21 | .to_string(), | ||
| 22 | ) | ||
| 23 | .into()); | ||
| 24 | } | ||
| 25 | _ => (), | ||
| 26 | } | ||
| 27 | |||
| 28 | let condition = Box::new(parse_expr(p)?); | ||
| 29 | |||
| 30 | let yes = match p.peek() { | ||
| 31 | Some(Token::OpenBrace) => { | ||
| 32 | let loc = p.loc(); | ||
| 33 | BlockWithLoc { | ||
| 34 | block: parse_block(p)?, | ||
| 35 | loc, | ||
| 36 | } | ||
| 37 | } | ||
| 38 | _ => { | ||
| 39 | return Err(p | ||
| 40 | .error("Expected a block after condition".to_string()) | ||
| 41 | .into()) | ||
| 42 | } | ||
| 43 | }; | ||
| 44 | |||
| 45 | let no = match p.peek() { | ||
| 46 | Some(Token::Else) => { | ||
| 47 | _ = p.next(); | ||
| 48 | match p.peek() { | ||
| 49 | Some(Token::OpenBrace) => { | ||
| 50 | let loc = p.loc(); | ||
| 51 | // let comments = p.take_comments(); | ||
| 52 | Some(BlockOrIf::Block(BlockWithLoc { | ||
| 53 | block: parse_block(p)?, | ||
| 54 | loc, | ||
| 55 | })) | ||
| 56 | } | ||
| 57 | Some(Token::If) => { | ||
| 58 | // TODO(@paperdave): Save location for the if | ||
| 59 | // let loc = p.loc(); | ||
| 60 | // let comments = p.take_comments(); | ||
| 61 | Some(BlockOrIf::If(Box::new(parse_expr_if(p)?))) | ||
| 62 | } | ||
| 63 | _ => return Err(p.error("Expected a block after else".to_string()).into()), | ||
| 64 | } | ||
| 65 | } | ||
| 66 | _ => None, | ||
| 67 | }; | ||
| 68 | |||
| 69 | Ok(If { condition, yes, no }) | ||
| 70 | } | ||
crates/parser/parse_fn_declaration.rs created+122| ... | @@ -0,0 +1,122 @@ | ||
| 1 | use ast::{DocComment, Expr, ExprData, FnDecl, Type}; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_block::parse_block; | ||
| 6 | use crate::parse_expr::parse_expr; | ||
| 7 | use crate::parse_param::parse_param; | ||
| 8 | use crate::parse_type::parse_type; | ||
| 9 | use crate::Parser; | ||
| 10 | |||
| 11 | /// Precondition: | ||
| 12 | pub fn parse_fn_declaration( | ||
| 13 | p: &mut Parser, | ||
| 14 | is_pub: bool, | ||
| 15 | comment: Option<DocComment>, | ||
| 16 | ) -> Result<FnDecl> { | ||
| 17 | let loc = p.loc(); | ||
| 18 | match p.next() { | ||
| 19 | Some(Token::Fn) => (), | ||
| 20 | _ => unreachable!(), | ||
| 21 | } | ||
| 22 | |||
| 23 | let name = match p.next() { | ||
| 24 | Some(Token::Identifier(name)) => name, | ||
| 25 | _ => return Err(p.error("Expected identifier after 'fn'".to_string()).into()), | ||
| 26 | }; | ||
| 27 | |||
| 28 | match p.peek() { | ||
| 29 | Some(Token::OpenParen) => p.next(), | ||
| 30 | _ => { | ||
| 31 | return Err(p | ||
| 32 | .error("Expected '(' after function name".to_string()) | ||
| 33 | .into()) | ||
| 34 | } | ||
| 35 | }; | ||
| 36 | |||
| 37 | let mut params = Vec::new(); | ||
| 38 | |||
| 39 | loop { | ||
| 40 | match p.peek() { | ||
| 41 | Some(Token::ClosedParen) => { | ||
| 42 | _ = p.next(); | ||
| 43 | break; | ||
| 44 | } | ||
| 45 | Some(Token::Identifier(_)) => { | ||
| 46 | params.push(parse_param(p)?); | ||
| 47 | |||
| 48 | match p.peek() { | ||
| 49 | Some(Token::Comma) => { | ||
| 50 | p.next(); | ||
| 51 | if let Some(Token::ClosedParen) = p.peek() { | ||
| 52 | p.next(); | ||
| 53 | break; | ||
| 54 | } | ||
| 55 | } | ||
| 56 | Some(Token::ClosedParen) => { | ||
| 57 | p.next(); | ||
| 58 | break; | ||
| 59 | } | ||
| 60 | _ => { | ||
| 61 | let actual = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x)); | ||
| 62 | return Err(p | ||
| 63 | .error(format!( | ||
| 64 | "Expected ',' or ')' after identifier, got {}", | ||
| 65 | actual | ||
| 66 | )) | ||
| 67 | .into()); | ||
| 68 | } | ||
| 69 | }; | ||
| 70 | } | ||
| 71 | _ => { | ||
| 72 | return Err(p | ||
| 73 | .error("Expected ')' or identifier after '('".to_string()) | ||
| 74 | .into()) | ||
| 75 | } | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | let return_type = match p.peek() { | ||
| 80 | Some(Token::RightArrow) => { | ||
| 81 | _ = p.next(); | ||
| 82 | parse_type(p)? | ||
| 83 | } | ||
| 84 | _ => Type::Infer, | ||
| 85 | }; | ||
| 86 | |||
| 87 | let was_allow_return = p.allow_return; | ||
| 88 | p.allow_return = true; | ||
| 89 | |||
| 90 | let body = match p.peek() { | ||
| 91 | Some(Token::OpenBrace) => { | ||
| 92 | let block_loc = p.loc(); | ||
| 93 | let block = parse_block(p)?; | ||
| 94 | Expr::new(ExprData::Block(block), block_loc, None) | ||
| 95 | } | ||
| 96 | Some(Token::BigRightArrow) => { | ||
| 97 | _ = p.next(); | ||
| 98 | parse_expr(p)? | ||
| 99 | } | ||
| 100 | _ => { | ||
| 101 | let actually = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x)); | ||
| 102 | return Err(p | ||
| 103 | .error(format!( | ||
| 104 | "Expected a block or '=>' to follow function declaration, but got {}", | ||
| 105 | actually, | ||
| 106 | )) | ||
| 107 | .into()); | ||
| 108 | } | ||
| 109 | }; | ||
| 110 | |||
| 111 | p.allow_return = was_allow_return; | ||
| 112 | |||
| 113 | Ok(FnDecl { | ||
| 114 | name, | ||
| 115 | return_type, | ||
| 116 | params, | ||
| 117 | body, | ||
| 118 | is_pub, | ||
| 119 | comment, | ||
| 120 | loc, | ||
| 121 | }) | ||
| 122 | } | ||
crates/parser/parse_module.rs created+44| ... | @@ -0,0 +1,44 @@ | ||
| 1 | use crate::parse_doc_comment::maybe_read_doc_comment; | ||
| 2 | use crate::parse_top_level_statement::parse_top_level_statement; | ||
| 3 | use crate::Parser; | ||
| 4 | use ast::Module; | ||
| 5 | use miette::Result; | ||
| 6 | use tokenizer::Token; | ||
| 7 | |||
| 8 | pub fn parse_module(p: &mut Parser) -> Result<Module> { | ||
| 9 | let mut statements = Vec::new(); | ||
| 10 | |||
| 11 | let file_doc_comment = maybe_read_doc_comment(p); | ||
| 12 | |||
| 13 | loop { | ||
| 14 | let is_statement_like_expression = p | ||
| 15 | .peek() | ||
| 16 | .map_or(false, |x| x.is_start_of_statement_like_expression()); | ||
| 17 | |||
| 18 | let statement = parse_top_level_statement(p)?; | ||
| 19 | |||
| 20 | match p.peek() { | ||
| 21 | Some(Token::Semicolon) => { | ||
| 22 | while let Some(Token::Semicolon) = p.peek() { | ||
| 23 | p.next(); | ||
| 24 | } | ||
| 25 | } | ||
| 26 | _ => { | ||
| 27 | if statement.require_semicolon(is_statement_like_expression) { | ||
| 28 | return Err(p | ||
| 29 | .error("Expected semicolon after statement".to_string()) | ||
| 30 | .into()); | ||
| 31 | } | ||
| 32 | } | ||
| 33 | }; | ||
| 34 | |||
| 35 | statements.push(statement); | ||
| 36 | |||
| 37 | if p.empty() { | ||
| 38 | return Ok(Module { | ||
| 39 | statements, | ||
| 40 | comment: file_doc_comment, | ||
| 41 | }); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
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+66| ... | @@ -0,0 +1,66 @@ | ||
| 1 | use ast::Statement; | ||
| 2 | use miette::Result; | ||
| 3 | use tokenizer::Token; | ||
| 4 | |||
| 5 | use crate::parse_declaration::parse_declaration; | ||
| 6 | use crate::parse_doc_comment::maybe_read_doc_comment; | ||
| 7 | use crate::parse_expr::parse_expr; | ||
| 8 | use crate::parse_fn_declaration::parse_fn_declaration; | ||
| 9 | use crate::Parser; | ||
| 10 | |||
| 11 | pub fn parse_statement(p: &mut Parser) -> Result<Statement> { | ||
| 12 | if let Some(comments) = p.take_comments() { | ||
| 13 | return Ok(Statement::Comment(comments)); | ||
| 14 | } | ||
| 15 | |||
| 16 | let m_doc_comment = maybe_read_doc_comment(p); | ||
| 17 | if let Some(doc_comment) = m_doc_comment.clone() { | ||
| 18 | if p.has_comments() { | ||
| 19 | // this implies that comments are immediately after the doc | ||
| 20 | return Err(p | ||
| 21 | .error_at(format!("Unattached doc comment"), doc_comment.loc) | ||
| 22 | .into()); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | match p.peek() { | ||
| 27 | Some(Token::DocComment(_)) => unreachable!(), | ||
| 28 | Some(Token::Pub) => { | ||
| 29 | _ = p.next(); | ||
| 30 | match p.peek() { | ||
| 31 | Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration( | ||
| 32 | p, | ||
| 33 | true, | ||
| 34 | m_doc_comment, | ||
| 35 | )?)), | ||
| 36 | Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration( | ||
| 37 | p, | ||
| 38 | true, | ||
| 39 | m_doc_comment, | ||
| 40 | )?)), | ||
| 41 | Some(_) => Err(p.error("Expected a declaration".to_string()).into()), | ||
| 42 | None => Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 43 | } | ||
| 44 | } | ||
| 45 | Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration( | ||
| 46 | p, | ||
| 47 | false, | ||
| 48 | m_doc_comment, | ||
| 49 | )?)), | ||
| 50 | Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration( | ||
| 51 | p, | ||
| 52 | false, | ||
| 53 | m_doc_comment, | ||
| 54 | )?)), | ||
| 55 | Some(_) => { | ||
| 56 | if let Some(doc_comment) = m_doc_comment { | ||
| 57 | Err(p | ||
| 58 | .error_at(format!("Unattached doc comment"), doc_comment.loc) | ||
| 59 | .into()) | ||
| 60 | } else { | ||
| 61 | Ok(Statement::Expr(parse_expr(p)?)) | ||
| 62 | } | ||
| 63 | } | ||
| 64 | None => Err(p.error("Unexpected end of file".to_string()).into()), | ||
| 65 | } | ||
| 66 | } | ||
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+144| ... | @@ -0,0 +1,144 @@ | ||
| 1 | mod parse_block; | ||
| 2 | mod parse_call_arguments; | ||
| 3 | mod parse_declaration; | ||
| 4 | mod parse_doc_comment; | ||
| 5 | mod parse_expr; | ||
| 6 | mod parse_expr_basic; | ||
| 7 | mod parse_expr_if; | ||
| 8 | mod parse_fn_declaration; | ||
| 9 | mod parse_module; | ||
| 10 | mod parse_param; | ||
| 11 | mod parse_statement; | ||
| 12 | mod parse_top_level_statement; | ||
| 13 | mod parse_type; | ||
| 14 | |||
| 15 | use ast::Comment; | ||
| 16 | use ast::Module; | ||
| 17 | use miette::Diagnostic; | ||
| 18 | use miette::Result; | ||
| 19 | use parse_module::parse_module; | ||
| 20 | use thiserror::Error; | ||
| 21 | use tokenizer::tokenize; | ||
| 22 | use tokenizer::{Loc, Token}; | ||
| 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 | /// TODO(@paperdave): switch to streaming tokenizer + one that can | ||
| 51 | /// report line and column info | ||
| 52 | tokens: Vec<Token>, | ||
| 53 | cursor: usize, | ||
| 54 | |||
| 55 | /// Whether or not the parser is at the top level | ||
| 56 | pub allow_return: bool, | ||
| 57 | |||
| 58 | comments: Vec<String>, | ||
| 59 | } | ||
| 60 | |||
| 61 | impl Parser { | ||
| 62 | pub fn new(tokens: Vec<Token>) -> Parser { | ||
| 63 | Parser { | ||
| 64 | tokens, | ||
| 65 | cursor: 0, | ||
| 66 | allow_return: false, | ||
| 67 | comments: vec![], | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | pub fn empty(&self) -> bool { | ||
| 72 | self.cursor == self.tokens.len() | ||
| 73 | } | ||
| 74 | |||
| 75 | // TODO(@paperdave): fix parser token stuff. its very messy rn | ||
| 76 | |||
| 77 | pub fn next(&mut self) -> Option<Token> { | ||
| 78 | if self.cursor == self.tokens.len() { | ||
| 79 | return None; | ||
| 80 | } | ||
| 81 | |||
| 82 | loop { | ||
| 83 | match self.tokens[self.cursor].clone() { | ||
| 84 | Token::Comment(comment) => { | ||
| 85 | self.comments.push(comment); | ||
| 86 | self.cursor += 1; | ||
| 87 | continue; | ||
| 88 | } | ||
| 89 | token => { | ||
| 90 | self.cursor += 1; | ||
| 91 | return Some(token); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn take_comments(&mut self) -> Option<Comment> { | ||
| 98 | if self.comments.len() == 0 { | ||
| 99 | return None; | ||
| 100 | } | ||
| 101 | let comments = self.comments.clone(); | ||
| 102 | self.comments = vec![]; | ||
| 103 | Some(Comment { | ||
| 104 | data: comments.join("\n"), | ||
| 105 | }) | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn has_comments(&mut self) -> bool { | ||
| 109 | self.peek(); | ||
| 110 | self.comments.len() > 0 | ||
| 111 | } | ||
| 112 | |||
| 113 | pub fn peek(&mut self) -> Option<Token> { | ||
| 114 | loop { | ||
| 115 | if self.cursor == self.tokens.len() { | ||
| 116 | return None; | ||
| 117 | } | ||
| 118 | |||
| 119 | match self.tokens[self.cursor].clone() { | ||
| 120 | Token::Comment(comment) => { | ||
| 121 | self.comments.push(comment); | ||
| 122 | self.cursor += 1; | ||
| 123 | continue; | ||
| 124 | } | ||
| 125 | token => { | ||
| 126 | return Some(token); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | pub fn error(&self, error: String) -> ParseError { | ||
| 133 | // TODO(@lino-levan): Add location information | ||
| 134 | ParseError::new(error, None) | ||
| 135 | } | ||
| 136 | |||
| 137 | fn loc(&self) -> Loc { | ||
| 138 | return Loc {}; | ||
| 139 | } | ||
| 140 | |||
| 141 | fn error_at(&self, error: String, loc: Loc) -> ParseError { | ||
| 142 | ParseError::new(error, Some(loc)) | ||
| 143 | } | ||
| 144 | } | ||
crates/sema/Cargo.toml created+14| ... | @@ -0,0 +1,14 @@ | ||
| 1 | [package] | ||
| 2 | name = "sema" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | path = "./sema.rs" | ||
| 8 | |||
| 9 | [dependencies] | ||
| 10 | miette.workspace = true | ||
| 11 | thiserror.workspace = true | ||
| 12 | num.workspace = true | ||
| 13 | |||
| 14 | ast = { path = "../ast" } | ||
| \ No newline at end of file | |||
crates/sema/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/sema/sema.rs created+98| ... | @@ -0,0 +1,98 @@ | ||
| 1 | use ast::Module; | ||
| 2 | use miette::Result; | ||
| 3 | |||
| 4 | pub fn analyze_module(_block: &mut Module) -> Result<()> { | ||
| 5 | // | ||
| 6 | Ok(()) | ||
| 7 | } | ||
| 8 | |||
| 9 | // for statement in &block.statements { | ||
| 10 | // check_expression(statement)?; | ||
| 11 | // } | ||
| 12 | |||
| 13 | // Ok(()) | ||
| 14 | // } | ||
| 15 | |||
| 16 | // pub fn resolve_type(expr: &Ast) -> Result<Type> { | ||
| 17 | // match expr { | ||
| 18 | // Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | ||
| 19 | // Ast::Declaration { | ||
| 20 | // name, | ||
| 21 | // decl_type, | ||
| 22 | // value, | ||
| 23 | // is_constant, | ||
| 24 | // } => Ok(Type::NoReturn), | ||
| 25 | // Ast::Binary { | ||
| 26 | // operator, | ||
| 27 | // left, | ||
| 28 | // right, | ||
| 29 | // } => merge_types(resolve_type(left)?, resolve_type(right)?), | ||
| 30 | // Ast::Function { | ||
| 31 | // name, | ||
| 32 | // arguments, | ||
| 33 | // return_type, | ||
| 34 | // block, | ||
| 35 | // } => todo!(), | ||
| 36 | // Ast::FunctionCall { name, arguments } => todo!(), | ||
| 37 | // Ast::If { | ||
| 38 | // condition, | ||
| 39 | // value, | ||
| 40 | // else_value, | ||
| 41 | // } => todo!(), | ||
| 42 | // Ast::Return(_) => Ok(Type::NoReturn), | ||
| 43 | // Ast::Public(_) => todo!(), // TODO: remove Public | ||
| 44 | // Ast::Block(_) => todo!(), | ||
| 45 | // Ast::Bool(_) => Ok(Type::Bool), | ||
| 46 | // Ast::String(_) => Ok(Type::String), | ||
| 47 | // Ast::Identifier(_) => todo!(), | ||
| 48 | // Ast::BuiltIn(_) => todo!(), | ||
| 49 | |||
| 50 | // Ast::Comment(_) => unreachable!(), | ||
| 51 | // Ast::DocComment(_) => unreachable!(), | ||
| 52 | // } | ||
| 53 | // } | ||
| 54 | |||
| 55 | // pub fn check_expression(expr: &Ast) -> Result<()> { | ||
| 56 | // panic!() | ||
| 57 | // match expr { | ||
| 58 | // Ast::Declaration { | ||
| 59 | // name, | ||
| 60 | // decl_type: return_type, | ||
| 61 | // value, | ||
| 62 | // is_constant, | ||
| 63 | // } => { | ||
| 64 | // check_expression(value)?; | ||
| 65 | // // coerce_type(return_type.clone(), resolve_type(value)?)?; | ||
| 66 | // } | ||
| 67 | // _ => {} | ||
| 68 | // // Ast::Operation { | ||
| 69 | // // operator, | ||
| 70 | // // left, | ||
| 71 | // // right, | ||
| 72 | // // } => Ok(()), | ||
| 73 | // // Ast::Function { | ||
| 74 | // // name, | ||
| 75 | // // arguments, | ||
| 76 | // // return_type, | ||
| 77 | // // block, | ||
| 78 | // // } => Ok(()), | ||
| 79 | // // Ast::FunctionCall { name, arguments } => Ok(()), | ||
| 80 | // // Ast::If { | ||
| 81 | // // condition, | ||
| 82 | // // value, | ||
| 83 | // // else_value, | ||
| 84 | // // } => Ok(()), | ||
| 85 | // // Ast::Return(_) => Ok(()), | ||
| 86 | // // Ast::Public(_) => Ok(()), | ||
| 87 | // // Ast::Block(_) => Ok(()), | ||
| 88 | // // Ast::Number(_) => Ok(()), | ||
| 89 | // // Ast::Bool(_) => Ok(()), | ||
| 90 | // // Ast::String(_) => Ok(()), | ||
| 91 | // // Ast::Identifier(_) => Ok(()), | ||
| 92 | // // Ast::BuiltIn(_) => Ok(()), | ||
| 93 | // // Ast::Comment(_) => Ok(()), | ||
| 94 | // // Ast::DocComment(_) => Ok(()), | ||
| 95 | // } | ||
| 96 | |||
| 97 | // Ok(()) | ||
| 98 | // } | ||
crates/sema/sema_scope.rs createdcrates/tokenizer/Cargo.toml+1-2| ... | @@ -4,9 +4,8 @@ version = "0.1.0" | ... | @@ -4,9 +4,8 @@ version = "0.1.0" |
| 4 | edition = "2021" | 4 | edition = "2021" |
| 5 | 5 | ||
| 6 | [lib] | 6 | [lib] |
| 7 | path="./tokenizer.rs" | ||
| 7 | 8 | ||
| 8 | [dependencies] | 9 | [dependencies] |
| 9 | miette.workspace = true | 10 | miette.workspace = true |
| 10 | thiserror.workspace = true | 11 | thiserror.workspace = true |
| 11 | |||
| 12 | util = { path = "../util" } |
crates/tokenizer/src/lib.rs deleted-227| ... | @@ -1,227 +0,0 @@ | ||
| 1 | use miette::{Diagnostic, Result}; | ||
| 2 | use thiserror::Error; | ||
| 3 | |||
| 4 | use util::Operator; | ||
| 5 | |||
| 6 | #[derive(Error, Diagnostic, Debug)] | ||
| 7 | #[error("UnknownCharacter")] | ||
| 8 | struct UnknownCharacter { | ||
| 9 | #[help] | ||
| 10 | error: String, | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Debug, Clone)] | ||
| 14 | pub enum Token { | ||
| 15 | // Literals | ||
| 16 | Identifier(String), | ||
| 17 | Number(f64), | ||
| 18 | String(String), | ||
| 19 | BuiltIn(String), | ||
| 20 | // Comments | ||
| 21 | Comment(String), | ||
| 22 | DocComment(String), | ||
| 23 | // Keywords | ||
| 24 | True, | ||
| 25 | False, | ||
| 26 | Let, | ||
| 27 | Var, | ||
| 28 | If, | ||
| 29 | Else, | ||
| 30 | Return, | ||
| 31 | 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), | ||
| 44 | } | ||
| 45 | |||
| 46 | pub fn tokenize(content: String) -> Result<Vec<Token>> { | ||
| 47 | let mut tokens: Vec<Token> = Vec::new(); | ||
| 48 | let mut chars = content.chars().enumerate().peekable(); | ||
| 49 | |||
| 50 | loop { | ||
| 51 | match chars.next() { | ||
| 52 | Some((_, c)) => match c { | ||
| 53 | 'a'..='z' | 'A'..='Z' | '_' | '@' => { | ||
| 54 | let mut current_token = String::new(); | ||
| 55 | current_token.push(c); | ||
| 56 | loop { | ||
| 57 | let c = chars.peek(); | ||
| 58 | if c.is_none() { | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | let (_, c) = c.unwrap(); | ||
| 62 | match c { | ||
| 63 | 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => { | ||
| 64 | current_token.push(*c); | ||
| 65 | chars.next(); | ||
| 66 | } | ||
| 67 | _ => break, | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | match current_token.as_str() { | ||
| 72 | "var" => tokens.push(Token::Var), | ||
| 73 | "let" => tokens.push(Token::Let), | ||
| 74 | "if" => tokens.push(Token::If), | ||
| 75 | "else" => tokens.push(Token::Else), | ||
| 76 | "pub" => tokens.push(Token::Pub), | ||
| 77 | "fn" => tokens.push(Token::Function), | ||
| 78 | "return" => tokens.push(Token::Return), | ||
| 79 | "true" => tokens.push(Token::True), | ||
| 80 | "false" => tokens.push(Token::False), | ||
| 81 | _ => { | ||
| 82 | if current_token.starts_with('@') { | ||
| 83 | tokens.push(Token::BuiltIn(current_token)); | ||
| 84 | } else { | ||
| 85 | tokens.push(Token::Identifier(current_token)) | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | } | ||
| 90 | '"' => { | ||
| 91 | let mut current_token = String::new(); | ||
| 92 | loop { | ||
| 93 | let c = chars.next(); | ||
| 94 | if c.is_none() { | ||
| 95 | break; | ||
| 96 | } | ||
| 97 | let (_, c) = c.unwrap(); | ||
| 98 | match c { | ||
| 99 | '"' => break, | ||
| 100 | _ => current_token.push(c), | ||
| 101 | } | ||
| 102 | } | ||
| 103 | tokens.push(Token::String(current_token)); | ||
| 104 | } | ||
| 105 | '0'..='9' => { | ||
| 106 | let mut current_token = String::new(); | ||
| 107 | current_token.push(c); | ||
| 108 | loop { | ||
| 109 | let c = chars.peek(); | ||
| 110 | if c.is_none() { | ||
| 111 | break; | ||
| 112 | } | ||
| 113 | let (_, c) = c.unwrap(); | ||
| 114 | match c { | ||
| 115 | '0'..='9' => { | ||
| 116 | current_token.push(*c); | ||
| 117 | chars.next(); | ||
| 118 | } | ||
| 119 | _ => break, | ||
| 120 | } | ||
| 121 | } | ||
| 122 | match current_token.parse::<f64>() { | ||
| 123 | Ok(v) => tokens.push(Token::Number(v)), | ||
| 124 | Err(_) => { | ||
| 125 | return Err(UnknownCharacter { | ||
| 126 | error: format!("Unknown character: {}", current_token), | ||
| 127 | } | ||
| 128 | .into()) | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | ':' => tokens.push(Token::Colon), | ||
| 133 | '=' => match chars.peek() { | ||
| 134 | Some((_, '=')) => { | ||
| 135 | chars.next(); | ||
| 136 | tokens.push(Token::Operator(Operator::Equals)); | ||
| 137 | } | ||
| 138 | _ => tokens.push(Token::Equals), | ||
| 139 | }, | ||
| 140 | ';' => tokens.push(Token::Semicolon), | ||
| 141 | '>' => match chars.peek() { | ||
| 142 | Some((_, '=')) => { | ||
| 143 | chars.next(); | ||
| 144 | tokens.push(Token::Operator(Operator::GreaterThanOrEquals)); | ||
| 145 | } | ||
| 146 | _ => tokens.push(Token::Operator(Operator::GreaterThan)), | ||
| 147 | }, | ||
| 148 | '<' => match chars.peek() { | ||
| 149 | Some((_, '=')) => { | ||
| 150 | chars.next(); | ||
| 151 | tokens.push(Token::Operator(Operator::LessThanOrEquals)); | ||
| 152 | } | ||
| 153 | _ => tokens.push(Token::Operator(Operator::LessThan)), | ||
| 154 | }, | ||
| 155 | '+' => tokens.push(Token::Operator(Operator::Add)), | ||
| 156 | '-' => match chars.peek() { | ||
| 157 | Some((_, '>')) => { | ||
| 158 | chars.next(); | ||
| 159 | tokens.push(Token::RightArrow); | ||
| 160 | } | ||
| 161 | _ => tokens.push(Token::Operator(Operator::Subtract)), | ||
| 162 | }, | ||
| 163 | '*' => match chars.peek() { | ||
| 164 | Some((_, '*')) => { | ||
| 165 | chars.next(); | ||
| 166 | tokens.push(Token::Operator(Operator::Power)); | ||
| 167 | } | ||
| 168 | _ => tokens.push(Token::Operator(Operator::Multiply)), | ||
| 169 | }, | ||
| 170 | '/' => { | ||
| 171 | match chars.peek() { | ||
| 172 | Some((_, '/')) => { | ||
| 173 | chars.next(); // Consume the second slash | ||
| 174 | |||
| 175 | let is_doc_comment = match chars.peek() { | ||
| 176 | Some((_, '/')) => { | ||
| 177 | chars.next(); // Consume the third slash | ||
| 178 | true | ||
| 179 | } | ||
| 180 | _ => false, | ||
| 181 | }; | ||
| 182 | |||
| 183 | let mut comment = String::new(); | ||
| 184 | loop { | ||
| 185 | let c = chars.next(); | ||
| 186 | if c.is_none() { | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | let (_, c) = c.unwrap(); | ||
| 190 | match c { | ||
| 191 | '\n' => break, | ||
| 192 | _ => { | ||
| 193 | comment.push(c.clone()); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | comment = comment.trim().to_string(); | ||
| 199 | |||
| 200 | if is_doc_comment { | ||
| 201 | tokens.push(Token::DocComment(comment)); | ||
| 202 | } else { | ||
| 203 | tokens.push(Token::Comment(comment)); | ||
| 204 | } | ||
| 205 | } | ||
| 206 | _ => tokens.push(Token::Operator(Operator::Divide)), | ||
| 207 | } | ||
| 208 | } | ||
| 209 | ',' => tokens.push(Token::Comma), | ||
| 210 | '(' => tokens.push(Token::OpenParanthesis), | ||
| 211 | ')' => tokens.push(Token::ClosedParanthesis), | ||
| 212 | '{' => tokens.push(Token::OpenBracket), | ||
| 213 | '}' => tokens.push(Token::ClosedBracket), | ||
| 214 | s if s.is_whitespace() => (), | ||
| 215 | _ => { | ||
| 216 | return Err(UnknownCharacter { | ||
| 217 | error: format!("Unsupported character: {}", c), | ||
| 218 | } | ||
| 219 | .into()) | ||
| 220 | } | ||
| 221 | }, | ||
| 222 | None => break, | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | Ok(tokens) | ||
| 227 | } | ||
crates/tokenizer/tokenizer.rs created+409| ... | @@ -0,0 +1,409 @@ | ||
| 1 | use miette::{Diagnostic, Result}; | ||
| 2 | use thiserror::Error; | ||
| 3 | |||
| 4 | #[derive(Debug, PartialEq, Clone)] | ||
| 5 | pub struct Loc {} | ||
| 6 | |||
| 7 | impl Loc { | ||
| 8 | /// TODO(@paperdave): Remove this. every usage of this is incorrect. | ||
| 9 | pub const EMPTY: Loc = Loc {}; | ||
| 10 | |||
| 11 | pub fn new() -> Self { | ||
| 12 | Self {} | ||
| 13 | } | ||
| 14 | } | ||
| 15 | |||
| 16 | #[derive(Error, Diagnostic, Debug)] | ||
| 17 | #[error("UnknownCharacter")] | ||
| 18 | struct UnknownCharacter { | ||
| 19 | #[help] | ||
| 20 | error: String, | ||
| 21 | } | ||
| 22 | |||
| 23 | #[derive(Debug, Clone)] | ||
| 24 | pub enum Token { | ||
| 25 | // Literals | ||
| 26 | Identifier(String), | ||
| 27 | Number(f64), | ||
| 28 | String(String), | ||
| 29 | // Comments | ||
| 30 | Comment(String), | ||
| 31 | DocComment(String), | ||
| 32 | // Keywords | ||
| 33 | Else, | ||
| 34 | False, | ||
| 35 | Fn, | ||
| 36 | If, | ||
| 37 | Let, | ||
| 38 | Pub, | ||
| 39 | Return, | ||
| 40 | True, | ||
| 41 | Var, | ||
| 42 | Void, | ||
| 43 | // Symbols | ||
| 44 | And, // & | ||
| 45 | AndAnd, // && | ||
| 46 | Bang, // ! | ||
| 47 | BangEqual, // != | ||
| 48 | BigRightArrow, // => | ||
| 49 | Caret, // ^ | ||
| 50 | ClosedBrace, // } | ||
| 51 | ClosedBracket, // ] | ||
| 52 | ClosedParen, // ) | ||
| 53 | Colon, // : | ||
| 54 | Comma, // , | ||
| 55 | Dot, // . | ||
| 56 | Equal, // = | ||
| 57 | EqualEqual, // == | ||
| 58 | Gt, // > | ||
| 59 | GtEqual, // >= | ||
| 60 | GtGt, // >> | ||
| 61 | GtGtGt, // >>> | ||
| 62 | Lt, // < | ||
| 63 | LtEqual, // <= | ||
| 64 | LtLt, // << | ||
| 65 | Minus, // - | ||
| 66 | OpenBrace, // { | ||
| 67 | OpenBracket, // [ | ||
| 68 | OpenParen, // ( | ||
| 69 | Or, // | | ||
| 70 | OrOr, // || | ||
| 71 | Percent, // % | ||
| 72 | Plus, // + | ||
| 73 | RightArrow, // -> | ||
| 74 | Semicolon, // ; | ||
| 75 | Slash, // / | ||
| 76 | Star, // * | ||
| 77 | StarStar, // ** | ||
| 78 | Tilde, // ~ | ||
| 79 | } | ||
| 80 | |||
| 81 | impl Token { | ||
| 82 | pub fn is_start_of_statement_like_expression(&self) -> bool { | ||
| 83 | match &self { | ||
| 84 | Token::If | Token::OpenBrace => true, | ||
| 85 | _ => false, | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | impl std::fmt::Display for Token { | ||
| 91 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| 92 | let token = match self { | ||
| 93 | Token::Identifier(_) => "identifier".to_string(), | ||
| 94 | |||
| 95 | Token::Number(n) => n.to_string(), | ||
| 96 | Token::String(s) => format!("\"{}\"", s), | ||
| 97 | Token::Comment(_) => "comment".to_string(), | ||
| 98 | Token::DocComment(_) => "documentation comment".to_string(), | ||
| 99 | |||
| 100 | Token::Else => "else".to_string(), | ||
| 101 | Token::False => "false".to_string(), | ||
| 102 | Token::Fn => "fn".to_string(), | ||
| 103 | Token::If => "if".to_string(), | ||
| 104 | Token::Let => "let".to_string(), | ||
| 105 | Token::Pub => "pub".to_string(), | ||
| 106 | Token::Return => "return".to_string(), | ||
| 107 | Token::True => "true".to_string(), | ||
| 108 | Token::Var => "var".to_string(), | ||
| 109 | Token::Void => "void".to_string(), | ||
| 110 | |||
| 111 | Token::And => "&".to_string(), | ||
| 112 | Token::AndAnd => "&&".to_string(), | ||
| 113 | Token::Bang => "!".to_string(), | ||
| 114 | Token::BangEqual => "!=".to_string(), | ||
| 115 | Token::BigRightArrow => "=>".to_string(), | ||
| 116 | Token::Caret => "^".to_string(), | ||
| 117 | Token::ClosedBrace => "}".to_string(), | ||
| 118 | Token::ClosedBracket => "]".to_string(), | ||
| 119 | Token::ClosedParen => ")".to_string(), | ||
| 120 | Token::Colon => ":".to_string(), | ||
| 121 | Token::Comma => ",".to_string(), | ||
| 122 | Token::Dot => ".".to_string(), | ||
| 123 | Token::Equal => "=".to_string(), | ||
| 124 | Token::EqualEqual => "==".to_string(), | ||
| 125 | Token::Gt => ">".to_string(), | ||
| 126 | Token::GtEqual => ">=".to_string(), | ||
| 127 | Token::GtGt => ">>".to_string(), | ||
| 128 | Token::GtGtGt => ">>>".to_string(), | ||
| 129 | Token::Lt => "<".to_string(), | ||
| 130 | Token::LtEqual => "<=".to_string(), | ||
| 131 | Token::LtLt => "<<".to_string(), | ||
| 132 | Token::Minus => "-".to_string(), | ||
| 133 | Token::OpenBrace => "{".to_string(), | ||
| 134 | Token::OpenBracket => "[".to_string(), | ||
| 135 | Token::OpenParen => "(".to_string(), | ||
| 136 | Token::Or => "|".to_string(), | ||
| 137 | Token::OrOr => "||".to_string(), | ||
| 138 | Token::Percent => "%".to_string(), | ||
| 139 | Token::Plus => "+".to_string(), | ||
| 140 | Token::RightArrow => "->".to_string(), | ||
| 141 | Token::Semicolon => ";".to_string(), | ||
| 142 | Token::Slash => "/".to_string(), | ||
| 143 | Token::Star => "*".to_string(), | ||
| 144 | Token::StarStar => "**".to_string(), | ||
| 145 | Token::Tilde => "~".to_string(), | ||
| 146 | }; | ||
| 147 | |||
| 148 | write!(f, "{}", token) | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | // TODO(@paperdave): rewrite this to not use a Vec<Token>. we probably dont need it. also store Loc | ||
| 153 | pub fn tokenize(content: String) -> Result<Vec<Token>> { | ||
| 154 | let mut tokens: Vec<Token> = Vec::new(); | ||
| 155 | let mut chars = content.chars().enumerate().peekable(); | ||
| 156 | |||
| 157 | loop { | ||
| 158 | match chars.next() { | ||
| 159 | Some((_, c)) => match c { | ||
| 160 | 'a'..='z' | 'A'..='Z' | '_' | '@' => { | ||
| 161 | let mut current_token = String::new(); | ||
| 162 | current_token.push(c); | ||
| 163 | loop { | ||
| 164 | let c = chars.peek(); | ||
| 165 | if c.is_none() { | ||
| 166 | break; | ||
| 167 | } | ||
| 168 | let (_, c) = c.unwrap(); | ||
| 169 | match c { | ||
| 170 | 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => { | ||
| 171 | current_token.push(*c); | ||
| 172 | chars.next(); | ||
| 173 | } | ||
| 174 | _ => break, | ||
| 175 | } | ||
| 176 | } | ||
| 177 | |||
| 178 | match current_token.as_str() { | ||
| 179 | "else" => tokens.push(Token::Else), | ||
| 180 | "false" => tokens.push(Token::False), | ||
| 181 | "fn" => tokens.push(Token::Fn), | ||
| 182 | "if" => tokens.push(Token::If), | ||
| 183 | "let" => tokens.push(Token::Let), | ||
| 184 | "pub" => tokens.push(Token::Pub), | ||
| 185 | "return" => tokens.push(Token::Return), | ||
| 186 | "true" => tokens.push(Token::True), | ||
| 187 | "var" => tokens.push(Token::Var), | ||
| 188 | "void" => tokens.push(Token::Void), | ||
| 189 | _ => tokens.push(Token::Identifier(current_token)), | ||
| 190 | } | ||
| 191 | } | ||
| 192 | '"' => { | ||
| 193 | let mut current_token = String::new(); | ||
| 194 | loop { | ||
| 195 | let c = chars.next(); | ||
| 196 | if c.is_none() { | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | let (_, c) = c.unwrap(); | ||
| 200 | match c { | ||
| 201 | '"' => break, | ||
| 202 | _ => current_token.push(c), | ||
| 203 | } | ||
| 204 | } | ||
| 205 | tokens.push(Token::String(current_token)); | ||
| 206 | } | ||
| 207 | '0'..='9' => { | ||
| 208 | let mut current_token = String::new(); | ||
| 209 | current_token.push(c); | ||
| 210 | loop { | ||
| 211 | let c = chars.peek(); | ||
| 212 | if c.is_none() { | ||
| 213 | break; | ||
| 214 | } | ||
| 215 | let (_, c) = c.unwrap(); | ||
| 216 | match c { | ||
| 217 | '0'..='9' => { | ||
| 218 | current_token.push(*c); | ||
| 219 | chars.next(); | ||
| 220 | } | ||
| 221 | _ => break, | ||
| 222 | } | ||
| 223 | } | ||
| 224 | match current_token.parse::<f64>() { | ||
| 225 | Ok(v) => tokens.push(Token::Number(v)), | ||
| 226 | Err(_) => { | ||
| 227 | return Err(UnknownCharacter { | ||
| 228 | error: format!("Unknown character: {}", current_token), | ||
| 229 | } | ||
| 230 | .into()) | ||
| 231 | } | ||
| 232 | } | ||
| 233 | } | ||
| 234 | ':' => tokens.push(Token::Colon), | ||
| 235 | '=' => match chars.peek() { | ||
| 236 | Some((_, '=')) => { | ||
| 237 | chars.next(); | ||
| 238 | tokens.push(Token::EqualEqual); | ||
| 239 | } | ||
| 240 | Some((_, '>')) => { | ||
| 241 | chars.next(); | ||
| 242 | tokens.push(Token::BigRightArrow); | ||
| 243 | } | ||
| 244 | _ => tokens.push(Token::Equal), | ||
| 245 | }, | ||
| 246 | ';' => tokens.push(Token::Semicolon), | ||
| 247 | '>' => match chars.peek() { | ||
| 248 | Some((_, '=')) => { | ||
| 249 | chars.next(); | ||
| 250 | tokens.push(Token::GtEqual); | ||
| 251 | } | ||
| 252 | Some((_, '>')) => { | ||
| 253 | chars.next(); | ||
| 254 | match chars.peek() { | ||
| 255 | Some((_, '>')) => { | ||
| 256 | chars.next(); | ||
| 257 | tokens.push(Token::GtGtGt); | ||
| 258 | } | ||
| 259 | _ => tokens.push(Token::GtGt), | ||
| 260 | } | ||
| 261 | } | ||
| 262 | _ => tokens.push(Token::Gt), | ||
| 263 | }, | ||
| 264 | '<' => match chars.peek() { | ||
| 265 | Some((_, '=')) => { | ||
| 266 | chars.next(); | ||
| 267 | tokens.push(Token::LtEqual); | ||
| 268 | } | ||
| 269 | Some((_, '<')) => { | ||
| 270 | chars.next(); | ||
| 271 | tokens.push(Token::LtLt); | ||
| 272 | } | ||
| 273 | _ => tokens.push(Token::Lt), | ||
| 274 | }, | ||
| 275 | '+' => tokens.push(Token::Plus), | ||
| 276 | '-' => match chars.peek() { | ||
| 277 | Some((_, '>')) => { | ||
| 278 | chars.next(); | ||
| 279 | tokens.push(Token::RightArrow); | ||
| 280 | } | ||
| 281 | _ => tokens.push(Token::Minus), | ||
| 282 | }, | ||
| 283 | '*' => match chars.peek() { | ||
| 284 | Some((_, '*')) => { | ||
| 285 | chars.next(); | ||
| 286 | tokens.push(Token::StarStar); | ||
| 287 | } | ||
| 288 | _ => tokens.push(Token::Star), | ||
| 289 | }, | ||
| 290 | '/' => { | ||
| 291 | match chars.peek() { | ||
| 292 | Some((_, '/')) => { | ||
| 293 | chars.next(); // Consume the second slash | ||
| 294 | |||
| 295 | let is_doc_comment = match chars.peek() { | ||
| 296 | Some((_, '/')) => { | ||
| 297 | chars.next(); // Consume the third slash | ||
| 298 | true | ||
| 299 | } | ||
| 300 | _ => false, | ||
| 301 | }; | ||
| 302 | |||
| 303 | let mut comment = String::new(); | ||
| 304 | loop { | ||
| 305 | // skip leading whitespace | ||
| 306 | while let Some((_, c)) = chars.peek() { | ||
| 307 | if !c.is_whitespace() { | ||
| 308 | break; | ||
| 309 | } | ||
| 310 | chars.next(); | ||
| 311 | } | ||
| 312 | // read the comment | ||
| 313 | while let Some((_, c)) = chars.next() { | ||
| 314 | if c == '\n' || c == '\r' { | ||
| 315 | break; | ||
| 316 | } | ||
| 317 | comment.push(c); | ||
| 318 | } | ||
| 319 | // trim trailing whitespace on line | ||
| 320 | comment = comment.trim_end().to_string(); | ||
| 321 | // skip to next non-whitespace character | ||
| 322 | while let Some((_, c)) = chars.peek() { | ||
| 323 | if !c.is_whitespace() { | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | chars.next(); | ||
| 327 | } | ||
| 328 | // check if we've hit another comment | ||
| 329 | let mut tmp = chars.clone(); | ||
| 330 | |||
| 331 | let is_another_comment = match tmp.peek() { | ||
| 332 | Some((_, '/')) => { | ||
| 333 | tmp.next(); // Consume the second slash | ||
| 334 | match tmp.peek() { | ||
| 335 | Some((_, '/')) => { | ||
| 336 | tmp.next(); // Consume the third slash | ||
| 337 | is_doc_comment | ||
| 338 | } | ||
| 339 | _ => !is_doc_comment, | ||
| 340 | } | ||
| 341 | } | ||
| 342 | _ => false, | ||
| 343 | }; | ||
| 344 | |||
| 345 | if is_another_comment { | ||
| 346 | // apply our temporary iterator to the main iterator | ||
| 347 | comment.push('\n'); | ||
| 348 | chars = tmp; | ||
| 349 | chars.next(); | ||
| 350 | } else { | ||
| 351 | break; | ||
| 352 | } | ||
| 353 | } | ||
| 354 | |||
| 355 | if is_doc_comment { | ||
| 356 | tokens.push(Token::DocComment(comment)); | ||
| 357 | } else { | ||
| 358 | tokens.push(Token::Comment(comment)); | ||
| 359 | } | ||
| 360 | } | ||
| 361 | _ => tokens.push(Token::Slash), | ||
| 362 | } | ||
| 363 | } | ||
| 364 | ',' => tokens.push(Token::Comma), | ||
| 365 | '(' => tokens.push(Token::OpenParen), | ||
| 366 | ')' => tokens.push(Token::ClosedParen), | ||
| 367 | '{' => tokens.push(Token::OpenBrace), | ||
| 368 | '}' => tokens.push(Token::ClosedBrace), | ||
| 369 | '[' => tokens.push(Token::OpenBracket), | ||
| 370 | ']' => tokens.push(Token::ClosedBracket), | ||
| 371 | '&' => match chars.peek() { | ||
| 372 | Some((_, '&')) => { | ||
| 373 | chars.next(); | ||
| 374 | tokens.push(Token::AndAnd); | ||
| 375 | } | ||
| 376 | _ => tokens.push(Token::And), | ||
| 377 | }, | ||
| 378 | '|' => match chars.peek() { | ||
| 379 | Some((_, '|')) => { | ||
| 380 | chars.next(); | ||
| 381 | tokens.push(Token::OrOr); | ||
| 382 | } | ||
| 383 | _ => tokens.push(Token::Or), | ||
| 384 | }, | ||
| 385 | '!' => match chars.peek() { | ||
| 386 | Some((_, '=')) => { | ||
| 387 | chars.next(); | ||
| 388 | tokens.push(Token::BangEqual); | ||
| 389 | } | ||
| 390 | _ => tokens.push(Token::Bang), | ||
| 391 | }, | ||
| 392 | '^' => tokens.push(Token::Caret), | ||
| 393 | '%' => tokens.push(Token::Percent), | ||
| 394 | '~' => tokens.push(Token::Tilde), | ||
| 395 | '.' => tokens.push(Token::Dot), | ||
| 396 | s if s.is_whitespace() => (), | ||
| 397 | _ => { | ||
| 398 | return Err(UnknownCharacter { | ||
| 399 | error: format!("Unexpected '{}' while tokenizing", c), | ||
| 400 | } | ||
| 401 | .into()) | ||
| 402 | } | ||
| 403 | }, | ||
| 404 | None => break, | ||
| 405 | } | ||
| 406 | } | ||
| 407 | |||
| 408 | Ok(tokens) | ||
| 409 | } | ||
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-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | 1 | let print = @raw("console.log"); |
| 2 | 2 | ||
| 3 | let x: bool = true; | 3 | let x: bool = true; |
| 4 | 4 |
examples/basics/expressions.blight+1-1| ... | @@ -7,5 +7,5 @@ let x: int = 5 + { | ... | @@ -7,5 +7,5 @@ let x: int = 5 + { |
| 7 | y | 7 | y |
| 8 | }; | 8 | }; |
| 9 | 9 | ||
| 10 | let print: any = @raw("console.log"); | 10 | let print = @raw("console.log"); |
| 11 | print(x); // 10 | 11 | print(x); // 10 |
examples/basics/fib.blight+1-1| ... | @@ -8,5 +8,5 @@ pub fn fib(n: int) -> int { | ... | @@ -8,5 +8,5 @@ pub fn fib(n: int) -> int { |
| 8 | } | 8 | } |
| 9 | } | 9 | } |
| 10 | 10 | ||
| 11 | let print: any = @raw("console.log"); | 11 | let print = @raw("console.log"); |
| 12 | print(fib(10)); | 12 | print(fib(10)); |
examples/basics/functions.blight+1-1| ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { | ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { |
| 4 | 4 | ||
| 5 | let result: int = bingus(10, 25); | 5 | let result: int = bingus(10, 25); |
| 6 | 6 | ||
| 7 | let print: any = @raw("console.log"); | 7 | let print = @raw("console.log"); |
| 8 | print(result); | 8 | print(result); |
examples/basics/operations.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let a: int = 10 - 2 / 4 * 15; | 1 | let a: int = 10 - 2 / 4 * 15; |
| 2 | 2 | ||
| 3 | let print: any = @raw("console.log"); | 3 | let print = @raw("console.log"); |
| 4 | print(a); | 4 | print(a); |
examples/basics/shadowing.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | 1 | let print = @raw("console.log"); |
| 2 | 2 | ||
| 3 | let x: int = 0; | 3 | let x: int = 0; |
| 4 | let x: int = 1; | 4 | let x: int = 1; |
examples/basics/stupid.blight created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | |||
| 2 | fn add(x: int, y: int) => x + y | ||
examples/basics/variables.blight+1-1| ... | @@ -6,5 +6,5 @@ let a: int = 0; | ... | @@ -6,5 +6,5 @@ let a: int = 0; |
| 6 | let b: float = 0; | 6 | let b: float = 0; |
| 7 | let c: bigint = 0; | 7 | let c: bigint = 0; |
| 8 | 8 | ||
| 9 | let print: any = @raw("console.log"); | 9 | let print = @raw("console.log"); |
| 10 | print(x); | 10 | print(x); |
examples/basics/weird.blight+1-1| ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { | ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { |
| 9 | x | 9 | x |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | let print: any = @raw("console.log"); | 12 | let print = @raw("console.log"); |
| 13 | print(bingus(true)); | 13 | print(bingus(true)); |
| 14 | print(bingus(false)); | 14 | print(bingus(false)); |
examples/interop/deno.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let writeTextFile: any = @raw("Deno.writeTextFile"); | 1 | let writeTextFile = @raw("Deno.writeTextFile"); |
| 2 | 2 | ||
| 3 | fn x() -> string { | 3 | fn x() -> string { |
| 4 | "hello" | 4 | "hello" |