| author | |
| committer | |
| log | 8133856c0042befc85ff2725f34dfb71d50b1095 |
| tree | c335dda758bfdc1f4d64d3e35917f7f33cb79d8c |
| parent | 9153cb96ee98310426d7dab102d4564af52589bd |
19 files changed, 1049 insertions(+), 175 deletions(-)
Cargo.lock+89-1| ... | @@ -75,6 +75,12 @@ dependencies = [ | ... | @@ -75,6 +75,12 @@ dependencies = [ |
| 75 | "util", | 75 | "util", |
| 76 | ] | 76 | ] |
| 77 | 77 | ||
| 78 | [[package]] | ||
| 79 | name = "autocfg" | ||
| 80 | version = "1.1.0" | ||
| 81 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 82 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" | ||
| 83 | |||
| 78 | [[package]] | 84 | [[package]] |
| 79 | name = "backtrace" | 85 | name = "backtrace" |
| 80 | version = "0.3.69" | 86 | version = "0.3.69" |
| ... | @@ -166,6 +172,7 @@ version = "0.1.0" | ... | @@ -166,6 +172,7 @@ version = "0.1.0" |
| 166 | dependencies = [ | 172 | dependencies = [ |
| 167 | "ast", | 173 | "ast", |
| 168 | "clap", | 174 | "clap", |
| 175 | "js_ast", | ||
| 169 | "js_printer", | 176 | "js_printer", |
| 170 | "miette", | 177 | "miette", |
| 171 | "thiserror", | 178 | "thiserror", |
| ... | @@ -227,17 +234,22 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" | ... | @@ -227,17 +234,22 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" |
| 227 | name = "js_ast" | 234 | name = "js_ast" |
| 228 | version = "0.1.0" | 235 | version = "0.1.0" |
| 229 | dependencies = [ | 236 | dependencies = [ |
| 237 | "ast", | ||
| 230 | "miette", | 238 | "miette", |
| 239 | "num", | ||
| 231 | "thiserror", | 240 | "thiserror", |
| 241 | "util", | ||
| 232 | ] | 242 | ] |
| 233 | 243 | ||
| 234 | [[package]] | 244 | [[package]] |
| 235 | name = "js_printer" | 245 | name = "js_printer" |
| 236 | version = "0.1.0" | 246 | version = "0.1.0" |
| 237 | dependencies = [ | 247 | dependencies = [ |
| 238 | "ast", | 248 | "js_ast", |
| 239 | "miette", | 249 | "miette", |
| 250 | "num", | ||
| 240 | "thiserror", | 251 | "thiserror", |
| 252 | "util", | ||
| 241 | ] | 253 | ] |
| 242 | 254 | ||
| 243 | [[package]] | 255 | [[package]] |
| ... | @@ -299,6 +311,82 @@ dependencies = [ | ... | @@ -299,6 +311,82 @@ dependencies = [ |
| 299 | "adler", | 311 | "adler", |
| 300 | ] | 312 | ] |
| 301 | 313 | ||
| 314 | [[package]] | ||
| 315 | name = "num" | ||
| 316 | version = "0.4.1" | ||
| 317 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 318 | checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" | ||
| 319 | dependencies = [ | ||
| 320 | "num-bigint", | ||
| 321 | "num-complex", | ||
| 322 | "num-integer", | ||
| 323 | "num-iter", | ||
| 324 | "num-rational", | ||
| 325 | "num-traits", | ||
| 326 | ] | ||
| 327 | |||
| 328 | [[package]] | ||
| 329 | name = "num-bigint" | ||
| 330 | version = "0.4.4" | ||
| 331 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 332 | checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" | ||
| 333 | dependencies = [ | ||
| 334 | "autocfg", | ||
| 335 | "num-integer", | ||
| 336 | "num-traits", | ||
| 337 | ] | ||
| 338 | |||
| 339 | [[package]] | ||
| 340 | name = "num-complex" | ||
| 341 | version = "0.4.4" | ||
| 342 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 343 | checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" | ||
| 344 | dependencies = [ | ||
| 345 | "num-traits", | ||
| 346 | ] | ||
| 347 | |||
| 348 | [[package]] | ||
| 349 | name = "num-integer" | ||
| 350 | version = "0.1.45" | ||
| 351 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 352 | checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" | ||
| 353 | dependencies = [ | ||
| 354 | "autocfg", | ||
| 355 | "num-traits", | ||
| 356 | ] | ||
| 357 | |||
| 358 | [[package]] | ||
| 359 | name = "num-iter" | ||
| 360 | version = "0.1.43" | ||
| 361 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 362 | checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" | ||
| 363 | dependencies = [ | ||
| 364 | "autocfg", | ||
| 365 | "num-integer", | ||
| 366 | "num-traits", | ||
| 367 | ] | ||
| 368 | |||
| 369 | [[package]] | ||
| 370 | name = "num-rational" | ||
| 371 | version = "0.4.1" | ||
| 372 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 373 | checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" | ||
| 374 | dependencies = [ | ||
| 375 | "autocfg", | ||
| 376 | "num-bigint", | ||
| 377 | "num-integer", | ||
| 378 | "num-traits", | ||
| 379 | ] | ||
| 380 | |||
| 381 | [[package]] | ||
| 382 | name = "num-traits" | ||
| 383 | version = "0.2.17" | ||
| 384 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 385 | checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" | ||
| 386 | dependencies = [ | ||
| 387 | "autocfg", | ||
| 388 | ] | ||
| 389 | |||
| 302 | [[package]] | 390 | [[package]] |
| 303 | name = "object" | 391 | name = "object" |
| 304 | version = "0.32.2" | 392 | version = "0.32.2" |
Cargo.toml+1| ... | @@ -14,3 +14,4 @@ members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crate | ... | @@ -14,3 +14,4 @@ members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crate |
| 14 | clap = { version = "4.4.12", features = ["derive"] } | 14 | clap = { version = "4.4.12", features = ["derive"] } |
| 15 | miette = { version = "5.10.0", features = ["fancy"] } | 15 | miette = { version = "5.10.0", features = ["fancy"] } |
| 16 | thiserror = "1.0.53" | 16 | thiserror = "1.0.53" |
| 17 | num = "0.4.1" |
crates/ast/src/parse_expression.rs+11-7| ... | @@ -4,12 +4,12 @@ use tokenizer::Token; | ... | @@ -4,12 +4,12 @@ use tokenizer::Token; |
| 4 | 4 | ||
| 5 | use super::error::AstError; | 5 | use super::error::AstError; |
| 6 | use super::parse_block::parse_block; | 6 | use super::parse_block::parse_block; |
| 7 | use super::types::{Ast, ParserContext}; | 7 | use super::types::{Ast, Block, ParserContext}; |
| 8 | 8 | ||
| 9 | pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> { | 9 | pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> { |
| 10 | let left = match tokens.next() { | 10 | let left = match tokens.next() { |
| 11 | Some(token) => match token { | 11 | Some(token) => match token { |
| 12 | Token::Number(number) => Ast::Number(number.clone()), | 12 | Token::Number(number) => Ast::Number(number), |
| 13 | Token::String(string) => Ast::String(string.clone()), | 13 | Token::String(string) => Ast::String(string.clone()), |
| 14 | Token::True => Ast::Bool(true), | 14 | Token::True => Ast::Bool(true), |
| 15 | Token::False => Ast::Bool(false), | 15 | Token::False => Ast::Bool(false), |
| ... | @@ -24,17 +24,21 @@ pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -24,17 +24,21 @@ pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> { |
| 24 | 24 | ||
| 25 | let value = parse_block(tokens)?; | 25 | let value = parse_block(tokens)?; |
| 26 | 26 | ||
| 27 | let mut else_value = None; | 27 | let mut else_value: Option<Block> = None; |
| 28 | 28 | ||
| 29 | match tokens.peek() { | 29 | match tokens.peek() { |
| 30 | Some(Token::Else) => { | 30 | Some(Token::Else) => { |
| 31 | tokens.next(); | 31 | tokens.next(); |
| 32 | match tokens.peek() { | 32 | match tokens.peek() { |
| 33 | Option::Some(Token::If) => { | 33 | Some(Token::If) => { |
| 34 | else_value = Some(Box::new(parse_expression(tokens)?)); | 34 | let else_expression = parse_expression(tokens)?; |
| 35 | else_value = Some(Block { | ||
| 36 | value: vec![else_expression], | ||
| 37 | implicit_return: true, | ||
| 38 | }); | ||
| 35 | } | 39 | } |
| 36 | Option::Some(Token::OpenBracket) => { | 40 | Some(Token::OpenBracket) => { |
| 37 | else_value = Some(Box::new(Ast::Block(parse_block(tokens)?))); | 41 | else_value = Some(parse_block(tokens)?); |
| 38 | } | 42 | } |
| 39 | _ => { | 43 | _ => { |
| 40 | return Err(AstError::new( | 44 | return Err(AstError::new( |
crates/ast/src/parse_statement.rs+17-12| ... | @@ -6,12 +6,17 @@ use super::error::AstError; | ... | @@ -6,12 +6,17 @@ use super::error::AstError; |
| 6 | use super::parse_block::parse_block; | 6 | use super::parse_block::parse_block; |
| 7 | use super::parse_expression::parse_expression; | 7 | use super::parse_expression::parse_expression; |
| 8 | use super::parse_type::parse_type; | 8 | use super::parse_type::parse_type; |
| 9 | use super::types::{Argument, Ast, ParserContext}; | 9 | use super::types::{Argument, Ast, Block, ParserContext}; |
| 10 | 10 | ||
| 11 | /// Precondition: There is at least one token in the iterator | 11 | /// Precondition: There is at least one token in the iterator |
| 12 | pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | 12 | pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 13 | let token = tokens.next().unwrap(); | 13 | let token = tokens.next().unwrap(); |
| 14 | match token { | 14 | match token { |
| 15 | Token::OpenBracket => { | ||
| 16 | // FRICK, we have to backtrack | ||
| 17 | tokens.back(); | ||
| 18 | Ok(Ast::Block(parse_block(tokens)?)) | ||
| 19 | } | ||
| 15 | Token::Pub => { | 20 | Token::Pub => { |
| 16 | match tokens.peek() { | 21 | match tokens.peek() { |
| 17 | Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (), | 22 | Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (), |
| ... | @@ -20,9 +25,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -20,9 +25,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 20 | 25 | ||
| 21 | let inner = parse_statement(tokens)?; | 26 | let inner = parse_statement(tokens)?; |
| 22 | 27 | ||
| 23 | return Ok(Ast::Public { | 28 | return Ok(Ast::Public(Box::new(inner))); |
| 24 | value: Box::new(inner), | ||
| 25 | }); | ||
| 26 | } | 29 | } |
| 27 | Token::Let => { | 30 | Token::Let => { |
| 28 | let name = match tokens.next() { | 31 | let name = match tokens.next() { |
| ... | @@ -86,9 +89,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -86,9 +89,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 86 | _ => return Err(AstError::new("Expected semicolon after return statement").into()), | 89 | _ => return Err(AstError::new("Expected semicolon after return statement").into()), |
| 87 | }; | 90 | }; |
| 88 | 91 | ||
| 89 | Ok(Ast::Return { | 92 | Ok(Ast::Return(Box::new(expression))) |
| 90 | return_value: Box::new(expression), | ||
| 91 | }) | ||
| 92 | } | 93 | } |
| 93 | Token::Function => { | 94 | Token::Function => { |
| 94 | let name = match tokens.next() { | 95 | let name = match tokens.next() { |
| ... | @@ -179,17 +180,21 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -179,17 +180,21 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 179 | 180 | ||
| 180 | let value = parse_block(tokens)?; | 181 | let value = parse_block(tokens)?; |
| 181 | 182 | ||
| 182 | let mut else_value = None; | 183 | let mut else_value: Option<Block> = None; |
| 183 | 184 | ||
| 184 | match tokens.peek() { | 185 | match tokens.peek() { |
| 185 | Some(Token::Else) => { | 186 | Some(Token::Else) => { |
| 186 | tokens.next(); | 187 | tokens.next(); |
| 187 | match tokens.peek() { | 188 | match tokens.peek() { |
| 188 | Option::Some(Token::If) => { | 189 | Some(Token::If) => { |
| 189 | else_value = Some(Box::new(parse_expression(tokens)?)); | 190 | let else_if = parse_statement(tokens)?; |
| 191 | else_value = Some(Block { | ||
| 192 | value: vec![else_if], | ||
| 193 | implicit_return: false, | ||
| 194 | }); | ||
| 190 | } | 195 | } |
| 191 | Option::Some(Token::OpenBracket) => { | 196 | Some(Token::OpenBracket) => { |
| 192 | else_value = Some(Box::new(Ast::Block(parse_block(tokens)?))); | 197 | else_value = Some(parse_block(tokens)?); |
| 193 | } | 198 | } |
| 194 | _ => { | 199 | _ => { |
| 195 | return Err(AstError::new( | 200 | return Err(AstError::new( |
crates/ast/src/types.rs+4-8| ... | @@ -64,9 +64,6 @@ pub struct Argument { | ... | @@ -64,9 +64,6 @@ pub struct Argument { |
| 64 | 64 | ||
| 65 | #[derive(Debug)] | 65 | #[derive(Debug)] |
| 66 | pub enum Ast { | 66 | pub enum Ast { |
| 67 | Public { | ||
| 68 | value: Box<Ast>, | ||
| 69 | }, | ||
| 70 | ConstantDeclaration { | 67 | ConstantDeclaration { |
| 71 | name: String, | 68 | name: String, |
| 72 | return_type: Type, | 69 | return_type: Type, |
| ... | @@ -82,9 +79,6 @@ pub enum Ast { | ... | @@ -82,9 +79,6 @@ pub enum Ast { |
| 82 | left: Box<Ast>, | 79 | left: Box<Ast>, |
| 83 | right: Box<Ast>, | 80 | right: Box<Ast>, |
| 84 | }, | 81 | }, |
| 85 | Return { | ||
| 86 | return_value: Box<Ast>, | ||
| 87 | }, | ||
| 88 | Function { | 82 | Function { |
| 89 | name: String, | 83 | name: String, |
| 90 | arguments: Vec<Argument>, | 84 | arguments: Vec<Argument>, |
| ... | @@ -98,10 +92,12 @@ pub enum Ast { | ... | @@ -98,10 +92,12 @@ pub enum Ast { |
| 98 | If { | 92 | If { |
| 99 | condition: Box<Ast>, | 93 | condition: Box<Ast>, |
| 100 | value: Block, | 94 | value: Block, |
| 101 | else_value: Option<Box<Ast>>, | 95 | else_value: Option<Block>, |
| 102 | }, | 96 | }, |
| 97 | Return(Box<Ast>), | ||
| 98 | Public(Box<Ast>), | ||
| 103 | Block(Block), | 99 | Block(Block), |
| 104 | Number(String), | 100 | Number(f64), |
| 105 | Bool(bool), | 101 | Bool(bool), |
| 106 | String(String), | 102 | String(String), |
| 107 | Identifier(String), | 103 | Identifier(String), |
crates/cli/Cargo.toml+1| ... | @@ -14,4 +14,5 @@ thiserror.workspace = true | ... | @@ -14,4 +14,5 @@ thiserror.workspace = true |
| 14 | 14 | ||
| 15 | tokenizer = { path = "../tokenizer" } | 15 | tokenizer = { path = "../tokenizer" } |
| 16 | ast = { path = "../ast" } | 16 | ast = { path = "../ast" } |
| 17 | js_ast = { path = "../js_ast" } | ||
| 17 | js_printer = { path = "../js_printer" } | 18 | js_printer = { path = "../js_printer" } |
crates/cli/src/main.rs+3-5| ... | @@ -3,9 +3,9 @@ use std::path::PathBuf; | ... | @@ -3,9 +3,9 @@ use std::path::PathBuf; |
| 3 | use clap::{Parser, Subcommand}; | 3 | use clap::{Parser, Subcommand}; |
| 4 | use miette::Result; | 4 | use miette::Result; |
| 5 | 5 | ||
| 6 | use tokenizer; | ||
| 7 | use ast; | 6 | use ast; |
| 8 | use js_printer; | 7 | use js_printer; |
| 8 | use tokenizer; | ||
| 9 | 9 | ||
| 10 | #[derive(Parser)] | 10 | #[derive(Parser)] |
| 11 | #[command(author, version, about, long_about = None)] | 11 | #[command(author, version, about, long_about = None)] |
| ... | @@ -42,7 +42,6 @@ fn get_runtime(runtime: &String) -> String { | ... | @@ -42,7 +42,6 @@ fn get_runtime(runtime: &String) -> String { |
| 42 | } | 42 | } |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | |||
| 46 | pub fn transpile(content: String) -> Result<String> { | 45 | pub fn transpile(content: String) -> Result<String> { |
| 47 | // 1. Tokenize the input | 46 | // 1. Tokenize the input |
| 48 | let tokens = tokenizer::tokenize(content)?; | 47 | let tokens = tokenizer::tokenize(content)?; |
| ... | @@ -53,14 +52,13 @@ pub fn transpile(content: String) -> Result<String> { | ... | @@ -53,14 +52,13 @@ pub fn transpile(content: String) -> Result<String> { |
| 53 | // 4. Optimize the AST | 52 | // 4. Optimize the AST |
| 54 | // optimize(tree.clone())?; | 53 | // optimize(tree.clone())?; |
| 55 | // 5. Translate the AST into javascript | 54 | // 5. Translate the AST into javascript |
| 56 | // translate(tree.clone())?; | 55 | let js_ast = js_ast::translate(blight_ast)?; |
| 57 | // 6. Print the JS AST | 56 | // 6. Print the JS AST |
| 58 | let javascript = js_printer::transpile(blight_ast)?; | 57 | let javascript = js_printer::print(js_ast); |
| 59 | // 7. Return the javascript | 58 | // 7. Return the javascript |
| 60 | return Ok(javascript); | 59 | return Ok(javascript); |
| 61 | } | 60 | } |
| 62 | 61 | ||
| 63 | |||
| 64 | fn main() -> Result<()> { | 62 | fn main() -> Result<()> { |
| 65 | let cli = Cli::parse(); | 63 | let cli = Cli::parse(); |
| 66 | 64 |
crates/js_ast/Cargo.toml+4| ... | @@ -8,3 +8,7 @@ edition = "2021" | ... | @@ -8,3 +8,7 @@ edition = "2021" |
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | miette.workspace = true | 9 | miette.workspace = true |
| 10 | thiserror.workspace = true | 10 | thiserror.workspace = true |
| 11 | num.workspace = true | ||
| 12 | |||
| 13 | ast = { path = "../ast" } | ||
| 14 | util = { path = "../util" } |
crates/js_ast/src/lib.rs+406| ... | @@ -0,0 +1,406 @@ | ... | @@ -0,0 +1,406 @@ |
| 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 | ctx.push_internal_variable(JsExpression::FunctionCall { | ||
| 113 | name: ctx.get_identifier(name.clone()), | ||
| 114 | arguments: handles, | ||
| 115 | }) | ||
| 116 | } | ||
| 117 | Ast::Operation { | ||
| 118 | operator, | ||
| 119 | left, | ||
| 120 | right, | ||
| 121 | } => { | ||
| 122 | let left_handle = get_expression_handle(left, ctx); | ||
| 123 | let right_handle = get_expression_handle(right, ctx); | ||
| 124 | JsExpression::Operation { | ||
| 125 | operator: operator.clone(), | ||
| 126 | left: Box::new(left_handle), | ||
| 127 | right: Box::new(right_handle), | ||
| 128 | } | ||
| 129 | } | ||
| 130 | Ast::Block(block) => { | ||
| 131 | if !block.implicit_return { | ||
| 132 | panic!("Expression blocks must have an implicit return"); | ||
| 133 | } | ||
| 134 | ctx.push_context(); | ||
| 135 | for (i, statement) in block.value.iter().enumerate() { | ||
| 136 | if i == block.value.len() - 1 { | ||
| 137 | break; | ||
| 138 | } else { | ||
| 139 | translate_statement(statement, ctx); | ||
| 140 | } | ||
| 141 | } | ||
| 142 | let result = get_expression_handle(block.value.last().unwrap(), ctx); | ||
| 143 | let value = ctx.pop_context(); | ||
| 144 | value.iter().for_each(|x| ctx.push(x.clone())); | ||
| 145 | result | ||
| 146 | } | ||
| 147 | _ => { | ||
| 148 | panic!("unimplemented: {:?}", ast); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | ||
| 154 | match ast { | ||
| 155 | Ast::ConstantDeclaration { | ||
| 156 | name, | ||
| 157 | return_type: _, | ||
| 158 | value, | ||
| 159 | } => { | ||
| 160 | let handle = get_expression_handle(value, ctx); | ||
| 161 | let rename = ctx.declare_variable(name.clone()); | ||
| 162 | ctx.push(JsStatement::ConstantDeclaration { | ||
| 163 | name: rename, | ||
| 164 | value: handle, | ||
| 165 | }); | ||
| 166 | } | ||
| 167 | Ast::VariableDeclaration { | ||
| 168 | name, | ||
| 169 | return_type: _, | ||
| 170 | value, | ||
| 171 | } => { | ||
| 172 | let handle = get_expression_handle(value, ctx); | ||
| 173 | let rename = ctx.declare_variable(name.clone()); | ||
| 174 | ctx.push(JsStatement::VariableDeclaration { | ||
| 175 | name: rename, | ||
| 176 | value: handle, | ||
| 177 | }); | ||
| 178 | } | ||
| 179 | Ast::Function { | ||
| 180 | name, | ||
| 181 | arguments, | ||
| 182 | return_type: _, | ||
| 183 | block, | ||
| 184 | } => { | ||
| 185 | let rename = ctx.declare_variable(name.clone()); | ||
| 186 | let arguments = arguments | ||
| 187 | .iter() | ||
| 188 | .map(|x| ctx.declare_variable(x.name.clone())) | ||
| 189 | .collect(); | ||
| 190 | |||
| 191 | ctx.push_context(); | ||
| 192 | for (i, statement) in block.value.iter().enumerate() { | ||
| 193 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 194 | let handle = get_expression_handle(statement, ctx); | ||
| 195 | ctx.push(JsStatement::Return(handle)); | ||
| 196 | } else { | ||
| 197 | translate_statement(statement, ctx); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | let value = ctx.pop_context(); | ||
| 201 | |||
| 202 | ctx.push(JsStatement::Function { | ||
| 203 | name: rename, | ||
| 204 | arguments, | ||
| 205 | value, | ||
| 206 | }); | ||
| 207 | } | ||
| 208 | Ast::Public(value) => { | ||
| 209 | translate_statement(value, ctx); | ||
| 210 | // I'm sorry, I just don't know how to get the original name properly... my bad | ||
| 211 | match ctx.peek() { | ||
| 212 | JsStatement::Function { name, .. } | ||
| 213 | | JsStatement::VariableDeclaration { name, .. } | ||
| 214 | | JsStatement::ConstantDeclaration { name, .. } => { | ||
| 215 | let original_name = name.split("$").next().unwrap().to_string(); | ||
| 216 | ctx.push(JsStatement::Export(Box::new( | ||
| 217 | JsStatement::ConstantDeclaration { | ||
| 218 | name: original_name, | ||
| 219 | value: JsExpression::Identifier(name.clone()), | ||
| 220 | }, | ||
| 221 | ))); | ||
| 222 | } | ||
| 223 | _ => { | ||
| 224 | panic!("Cannot make non-function public"); | ||
| 225 | } | ||
| 226 | } | ||
| 227 | } | ||
| 228 | Ast::Return(value) => { | ||
| 229 | let handle = get_expression_handle(value, ctx); | ||
| 230 | ctx.push(JsStatement::Return(handle)); | ||
| 231 | } | ||
| 232 | Ast::Block(block) => { | ||
| 233 | ctx.push_context(); | ||
| 234 | for (i, statement) in block.value.iter().enumerate() { | ||
| 235 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 236 | let handle = get_expression_handle(statement, ctx); | ||
| 237 | ctx.push(JsStatement::Return(handle)); | ||
| 238 | } else { | ||
| 239 | translate_statement(statement, ctx); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | let value = ctx.pop_context(); | ||
| 243 | value.iter().for_each(|x| ctx.push(x.clone())); | ||
| 244 | } | ||
| 245 | Ast::If { | ||
| 246 | condition, | ||
| 247 | value: block, | ||
| 248 | else_value, | ||
| 249 | } => { | ||
| 250 | let handle = get_expression_handle(condition, ctx); | ||
| 251 | |||
| 252 | ctx.push_context(); | ||
| 253 | for (i, statement) in block.value.iter().enumerate() { | ||
| 254 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 255 | let handle = get_expression_handle(statement, ctx); | ||
| 256 | ctx.push(JsStatement::Return(handle)); | ||
| 257 | } else { | ||
| 258 | translate_statement(statement, ctx); | ||
| 259 | } | ||
| 260 | } | ||
| 261 | let value = ctx.pop_context(); | ||
| 262 | let else_value = match else_value { | ||
| 263 | Some(block) => { | ||
| 264 | ctx.push_context(); | ||
| 265 | for (i, statement) in block.value.iter().enumerate() { | ||
| 266 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 267 | let handle = get_expression_handle(statement, ctx); | ||
| 268 | ctx.push(JsStatement::Return(handle)); | ||
| 269 | } else { | ||
| 270 | translate_statement(statement, ctx); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | Some(ctx.pop_context()) | ||
| 274 | } | ||
| 275 | None => None, | ||
| 276 | }; | ||
| 277 | |||
| 278 | ctx.push(JsStatement::If { | ||
| 279 | condition: handle, | ||
| 280 | value, | ||
| 281 | else_value, | ||
| 282 | }); | ||
| 283 | } | ||
| 284 | Ast::FunctionCall { name, arguments } => { | ||
| 285 | let mut handles = Vec::new(); | ||
| 286 | for argument in arguments { | ||
| 287 | handles.push(get_expression_handle(argument, ctx)); | ||
| 288 | } | ||
| 289 | ctx.push(JsStatement::Expression(JsExpression::FunctionCall { | ||
| 290 | name: match name.as_str() { | ||
| 291 | "print" => "console.log".to_string(), | ||
| 292 | _ => name.clone(), | ||
| 293 | }, | ||
| 294 | arguments: handles, | ||
| 295 | })); | ||
| 296 | } | ||
| 297 | Ast::Comment(_) | Ast::DocComment(_) => {} | ||
| 298 | _ => { | ||
| 299 | panic!("unimplemented: {:?}", ast); | ||
| 300 | } | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | pub fn translate(_ast: Vec<Ast>) -> Result<Vec<JsStatement>> { | ||
| 305 | let mut ctx = TranslateContext::new(); | ||
| 306 | |||
| 307 | for ast in _ast { | ||
| 308 | translate_statement(&ast, &mut ctx); | ||
| 309 | } | ||
| 310 | |||
| 311 | Ok(ctx.pop_context()) | ||
| 312 | } | ||
| 313 | |||
| 314 | #[cfg(test)] | ||
| 315 | mod tests { | ||
| 316 | use crate::translate; | ||
| 317 | use crate::{JsExpression, JsStatement}; | ||
| 318 | use ast::{Ast, Type}; | ||
| 319 | use util::Operator; | ||
| 320 | |||
| 321 | #[test] | ||
| 322 | fn test_translate() { | ||
| 323 | let ast = Ast::ConstantDeclaration { | ||
| 324 | name: "hi".to_string(), | ||
| 325 | return_type: Type::Int, | ||
| 326 | value: Box::new(Ast::Operation { | ||
| 327 | operator: Operator::Add, | ||
| 328 | left: Box::new(Ast::Operation { | ||
| 329 | operator: Operator::Add, | ||
| 330 | left: Box::new(Ast::Number(1.0)), | ||
| 331 | right: Box::new(Ast::Operation { | ||
| 332 | operator: Operator::Add, | ||
| 333 | left: Box::new(Ast::Number(2.0)), | ||
| 334 | right: Box::new(Ast::Number(3.0)), | ||
| 335 | }), | ||
| 336 | }), | ||
| 337 | right: Box::new(Ast::Operation { | ||
| 338 | operator: Operator::Add, | ||
| 339 | left: Box::new(Ast::Number(4.0)), | ||
| 340 | right: Box::new(Ast::Number(5.0)), | ||
| 341 | }), | ||
| 342 | }), | ||
| 343 | }; | ||
| 344 | assert_eq!( | ||
| 345 | translate(vec![ast]).unwrap(), | ||
| 346 | vec![ | ||
| 347 | JsStatement::ConstantDeclaration { | ||
| 348 | name: "$0".to_string(), | ||
| 349 | value: JsExpression::Number(1.0) | ||
| 350 | }, | ||
| 351 | JsStatement::ConstantDeclaration { | ||
| 352 | name: "$1".to_string(), | ||
| 353 | value: JsExpression::Number(2.0) | ||
| 354 | }, | ||
| 355 | JsStatement::ConstantDeclaration { | ||
| 356 | name: "$2".to_string(), | ||
| 357 | value: JsExpression::Number(3.0) | ||
| 358 | }, | ||
| 359 | JsStatement::ConstantDeclaration { | ||
| 360 | name: "$3".to_string(), | ||
| 361 | value: JsExpression::Operation { | ||
| 362 | operator: Operator::Add, | ||
| 363 | left: Box::new(JsExpression::Identifier("$1".to_string())), | ||
| 364 | right: Box::new(JsExpression::Identifier("$2".to_string())) | ||
| 365 | } | ||
| 366 | }, | ||
| 367 | JsStatement::ConstantDeclaration { | ||
| 368 | name: "$4".to_string(), | ||
| 369 | value: JsExpression::Operation { | ||
| 370 | operator: Operator::Add, | ||
| 371 | left: Box::new(JsExpression::Identifier("$0".to_string())), | ||
| 372 | right: Box::new(JsExpression::Identifier("$3".to_string())) | ||
| 373 | } | ||
| 374 | }, | ||
| 375 | JsStatement::ConstantDeclaration { | ||
| 376 | name: "$5".to_string(), | ||
| 377 | value: JsExpression::Number(4.0) | ||
| 378 | }, | ||
| 379 | JsStatement::ConstantDeclaration { | ||
| 380 | name: "$6".to_string(), | ||
| 381 | value: JsExpression::Number(5.0) | ||
| 382 | }, | ||
| 383 | JsStatement::ConstantDeclaration { | ||
| 384 | name: "$7".to_string(), | ||
| 385 | value: JsExpression::Operation { | ||
| 386 | operator: Operator::Add, | ||
| 387 | left: Box::new(JsExpression::Identifier("$5".to_string())), | ||
| 388 | right: Box::new(JsExpression::Identifier("$6".to_string())) | ||
| 389 | } | ||
| 390 | }, | ||
| 391 | JsStatement::ConstantDeclaration { | ||
| 392 | name: "$8".to_string(), | ||
| 393 | value: JsExpression::Operation { | ||
| 394 | operator: Operator::Add, | ||
| 395 | left: Box::new(JsExpression::Identifier("$4".to_string())), | ||
| 396 | right: Box::new(JsExpression::Identifier("$7".to_string())) | ||
| 397 | } | ||
| 398 | }, | ||
| 399 | JsStatement::ConstantDeclaration { | ||
| 400 | name: "hi".to_string(), | ||
| 401 | value: JsExpression::Identifier("$8".to_string()) | ||
| 402 | } | ||
| 403 | ] | ||
| 404 | ) | ||
| 405 | } | ||
| 406 | } |
crates/js_ast/src/types.rs created+53| ... | @@ -0,0 +1,53 @@ | ||
| 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 | Array(Vec<JsExpression>), | ||
| 14 | Object(HashMap<String, JsExpression>), | ||
| 15 | Operation { | ||
| 16 | operator: Operator, | ||
| 17 | left: Box<JsExpression>, | ||
| 18 | right: Box<JsExpression>, | ||
| 19 | }, | ||
| 20 | Function { | ||
| 21 | arguments: Vec<String>, | ||
| 22 | value: Vec<JsStatement>, | ||
| 23 | }, | ||
| 24 | FunctionCall { | ||
| 25 | name: String, | ||
| 26 | arguments: Vec<JsExpression>, | ||
| 27 | }, | ||
| 28 | } | ||
| 29 | |||
| 30 | #[derive(Debug, PartialEq, Clone)] | ||
| 31 | pub enum JsStatement { | ||
| 32 | Expression(JsExpression), | ||
| 33 | Export(Box<JsStatement>), | ||
| 34 | Return(JsExpression), | ||
| 35 | VariableDeclaration { | ||
| 36 | name: String, | ||
| 37 | value: JsExpression, | ||
| 38 | }, | ||
| 39 | ConstantDeclaration { | ||
| 40 | name: String, | ||
| 41 | value: JsExpression, | ||
| 42 | }, | ||
| 43 | Function { | ||
| 44 | name: String, | ||
| 45 | arguments: Vec<String>, | ||
| 46 | value: Vec<JsStatement>, | ||
| 47 | }, | ||
| 48 | If { | ||
| 49 | condition: JsExpression, | ||
| 50 | value: Vec<JsStatement>, | ||
| 51 | else_value: Option<Vec<JsStatement>>, | ||
| 52 | }, | ||
| 53 | } | ||
crates/js_printer/Cargo.toml+3-1| ... | @@ -8,5 +8,7 @@ edition = "2021" | ... | @@ -8,5 +8,7 @@ edition = "2021" |
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | miette.workspace = true | 9 | miette.workspace = true |
| 10 | thiserror.workspace = true | 10 | thiserror.workspace = true |
| 11 | num.workspace = true | ||
| 11 | 12 | ||
| 12 | ast = { path = "../ast" } | 13 | js_ast = { path = "../js_ast" } |
| 14 | util = { path = "../util" } |
crates/js_printer/src/lib.rs+7-130| ... | @@ -1,139 +1,16 @@ | ... | @@ -1,139 +1,16 @@ |
| 1 | use miette::Result; | 1 | mod print_expression; |
| 2 | mod print_statement; | ||
| 2 | 3 | ||
| 3 | use ast::{Ast, Type}; | 4 | use print_statement::print_statement; |
| 4 | 5 | ||
| 5 | fn transpile_helper(tree: &Ast) -> Result<String> { | 6 | use js_ast::JsStatement; |
| 6 | Ok(match tree { | ||
| 7 | Ast::Number(number) => format!("{}", number), | ||
| 8 | Ast::Identifier(name) => format!("{}", name), | ||
| 9 | Ast::Bool(value) => format!("{}", value), | ||
| 10 | Ast::String(value) => format!("\"{}\"", value), | ||
| 11 | Ast::Comment(comment) => format!("// {}\n", comment), | ||
| 12 | Ast::DocComment(comment) => format!("/** {} */\n", comment), | ||
| 13 | Ast::Operation { | ||
| 14 | operator, | ||
| 15 | left, | ||
| 16 | right, | ||
| 17 | } => format!( | ||
| 18 | "{} {} {}", | ||
| 19 | transpile_helper(left)?, | ||
| 20 | operator.clone().to_string(), | ||
| 21 | transpile_helper(right)? | ||
| 22 | ), | ||
| 23 | Ast::Public { value } => format!("export {}", transpile_helper(value)?), | ||
| 24 | Ast::VariableDeclaration { | ||
| 25 | name, | ||
| 26 | return_type, | ||
| 27 | value, | ||
| 28 | } => match return_type { | ||
| 29 | Type::Bigint => format!("let {} = BigInt({});\n", name, transpile_helper(value)?), | ||
| 30 | _ => format!("let {} = {};\n", name, transpile_helper(value)?), | ||
| 31 | }, | ||
| 32 | Ast::ConstantDeclaration { | ||
| 33 | name, | ||
| 34 | return_type, | ||
| 35 | value, | ||
| 36 | } => match return_type { | ||
| 37 | Type::Bigint => format!("const {} = BigInt({});\n", name, transpile_helper(value)?), | ||
| 38 | _ => format!("const {} = {};\n", name, transpile_helper(value)?), | ||
| 39 | }, | ||
| 40 | Ast::Return { return_value } => format!("return {};\n", transpile_helper(return_value)?), | ||
| 41 | Ast::Function { | ||
| 42 | name, | ||
| 43 | arguments, | ||
| 44 | return_type: _, | ||
| 45 | block, | ||
| 46 | } => { | ||
| 47 | let mut result = format!("function {}(", name); | ||
| 48 | 7 | ||
| 49 | for (i, argument) in arguments.iter().enumerate() { | 8 | pub fn print(forest: Vec<JsStatement>) -> String { |
| 50 | if i == 0 { | ||
| 51 | result += argument.name.as_str(); | ||
| 52 | } else { | ||
| 53 | result += format!(", {}", argument.name).as_str(); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | result += ") {\n"; | ||
| 58 | |||
| 59 | for (i, statement) in block.value.iter().enumerate() { | ||
| 60 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 61 | result += format!("return {};\n", transpile_helper(statement)?).as_str(); | ||
| 62 | break; | ||
| 63 | } | ||
| 64 | result += transpile_helper(statement)?.as_str(); | ||
| 65 | } | ||
| 66 | |||
| 67 | result += "}\n"; | ||
| 68 | |||
| 69 | result | ||
| 70 | } | ||
| 71 | Ast::If { | ||
| 72 | condition, | ||
| 73 | value, | ||
| 74 | else_value, | ||
| 75 | } => { | ||
| 76 | let mut result = format!("if ({}) {{\n", transpile_helper(condition)?,); | ||
| 77 | |||
| 78 | for (i, statement) in value.value.iter().enumerate() { | ||
| 79 | if value.implicit_return && i == value.value.len() - 1 { | ||
| 80 | result += format!("return {};\n", transpile_helper(statement)?).as_str(); | ||
| 81 | break; | ||
| 82 | } | ||
| 83 | result += transpile_helper(statement)?.as_str(); | ||
| 84 | } | ||
| 85 | |||
| 86 | result += "}"; | ||
| 87 | |||
| 88 | if let Some(val) = else_value { | ||
| 89 | result += format!(" else {{\n{}}}", transpile_helper(val)?).as_str(); | ||
| 90 | } else { | ||
| 91 | result += "\n"; | ||
| 92 | } | ||
| 93 | |||
| 94 | result | ||
| 95 | } | ||
| 96 | Ast::FunctionCall { name, arguments } => { | ||
| 97 | let mut result = match name.as_str() { | ||
| 98 | "print" => String::from("console.log("), | ||
| 99 | _ => format!("{}(", name), | ||
| 100 | }; | ||
| 101 | |||
| 102 | for (i, argument) in arguments.iter().enumerate() { | ||
| 103 | if i == 0 { | ||
| 104 | result += transpile_helper(argument)?.as_str(); | ||
| 105 | } else { | ||
| 106 | result += format!(", {}", transpile_helper(argument)?).as_str(); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | result += ")"; | ||
| 111 | |||
| 112 | result | ||
| 113 | } | ||
| 114 | Ast::Block(block) => { | ||
| 115 | let mut result = String::from("(()=>{\n"); | ||
| 116 | for (i, statement) in block.value.iter().enumerate() { | ||
| 117 | if block.implicit_return && i == block.value.len() - 1 { | ||
| 118 | result += format!("return {};\n", transpile_helper(statement)?).as_str(); | ||
| 119 | break; | ||
| 120 | } | ||
| 121 | result += transpile_helper(statement)?.as_str(); | ||
| 122 | } | ||
| 123 | |||
| 124 | result += "})()\n"; | ||
| 125 | |||
| 126 | result | ||
| 127 | } | ||
| 128 | }) | ||
| 129 | } | ||
| 130 | |||
| 131 | pub fn transpile(forest: Vec<Ast>) -> Result<String> { | ||
| 132 | let mut result = String::new(); | 9 | let mut result = String::new(); |
| 133 | 10 | ||
| 134 | for tree in forest { | 11 | for tree in forest { |
| 135 | result.push_str(&transpile_helper(&tree)?); | 12 | result.push_str(&print_statement(&tree)); |
| 136 | } | 13 | } |
| 137 | 14 | ||
| 138 | Ok(result) | 15 | result |
| 139 | } | 16 | } |
crates/js_printer/src/print_expression.rs created+261| ... | @@ -0,0 +1,261 @@ | ||
| 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::Number(value) => format!("{}", value), | ||
| 8 | JsExpression::BigInt(value) => format!("{}n", value), | ||
| 9 | JsExpression::String(value) => { | ||
| 10 | let mut result = String::new(); | ||
| 11 | // TODO: This is an INCREDIBLY naive implementation of escaping. | ||
| 12 | // Also it's probably not even correct. | ||
| 13 | for char in value.chars() { | ||
| 14 | match char { | ||
| 15 | '\n' => result.push_str("\\n"), | ||
| 16 | '\r' => result.push_str("\\r"), | ||
| 17 | '\t' => result.push_str("\\t"), | ||
| 18 | '"' => result.push_str("\\\""), | ||
| 19 | '\\' => result.push_str("\\\\"), | ||
| 20 | _ => result.push(char), | ||
| 21 | } | ||
| 22 | } | ||
| 23 | format!("\"{}\"", result) | ||
| 24 | } | ||
| 25 | JsExpression::Bool(value) => format!("{}", value), | ||
| 26 | JsExpression::Identifier(value) => format!("{}", value), | ||
| 27 | JsExpression::Array(value) => format!( | ||
| 28 | "[{}]", | ||
| 29 | value | ||
| 30 | .iter() | ||
| 31 | .map(|x| print_expression(x)) | ||
| 32 | .collect::<Vec<String>>() | ||
| 33 | .join(",") | ||
| 34 | ), | ||
| 35 | JsExpression::Object(value) => format!( | ||
| 36 | "{{{}}}", | ||
| 37 | value | ||
| 38 | .iter() | ||
| 39 | .map(|(k, v)| format!("\"{}\":{}", k, print_expression(v))) | ||
| 40 | .collect::<Vec<String>>() | ||
| 41 | .join(",") | ||
| 42 | ), | ||
| 43 | JsExpression::Operation { | ||
| 44 | operator, | ||
| 45 | left, | ||
| 46 | right, | ||
| 47 | } => format!( | ||
| 48 | "{}{}{}", | ||
| 49 | print_expression(left), | ||
| 50 | operator.clone().to_string(), | ||
| 51 | print_expression(right) | ||
| 52 | ), | ||
| 53 | JsExpression::Function { arguments, value } => format!( | ||
| 54 | "({})=>{{{}}}", | ||
| 55 | arguments.join(","), | ||
| 56 | value | ||
| 57 | .iter() | ||
| 58 | .map(|x| format!("{}", print_statement(x))) | ||
| 59 | .collect::<Vec<String>>() | ||
| 60 | .join("") | ||
| 61 | ), | ||
| 62 | JsExpression::FunctionCall { name, arguments } => format!( | ||
| 63 | "{}({})", | ||
| 64 | name, | ||
| 65 | arguments | ||
| 66 | .iter() | ||
| 67 | .map(|x| print_expression(x)) | ||
| 68 | .collect::<Vec<String>>() | ||
| 69 | .join(",") | ||
| 70 | ), | ||
| 71 | } | ||
| 72 | } | ||
| 73 | |||
| 74 | #[cfg(test)] | ||
| 75 | mod tests { | ||
| 76 | use crate::print_expression::print_expression; | ||
| 77 | use js_ast::JsExpression; | ||
| 78 | use js_ast::JsStatement; | ||
| 79 | use num::{bigint::Sign, BigInt}; | ||
| 80 | use std::collections::HashMap; | ||
| 81 | use util::Operator; | ||
| 82 | |||
| 83 | #[test] | ||
| 84 | fn number() { | ||
| 85 | assert_eq!(print_expression(&JsExpression::Number(1.0)), "1"); | ||
| 86 | assert_eq!(print_expression(&JsExpression::Number(1.5)), "1.5"); | ||
| 87 | assert_eq!( | ||
| 88 | print_expression(&JsExpression::Number(1.5e10)), | ||
| 89 | "15000000000" | ||
| 90 | ); | ||
| 91 | assert_eq!( | ||
| 92 | print_expression(&JsExpression::Number(1.5e-10)), | ||
| 93 | "0.00000000015" | ||
| 94 | ); | ||
| 95 | } | ||
| 96 | |||
| 97 | #[test] | ||
| 98 | fn bigint() { | ||
| 99 | assert_eq!( | ||
| 100 | print_expression(&JsExpression::BigInt(BigInt::new(Sign::Plus, vec![1]))), | ||
| 101 | "1n" | ||
| 102 | ); | ||
| 103 | assert_eq!( | ||
| 104 | print_expression(&JsExpression::BigInt(BigInt::new( | ||
| 105 | Sign::Plus, | ||
| 106 | vec![10000000] | ||
| 107 | ))), | ||
| 108 | "10000000n" | ||
| 109 | ); | ||
| 110 | assert_eq!( | ||
| 111 | print_expression(&JsExpression::BigInt(BigInt::new( | ||
| 112 | Sign::Minus, | ||
| 113 | vec![10000000] | ||
| 114 | ))), | ||
| 115 | "-10000000n" | ||
| 116 | ); | ||
| 117 | } | ||
| 118 | |||
| 119 | #[test] | ||
| 120 | fn string() { | ||
| 121 | assert_eq!( | ||
| 122 | print_expression(&JsExpression::String("".to_string())), | ||
| 123 | "\"\"" | ||
| 124 | ); | ||
| 125 | assert_eq!( | ||
| 126 | print_expression(&JsExpression::String("hello".to_string())), | ||
| 127 | "\"hello\"" | ||
| 128 | ); | ||
| 129 | assert_eq!( | ||
| 130 | print_expression(&JsExpression::String("hello\nworld".to_string())), | ||
| 131 | "\"hello\\nworld\"" | ||
| 132 | ); | ||
| 133 | } | ||
| 134 | |||
| 135 | #[test] | ||
| 136 | fn bool() { | ||
| 137 | assert_eq!(print_expression(&JsExpression::Bool(true)), "true"); | ||
| 138 | assert_eq!(print_expression(&JsExpression::Bool(false)), "false"); | ||
| 139 | } | ||
| 140 | |||
| 141 | #[test] | ||
| 142 | fn identifier() { | ||
| 143 | assert_eq!( | ||
| 144 | print_expression(&JsExpression::Identifier("hello".to_string())), | ||
| 145 | "hello" | ||
| 146 | ); | ||
| 147 | } | ||
| 148 | |||
| 149 | #[test] | ||
| 150 | fn array() { | ||
| 151 | assert_eq!(print_expression(&JsExpression::Array(vec![])), "[]"); | ||
| 152 | assert_eq!( | ||
| 153 | print_expression(&JsExpression::Array(vec![JsExpression::Number(1.0)])), | ||
| 154 | "[1]" | ||
| 155 | ); | ||
| 156 | assert_eq!( | ||
| 157 | print_expression(&JsExpression::Array(vec![ | ||
| 158 | JsExpression::Number(1.0), | ||
| 159 | JsExpression::Number(2.0) | ||
| 160 | ])), | ||
| 161 | "[1,2]" | ||
| 162 | ); | ||
| 163 | } | ||
| 164 | |||
| 165 | #[test] | ||
| 166 | fn object() { | ||
| 167 | assert_eq!( | ||
| 168 | print_expression(&JsExpression::Object(HashMap::new())), | ||
| 169 | "{}" | ||
| 170 | ); | ||
| 171 | let mut map = HashMap::new(); | ||
| 172 | map.insert("hello".to_string(), JsExpression::Number(1.0)); | ||
| 173 | assert_eq!( | ||
| 174 | print_expression(&JsExpression::Object(map)), | ||
| 175 | "{\"hello\":1}" | ||
| 176 | ); | ||
| 177 | } | ||
| 178 | |||
| 179 | #[test] | ||
| 180 | fn operation() { | ||
| 181 | assert_eq!( | ||
| 182 | print_expression(&JsExpression::Operation { | ||
| 183 | operator: Operator::Add, | ||
| 184 | left: Box::new(JsExpression::Number(1.0)), | ||
| 185 | right: Box::new(JsExpression::Number(2.0)), | ||
| 186 | }), | ||
| 187 | "1+2" | ||
| 188 | ); | ||
| 189 | assert_eq!( | ||
| 190 | print_expression(&JsExpression::Operation { | ||
| 191 | operator: Operator::Subtract, | ||
| 192 | left: Box::new(JsExpression::Number(1.0)), | ||
| 193 | right: Box::new(JsExpression::Number(2.0)), | ||
| 194 | }), | ||
| 195 | "1-2" | ||
| 196 | ); | ||
| 197 | } | ||
| 198 | |||
| 199 | #[test] | ||
| 200 | fn function() { | ||
| 201 | use crate::print_expression::print_expression; | ||
| 202 | use js_ast::JsExpression; | ||
| 203 | |||
| 204 | assert_eq!( | ||
| 205 | print_expression(&JsExpression::Function { | ||
| 206 | arguments: vec![], | ||
| 207 | value: vec![], | ||
| 208 | }), | ||
| 209 | "()=>{}" | ||
| 210 | ); | ||
| 211 | assert_eq!( | ||
| 212 | print_expression(&JsExpression::Function { | ||
| 213 | arguments: vec!["a".to_string()], | ||
| 214 | value: vec![], | ||
| 215 | }), | ||
| 216 | "(a)=>{}" | ||
| 217 | ); | ||
| 218 | assert_eq!( | ||
| 219 | print_expression(&JsExpression::Function { | ||
| 220 | arguments: vec!["a".to_string(), "b".to_string()], | ||
| 221 | value: vec![], | ||
| 222 | }), | ||
| 223 | "(a,b)=>{}" | ||
| 224 | ); | ||
| 225 | assert_eq!( | ||
| 226 | print_expression(&JsExpression::Function { | ||
| 227 | arguments: vec!["a".to_string(), "b".to_string()], | ||
| 228 | value: vec![JsStatement::VariableDeclaration { | ||
| 229 | name: "a".to_string(), | ||
| 230 | value: JsExpression::Number(1.0) | ||
| 231 | }], | ||
| 232 | }), | ||
| 233 | "(a,b)=>{let a=1;}" | ||
| 234 | ); | ||
| 235 | } | ||
| 236 | |||
| 237 | #[test] | ||
| 238 | fn function_call() { | ||
| 239 | assert_eq!( | ||
| 240 | print_expression(&JsExpression::FunctionCall { | ||
| 241 | name: "hello".to_string(), | ||
| 242 | arguments: vec![], | ||
| 243 | }), | ||
| 244 | "hello()" | ||
| 245 | ); | ||
| 246 | assert_eq!( | ||
| 247 | print_expression(&JsExpression::FunctionCall { | ||
| 248 | name: "hello".to_string(), | ||
| 249 | arguments: vec![JsExpression::Number(1.0)], | ||
| 250 | }), | ||
| 251 | "hello(1)" | ||
| 252 | ); | ||
| 253 | assert_eq!( | ||
| 254 | print_expression(&JsExpression::FunctionCall { | ||
| 255 | name: "hello".to_string(), | ||
| 256 | arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], | ||
| 257 | }), | ||
| 258 | "hello(1,2)" | ||
| 259 | ); | ||
| 260 | } | ||
| 261 | } | ||
crates/js_printer/src/print_statement.rs created+145| ... | @@ -0,0 +1,145 @@ | ||
| 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/tokenizer/src/lib.rs+11-3| ... | @@ -14,7 +14,7 @@ struct UnknownCharacter { | ... | @@ -14,7 +14,7 @@ struct UnknownCharacter { |
| 14 | pub enum Token { | 14 | pub enum Token { |
| 15 | // Literals | 15 | // Literals |
| 16 | Identifier(String), | 16 | Identifier(String), |
| 17 | Number(String), | 17 | Number(f64), |
| 18 | String(String), | 18 | String(String), |
| 19 | // Comments | 19 | // Comments |
| 20 | Comment(String), | 20 | Comment(String), |
| ... | @@ -112,7 +112,15 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -112,7 +112,15 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 112 | _ => break, | 112 | _ => break, |
| 113 | } | 113 | } |
| 114 | } | 114 | } |
| 115 | tokens.push(Token::Number(current_token)); | 115 | match current_token.parse::<f64>() { |
| 116 | Ok(v) => tokens.push(Token::Number(v)), | ||
| 117 | Err(_) => { | ||
| 118 | return Err(UnknownCharacter { | ||
| 119 | error: format!("Unknown character: {}", current_token), | ||
| 120 | } | ||
| 121 | .into()) | ||
| 122 | } | ||
| 123 | } | ||
| 116 | } | 124 | } |
| 117 | ':' => tokens.push(Token::Colon), | 125 | ':' => tokens.push(Token::Colon), |
| 118 | '=' => match chars.peek() { | 126 | '=' => match chars.peek() { |
| ... | @@ -130,7 +138,7 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -130,7 +138,7 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 130 | chars.next(); | 138 | chars.next(); |
| 131 | tokens.push(Token::RightArrow); | 139 | tokens.push(Token::RightArrow); |
| 132 | } | 140 | } |
| 133 | _ => tokens.push(Token::Operator(Operator::Substract)), | 141 | _ => tokens.push(Token::Operator(Operator::Subtract)), |
| 134 | }, | 142 | }, |
| 135 | '*' => match chars.peek() { | 143 | '*' => match chars.peek() { |
| 136 | Some((_, '*')) => { | 144 | Some((_, '*')) => { |
crates/util/src/lib.rs+3-3| ... | @@ -1,7 +1,7 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | #[derive(Debug, Clone)] | 1 | #[derive(Debug, Clone, PartialEq)] |
| 2 | pub enum Operator { | 2 | pub enum Operator { |
| 3 | Add, | 3 | Add, |
| 4 | Substract, | 4 | Subtract, |
| 5 | Multiply, | 5 | Multiply, |
| 6 | Divide, | 6 | Divide, |
| 7 | Power, | 7 | Power, |
| ... | @@ -13,7 +13,7 @@ impl Operator { | ... | @@ -13,7 +13,7 @@ impl Operator { |
| 13 | pub fn to_string(self) -> String { | 13 | pub fn to_string(self) -> String { |
| 14 | String::from(match self { | 14 | String::from(match self { |
| 15 | Self::Add => "+", | 15 | Self::Add => "+", |
| 16 | Self::Substract => "-", | 16 | Self::Subtract => "-", |
| 17 | Self::Divide => "/", | 17 | Self::Divide => "/", |
| 18 | Self::Multiply => "*", | 18 | Self::Multiply => "*", |
| 19 | Self::Power => "**", | 19 | Self::Power => "**", |
examples/basics/fib.blight+5-5| ... | @@ -1,11 +1,11 @@ | ... | @@ -1,11 +1,11 @@ |
| 1 | pub fn fib(n: int) -> int { | 1 | pub fn fib(n: int) -> int { |
| 2 | if n == 0 { | 2 | if n == 0 { |
| 3 | return 0; | 3 | 0 |
| 4 | } else if n == 1 { | ||
| 5 | 1 | ||
| 6 | } else { | ||
| 7 | fib(n - 1) + fib(n - 2) | ||
| 4 | } | 8 | } |
| 5 | if n == 1 { | ||
| 6 | return 1; | ||
| 7 | } | ||
| 8 | fib(n - 1) + fib(n - 2) | ||
| 9 | } | 9 | } |
| 10 | 10 | ||
| 11 | print(fib(10)); | 11 | print(fib(10)); |
examples/basics/shadowing.blight created+12| ... | @@ -0,0 +1,12 @@ | ||
| 1 | let x: int = 0; | ||
| 2 | let x: int = 1; | ||
| 3 | print(x); // 1 | ||
| 4 | |||
| 5 | let y: int = 0; | ||
| 6 | { | ||
| 7 | let y: int = 1; | ||
| 8 | { | ||
| 9 | let y: int = 2; | ||
| 10 | } | ||
| 11 | } | ||
| 12 | print(y); // 0 | ||
examples/basics/weird.blight created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | // Just trying to break the blight compiler | ||
| 2 | fn bingus(y: bool) -> int { | ||
| 3 | let x: int = 5 + { | ||
| 4 | if y { | ||
| 5 | return 100000; | ||
| 6 | } | ||
| 7 | 6 | ||
| 8 | }; | ||
| 9 | x | ||
| 10 | } | ||
| 11 | |||
| 12 | print(bingus(true)); | ||
| 13 | print(bingus(false)); | ||