From b9cad6ad285b99192b686be1cea8b1b8cfcdd6c1 Mon Sep 17 00:00:00 2001 From: chloe caruso Date: Sat, 20 Jan 2024 21:15:12 -0800 Subject: [PATCH] it compiles again --- Cargo.lock | 1 + crates/ast/ast.rs | 57 ++- crates/js_ast/Cargo.toml | 2 + crates/js_ast/js_ast.rs | 273 ++++++++++++++ crates/js_ast/lib.rs | 411 ++++++++++++++++++++++ crates/js_ast/src/lib.rs | 333 ------------------ crates/js_ast/src/types.rs | 50 --- crates/js_printer/src/print_expression.rs | 175 ++++----- crates/js_printer/src/print_statement.rs | 41 ++- crates/parser/parse_expr_if.rs | 19 +- crates/tokenizer/src/lib.rs | 10 +- examples/basics/comments.blight | 11 +- 12 files changed, 886 insertions(+), 497 deletions(-) create mode 100644 crates/js_ast/js_ast.rs create mode 100644 crates/js_ast/lib.rs delete mode 100644 crates/js_ast/src/lib.rs delete mode 100644 crates/js_ast/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 4c8ddfacf5557906451853c5edb4394a656e2af5..03588a003888ec3e8fa82a3bacbbed69357e14c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -240,6 +240,7 @@ dependencies = [ "miette", "num", "thiserror", + "tokenizer", ] [[package]] diff --git a/crates/ast/ast.rs b/crates/ast/ast.rs index bf9cd16e386e9321b7bd123de01cb21d622daa9a..123a54fe5567f5ce9fab3273f1c98753a84ab2ac 100644 --- a/crates/ast/ast.rs +++ b/crates/ast/ast.rs @@ -59,7 +59,7 @@ pub struct DocComment { pub loc: Loc, } -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct Comment { pub data: String, } @@ -98,7 +98,7 @@ pub struct BlockWithLoc { pub struct If { pub condition: Box, pub yes: BlockWithLoc, - pub no: Option, + pub no: Option>, } #[derive(Debug, Clone)] @@ -166,6 +166,52 @@ impl BuiltinId { } } +impl ExprData { + pub fn is_valid_js_expression(&self) -> bool { + match self { + // A block containing just expressions can be an expression + ExprData::Block(block) => block.is_valid_js_expression(), + ExprData::If(expr) => expr.is_valid_js_expression(), + ExprData::Return(_) => false, + ExprData::Builtin { id, .. } => match id { + BuiltinId::Raw => true, + }, + _ => true, + } + } +} + +impl Block { + pub fn is_valid_js_expression(&self) -> bool { + self.statements + .iter() + .all(|statement| statement.is_valid_js_expression()) + } +} + +impl Expr { + pub fn is_valid_js_expression(&self) -> bool { + self.data.is_valid_js_expression() + } +} + +impl If { + pub fn is_valid_js_expression(&self) -> bool { + self.condition.is_valid_js_expression() + && self.yes.is_valid_js_expression() + && match &self.no { + Some(no) => no.is_valid_js_expression(), + None => true, + } + } +} + +impl BlockWithLoc { + pub fn is_valid_js_expression(&self) -> bool { + self.block.is_valid_js_expression() + } +} + impl Statement { pub fn requires_semicolon(&self) -> bool { match &self { @@ -178,6 +224,13 @@ impl Statement { } } + pub fn is_valid_js_expression(&self) -> bool { + match self { + Statement::Expr(expr) => expr.data.is_valid_js_expression(), + _ => false, + } + } + pub fn to_top_level(self) -> TopLevelStatement { match self { Statement::Expr(expr) => TopLevelStatement::Expr(expr), diff --git a/crates/js_ast/Cargo.toml b/crates/js_ast/Cargo.toml index dbbaf2f2c722dce465082656b9ac56a078b9730e..f7b5438968013029fa069bd2d9040847c5454392 100644 --- a/crates/js_ast/Cargo.toml +++ b/crates/js_ast/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [lib] +path = "./lib.rs" [dependencies] miette.workspace = true @@ -11,3 +12,4 @@ thiserror.workspace = true num.workspace = true ast = { path = "../ast" } +tokenizer = { path = "../tokenizer" } # TODO: move Loc out of the tokenizer diff --git a/crates/js_ast/js_ast.rs b/crates/js_ast/js_ast.rs new file mode 100644 index 0000000000000000000000000000000000000000..bc68f6e919939807b35f6a473819937156393548 --- /dev/null +++ b/crates/js_ast/js_ast.rs @@ -0,0 +1,273 @@ +use ast::{BinaryOperator, BlockWithLoc, BuiltinId, Comment, Expr, UnaryOperator}; +use num::bigint::BigInt; +use tokenizer::Loc; + +#[derive(Debug, PartialEq, Clone)] +pub struct JsExpr { + pub data: JsExprData, + pub loc: Loc, + pub comment: Option, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct JsKV { + pub key: JsExpr, + pub value: JsExpr, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum JsExprData { + /// node emitted by the '@raw' building + Raw(String), + // Litearls + Number(f64), + BigInt(BigInt), + String(String), + Bool(bool), + Array(Vec), + Object(Vec), + Null, + Undefined, + + Identifier(String), + Unary { + operator: UnaryOperator, + value: Box, + }, + Binary { + operator: BinaryOperator, + left: Box, + right: Box, + }, + Dot { + base: Box, + name: String, + }, + Index { + base: Box, + index: Box, + }, + Call { + base: Box, + arguments: Vec, + }, + Function { + name: String, + params: Vec, + block: Vec, + }, + If { + condition: Box, + yes: Vec, + no: Option>, + }, + ArrowFunction { + params: Vec, + block: Vec, + }, + Comma { + items: Vec, + }, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct JsStatement { + pub data: JsStatementData, + pub loc: Loc, + pub comment: Option, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum JsStatementData { + Expression(JsExpr), + Return(JsExpr), + Export(JsExport), + Declaration { + name: String, + init: JsExpr, + is_constant: bool, + }, + Function { + name: String, + params: Vec, + block: JsBlock, + }, + If { + condition: JsExpr, + yes: JsBlock, + no: Option, + }, +} + +#[derive(Debug, PartialEq, Clone)] +pub enum JsExport { + Named(Vec), + Default(JsExpr), + Equals(JsExpr), +} + +#[derive(Debug, PartialEq, Clone)] +pub struct JsBlock { + pub statements: Vec, + pub loc: Loc, +} + +impl JsExpr { + pub fn from_blight(expr: &Expr) -> Option { + use ast::ExprData::*; + + let loc = &expr.loc; + let comment = &expr.comment; + + match &expr.data { + Block(block) => Self::comma_expr_from_blight_block(&BlockWithLoc { + block: block.clone(), + loc: loc.clone(), + }), + Binary { op, left, right } => { + let left = Self::from_blight(&*left)?; + let right = Self::from_blight(&*right)?; + Some(JsExpr::new( + JsExprData::Binary { + operator: op.clone(), + left: Box::new(left), + right: Box::new(right), + }, + loc.clone(), + comment.clone(), + )) + } + Unary { op, value } => { + let value = Self::from_blight(&*value)?; + Some(JsExpr::new( + JsExprData::Unary { + operator: op.clone(), + value: Box::new(value), + }, + loc.clone(), + comment.clone(), + )) + } + Fn { + name, + params, + return_type, + block, + } => { + let block = Self::comma_expr_from_blight_block(&BlockWithLoc { + block: block.clone(), + loc: loc.clone(), + })?; + Some(JsExpr::new( + JsExprData::Function { + name: name.clone(), + params: vec![], + block: vec![JsStatement::new( + JsStatementData::Expression(block), + loc.clone(), + comment.clone(), + )], + }, + loc.clone(), + comment.clone(), + )) + } + Builtin { id, arguments } => match id { + BuiltinId::Raw => { + let mut args = arguments.iter(); + let arg = args.next().unwrap(); + let arg = match &arg.data { + Literal(ast::Literal::String(s)) => s, + _ => panic!("raw expects a string"), + }; + Some(JsExpr::new( + JsExprData::Raw(arg.clone()), + loc.clone(), + comment.clone(), + )) + } + }, + If(expr) => { + let condition = Self::from_blight(&expr.condition)?; + let yes = Self::comma_expr_from_blight_block(&expr.yes)?; + let no = match &expr.no { + Some(no) => Some(Self::from_blight(&no)?), + None => None, + }; + Some(JsExpr::new( + JsExprData::If { + condition: Box::new(condition), + yes: vec![yes], + no: no.map(Box::new), + }, + loc.clone(), + comment.clone(), + )) + } + + Call { base, arguments } => todo!(), + Field { base, field } => todo!(), + + Return(_) => None, + + Literal(_) => todo!(), + Identifier(_) => todo!(), + // Literal(ast::Literal::Number(n)) => { + // Some(JsExpr::new(JsExprData::Number(n), loc.clone(), comment)) + // } + // Literal(ast::Literal::Bool(b)) => Some(JsExpr::new(JsExprData::Bool(b), loc, comment)), + // Literal(ast::Literal::String(s)) => { + // Some(JsExpr::new(JsExprData::String(s), loc, comment)) + // } + // Literal(ast::Literal::Void) => Some(JsExpr::new(JsExprData::Null, loc, comment)), + // Identifier(id) => Some(JsExpr::new(JsExprData::Identifier(id), loc, comment)), + } + } + + /// Since blocks are not valid expressions in JS, this creates a + /// comma expression from a block with more than one statement. + /// + /// Not possible to create an ast => None + pub fn comma_expr_from_blight_block(b: &ast::BlockWithLoc) -> Option { + if !b.block.has_implicit_return { + return Some(JsExpr::new(JsExprData::Null, b.loc.clone(), None)); + } + // a block expression with a single statement is just that statement + if b.block.statements.len() == 1 && b.block.has_implicit_return { + match b.block.statements.first().unwrap() { + ast::Statement::Expr(expr) => Self::from_blight(expr), + _ => return None, + }; + } + let mut statements = Vec::new(); + for statement in &b.block.statements { + match statement { + ast::Statement::Expr(expr) => { + if let Some(expr) = Self::from_blight(&expr) { + statements.push(expr); + } + } + _ => return None, + } + } + // a block without implicit return is to be type void, so we append null + if !b.block.has_implicit_return { + statements.push(JsExpr::new(JsExprData::Null, b.loc.clone(), None)); + } + Some(JsExpr::new( + JsExprData::Comma { items: statements }, + b.loc.clone(), + None, + )) + } + + pub fn new(data: JsExprData, loc: Loc, comment: Option) -> Self { + Self { data, loc, comment } + } +} + +impl JsStatement { + pub fn new(data: JsStatementData, loc: Loc, comment: Option) -> Self { + Self { data, loc, comment } + } +} diff --git a/crates/js_ast/lib.rs b/crates/js_ast/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..0c27a7e5204b91d8e1acb6f61f4ec69f044d4cbd --- /dev/null +++ b/crates/js_ast/lib.rs @@ -0,0 +1,411 @@ +use std::collections::HashMap; + +use ast::{BlockWithLoc, Expr, ExprData, Literal, Module, Statement}; +pub use js_ast::*; +use miette::Result; +use tokenizer::Loc; + +mod js_ast; + +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: JsExpr) -> JsExpr { + let name = format!("${}", self.internal_counter); + let loc = value.loc.clone(); + self.context + .last_mut() + .unwrap() + .statements + .push(JsStatement::new( + JsStatementData::Declaration { + name: name.clone(), + init: value, + is_constant: false, + }, + loc, + None, + )); + self.internal_counter += 1; + JsExpr::new(JsExprData::Identifier(name), Loc::EMPTY, None) + } + + 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); + } +} + +fn get_block_handle(b: &ast::BlockWithLoc, ctx: &mut TranslateContext) -> JsExpr { + // :) + if b.is_valid_js_expression() { + return JsExpr::comma_expr_from_blight_block(b).unwrap(); + } + + ctx.push_context(); + for (i, statement) in b.block.statements.iter().enumerate() { + if i == b.block.statements.len() - 1 { + break; + } else { + translate_statement(statement, ctx); + } + } + let result = match b.block.statements.last().unwrap() { + ast::Statement::Expr(expr) => get_expression_handle(expr, ctx), + stmt => { + translate_statement(stmt, ctx); + JsExpr::new(JsExprData::Null, Loc::EMPTY, None) + } + }; + let value = ctx.pop_context(); + value.iter().for_each(|x| ctx.push(x.clone())); + result +} + +// 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: &Expr, ctx: &mut TranslateContext) -> JsExpr { + match ast.data.clone() { + ExprData::Literal(Literal::String(value)) => JsExpr::new( + JsExprData::String(value), + ast.loc.clone(), + ast.comment.clone(), + ), + ExprData::Literal(Literal::Number(value)) => JsExpr::new( + JsExprData::Number(value), + ast.loc.clone(), + ast.comment.clone(), + ), + ExprData::Literal(Literal::Void) => { + JsExpr::new(JsExprData::Null, ast.loc.clone(), ast.comment.clone()) + } + ExprData::Literal(Literal::Bool(b)) => { + JsExpr::new(JsExprData::Bool(b), ast.loc.clone(), ast.comment.clone()) + } + + ExprData::Identifier(value) => JsExpr::new( + JsExprData::Identifier(ctx.get_identifier(value.clone())), + ast.loc.clone(), + ast.comment.clone(), + ), + ExprData::Call { base, arguments } => { + let mut handles = Vec::new(); + for argument in arguments { + handles.push(get_expression_handle(&argument, ctx)); + } + let base = get_expression_handle(&base, ctx); + ctx.push_internal_variable(JsExpr::new( + JsExprData::Call { + base: Box::new(base), + arguments: handles, + }, + ast.loc.clone(), + ast.comment.clone(), + )) + } + ExprData::Builtin { id, arguments } => match id { + ast::BuiltinId::Raw => { + if arguments.len() != 1 { + panic!("@raw expects exactly one argument"); + } + match arguments.get(0).unwrap() { + Expr { + data: ExprData::Literal(Literal::String(value)), + .. + } => JsExpr::new( + JsExprData::Raw(value.clone()), + ast.loc.clone(), + ast.comment.clone(), + ), + _ => panic!("@raw expects a string literal"), + } + } + }, + ExprData::Binary { op, left, right } => { + let left_handle = get_expression_handle(&left, ctx); + let right_handle = get_expression_handle(&right, ctx); + JsExpr::new( + JsExprData::Binary { + operator: op.clone(), + left: Box::new(left_handle), + right: Box::new(right_handle), + }, + ast.loc.clone(), + ast.comment.clone(), + ) + } + + ExprData::Unary { op, value } => { + let value_handle = get_expression_handle(&value, ctx); + JsExpr::new( + JsExprData::Unary { + operator: op.clone(), + value: Box::new(value_handle), + }, + ast.loc.clone(), + ast.comment.clone(), + ) + } + + ExprData::Field { base, field } => { + let base = Box::new(get_expression_handle(&base, ctx)); + + match field.data { + ExprData::Literal(Literal::String(name)) => JsExpr::new( + JsExprData::Dot { base, name }, + ast.loc.clone(), + ast.comment.clone(), + ), + _ => JsExpr::new( + JsExprData::Index { + base, + index: Box::new(get_expression_handle(&field, ctx)), + }, + ast.loc.clone(), + ast.comment.clone(), + ), + } + } + + ExprData::Block(block) => get_block_handle( + &BlockWithLoc { + block, + loc: ast.loc.clone(), + }, + ctx, + ), + + ExprData::If(expr) => { + let handle = get_expression_handle(&expr.condition, ctx); + + let yes = get_block_handle(&expr.yes, ctx); + let no = match expr.no { + Some(block) => Some(get_expression_handle(&block, ctx)), + None => None, + }; + + // ctx.push(JsStatement::new( + // JsStatementData::If { + // condition: handle, + // yes, + // no, + // }, + // ast.loc, + // ast.comment, + // )); + + // panic!() + handle + } + ExprData::Fn { + name, + params, + return_type, + block, + } => todo!(), + ExprData::Return(_) => todo!(), + } +} + +fn translate_statement(ast: &Statement, ctx: &mut TranslateContext) { + match ast { + Statement::Decl(decl) => { + let handle = get_expression_handle(&decl.init, ctx); + let rename = ctx.declare_variable(decl.name.clone()); + ctx.push(JsStatement::new( + JsStatementData::Declaration { + name: rename, + init: handle, + is_constant: decl.is_constant.clone(), + }, + decl.loc.clone(), + None, + )); + } + Statement::Expr(expr) => { + let h = get_expression_handle(expr, ctx); + ctx.push(JsStatement::new( + JsStatementData::Expression(h), + expr.loc.clone(), + None, + )) + } + Statement::Comment(_) => {} // 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::Declaration { name, .. } => { + // let original_name = name.split("$").next().unwrap().to_string(); + // ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { + // name: original_name, + // value: JsExpr::Identifier(name.clone()), + // is_constant: true, + // }))); + // } + // _ => { + // 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::FunctionCall { name, arguments } => { + // let mut handles = Vec::new(); + // for argument in arguments { + // handles.push(get_expression_handle(argument, ctx)); + // } + // match *name.clone() { + // Ast::Identifier(name) => { + // ctx.push(JsStatement::Expression(JsExpr::Call { + // name: Box::new(JsExpr::Identifier(ctx.get_identifier(name.clone()))), + // arguments: handles, + // })); + // } + // Ast::BuiltIn(name) => match name.as_str() { + // "@raw" => { + // if handles.len() != 1 { + // panic!("@raw expects exactly one argument"); + // } + // match handles.get(0).unwrap() { + // JsExpr::String(value) => { + // ctx.push(JsStatement::Expression(JsExpr::Call { + // name: Box::new(JsExpr::Raw(value.clone())), + // arguments: handles, + // })); + // } + // _ => panic!("@raw expects a string argument"), + // } + // } + // _ => panic!("Unknown built-in function {}", name), + // }, + // _ => panic!("Function name must be an identifier"), + // } + // } + // Ast::Comment(_) | Ast::DocComment(_) => {} + // _ => { + // panic!("unimplemented: {:?}", ast); + // } + } +} + +pub fn translate(module: &Module) -> Result> { + let mut ctx = TranslateContext::new(); + + for ast in module.statements.iter() { + translate_statement(&ast.clone().to_statement(), &mut ctx); + } + + Ok(ctx.pop_context()) +} diff --git a/crates/js_ast/src/lib.rs b/crates/js_ast/src/lib.rs deleted file mode 100644 index 57a3e02c1bcea90ebbd0ed2d0cbc470aac67610c..0000000000000000000000000000000000000000 --- a/crates/js_ast/src/lib.rs +++ /dev/null @@ -1,333 +0,0 @@ -use std::collections::HashMap; - -use ast::{Expr, ExprData, Literal, Module, Statement}; -use miette::Result; - -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::Declaration { - name: format!("${}", self.internal_counter), - value, - is_constant: false, - }); - 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: &Expr, ctx: &mut TranslateContext) -> JsExpression { - match ast.data.clone() { - ExprData::Literal(Literal::String(value)) => JsExpression::String(value), - ExprData::Literal(Literal::Number(value)) => JsExpression::Number(value), - ExprData::Literal(Literal::Void) => JsExpression::Identifier("null".to_string()), - ExprData::Literal(Literal::Bool(value)) => JsExpression::Identifier(if value { - "true".to_string() - } else { - "false".to_string() - }), - - ExprData::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())), - ExprData::Call { base, arguments } => { - let mut handles = Vec::new(); - for argument in arguments { - handles.push(get_expression_handle(&argument, ctx)); - } - let base = get_expression_handle(&base, ctx); - ctx.push_internal_variable(JsExpression::Call { - base: Box::new(base), - arguments: handles, - }) - } - ExprData::Builtin { id, arguments } => match id { - ast::BuiltinId::Raw => { - if arguments.len() != 1 { - panic!("@raw expects exactly one argument"); - } - match arguments.get(0).unwrap() { - Expr { - data: ExprData::Literal(Literal::String(value)), - .. - } => JsExpression::Raw(value.clone()), - _ => panic!("@raw expects a string literal"), - } - } - }, - ExprData::Binary { op, left, right } => { - let left_handle = get_expression_handle(&left, ctx); - let right_handle = get_expression_handle(&right, ctx); - JsExpression::Binary { - operator: op.clone(), - left: Box::new(left_handle), - right: Box::new(right_handle), - } - } - ExprData::Block(block) => { - if !block.has_implicit_return { - panic!("Expression blocks must have an implicit return"); - } - ctx.push_context(); - for (i, statement) in block.statements.iter().enumerate() { - if i == block.statements.len() - 1 { - break; - } else { - translate_statement(statement, ctx); - } - } - todo!() - // let result = get_expression_handle(block.statements.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: &Statement, ctx: &mut TranslateContext) { - match ast { - Statement::Decl(decl) => { - let handle = get_expression_handle(&decl.init, ctx); - let rename = ctx.declare_variable(decl.name.clone()); - ctx.push(JsStatement::Declaration { - name: rename, - value: handle, - is_constant: decl.is_constant.clone(), - }); - } - Statement::Expr(_) => todo!(), - Statement::Comment(_) => todo!(), - // 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::Declaration { name, .. } => { - // let original_name = name.split("$").next().unwrap().to_string(); - // ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { - // name: original_name, - // value: JsExpression::Identifier(name.clone()), - // is_constant: true, - // }))); - // } - // _ => { - // 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)); - // } - // match *name.clone() { - // Ast::Identifier(name) => { - // ctx.push(JsStatement::Expression(JsExpression::Call { - // name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))), - // arguments: handles, - // })); - // } - // Ast::BuiltIn(name) => match name.as_str() { - // "@raw" => { - // if handles.len() != 1 { - // panic!("@raw expects exactly one argument"); - // } - // match handles.get(0).unwrap() { - // JsExpression::String(value) => { - // ctx.push(JsStatement::Expression(JsExpression::Call { - // name: Box::new(JsExpression::Raw(value.clone())), - // arguments: handles, - // })); - // } - // _ => panic!("@raw expects a string argument"), - // } - // } - // _ => panic!("Unknown built-in function {}", name), - // }, - // _ => panic!("Function name must be an identifier"), - // } - // } - // Ast::Comment(_) | Ast::DocComment(_) => {} - // _ => { - // panic!("unimplemented: {:?}", ast); - // } - } -} - -pub fn translate(module: &Module) -> Result> { - let mut ctx = TranslateContext::new(); - - for ast in module.statements.iter() { - translate_statement(&ast.clone().to_statement(), &mut ctx); - } - - Ok(ctx.pop_context()) -} diff --git a/crates/js_ast/src/types.rs b/crates/js_ast/src/types.rs deleted file mode 100644 index f53fb3f665655d739d189bd01a1ae334579b7167..0000000000000000000000000000000000000000 --- a/crates/js_ast/src/types.rs +++ /dev/null @@ -1,50 +0,0 @@ -use ast::BinaryOperator; -use num::bigint::BigInt; -use std::collections::HashMap; - -#[derive(Debug, PartialEq, Clone)] -pub enum JsExpression { - Number(f64), - BigInt(BigInt), - String(String), - Bool(bool), - Identifier(String), - Raw(String), - Array(Vec), - Object(HashMap), - Binary { - operator: BinaryOperator, - left: Box, - right: Box, - }, - Function { - arguments: Vec, - value: Vec, - }, - Call { - base: Box, - arguments: Vec, - }, -} - -#[derive(Debug, PartialEq, Clone)] -pub enum JsStatement { - Expression(JsExpression), - Export(Box), - Return(JsExpression), - Declaration { - name: String, - value: JsExpression, - is_constant: bool, - }, - Function { - name: String, - arguments: Vec, - value: Vec, - }, - If { - condition: JsExpression, - value: Vec, - else_value: Option>, - }, -} diff --git a/crates/js_printer/src/print_expression.rs b/crates/js_printer/src/print_expression.rs index 15810d077d963fe78e1c45c6c4247403dce9677a..26122cadfe2a55e259a05322fb103b3a5f64e9c9 100644 --- a/crates/js_printer/src/print_expression.rs +++ b/crates/js_printer/src/print_expression.rs @@ -1,13 +1,13 @@ -use js_ast::JsExpression; +use js_ast::{JsExpr, JsExprData}; use crate::print_statement::print_statement; -pub fn print_expression(statement: &JsExpression) -> String { - match statement { - JsExpression::Raw(value) => format!("{}", value), - JsExpression::Number(value) => format!("{}", value), - JsExpression::BigInt(value) => format!("{}n", value), - JsExpression::String(value) => { +pub fn print_expression(expr: &JsExpr) -> String { + match &expr.data { + JsExprData::Raw(value) => format!("{}", value), + JsExprData::Number(value) => format!("{}", value), + JsExprData::BigInt(value) => format!("{}n", value), + JsExprData::String(value) => { let mut result = String::new(); // TODO: This is an INCREDIBLY naive implementation of escaping. // Also it's probably not even correct. @@ -23,9 +23,9 @@ pub fn print_expression(statement: &JsExpression) -> String { } format!("\"{}\"", result) } - JsExpression::Bool(value) => format!("{}", value), - JsExpression::Identifier(value) => format!("{}", value), - JsExpression::Array(value) => format!( + JsExprData::Bool(value) => format!("{}", value), + JsExprData::Identifier(value) => format!("{}", value), + JsExprData::Array(value) => format!( "[{}]", value .iter() @@ -33,67 +33,83 @@ pub fn print_expression(statement: &JsExpression) -> String { .collect::>() .join(",") ), - JsExpression::Object(value) => format!( + JsExprData::Object(value) => format!( "{{{}}}", value .iter() - .map(|(k, v)| format!("\"{}\":{}", k, print_expression(v))) + .map(|entry| format!( + "{}:{}", + print_expression(&entry.key), + print_expression(&entry.value) + )) .collect::>() .join(",") ), - JsExpression::Binary { + JsExprData::Binary { operator, left, right, } => format!( "{}{}{}", - print_expression(left), + print_expression(&left), operator.clone().to_string(), - print_expression(right) + print_expression(&right) ), - JsExpression::Function { arguments, value } => format!( - "({})=>{{{}}}", - arguments.join(","), - value - .iter() - .map(|x| format!("{}", print_statement(x))) - .collect::>() - .join("") - ), - JsExpression::Call { base, arguments } => format!( + // JsExprData::Function { + // arguments, + // value, + // name: _, + // params: _, + // block, + // } => format!( + // "({})=>{{{}}}", + // arguments.join(","), + // value + // .iter() + // .map(|x| format!("{}", print_statement(x))) + // .collect::>() + // .join("") + // ), + JsExprData::Call { base, arguments } => format!( "{}({})", - match *base.clone() { - JsExpression::Identifier(value) => value.clone(), - JsExpression::Raw(value) => value.clone(), - _ => panic!("Function call name must be an identifier"), - }, + print_expression(&base), arguments .iter() .map(|x| print_expression(x)) .collect::>() .join(",") ), + JsExprData::Null => todo!(), + JsExprData::Undefined => todo!(), + JsExprData::Unary { operator, value } => todo!(), + JsExprData::Dot { base, name } => todo!(), + JsExprData::Index { base, index } => todo!(), + JsExprData::Function { + name, + params, + block, + } => todo!(), + JsExprData::If { condition, yes, no } => todo!(), + JsExprData::ArrowFunction { params, block } => todo!(), + JsExprData::Comma { items } => todo!(), } } #[cfg(test)] mod tests { use crate::print_expression::print_expression; - use js_ast::JsExpression; + use js_ast::JsExprData; use js_ast::JsStatement; use num::{bigint::Sign, BigInt}; use std::collections::HashMap; #[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(&JsExprData::Number(1.0)), "1"); + assert_eq!(print_expression(&JsExprData::Number(1.5)), "1.5"); + assert_eq!(print_expression(&JsExprData::Number(1.5e10)), "15000000000"); assert_eq!( - print_expression(&JsExpression::Number(1.5e10)), - "15000000000" - ); - assert_eq!( - print_expression(&JsExpression::Number(1.5e-10)), + print_expression(&JsExprData::Number(1.5e-10)), "0.00000000015" ); } @@ -101,18 +117,15 @@ mod tests { #[test] fn bigint() { assert_eq!( - print_expression(&JsExpression::BigInt(BigInt::new(Sign::Plus, vec![1]))), + print_expression(&JsExprData::BigInt(BigInt::new(Sign::Plus, vec![1]))), "1n" ); assert_eq!( - print_expression(&JsExpression::BigInt(BigInt::new( - Sign::Plus, - vec![10000000] - ))), + print_expression(&JsExprData::BigInt(BigInt::new(Sign::Plus, vec![10000000]))), "10000000n" ); assert_eq!( - print_expression(&JsExpression::BigInt(BigInt::new( + print_expression(&JsExprData::BigInt(BigInt::new( Sign::Minus, vec![10000000] ))), @@ -123,44 +136,44 @@ mod tests { #[test] fn string() { assert_eq!( - print_expression(&JsExpression::String("".to_string())), + print_expression(&JsExprData::String("".to_string())), "\"\"" ); assert_eq!( - print_expression(&JsExpression::String("hello".to_string())), + print_expression(&JsExprData::String("hello".to_string())), "\"hello\"" ); assert_eq!( - print_expression(&JsExpression::String("hello\nworld".to_string())), + print_expression(&JsExprData::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"); + assert_eq!(print_expression(&JsExprData::Bool(true)), "true"); + assert_eq!(print_expression(&JsExprData::Bool(false)), "false"); } #[test] fn identifier() { assert_eq!( - print_expression(&JsExpression::Identifier("hello".to_string())), + print_expression(&JsExprData::Identifier("hello".to_string())), "hello" ); } #[test] fn array() { - assert_eq!(print_expression(&JsExpression::Array(vec![])), "[]"); + assert_eq!(print_expression(&JsExprData::Array(vec![])), "[]"); assert_eq!( - print_expression(&JsExpression::Array(vec![JsExpression::Number(1.0)])), + print_expression(&JsExprData::Array(vec![JsExprData::Number(1.0)])), "[1]" ); assert_eq!( - print_expression(&JsExpression::Array(vec![ - JsExpression::Number(1.0), - JsExpression::Number(2.0) + print_expression(&JsExprData::Array(vec![ + JsExprData::Number(1.0), + JsExprData::Number(2.0) ])), "[1,2]" ); @@ -168,33 +181,27 @@ mod tests { #[test] fn object() { - assert_eq!( - print_expression(&JsExpression::Object(HashMap::new())), - "{}" - ); + assert_eq!(print_expression(&JsExprData::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}" - ); + map.insert("hello".to_string(), JsExprData::Number(1.0)); + assert_eq!(print_expression(&JsExprData::Object(map)), "{\"hello\":1}"); } #[test] fn operation() { assert_eq!( - print_expression(&JsExpression::Binary { + print_expression(&JsExprData::Binary { operator: AnyOperator::Add, - left: Box::new(JsExpression::Number(1.0)), - right: Box::new(JsExpression::Number(2.0)), + left: Box::new(JsExprData::Number(1.0)), + right: Box::new(JsExprData::Number(2.0)), }), "1+2" ); assert_eq!( - print_expression(&JsExpression::Binary { + print_expression(&JsExprData::Binary { operator: AnyOperator::Sub, - left: Box::new(JsExpression::Number(1.0)), - right: Box::new(JsExpression::Number(2.0)), + left: Box::new(JsExprData::Number(1.0)), + right: Box::new(JsExprData::Number(2.0)), }), "1-2" ); @@ -203,35 +210,35 @@ mod tests { #[test] fn function() { use crate::print_expression::print_expression; - use js_ast::JsExpression; + use js_ast::JsExprData; assert_eq!( - print_expression(&JsExpression::Function { + print_expression(&JsExprData::Function { arguments: vec![], value: vec![], }), "()=>{}" ); assert_eq!( - print_expression(&JsExpression::Function { + print_expression(&JsExprData::Function { arguments: vec!["a".to_string()], value: vec![], }), "(a)=>{}" ); assert_eq!( - print_expression(&JsExpression::Function { + print_expression(&JsExprData::Function { arguments: vec!["a".to_string(), "b".to_string()], value: vec![], }), "(a,b)=>{}" ); assert_eq!( - print_expression(&JsExpression::Function { + print_expression(&JsExprData::Function { arguments: vec!["a".to_string(), "b".to_string()], value: vec![JsStatement::Declaration { name: "a".to_string(), - value: JsExpression::Number(1.0) + value: JsExprData::Number(1.0) }], }), "(a,b)=>{let a=1;}" @@ -241,23 +248,23 @@ mod tests { #[test] fn function_call() { assert_eq!( - print_expression(&JsExpression::Call { - name: Box::new(JsExpression::Identifier("hello".to_string())), + print_expression(&JsExprData::Call { + name: Box::new(JsExprData::Identifier("hello".to_string())), arguments: vec![], }), "hello()" ); assert_eq!( - print_expression(&JsExpression::Call { - name: Box::new(JsExpression::Identifier("hello".to_string())), - arguments: vec![JsExpression::Number(1.0)], + print_expression(&JsExprData::Call { + name: Box::new(JsExprData::Identifier("hello".to_string())), + arguments: vec![JsExprData::Number(1.0)], }), "hello(1)" ); assert_eq!( - print_expression(&JsExpression::Call { - name: Box::new(JsExpression::Identifier("hello".to_string())), - arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)], + print_expression(&JsExprData::Call { + name: Box::new(JsExprData::Identifier("hello".to_string())), + arguments: vec![JsExprData::Number(1.0), JsExprData::Number(2.0)], }), "hello(1,2)" ); diff --git a/crates/js_printer/src/print_statement.rs b/crates/js_printer/src/print_statement.rs index 2f4c37399b38c214fc2e8036fbb232aaf0017234..57fdb827b637716f5c05b2065c39e621eff95d62 100644 --- a/crates/js_printer/src/print_statement.rs +++ b/crates/js_printer/src/print_statement.rs @@ -1,15 +1,16 @@ -use js_ast::JsStatement; +use js_ast::{JsStatement, JsStatementData}; 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::Declaration { + match &statement.data { + JsStatementData::Expression(value) => format!("{};", print_expression(&value)), + // JsStatementData::Export(value) => format!("export {}", print_statement(value)), + JsStatementData::Export(value) => todo!(), + JsStatementData::Return(value) => format!("return {};", print_expression(&value)), + JsStatementData::Declaration { name, - value, + init, is_constant, } => { format!( @@ -19,32 +20,30 @@ pub fn print_statement(statement: &JsStatement) -> String { false => "let", }, name, - print_expression(value) + print_expression(&init) ) } - JsStatement::Function { + JsStatementData::Function { name, - arguments, - value, + block, + params, } => format!( "function {}({}){{{}}}", name, - arguments.join(","), - value + params.join(","), + block + .statements .iter() .map(|x| format!("{}", print_statement(x))) .collect::>() .join("") ), - JsStatement::If { - condition, - value, - else_value, - } => { - let else_value = match else_value { + JsStatementData::If { condition, yes, no } => { + let else_value = match no { Some(value) => format!( "else{{{}}}", value + .statements .iter() .map(|x| format!("{}", print_statement(x))) .collect::>() @@ -54,8 +53,8 @@ pub fn print_statement(statement: &JsStatement) -> String { }; format!( "if({}){{{}}}{}", - print_expression(condition), - value + print_expression(&condition), + yes.statements .iter() .map(|x| format!("{}", print_statement(x))) .collect::>() diff --git a/crates/parser/parse_expr_if.rs b/crates/parser/parse_expr_if.rs index d480a09d7d30075449793e398b15c3103f06e751..0efc61ee5e3b52a9d221fca3f692d782073b6c5e 100644 --- a/crates/parser/parse_expr_if.rs +++ b/crates/parser/parse_expr_if.rs @@ -1,4 +1,4 @@ -use ast::{BlockWithLoc, If}; +use ast::{BlockWithLoc, Expr, ExprData, If}; use miette::Result; use tokenizer::Token; @@ -48,10 +48,21 @@ pub fn parse_expr_if(p: &mut Parser) -> Result { match p.peek() { Some(Token::OpenBrace) => { let loc = p.loc(); - Some(BlockWithLoc { - block: parse_block(p)?, + let comments = p.take_comments(); + Some(Box::new(Expr::new( + ExprData::Block(parse_block(p)?), loc, - }) + comments, + ))) + } + Some(Token::If) => { + let loc = p.loc(); + let comments = p.take_comments(); + Some(Box::new(Expr::new( + ExprData::If(parse_expr_if(p)?), + loc, + comments, + ))) } _ => return Err(p.error("Expected a block after else".to_string()).into()), } diff --git a/crates/tokenizer/src/lib.rs b/crates/tokenizer/src/lib.rs index 5b2095c6d430acbe3f687f6324b22018a349a6dd..2d906f20a2b75c81a5168c92c3d591314c8d2c18 100644 --- a/crates/tokenizer/src/lib.rs +++ b/crates/tokenizer/src/lib.rs @@ -1,9 +1,17 @@ use miette::{Diagnostic, Result}; use thiserror::Error; -#[derive(Debug, Clone)] +#[derive(Debug, PartialEq, Clone)] pub struct Loc {} +impl Loc { + pub const EMPTY: Loc = Loc {}; + + pub fn new() -> Self { + Self {} + } +} + #[derive(Error, Diagnostic, Debug)] #[error("UnknownCharacter")] struct UnknownCharacter { diff --git a/examples/basics/comments.blight b/examples/basics/comments.blight index 773d675b9cc0a27ff6d2419988e72893c7a09f62..cef4db9adcbbb9a7735efe8c97e0bea4e4a39b12 100644 --- a/examples/basics/comments.blight +++ b/examples/basics/comments.blight @@ -1,2 +1,9 @@ -2 // yapping - + 2; \ No newline at end of file +// this is a regular comment +let x: int = 0; + +/// this is a doc comment +let y: int = 0; + +/// Doc comments can be multiple lines. +/// This can be done by simply adding another triple-slash to the next line. +let z: int = 0; -- 2.54.0