diff --git a/Cargo.lock b/Cargo.lock index 431bca49c99604d1ff65f499ed5cd500516225f8..426f40ae047164d1a7ae0edd8328a6bcc3bc0da0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -75,6 +75,12 @@ dependencies = [ "util", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "backtrace" version = "0.3.69" @@ -166,6 +172,7 @@ version = "0.1.0" dependencies = [ "ast", "clap", + "js_ast", "js_printer", "miette", "thiserror", @@ -227,17 +234,22 @@ checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" name = "js_ast" version = "0.1.0" dependencies = [ + "ast", "miette", + "num", "thiserror", + "util", ] [[package]] name = "js_printer" version = "0.1.0" dependencies = [ - "ast", + "js_ast", "miette", + "num", "thiserror", + "util", ] [[package]] @@ -299,6 +311,82 @@ dependencies = [ "adler", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.32.2" diff --git a/Cargo.toml b/Cargo.toml index d356ed10d06269bec3fb99d25e5382e14ac1b2c9..e0aaabdca72cfe022aef071763d33f4ee6b7997c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,3 +14,4 @@ members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crate clap = { version = "4.4.12", features = ["derive"] } miette = { version = "5.10.0", features = ["fancy"] } thiserror = "1.0.53" +num = "0.4.1" diff --git a/crates/ast/src/parse_expression.rs b/crates/ast/src/parse_expression.rs index 022a870acabd00c2031b97d4fbfe8a35a51f0709..1b4467820c608c13daa9a062466b0e487fcf1e75 100644 --- a/crates/ast/src/parse_expression.rs +++ b/crates/ast/src/parse_expression.rs @@ -4,12 +4,12 @@ use tokenizer::Token; use super::error::AstError; use super::parse_block::parse_block; -use super::types::{Ast, ParserContext}; +use super::types::{Ast, Block, ParserContext}; pub fn parse_expression(tokens: &mut ParserContext) -> Result { let left = match tokens.next() { Some(token) => match token { - Token::Number(number) => Ast::Number(number.clone()), + Token::Number(number) => Ast::Number(number), Token::String(string) => Ast::String(string.clone()), Token::True => Ast::Bool(true), Token::False => Ast::Bool(false), @@ -24,17 +24,21 @@ pub fn parse_expression(tokens: &mut ParserContext) -> Result { let value = parse_block(tokens)?; - let mut else_value = None; + let mut else_value: Option = None; match tokens.peek() { Some(Token::Else) => { tokens.next(); match tokens.peek() { - Option::Some(Token::If) => { - else_value = Some(Box::new(parse_expression(tokens)?)); + Some(Token::If) => { + let else_expression = parse_expression(tokens)?; + else_value = Some(Block { + value: vec![else_expression], + implicit_return: true, + }); } - Option::Some(Token::OpenBracket) => { - else_value = Some(Box::new(Ast::Block(parse_block(tokens)?))); + Some(Token::OpenBracket) => { + else_value = Some(parse_block(tokens)?); } _ => { return Err(AstError::new( diff --git a/crates/ast/src/parse_statement.rs b/crates/ast/src/parse_statement.rs index c7b78e92a5babc909209de498c7e1596016547e4..8cd4b63e89df1f9b19a88a30920aaac7aecba055 100644 --- a/crates/ast/src/parse_statement.rs +++ b/crates/ast/src/parse_statement.rs @@ -6,12 +6,17 @@ use super::error::AstError; use super::parse_block::parse_block; use super::parse_expression::parse_expression; use super::parse_type::parse_type; -use super::types::{Argument, Ast, ParserContext}; +use super::types::{Argument, Ast, Block, ParserContext}; /// Precondition: There is at least one token in the iterator pub fn parse_statement(tokens: &mut ParserContext) -> Result { let token = tokens.next().unwrap(); match token { + Token::OpenBracket => { + // FRICK, we have to backtrack + tokens.back(); + Ok(Ast::Block(parse_block(tokens)?)) + } Token::Pub => { match tokens.peek() { Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (), @@ -20,9 +25,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result { let inner = parse_statement(tokens)?; - return Ok(Ast::Public { - value: Box::new(inner), - }); + return Ok(Ast::Public(Box::new(inner))); } Token::Let => { let name = match tokens.next() { @@ -86,9 +89,7 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result { _ => return Err(AstError::new("Expected semicolon after return statement").into()), }; - Ok(Ast::Return { - return_value: Box::new(expression), - }) + Ok(Ast::Return(Box::new(expression))) } Token::Function => { let name = match tokens.next() { @@ -179,17 +180,21 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result { let value = parse_block(tokens)?; - let mut else_value = None; + let mut else_value: Option = None; match tokens.peek() { Some(Token::Else) => { tokens.next(); match tokens.peek() { - Option::Some(Token::If) => { - else_value = Some(Box::new(parse_expression(tokens)?)); + Some(Token::If) => { + let else_if = parse_statement(tokens)?; + else_value = Some(Block { + value: vec![else_if], + implicit_return: false, + }); } - Option::Some(Token::OpenBracket) => { - else_value = Some(Box::new(Ast::Block(parse_block(tokens)?))); + Some(Token::OpenBracket) => { + else_value = Some(parse_block(tokens)?); } _ => { return Err(AstError::new( diff --git a/crates/ast/src/types.rs b/crates/ast/src/types.rs index d6fb9048e1bb0234378cec9be32298379699f2dd..9551f067516b0faf8c8333e6aa61bf56ca379ed4 100644 --- a/crates/ast/src/types.rs +++ b/crates/ast/src/types.rs @@ -64,9 +64,6 @@ pub struct Argument { #[derive(Debug)] pub enum Ast { - Public { - value: Box, - }, ConstantDeclaration { name: String, return_type: Type, @@ -82,9 +79,6 @@ pub enum Ast { left: Box, right: Box, }, - Return { - return_value: Box, - }, Function { name: String, arguments: Vec, @@ -98,10 +92,12 @@ pub enum Ast { If { condition: Box, value: Block, - else_value: Option>, + else_value: Option, }, + Return(Box), + Public(Box), Block(Block), - Number(String), + Number(f64), Bool(bool), String(String), Identifier(String), diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 081c5f730700279570c4bfab7384d165da019b61..0f6618aef8ab0f73dfad007939f2c13f827f0fa5 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,4 +14,5 @@ thiserror.workspace = true tokenizer = { path = "../tokenizer" } ast = { path = "../ast" } +js_ast = { path = "../js_ast" } js_printer = { path = "../js_printer" } diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 35a97b220d8865da2dfa990e4b0c2cb200fe0a5f..7385f4c91b35d1d32c21ca4c9923576b76b40869 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -3,9 +3,9 @@ use std::path::PathBuf; use clap::{Parser, Subcommand}; use miette::Result; -use tokenizer; use ast; use js_printer; +use tokenizer; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -42,7 +42,6 @@ fn get_runtime(runtime: &String) -> String { } } - pub fn transpile(content: String) -> Result { // 1. Tokenize the input let tokens = tokenizer::tokenize(content)?; @@ -53,14 +52,13 @@ pub fn transpile(content: String) -> Result { // 4. Optimize the AST // optimize(tree.clone())?; // 5. Translate the AST into javascript - // translate(tree.clone())?; + let js_ast = js_ast::translate(blight_ast)?; // 6. Print the JS AST - let javascript = js_printer::transpile(blight_ast)?; + let javascript = js_printer::print(js_ast); // 7. Return the javascript return Ok(javascript); } - fn main() -> Result<()> { let cli = Cli::parse(); diff --git a/crates/js_ast/Cargo.toml b/crates/js_ast/Cargo.toml index c7f8ec140f6dcdd2810a7842a509e7fae4214bcd..ae2bc9036ff37259b6f1bbd79effdf1c8e293511 100644 --- a/crates/js_ast/Cargo.toml +++ b/crates/js_ast/Cargo.toml @@ -8,3 +8,7 @@ edition = "2021" [dependencies] miette.workspace = true thiserror.workspace = true +num.workspace = true + +ast = { path = "../ast" } +util = { path = "../util" } diff --git a/crates/js_ast/src/lib.rs b/crates/js_ast/src/lib.rs index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..56e87fae25b384bb4d4ced8aadd487ff87013804 100644 --- a/crates/js_ast/src/lib.rs +++ b/crates/js_ast/src/lib.rs @@ -0,0 +1,406 @@ +use std::collections::HashMap; + +use miette::Result; + +use ast::Ast; + +mod types; +pub use types::{JsExpression, JsStatement}; + +struct StackContext { + statements: Vec, + /// Variables that are declared in this context. + /// The number is the number of times the variable has been declared. + /// This is used to allow for shadowing + variable_map: HashMap, + block: u32, +} + +impl StackContext { + fn new(block: u32) -> Self { + StackContext { + statements: Vec::new(), + variable_map: HashMap::new(), + block, + } + } +} + +struct TranslateContext { + context: Vec, + internal_counter: u32, + block_counter: u32, +} + +impl TranslateContext { + fn new() -> Self { + TranslateContext { + context: vec![StackContext::new(0)], + internal_counter: 0, + block_counter: 0, + } + } + + fn push_context(self: &mut Self) { + self.block_counter += 1; + self.context.push(StackContext::new(self.block_counter)); + } + + fn pop_context(self: &mut Self) -> Vec { + self.context.pop().unwrap().statements + } + + fn push_internal_variable(self: &mut Self, value: JsExpression) -> JsExpression { + self.context + .last_mut() + .unwrap() + .statements + .push(JsStatement::VariableDeclaration { + name: format!("${}", self.internal_counter), + value, + }); + self.internal_counter += 1; + JsExpression::Identifier(format!("${}", self.internal_counter - 1)) + } + + fn push(self: &mut Self, statement: JsStatement) { + self.context.last_mut().unwrap().statements.push(statement); + } + + fn peek(self: &Self) -> &JsStatement { + self.context.last().unwrap().statements.last().unwrap() + } + + fn declare_variable(self: &mut Self, name: String) -> String { + let context = self.context.last_mut().unwrap(); + if let Some(value) = context.variable_map.get_mut(name.as_str()) { + *value += 1; + format!("{}${}${}", name, value, context.block) + } else { + context.variable_map.insert(name.to_string(), 0); + format!("{}$0${}", name, context.block) + } + } + + fn get_identifier(self: &Self, name: String) -> String { + for context in self.context.iter().rev() { + if let Some(value) = context.variable_map.get(name.as_str()) { + return format!("{}${}${}", name, value, context.block); + } + } + panic!("Variable {} not found", name); + } +} + +// basically, expressions are really hard to translate +// in blight, everything is an expression +// in js, very few things are expressions +// here, we recursively expand out expressions into statements +// we then store the result of all the expressions in a variable +// and then return the name of that variable +fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression { + match ast { + Ast::Number(value) => JsExpression::Number(*value), + Ast::String(value) => JsExpression::String(value.clone()), + Ast::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), + Ast::Bool(value) => JsExpression::Bool(*value), + Ast::FunctionCall { name, arguments } => { + let mut handles = Vec::new(); + for argument in arguments { + handles.push(get_expression_handle(argument, ctx)); + } + ctx.push_internal_variable(JsExpression::FunctionCall { + name: ctx.get_identifier(name.clone()), + arguments: handles, + }) + } + Ast::Operation { + operator, + left, + right, + } => { + let left_handle = get_expression_handle(left, ctx); + let right_handle = get_expression_handle(right, ctx); + JsExpression::Operation { + operator: operator.clone(), + left: Box::new(left_handle), + right: Box::new(right_handle), + } + } + Ast::Block(block) => { + if !block.implicit_return { + panic!("Expression blocks must have an implicit return"); + } + ctx.push_context(); + for (i, statement) in block.value.iter().enumerate() { + if i == block.value.len() - 1 { + break; + } else { + translate_statement(statement, ctx); + } + } + let result = get_expression_handle(block.value.last().unwrap(), ctx); + let value = ctx.pop_context(); + value.iter().for_each(|x| ctx.push(x.clone())); + result + } + _ => { + panic!("unimplemented: {:?}", ast); + } + } +} + +fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { + match ast { + Ast::ConstantDeclaration { + name, + return_type: _, + value, + } => { + let handle = get_expression_handle(value, ctx); + let rename = ctx.declare_variable(name.clone()); + ctx.push(JsStatement::ConstantDeclaration { + name: rename, + value: handle, + }); + } + Ast::VariableDeclaration { + name, + return_type: _, + value, + } => { + let handle = get_expression_handle(value, ctx); + let rename = ctx.declare_variable(name.clone()); + ctx.push(JsStatement::VariableDeclaration { + name: rename, + value: handle, + }); + } + Ast::Function { + name, + arguments, + return_type: _, + block, + } => { + let rename = ctx.declare_variable(name.clone()); + let arguments = arguments + .iter() + .map(|x| ctx.declare_variable(x.name.clone())) + .collect(); + + ctx.push_context(); + for (i, statement) in block.value.iter().enumerate() { + if block.implicit_return && i == block.value.len() - 1 { + let handle = get_expression_handle(statement, ctx); + ctx.push(JsStatement::Return(handle)); + } else { + translate_statement(statement, ctx); + } + } + let value = ctx.pop_context(); + + ctx.push(JsStatement::Function { + name: rename, + arguments, + value, + }); + } + Ast::Public(value) => { + translate_statement(value, ctx); + // I'm sorry, I just don't know how to get the original name properly... my bad + match ctx.peek() { + JsStatement::Function { name, .. } + | JsStatement::VariableDeclaration { name, .. } + | JsStatement::ConstantDeclaration { name, .. } => { + let original_name = name.split("$").next().unwrap().to_string(); + ctx.push(JsStatement::Export(Box::new( + JsStatement::ConstantDeclaration { + name: original_name, + value: JsExpression::Identifier(name.clone()), + }, + ))); + } + _ => { + panic!("Cannot make non-function public"); + } + } + } + Ast::Return(value) => { + let handle = get_expression_handle(value, ctx); + ctx.push(JsStatement::Return(handle)); + } + Ast::Block(block) => { + ctx.push_context(); + for (i, statement) in block.value.iter().enumerate() { + if block.implicit_return && i == block.value.len() - 1 { + let handle = get_expression_handle(statement, ctx); + ctx.push(JsStatement::Return(handle)); + } else { + translate_statement(statement, ctx); + } + } + let value = ctx.pop_context(); + value.iter().for_each(|x| ctx.push(x.clone())); + } + Ast::If { + condition, + value: block, + else_value, + } => { + let handle = get_expression_handle(condition, ctx); + + ctx.push_context(); + for (i, statement) in block.value.iter().enumerate() { + if block.implicit_return && i == block.value.len() - 1 { + let handle = get_expression_handle(statement, ctx); + ctx.push(JsStatement::Return(handle)); + } else { + translate_statement(statement, ctx); + } + } + let value = ctx.pop_context(); + let else_value = match else_value { + Some(block) => { + ctx.push_context(); + for (i, statement) in block.value.iter().enumerate() { + if block.implicit_return && i == block.value.len() - 1 { + let handle = get_expression_handle(statement, ctx); + ctx.push(JsStatement::Return(handle)); + } else { + translate_statement(statement, ctx); + } + } + Some(ctx.pop_context()) + } + None => None, + }; + + ctx.push(JsStatement::If { + condition: handle, + value, + else_value, + }); + } + Ast::FunctionCall { name, arguments } => { + let mut handles = Vec::new(); + for argument in arguments { + handles.push(get_expression_handle(argument, ctx)); + } + ctx.push(JsStatement::Expression(JsExpression::FunctionCall { + name: match name.as_str() { + "print" => "console.log".to_string(), + _ => name.clone(), + }, + arguments: handles, + })); + } + Ast::Comment(_) | Ast::DocComment(_) => {} + _ => { + panic!("unimplemented: {:?}", ast); + } + } +} + +pub fn translate(_ast: Vec) -> Result> { + let mut ctx = TranslateContext::new(); + + for ast in _ast { + translate_statement(&ast, &mut ctx); + } + + Ok(ctx.pop_context()) +} + +#[cfg(test)] +mod tests { + use crate::translate; + use crate::{JsExpression, JsStatement}; + use ast::{Ast, Type}; + use util::Operator; + + #[test] + fn test_translate() { + let ast = Ast::ConstantDeclaration { + name: "hi".to_string(), + return_type: Type::Int, + value: Box::new(Ast::Operation { + operator: Operator::Add, + left: Box::new(Ast::Operation { + operator: Operator::Add, + left: Box::new(Ast::Number(1.0)), + right: Box::new(Ast::Operation { + operator: Operator::Add, + left: Box::new(Ast::Number(2.0)), + right: Box::new(Ast::Number(3.0)), + }), + }), + right: Box::new(Ast::Operation { + operator: Operator::Add, + left: Box::new(Ast::Number(4.0)), + right: Box::new(Ast::Number(5.0)), + }), + }), + }; + assert_eq!( + translate(vec![ast]).unwrap(), + vec![ + JsStatement::ConstantDeclaration { + name: "$0".to_string(), + value: JsExpression::Number(1.0) + }, + JsStatement::ConstantDeclaration { + name: "$1".to_string(), + value: JsExpression::Number(2.0) + }, + JsStatement::ConstantDeclaration { + name: "$2".to_string(), + value: JsExpression::Number(3.0) + }, + JsStatement::ConstantDeclaration { + name: "$3".to_string(), + value: JsExpression::Operation { + operator: Operator::Add, + left: Box::new(JsExpression::Identifier("$1".to_string())), + right: Box::new(JsExpression::Identifier("$2".to_string())) + } + }, + JsStatement::ConstantDeclaration { + name: "$4".to_string(), + value: JsExpression::Operation { + operator: Operator::Add, + left: Box::new(JsExpression::Identifier("$0".to_string())), + right: Box::new(JsExpression::Identifier("$3".to_string())) + } + }, + JsStatement::ConstantDeclaration { + name: "$5".to_string(), + value: JsExpression::Number(4.0) + }, + JsStatement::ConstantDeclaration { + name: "$6".to_string(), + value: JsExpression::Number(5.0) + }, + JsStatement::ConstantDeclaration { + name: "$7".to_string(), + value: JsExpression::Operation { + operator: Operator::Add, + left: Box::new(JsExpression::Identifier("$5".to_string())), + right: Box::new(JsExpression::Identifier("$6".to_string())) + } + }, + JsStatement::ConstantDeclaration { + name: "$8".to_string(), + value: JsExpression::Operation { + operator: Operator::Add, + left: Box::new(JsExpression::Identifier("$4".to_string())), + right: Box::new(JsExpression::Identifier("$7".to_string())) + } + }, + JsStatement::ConstantDeclaration { + name: "hi".to_string(), + value: JsExpression::Identifier("$8".to_string()) + } + ] + ) + } +} diff --git a/crates/js_ast/src/types.rs b/crates/js_ast/src/types.rs new file mode 100644 index 0000000000000000000000000000000000000000..022641d491214b181b64f9af75bb54ae247661b3 --- /dev/null +++ b/crates/js_ast/src/types.rs @@ -0,0 +1,53 @@ +use num::bigint::BigInt; +use std::collections::HashMap; + +use util::Operator; + +#[derive(Debug, PartialEq, Clone)] +pub enum JsExpression { + Number(f64), + BigInt(BigInt), + String(String), + Bool(bool), + Identifier(String), + Array(Vec), + Object(HashMap), + Operation { + operator: Operator, + left: Box, + right: Box, + }, + Function { + arguments: Vec, + value: Vec, + }, + FunctionCall { + name: String, + arguments: Vec, + }, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum JsStatement { + Expression(JsExpression), + Export(Box), + Return(JsExpression), + VariableDeclaration { + name: String, + value: JsExpression, + }, + ConstantDeclaration { + name: String, + value: JsExpression, + }, + Function { + name: String, + arguments: Vec, + value: Vec, + }, + If { + condition: JsExpression, + value: Vec, + else_value: Option>, + }, +} diff --git a/crates/js_printer/Cargo.toml b/crates/js_printer/Cargo.toml index 6ba59bf96c3a398efd353cd18596d91d7eabcf4e..0071929ae0046ab381623d16ae23e397e7f36393 100644 --- a/crates/js_printer/Cargo.toml +++ b/crates/js_printer/Cargo.toml @@ -8,5 +8,7 @@ edition = "2021" [dependencies] miette.workspace = true thiserror.workspace = true +num.workspace = true -ast = { path = "../ast" } +js_ast = { path = "../js_ast" } +util = { path = "../util" } diff --git a/crates/js_printer/src/lib.rs b/crates/js_printer/src/lib.rs index 13a4bd6d2c11f2aa0f7e0d7627b8c58ffc3ace47..49f3aa65ab7579c2a6e9a4b0470a4bc256e51557 100644 --- a/crates/js_printer/src/lib.rs +++ b/crates/js_printer/src/lib.rs @@ -1,139 +1,16 @@ -use miette::Result; +mod print_expression; +mod print_statement; -use ast::{Ast, Type}; +use print_statement::print_statement; -fn transpile_helper(tree: &Ast) -> Result { - Ok(match tree { - Ast::Number(number) => format!("{}", number), - Ast::Identifier(name) => format!("{}", name), - Ast::Bool(value) => format!("{}", value), - Ast::String(value) => format!("\"{}\"", value), - Ast::Comment(comment) => format!("// {}\n", comment), - Ast::DocComment(comment) => format!("/** {} */\n", comment), - Ast::Operation { - operator, - left, - right, - } => format!( - "{} {} {}", - transpile_helper(left)?, - operator.clone().to_string(), - transpile_helper(right)? - ), - Ast::Public { value } => format!("export {}", transpile_helper(value)?), - Ast::VariableDeclaration { - name, - return_type, - value, - } => match return_type { - Type::Bigint => format!("let {} = BigInt({});\n", name, transpile_helper(value)?), - _ => format!("let {} = {};\n", name, transpile_helper(value)?), - }, - Ast::ConstantDeclaration { - name, - return_type, - value, - } => match return_type { - Type::Bigint => format!("const {} = BigInt({});\n", name, transpile_helper(value)?), - _ => format!("const {} = {};\n", name, transpile_helper(value)?), - }, - Ast::Return { return_value } => format!("return {};\n", transpile_helper(return_value)?), - Ast::Function { - name, - arguments, - return_type: _, - block, - } => { - let mut result = format!("function {}(", name); +use js_ast::JsStatement; - for (i, argument) in arguments.iter().enumerate() { - if i == 0 { - result += argument.name.as_str(); - } else { - result += format!(", {}", argument.name).as_str(); - } - } - - result += ") {\n"; - - for (i, statement) in block.value.iter().enumerate() { - if block.implicit_return && i == block.value.len() - 1 { - result += format!("return {};\n", transpile_helper(statement)?).as_str(); - break; - } - result += transpile_helper(statement)?.as_str(); - } - - result += "}\n"; - - result - } - Ast::If { - condition, - value, - else_value, - } => { - let mut result = format!("if ({}) {{\n", transpile_helper(condition)?,); - - for (i, statement) in value.value.iter().enumerate() { - if value.implicit_return && i == value.value.len() - 1 { - result += format!("return {};\n", transpile_helper(statement)?).as_str(); - break; - } - result += transpile_helper(statement)?.as_str(); - } - - result += "}"; - - if let Some(val) = else_value { - result += format!(" else {{\n{}}}", transpile_helper(val)?).as_str(); - } else { - result += "\n"; - } - - result - } - Ast::FunctionCall { name, arguments } => { - let mut result = match name.as_str() { - "print" => String::from("console.log("), - _ => format!("{}(", name), - }; - - for (i, argument) in arguments.iter().enumerate() { - if i == 0 { - result += transpile_helper(argument)?.as_str(); - } else { - result += format!(", {}", transpile_helper(argument)?).as_str(); - } - } - - result += ")"; - - result - } - Ast::Block(block) => { - let mut result = String::from("(()=>{\n"); - for (i, statement) in block.value.iter().enumerate() { - if block.implicit_return && i == block.value.len() - 1 { - result += format!("return {};\n", transpile_helper(statement)?).as_str(); - break; - } - result += transpile_helper(statement)?.as_str(); - } - - result += "})()\n"; - - result - } - }) -} - -pub fn transpile(forest: Vec) -> Result { +pub fn print(forest: Vec) -> String { let mut result = String::new(); for tree in forest { - result.push_str(&transpile_helper(&tree)?); + result.push_str(&print_statement(&tree)); } - Ok(result) + result } diff --git a/crates/js_printer/src/print_expression.rs b/crates/js_printer/src/print_expression.rs new file mode 100644 index 0000000000000000000000000000000000000000..be771126970a835788f3a7f92e65e83e4f84143b --- /dev/null +++ b/crates/js_printer/src/print_expression.rs @@ -0,0 +1,261 @@ +use js_ast::JsExpression; + +use crate::print_statement::print_statement; + +pub fn print_expression(statement: &JsExpression) -> String { + match statement { + JsExpression::Number(value) => format!("{}", value), + JsExpression::BigInt(value) => format!("{}n", value), + JsExpression::String(value) => { + let mut result = String::new(); + // TODO: This is an INCREDIBLY naive implementation of escaping. + // Also it's probably not even correct. + for char in value.chars() { + match char { + '\n' => result.push_str("\\n"), + '\r' => result.push_str("\\r"), + '\t' => result.push_str("\\t"), + '"' => result.push_str("\\\""), + '\\' => result.push_str("\\\\"), + _ => result.push(char), + } + } + format!("\"{}\"", result) + } + JsExpression::Bool(value) => format!("{}", value), + JsExpression::Identifier(value) => format!("{}", value), + JsExpression::Array(value) => format!( + "[{}]", + value + .iter() + .map(|x| print_expression(x)) + .collect::>() + .join(",") + ), + JsExpression::Object(value) => format!( + "{{{}}}", + value + .iter() + .map(|(k, v)| format!("\"{}\":{}", k, print_expression(v))) + .collect::>() + .join(",") + ), + JsExpression::Operation { + operator, + left, + right, + } => format!( + "{}{}{}", + print_expression(left), + operator.clone().to_string(), + print_expression(right) + ), + JsExpression::Function { arguments, value } => format!( + "({})=>{{{}}}", + arguments.join(","), + value + .iter() + .map(|x| format!("{}", print_statement(x))) + .collect::>() + .join("") + ), + JsExpression::FunctionCall { name, arguments } => format!( + "{}({})", + name, + arguments + .iter() + .map(|x| print_expression(x)) + .collect::>() + .join(",") + ), + } +} + +#[cfg(test)] +mod tests { + use crate::print_expression::print_expression; + use js_ast::JsExpression; + use js_ast::JsStatement; + use num::{bigint::Sign, BigInt}; + use std::collections::HashMap; + use util::Operator; + + #[test] + fn number() { + assert_eq!(print_expression(&JsExpression::Number(1.0)), "1"); + assert_eq!(print_expression(&JsExpression::Number(1.5)), "1.5"); + assert_eq!( + print_expression(&JsExpression::Number(1.5e10)), + "15000000000" + ); + assert_eq!( + print_expression(&JsExpression::Number(1.5e-10)), + "0.00000000015" + ); + } + + #[test] + fn bigint() { + assert_eq!( + print_expression(&JsExpression::BigInt(BigInt::new(Sign::Plus, vec![1]))), + "1n" + ); + assert_eq!( + print_expression(&JsExpression::BigInt(BigInt::new( + Sign::Plus, + vec![10000000] + ))), + "10000000n" + ); + assert_eq!( + print_expression(&JsExpression::BigInt(BigInt::new( + Sign::Minus, + vec![10000000] + ))), + "-10000000n" + ); + } + + #[test] + fn string() { + assert_eq!( + print_expression(&JsExpression::String("".to_string())), + "\"\"" + ); + assert_eq!( + print_expression(&JsExpression::String("hello".to_string())), + "\"hello\"" + ); + assert_eq!( + print_expression(&JsExpression::String("hello\nworld".to_string())), + "\"hello\\nworld\"" + ); + } + + #[test] + fn bool() { + assert_eq!(print_expression(&JsExpression::Bool(true)), "true"); + assert_eq!(print_expression(&JsExpression::Bool(false)), "false"); + } + + #[test] + fn identifier() { + assert_eq!( + print_expression(&JsExpression::Identifier("hello".to_string())), + "hello" + ); + } + + #[test] + fn array() { + assert_eq!(print_expression(&JsExpression::Array(vec![])), "[]"); + assert_eq!( + print_expression(&JsExpression::Array(vec![JsExpression::Number(1.0)])), + "[1]" + ); + assert_eq!( + print_expression(&JsExpression::Array(vec![ + JsExpression::Number(1.0), + JsExpression::Number(2.0) + ])), + "[1,2]" + ); + } + + #[test] + fn object() { + assert_eq!( + print_expression(&JsExpression::Object(HashMap::new())), + "{}" + ); + let mut map = HashMap::new(); + map.insert("hello".to_string(), JsExpression::Number(1.0)); + assert_eq!( + print_expression(&JsExpression::Object(map)), + "{\"hello\":1}" + ); + } + + #[test] + fn operation() { + assert_eq!( + print_expression(&JsExpression::Operation { + operator: Operator::Add, + left: Box::new(JsExpression::Number(1.0)), + right: Box::new(JsExpression::Number(2.0)), + }), + "1+2" + ); + assert_eq!( + print_expression(&JsExpression::Operation { + operator: Operator::Subtract, + left: Box::new(JsExpression::Number(1.0)), + right: Box::new(JsExpression::Number(2.0)), + }), + "1-2" + ); + } + + #[test] + fn function() { + use crate::print_expression::print_expression; + use js_ast::JsExpression; + + assert_eq!( + print_expression(&JsExpression::Function { + arguments: vec![], + value: vec![], + }), + "()=>{}" + ); + assert_eq!( + print_expression(&JsExpression::Function { + arguments: vec!["a".to_string()], + value: vec![], + }), + "(a)=>{}" + ); + assert_eq!( + print_expression(&JsExpression::Function { + arguments: vec!["a".to_string(), "b".to_string()], + value: vec![], + }), + "(a,b)=>{}" + ); + assert_eq!( + print_expression(&JsExpression::Function { + arguments: vec!["a".to_string(), "b".to_string()], + value: vec![JsStatement::VariableDeclaration { + name: "a".to_string(), + value: JsExpression::Number(1.0) + }], + }), + "(a,b)=>{let a=1;}" + ); + } + + #[test] + fn function_call() { + assert_eq!( + print_expression(&JsExpression::FunctionCall { + name: "hello".to_string(), + arguments: vec![], + }), + "hello()" + ); + assert_eq!( + print_expression(&JsExpression::FunctionCall { + name: "hello".to_string(), + arguments: vec![JsExpression::Number(1.0)], + }), + "hello(1)" + ); + assert_eq!( + print_expression(&JsExpression::FunctionCall { + name: "hello".to_string(), + arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], + }), + "hello(1,2)" + ); + } +} diff --git a/crates/js_printer/src/print_statement.rs b/crates/js_printer/src/print_statement.rs new file mode 100644 index 0000000000000000000000000000000000000000..4a9bf75b17a5f0fde0844473aad91c65dbb51b92 --- /dev/null +++ b/crates/js_printer/src/print_statement.rs @@ -0,0 +1,145 @@ +use js_ast::JsStatement; + +use crate::print_expression::print_expression; + +pub fn print_statement(statement: &JsStatement) -> String { + match statement { + JsStatement::Expression(value) => format!("{};", print_expression(value)), + JsStatement::Export(value) => format!("export {}", print_statement(value)), + JsStatement::Return(value) => format!("return {};", print_expression(value)), + JsStatement::VariableDeclaration { name, value } => { + format!("let {}={};", name, print_expression(value)) + } + JsStatement::ConstantDeclaration { name, value } => { + format!("const {}={};", name, print_expression(value)) + } + JsStatement::Function { + name, + arguments, + value, + } => format!( + "function {}({}){{{}}}", + name, + arguments.join(","), + value + .iter() + .map(|x| format!("{}", print_statement(x))) + .collect::>() + .join("") + ), + JsStatement::If { + condition, + value, + else_value, + } => { + let else_value = match else_value { + Some(value) => format!( + "else{{{}}}", + value + .iter() + .map(|x| format!("{}", print_statement(x))) + .collect::>() + .join("") + ), + None => "".to_string(), + }; + format!( + "if({}){{{}}}{}", + print_expression(condition), + value + .iter() + .map(|x| format!("{}", print_statement(x))) + .collect::>() + .join(""), + else_value + ) + } + } +} + +#[cfg(test)] +mod tests { + use crate::print_statement::print_statement; + use js_ast::JsExpression; + use js_ast::JsStatement; + + #[test] + fn variable_declaration() { + assert_eq!( + print_statement(&JsStatement::VariableDeclaration { + name: "x".to_string(), + value: JsExpression::Number(1.0), + }), + "let x=1;" + ); + } + + #[test] + fn constant_declaration() { + assert_eq!( + print_statement(&JsStatement::ConstantDeclaration { + name: "x".to_string(), + value: JsExpression::Number(1.0), + }), + "const x=1;" + ); + } + + #[test] + fn function_declaration() { + assert_eq!( + print_statement(&JsStatement::Function { + name: "x".to_string(), + arguments: vec!["y".to_string()], + value: vec![JsStatement::Expression(JsExpression::Number(1.0))], + }), + "function x(y){1;}" + ); + } + + #[test] + fn expression() { + assert_eq!( + print_statement(&JsStatement::Expression(JsExpression::Number(1.0))), + "1;" + ); + } + + #[test] + fn export() { + assert_eq!( + print_statement(&JsStatement::Export(Box::new(JsStatement::Expression( + JsExpression::Number(1.0) + )))), + "export 1;" + ); + } + + #[test] + fn return_statement() { + assert_eq!( + print_statement(&JsStatement::Return(JsExpression::Number(1.0))), + "return 1;" + ); + } + + #[test] + fn if_statement() { + assert_eq!( + print_statement(&JsStatement::If { + condition: JsExpression::Number(1.0), + value: vec![JsStatement::Expression(JsExpression::Number(2.0))], + else_value: None, + }), + "if(1){2;}" + ); + assert_eq!( + print_statement(&JsStatement::If { + condition: JsExpression::Number(1.0), + value: vec![JsStatement::Expression(JsExpression::Number(2.0))], + else_value: Some(vec![JsStatement::Expression(JsExpression::Number(3.0))]), + }), + "if(1){2;}else{3;}" + ); + } +} diff --git a/crates/tokenizer/src/lib.rs b/crates/tokenizer/src/lib.rs index 85b4a6fe3443285dd78fe062f7ac5575a7057611..5dc2c9f62e6a9beb435edeb98dfe5d911f7eddfd 100644 --- a/crates/tokenizer/src/lib.rs +++ b/crates/tokenizer/src/lib.rs @@ -14,7 +14,7 @@ struct UnknownCharacter { pub enum Token { // Literals Identifier(String), - Number(String), + Number(f64), String(String), // Comments Comment(String), @@ -112,7 +112,15 @@ pub fn tokenize(content: String) -> Result> { _ => break, } } - tokens.push(Token::Number(current_token)); + match current_token.parse::() { + Ok(v) => tokens.push(Token::Number(v)), + Err(_) => { + return Err(UnknownCharacter { + error: format!("Unknown character: {}", current_token), + } + .into()) + } + } } ':' => tokens.push(Token::Colon), '=' => match chars.peek() { @@ -130,7 +138,7 @@ pub fn tokenize(content: String) -> Result> { chars.next(); tokens.push(Token::RightArrow); } - _ => tokens.push(Token::Operator(Operator::Substract)), + _ => tokens.push(Token::Operator(Operator::Subtract)), }, '*' => match chars.peek() { Some((_, '*')) => { diff --git a/crates/util/src/lib.rs b/crates/util/src/lib.rs index 25de3ac1c7c30d6191678c51bb23193e298bd284..28b0b425b3367c040ceb35a390723518c2c1e8b6 100644 --- a/crates/util/src/lib.rs +++ b/crates/util/src/lib.rs @@ -1,7 +1,7 @@ -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum Operator { Add, - Substract, + Subtract, Multiply, Divide, Power, @@ -13,7 +13,7 @@ impl Operator { pub fn to_string(self) -> String { String::from(match self { Self::Add => "+", - Self::Substract => "-", + Self::Subtract => "-", Self::Divide => "/", Self::Multiply => "*", Self::Power => "**", diff --git a/examples/basics/fib.blight b/examples/basics/fib.blight index 2ab4f7adcf7235373787e76b82fd178d15f3e2d6..95762dfe0f74f8409b8371ff4a3d6c4e92d625a1 100644 --- a/examples/basics/fib.blight +++ b/examples/basics/fib.blight @@ -1,11 +1,11 @@ pub fn fib(n: int) -> int { if n == 0 { - return 0; + 0 + } else if n == 1 { + 1 + } else { + fib(n - 1) + fib(n - 2) } - if n == 1 { - return 1; - } - fib(n - 1) + fib(n - 2) } print(fib(10)); diff --git a/examples/basics/shadowing.blight b/examples/basics/shadowing.blight new file mode 100644 index 0000000000000000000000000000000000000000..7e2b4c6228d78014922200d4b84b41bc477b1b7d --- /dev/null +++ b/examples/basics/shadowing.blight @@ -0,0 +1,12 @@ +let x: int = 0; +let x: int = 1; +print(x); // 1 + +let y: int = 0; +{ + let y: int = 1; + { + let y: int = 2; + } +} +print(y); // 0 diff --git a/examples/basics/weird.blight b/examples/basics/weird.blight new file mode 100644 index 0000000000000000000000000000000000000000..9a78b1625bca53e3c2fe224a5c3efe7fe07e2fac --- /dev/null +++ b/examples/basics/weird.blight @@ -0,0 +1,13 @@ +// Just trying to break the blight compiler +fn bingus(y: bool) -> int { + let x: int = 5 + { + if y { + return 100000; + } + 6 + }; + x +} + +print(bingus(true)); +print(bingus(false));