| 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 | target |
| 2 | *.js |
Cargo.lock+26-8| ... | ... | @@ -70,9 +70,9 @@ name = "ast" |
| 70 | 70 | version = "0.1.0" |
| 71 | 71 | dependencies = [ |
| 72 | 72 | "miette", |
| 73 | "num", | |
| 73 | 74 | "thiserror", |
| 74 | 75 | "tokenizer", |
| 75 | "util", | |
| 76 | 76 | ] |
| 77 | 77 | |
| 78 | 78 | [[package]] |
| ... | ... | @@ -175,6 +175,8 @@ dependencies = [ |
| 175 | 175 | "js_ast", |
| 176 | 176 | "js_printer", |
| 177 | 177 | "miette", |
| 178 | "parser", | |
| 179 | "sema", | |
| 178 | 180 | "thiserror", |
| 179 | 181 | "tokenizer", |
| 180 | 182 | ] |
| ... | ... | @@ -237,8 +239,9 @@ dependencies = [ |
| 237 | 239 | "ast", |
| 238 | 240 | "miette", |
| 239 | 241 | "num", |
| 242 | "parser", | |
| 240 | 243 | "thiserror", |
| 241 | "util", | |
| 244 | "tokenizer", | |
| 242 | 245 | ] |
| 243 | 246 | |
| 244 | 247 | [[package]] |
| ... | ... | @@ -249,7 +252,6 @@ dependencies = [ |
| 249 | 252 | "miette", |
| 250 | 253 | "num", |
| 251 | 254 | "thiserror", |
| 252 | "util", | |
| 253 | 255 | ] |
| 254 | 256 | |
| 255 | 257 | [[package]] |
| ... | ... | @@ -408,6 +410,17 @@ version = "3.5.0" |
| 408 | 410 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 409 | 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 | 424 | [[package]] |
| 412 | 425 | name = "proc-macro2" |
| 413 | 426 | version = "1.0.76" |
| ... | ... | @@ -445,6 +458,16 @@ dependencies = [ |
| 445 | 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 | 471 | [[package]] |
| 449 | 472 | name = "smawk" |
| 450 | 473 | version = "0.3.2" |
| ... | ... | @@ -543,7 +566,6 @@ version = "0.1.0" |
| 543 | 566 | dependencies = [ |
| 544 | 567 | "miette", |
| 545 | 568 | "thiserror", |
| 546 | "util", | |
| 547 | 569 | ] |
| 548 | 570 | |
| 549 | 571 | [[package]] |
| ... | ... | @@ -570,10 +592,6 @@ version = "0.2.1" |
| 570 | 592 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 571 | 593 | checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" |
| 572 | 594 | |
| 573 | [[package]] | |
| 574 | name = "util" | |
| 575 | version = "0.1.0" | |
| 576 | ||
| 577 | 595 | [[package]] |
| 578 | 596 | name = "winapi" |
| 579 | 597 | version = "0.3.9" |
Cargo.toml+10-3| ... | ... | @@ -1,14 +1,21 @@ |
| 1 | 1 | [workspace.package] |
| 2 | 2 | version = "0.1.0" |
| 3 | name = "blight" | |
| 4 | 3 | edition = "2023" |
| 5 | 4 | license = "MIT" |
| 6 | authors = ["Lino Le Van"] | |
| 5 | authors = ["Lino Le Van", "Dave Caruso"] | |
| 7 | 6 | repository = "https://github.com/lino-levan/blight" |
| 8 | 7 | |
| 9 | 8 | [workspace] |
| 10 | 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 | 20 | [workspace.dependencies] |
| 14 | 21 | clap = { version = "4.4.12", features = ["derive"] } |
crates/ast/Cargo.toml+2-1| ... | ... | @@ -4,10 +4,11 @@ version = "0.1.0" |
| 4 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| 7 | path = "./lib.rs" | |
| 7 | 8 | |
| 8 | 9 | [dependencies] |
| 9 | 10 | miette.workspace = true |
| 10 | 11 | thiserror.workspace = true |
| 12 | num.workspace = true | |
| 11 | 13 | |
| 12 | 14 | tokenizer = { path = "../tokenizer" } |
| 13 | util = { path = "../util" } |
crates/ast/ast.rs created+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 | 5 | |
| 6 | 6 | [[bin]] |
| 7 | 7 | name = "blight" |
| 8 | path = "./src/main.rs" | |
| 8 | path = "./main.rs" | |
| 9 | 9 | |
| 10 | 10 | [dependencies] |
| 11 | 11 | clap.workspace = true |
| ... | ... | @@ -14,5 +14,7 @@ thiserror.workspace = true |
| 14 | 14 | |
| 15 | 15 | tokenizer = { path = "../tokenizer" } |
| 16 | 16 | ast = { path = "../ast" } |
| 17 | parser = { path = "../parser" } | |
| 17 | 18 | js_ast = { path = "../js_ast" } |
| 18 | 19 | js_printer = { path = "../js_printer" } |
| 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 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| 7 | path = "./lib.rs" | |
| 7 | 8 | |
| 8 | 9 | [dependencies] |
| 9 | 10 | miette.workspace = true |
| ... | ... | @@ -11,4 +12,5 @@ thiserror.workspace = true |
| 11 | 12 | num.workspace = true |
| 12 | 13 | |
| 13 | 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 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| 7 | path = "./lib.rs" | |
| 7 | 8 | |
| 8 | 9 | [dependencies] |
| 9 | 10 | miette.workspace = true |
| ... | ... | @@ -11,4 +12,3 @@ thiserror.workspace = true |
| 11 | 12 | num.workspace = true |
| 12 | 13 | |
| 13 | 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 | 4 | edition = "2021" |
| 5 | 5 | |
| 6 | 6 | [lib] |
| 7 | path="./tokenizer.rs" | |
| 7 | 8 | |
| 8 | 9 | [dependencies] |
| 9 | 10 | miette.workspace = true |
| 10 | 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 | let print: any = @raw("console.log"); | |
| 1 | let print = @raw("console.log"); | |
| 2 | 2 | |
| 3 | 3 | let x: bool = true; |
| 4 | 4 |
examples/basics/expressions.blight+1-1| ... | ... | @@ -7,5 +7,5 @@ let x: int = 5 + { |
| 7 | 7 | y |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | let print: any = @raw("console.log"); | |
| 10 | let print = @raw("console.log"); | |
| 11 | 11 | print(x); // 10 |
examples/basics/fib.blight+1-1| ... | ... | @@ -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 | 12 | print(fib(10)); |
examples/basics/functions.blight+1-1| ... | ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { |
| 4 | 4 | |
| 5 | 5 | let result: int = bingus(10, 25); |
| 6 | 6 | |
| 7 | let print: any = @raw("console.log"); | |
| 7 | let print = @raw("console.log"); | |
| 8 | 8 | print(result); |
examples/basics/operations.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | 1 | let a: int = 10 - 2 / 4 * 15; |
| 2 | 2 | |
| 3 | let print: any = @raw("console.log"); | |
| 3 | let print = @raw("console.log"); | |
| 4 | 4 | print(a); |
examples/basics/shadowing.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | |
| 1 | let print = @raw("console.log"); | |
| 2 | 2 | |
| 3 | 3 | let x: int = 0; |
| 4 | 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 | 6 | let b: float = 0; |
| 7 | 7 | let c: bigint = 0; |
| 8 | 8 | |
| 9 | let print: any = @raw("console.log"); | |
| 9 | let print = @raw("console.log"); | |
| 10 | 10 | print(x); |
examples/basics/weird.blight+1-1| ... | ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { |
| 9 | 9 | x |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | let print: any = @raw("console.log"); | |
| 12 | let print = @raw("console.log"); | |
| 13 | 13 | print(bingus(true)); |
| 14 | 14 | print(bingus(false)); |
examples/interop/deno.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | let writeTextFile: any = @raw("Deno.writeTextFile"); | |
| 1 | let writeTextFile = @raw("Deno.writeTextFile"); | |
| 2 | 2 | |
| 3 | 3 | fn x() -> string { |
| 4 | 4 | "hello" |