25 files changed, 437 insertions(+), 104 deletions(-)
.gitignore+1| ... | ... | @@ -1 +1,2 @@ |
| 1 | 1 | target |
| 2 | *.js |
Cargo.lock+12| ... | ... | @@ -126,6 +126,17 @@ version = "1.0.0" |
| 126 | 126 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 127 | 127 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" |
| 128 | 128 | |
| 129 | [[package]] | |
| 130 | name = "checker" | |
| 131 | version = "0.1.0" | |
| 132 | dependencies = [ | |
| 133 | "ast", | |
| 134 | "miette", | |
| 135 | "num", | |
| 136 | "thiserror", | |
| 137 | "util", | |
| 138 | ] | |
| 139 | ||
| 129 | 140 | [[package]] |
| 130 | 141 | name = "clap" |
| 131 | 142 | version = "4.4.14" |
| ... | ... | @@ -171,6 +182,7 @@ name = "cli" |
| 171 | 182 | version = "0.1.0" |
| 172 | 183 | dependencies = [ |
| 173 | 184 | "ast", |
| 185 | "checker", | |
| 174 | 186 | "clap", |
| 175 | 187 | "js_ast", |
| 176 | 188 | "js_printer", |
crates/ast/src/error.rs+4| ... | ... | @@ -14,4 +14,8 @@ impl AstError { |
| 14 | 14 | error: String::from(error), |
| 15 | 15 | } |
| 16 | 16 | } |
| 17 | ||
| 18 | pub fn from_string(error: String) -> AstError { | |
| 19 | AstError { error } | |
| 20 | } | |
| 17 | 21 | } |
crates/ast/src/lib.rs+3-3| ... | ... | @@ -13,9 +13,9 @@ use tokenizer::Token; |
| 13 | 13 | use parse_statement::parse_statement; |
| 14 | 14 | use types::ParserContext; |
| 15 | 15 | |
| 16 | pub use types::{Ast, Block, Type}; | |
| 16 | pub use types::{Ast, Block, Module, Type}; | |
| 17 | 17 | |
| 18 | pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { | |
| 18 | pub fn parse(tokens: Vec<Token>) -> Result<Module> { | |
| 19 | 19 | let mut ctx = ParserContext::new(tokens); |
| 20 | 20 | let mut ast: Vec<Ast> = Vec::new(); |
| 21 | 21 | |
| ... | ... | @@ -23,5 +23,5 @@ pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { |
| 23 | 23 | ast.push(parse_statement(&mut ctx)?); |
| 24 | 24 | } |
| 25 | 25 | |
| 26 | Ok(ast) | |
| 26 | Ok(Module { value: ast }) | |
| 27 | 27 | } |
crates/ast/src/parse_statement.rs+17-33| ... | ... | @@ -2,6 +2,8 @@ use miette::Result; |
| 2 | 2 | |
| 3 | 3 | use tokenizer::Token; |
| 4 | 4 | |
| 5 | use crate::Type; | |
| 6 | ||
| 5 | 7 | use super::error::AstError; |
| 6 | 8 | use super::parse_block::parse_block; |
| 7 | 9 | use super::parse_expression::parse_expression; |
| ... | ... | @@ -38,41 +40,22 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 38 | 40 | |
| 39 | 41 | return Ok(Ast::Public(Box::new(inner))); |
| 40 | 42 | } |
| 41 | Token::Let => { | |
| 42 | let name = match tokens.next() { | |
| 43 | Some(Token::Identifier(name)) => name, | |
| 44 | _ => return Err(AstError::new("Expected identifier after var").into()), | |
| 45 | }; | |
| 46 | let return_type = match tokens.next() { | |
| 47 | Some(Token::Colon) => parse_type(tokens)?, | |
| 48 | _ => return Err(AstError::new("Expected colon after identifier").into()), | |
| 49 | }; | |
| 50 | let value = match tokens.next() { | |
| 51 | Some(Token::Equals) => parse_expression(tokens)?, | |
| 52 | _ => return Err(AstError::new("Expected equals after identifier").into()), | |
| 53 | }; | |
| 54 | match tokens.next() { | |
| 55 | Some(Token::Semicolon) => (), | |
| 56 | _ => { | |
| 57 | return Err( | |
| 58 | AstError::new("Expected semicolon after constant declaration").into(), | |
| 59 | ) | |
| 60 | } | |
| 43 | Token::Var | Token::Let => { | |
| 44 | let is_constant = match token { | |
| 45 | Token::Var => false, | |
| 46 | Token::Let => true, | |
| 47 | _ => unreachable!(), | |
| 61 | 48 | }; |
| 62 | Ok(Ast::ConstantDeclaration { | |
| 63 | name: name.clone(), | |
| 64 | return_type, | |
| 65 | value: Box::new(value), | |
| 66 | }) | |
| 67 | } | |
| 68 | Token::Var => { | |
| 69 | 49 | let name = match tokens.next() { |
| 70 | 50 | Some(Token::Identifier(name)) => name, |
| 71 | 51 | _ => return Err(AstError::new("Expected identifier after var").into()), |
| 72 | 52 | }; |
| 73 | let return_type = match tokens.next() { | |
| 74 | Some(Token::Colon) => parse_type(tokens)?, | |
| 75 | _ => return Err(AstError::new("Expected colon after identifier").into()), | |
| 53 | let return_type = match tokens.peek() { | |
| 54 | Some(Token::Colon) => { | |
| 55 | tokens.next(); | |
| 56 | parse_type(tokens)? | |
| 57 | } | |
| 58 | _ => Type::Infer, | |
| 76 | 59 | }; |
| 77 | 60 | let value = match tokens.next() { |
| 78 | 61 | Some(Token::Equals) => parse_expression(tokens)?, |
| ... | ... | @@ -82,14 +65,15 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 82 | 65 | Some(Token::Semicolon) => (), |
| 83 | 66 | _ => { |
| 84 | 67 | return Err( |
| 85 | AstError::new("Expected semicolon after variable declaration").into(), | |
| 68 | AstError::new("Expected semicolon after constant declaration").into(), | |
| 86 | 69 | ) |
| 87 | 70 | } |
| 88 | 71 | }; |
| 89 | Ok(Ast::VariableDeclaration { | |
| 72 | Ok(Ast::Declaration { | |
| 90 | 73 | name: name.clone(), |
| 91 | return_type, | |
| 74 | decl_type: return_type, | |
| 92 | 75 | value: Box::new(value), |
| 76 | is_constant, | |
| 93 | 77 | }) |
| 94 | 78 | } |
| 95 | 79 | Token::Return => { |
crates/ast/src/parse_type.rs+11-4| ... | ... | @@ -10,12 +10,19 @@ pub fn parse_type(tokens: &mut ParserContext) -> Result<Type> { |
| 10 | 10 | Some(Token::Identifier(typ)) => match typ.as_str() { |
| 11 | 11 | "int" => Ok(Type::Int), |
| 12 | 12 | "float" => Ok(Type::Float), |
| 13 | "bigint" => Ok(Type::Bigint), | |
| 13 | "bigint" => Ok(Type::BigInt), | |
| 14 | 14 | "bool" => Ok(Type::Bool), |
| 15 | 15 | "string" => Ok(Type::String), |
| 16 | "any" => Ok(Type::Any), | |
| 17 | _ => return Err(AstError::new("Expected type").into()), | |
| 16 | _ => { | |
| 17 | return Err( | |
| 18 | AstError::from_string(format!( | |
| 19 | "Invalid type '{}' (TODO: add custom type support)", | |
| 20 | typ | |
| 21 | ) | |
| 22 | ).into()) | |
| 23 | } | |
| 18 | 24 | }, |
| 19 | _ => return Err(AstError::new("Expected type").into()), | |
| 25 | Some(unexpected) => return Err(AstError::from_string(format!("Expected type, got {}", unexpected)).into()), | |
| 26 | None => return Err(AstError::from_string("Expected type, got EOF".to_string()).into()), | |
| 20 | 27 | } |
| 21 | 28 | } |
crates/ast/src/types.rs+54-10| ... | ... | @@ -41,14 +41,62 @@ impl ParserContext { |
| 41 | 41 | } |
| 42 | 42 | } |
| 43 | 43 | |
| 44 | #[derive(Debug, Clone)] | |
| 44 | #[derive(Debug, Clone, PartialEq)] | |
| 45 | 45 | pub enum Type { |
| 46 | /// Variables without an explicit type are assigned this until | |
| 47 | /// the type inferencer figures out what this is. There is not a | |
| 48 | /// keyword for this. | |
| 49 | Infer, | |
| 50 | /// The type assigned to numeric literals. This allows integer literals | |
| 51 | /// to coerce to integer or float depending on the context, but prevents | |
| 52 | /// implicit casting of runtime values. | |
| 53 | NumberLiteral(f64), | |
| 54 | /// Return value of `@raw`. All type checks are disabled. | |
| 55 | Any, | |
| 56 | /// noreturn is the type of 'return', aka a value that never exists | |
| 57 | NoReturn, | |
| 58 | /// 32 signed integer | |
| 46 | 59 | Int, |
| 60 | /// 64 signed JS float | |
| 47 | 61 | Float, |
| 48 | Bigint, | |
| 62 | /// Boolean | |
| 49 | 63 | Bool, |
| 64 | /// JS BigInt | |
| 65 | BigInt, | |
| 66 | /// JS String | |
| 50 | 67 | String, |
| 51 | Any, | |
| 68 | /// A union of multiple possible types. Might be removed | |
| 69 | Union(Vec<Type>), | |
| 70 | } | |
| 71 | ||
| 72 | impl std::fmt::Display for Type { | |
| 73 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| 74 | match self { | |
| 75 | Type::Infer => write!(f, "infered type"), | |
| 76 | Type::Any => write!(f, "opaque js value"), | |
| 77 | Type::NumberLiteral(_) => write!(f, "numeric literal"), | |
| 78 | Type::Int => write!(f, "int"), | |
| 79 | Type::Float => write!(f, "float"), | |
| 80 | Type::BigInt => write!(f, "bigint"), | |
| 81 | Type::Bool => write!(f, "bool"), | |
| 82 | Type::String => write!(f, "string"), | |
| 83 | Type::NoReturn => write!(f, "noreturn"), | |
| 84 | Type::Union(of) => { | |
| 85 | for (i, t) in of.iter().enumerate() { | |
| 86 | if i != 0 { | |
| 87 | write!(f, " | ")?; | |
| 88 | } | |
| 89 | write!(f, "{}", t)?; | |
| 90 | } | |
| 91 | Ok(()) | |
| 92 | } | |
| 93 | } | |
| 94 | } | |
| 95 | } | |
| 96 | ||
| 97 | #[derive(Debug, Clone)] | |
| 98 | pub struct Module { | |
| 99 | pub value: Vec<Ast>, | |
| 52 | 100 | } |
| 53 | 101 | |
| 54 | 102 | #[derive(Debug, Clone)] |
| ... | ... | @@ -65,15 +113,11 @@ pub struct Argument { |
| 65 | 113 | |
| 66 | 114 | #[derive(Debug, Clone)] |
| 67 | 115 | pub enum Ast { |
| 68 | ConstantDeclaration { | |
| 69 | name: String, | |
| 70 | return_type: Type, | |
| 71 | value: Box<Ast>, | |
| 72 | }, | |
| 73 | VariableDeclaration { | |
| 116 | Declaration { | |
| 74 | 117 | name: String, |
| 75 | return_type: Type, | |
| 118 | decl_type: Type, | |
| 76 | 119 | value: Box<Ast>, |
| 120 | is_constant: bool, | |
| 77 | 121 | }, |
| 78 | 122 | Operation { |
| 79 | 123 | operator: Operator, |
crates/checker/Cargo.toml created+14| ... | ... | @@ -0,0 +1,14 @@ |
| 1 | [package] | |
| 2 | name = "checker" | |
| 3 | version = "0.1.0" | |
| 4 | edition = "2021" | |
| 5 | ||
| 6 | [lib] | |
| 7 | ||
| 8 | [dependencies] | |
| 9 | miette.workspace = true | |
| 10 | thiserror.workspace = true | |
| 11 | num.workspace = true | |
| 12 | ||
| 13 | ast = { path = "../ast" } | |
| 14 | util = { path = "../util" } |
crates/checker/src/lib.rs created+203| ... | ... | @@ -0,0 +1,203 @@ |
| 1 | use ast::{Ast, Module, Type}; | |
| 2 | use miette::Result; | |
| 3 | ||
| 4 | use miette::Diagnostic; | |
| 5 | use thiserror::Error; | |
| 6 | ||
| 7 | #[derive(Error, Diagnostic, Debug)] | |
| 8 | #[error("TypeError")] | |
| 9 | pub struct TypeError { | |
| 10 | #[help] | |
| 11 | error: String, | |
| 12 | } | |
| 13 | ||
| 14 | impl TypeError { | |
| 15 | pub fn new(error: String) -> Self { | |
| 16 | Self { error } | |
| 17 | } | |
| 18 | } | |
| 19 | ||
| 20 | pub fn check_module(block: &Module) -> Result<()> { | |
| 21 | for statement in &block.value { | |
| 22 | check_expression(statement)?; | |
| 23 | } | |
| 24 | ||
| 25 | Ok(()) | |
| 26 | } | |
| 27 | ||
| 28 | pub fn resolve_type(expr: &Ast) -> Result<Type> { | |
| 29 | match expr { | |
| 30 | Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | |
| 31 | Ast::Declaration { | |
| 32 | name, | |
| 33 | decl_type, | |
| 34 | value, | |
| 35 | is_constant, | |
| 36 | } => Ok(Type::NoReturn), | |
| 37 | Ast::Operation { | |
| 38 | operator, | |
| 39 | left, | |
| 40 | right, | |
| 41 | } => merge_types(resolve_type(left)?, resolve_type(right)?), | |
| 42 | Ast::Function { | |
| 43 | name, | |
| 44 | arguments, | |
| 45 | return_type, | |
| 46 | block, | |
| 47 | } => todo!(), | |
| 48 | Ast::FunctionCall { name, arguments } => todo!(), | |
| 49 | Ast::If { | |
| 50 | condition, | |
| 51 | value, | |
| 52 | else_value, | |
| 53 | } => todo!(), | |
| 54 | Ast::Return(_) => Ok(Type::NoReturn), | |
| 55 | Ast::Public(_) => todo!(), // TODO: remove Public | |
| 56 | Ast::Block(_) => todo!(), | |
| 57 | Ast::Bool(_) => Ok(Type::Bool), | |
| 58 | Ast::String(_) => Ok(Type::String), | |
| 59 | Ast::Identifier(_) => todo!(), | |
| 60 | Ast::BuiltIn(_) => todo!(), | |
| 61 | ||
| 62 | Ast::Comment(_) => unreachable!(), | |
| 63 | Ast::DocComment(_) => unreachable!(), | |
| 64 | } | |
| 65 | } | |
| 66 | ||
| 67 | pub fn merge_types(a: Type, b: Type) -> Result<Type> { | |
| 68 | match a { | |
| 69 | Type::Infer => return Ok(b), | |
| 70 | Type::Any => return Ok(Type::Any), | |
| 71 | _ => {} | |
| 72 | } | |
| 73 | ||
| 74 | match b { | |
| 75 | Type::Infer => return Ok(a), | |
| 76 | Type::Any => return Ok(Type::Any), | |
| 77 | _ => {} | |
| 78 | } | |
| 79 | ||
| 80 | if a == b { | |
| 81 | return Ok(a); | |
| 82 | } | |
| 83 | ||
| 84 | Err(TypeError::new(format!("Cannot merge {} and {}", a, b)).into()) | |
| 85 | } | |
| 86 | ||
| 87 | pub fn coerce_type(target: Type, source: Type) -> Result<Type> { | |
| 88 | match source { | |
| 89 | Type::Any => return Ok(target), | |
| 90 | _ => {} | |
| 91 | } | |
| 92 | match target { | |
| 93 | Type::Infer => Ok(source), | |
| 94 | Type::Any => Ok(Type::Any), | |
| 95 | ||
| 96 | Type::NumberLiteral(_) => unreachable!(), | |
| 97 | ||
| 98 | Type::Int => match source { | |
| 99 | Type::Int => Ok(Type::Int), | |
| 100 | Type::Float => Ok(Type::Int), | |
| 101 | Type::NumberLiteral(value) => { | |
| 102 | if value.fract() != 0.0 { | |
| 103 | Err( | |
| 104 | TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | |
| 105 | .into(), | |
| 106 | ) | |
| 107 | } else if value > i32::MAX.into() || value < i32::MIN.into() { | |
| 108 | Err(TypeError::new(format!( | |
| 109 | "Numeric Literal '{}' is out of range for an integer ({}..{})", | |
| 110 | value, | |
| 111 | i32::MIN, | |
| 112 | i32::MAX | |
| 113 | )) | |
| 114 | .into()) | |
| 115 | } else { | |
| 116 | Ok(Type::Int) | |
| 117 | } | |
| 118 | } | |
| 119 | t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | |
| 120 | }, | |
| 121 | ||
| 122 | Type::BigInt => match source { | |
| 123 | Type::Int => Ok(Type::Int), | |
| 124 | Type::Float => Ok(Type::Int), | |
| 125 | Type::NumberLiteral(value) => { | |
| 126 | if value.fract() != 0.0 { | |
| 127 | Err( | |
| 128 | TypeError::new(format!("Numeric Literal '{}' is not an integer", value)) | |
| 129 | .into(), | |
| 130 | ) | |
| 131 | } else { | |
| 132 | Ok(Type::BigInt) | |
| 133 | } | |
| 134 | } | |
| 135 | t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()), | |
| 136 | }, | |
| 137 | ||
| 138 | Type::Float => match source { | |
| 139 | Type::Int => Ok(Type::Float), | |
| 140 | Type::Float => Ok(Type::Float), | |
| 141 | Type::NumberLiteral(value) => { | |
| 142 | if value.fract() == 0.0 && value <= f64::MAX.into() && value >= f64::MIN.into() { | |
| 143 | Ok(Type::Float) | |
| 144 | } else { | |
| 145 | Err(TypeError::new(format!("Numeric Literal")).into()) | |
| 146 | } | |
| 147 | } | |
| 148 | _ => Err(TypeError::new(format!("Cannot coerce int to float")).into()), | |
| 149 | }, | |
| 150 | ||
| 151 | other => { | |
| 152 | if other == source { | |
| 153 | Ok(other) | |
| 154 | } else { | |
| 155 | Err(TypeError::new(format!("Cannot coerce {} to {}", source, other)).into()) | |
| 156 | } | |
| 157 | } | |
| 158 | } | |
| 159 | } | |
| 160 | ||
| 161 | pub fn check_expression(expr: &Ast) -> Result<()> { | |
| 162 | match expr { | |
| 163 | Ast::Declaration { | |
| 164 | name, | |
| 165 | decl_type: return_type, | |
| 166 | value, | |
| 167 | is_constant, | |
| 168 | } => { | |
| 169 | check_expression(value)?; | |
| 170 | coerce_type(return_type.clone(), resolve_type(value)?)?; | |
| 171 | } | |
| 172 | _ => {} | |
| 173 | // Ast::Operation { | |
| 174 | // operator, | |
| 175 | // left, | |
| 176 | // right, | |
| 177 | // } => Ok(()), | |
| 178 | // Ast::Function { | |
| 179 | // name, | |
| 180 | // arguments, | |
| 181 | // return_type, | |
| 182 | // block, | |
| 183 | // } => Ok(()), | |
| 184 | // Ast::FunctionCall { name, arguments } => Ok(()), | |
| 185 | // Ast::If { | |
| 186 | // condition, | |
| 187 | // value, | |
| 188 | // else_value, | |
| 189 | // } => Ok(()), | |
| 190 | // Ast::Return(_) => Ok(()), | |
| 191 | // Ast::Public(_) => Ok(()), | |
| 192 | // Ast::Block(_) => Ok(()), | |
| 193 | // Ast::Number(_) => Ok(()), | |
| 194 | // Ast::Bool(_) => Ok(()), | |
| 195 | // Ast::String(_) => Ok(()), | |
| 196 | // Ast::Identifier(_) => Ok(()), | |
| 197 | // Ast::BuiltIn(_) => Ok(()), | |
| 198 | // Ast::Comment(_) => Ok(()), | |
| 199 | // Ast::DocComment(_) => Ok(()), | |
| 200 | } | |
| 201 | ||
| 202 | Ok(()) | |
| 203 | } |
crates/cli/Cargo.toml+1| ... | ... | @@ -16,3 +16,4 @@ tokenizer = { path = "../tokenizer" } |
| 16 | 16 | ast = { path = "../ast" } |
| 17 | 17 | js_ast = { path = "../js_ast" } |
| 18 | 18 | js_printer = { path = "../js_printer" } |
| 19 | checker = { path = "../checker" } |
crates/cli/src/main.rs+38-5| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | use std::path::PathBuf; | |
| 1 | use std::{io::Write, path::PathBuf}; | |
| 2 | 2 | |
| 3 | use checker; | |
| 3 | 4 | use clap::{Parser, Subcommand}; |
| 4 | 5 | use miette::Result; |
| 5 | 6 | |
| ... | ... | @@ -29,6 +30,9 @@ enum Commands { |
| 29 | 30 | /// What javascript runtime to compile to |
| 30 | 31 | #[arg(short, long, default_value = "deno")] |
| 31 | 32 | target: String, |
| 33 | /// Format the output javascript? | |
| 34 | #[arg(long, default_value_t = true)] | |
| 35 | format: bool, | |
| 32 | 36 | }, |
| 33 | 37 | Run { |
| 34 | 38 | file: PathBuf, |
| ... | ... | @@ -51,11 +55,11 @@ pub fn transpile(content: String) -> Result<String> { |
| 51 | 55 | // 2. Parse the tokens into an AST |
| 52 | 56 | let blight_ast = ast::parse(tokens)?; |
| 53 | 57 | // 3. Type check the AST |
| 54 | // type_check(tree.clone())?; | |
| 58 | checker::check_module(&blight_ast)?; | |
| 55 | 59 | // 4. Optimize the AST |
| 56 | 60 | // optimize(tree.clone())?; |
| 57 | 61 | // 5. Translate the AST into javascript |
| 58 | let js_ast = js_ast::translate(blight_ast)?; | |
| 62 | let js_ast = js_ast::translate(&blight_ast)?; | |
| 59 | 63 | // 6. Print the JS AST |
| 60 | 64 | let javascript = js_printer::print(js_ast); |
| 61 | 65 | // 7. Return the javascript |
| ... | ... | @@ -70,9 +74,38 @@ fn main() -> Result<()> { |
| 70 | 74 | file, |
| 71 | 75 | stdout, |
| 72 | 76 | target: _, |
| 77 | format, | |
| 73 | 78 | } => { |
| 74 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | |
| 75 | let result = transpile(contents.clone())?; | |
| 79 | let contents = match std::fs::read_to_string(file.clone()) { | |
| 80 | Ok(contents) => contents, | |
| 81 | Err(e) => { | |
| 82 | eprintln!("Failed to read {}: {}", file.display(), e); | |
| 83 | std::process::exit(1); | |
| 84 | } | |
| 85 | }; | |
| 86 | ||
| 87 | let mut result = transpile(contents.clone())?; | |
| 88 | ||
| 89 | if format { | |
| 90 | let mut formatter = std::process::Command::new("deno") | |
| 91 | .stdin(std::process::Stdio::piped()) | |
| 92 | .stdout(std::process::Stdio::piped()) | |
| 93 | .stderr(std::process::Stdio::inherit()) | |
| 94 | .arg("fmt") | |
| 95 | .arg("-") | |
| 96 | .spawn() | |
| 97 | .expect("Failed to execute 'deno fmt' on output"); | |
| 98 | let mut stdin = formatter.stdin.take().expect("Failed to open stdin"); | |
| 99 | stdin | |
| 100 | .write_all(result.as_bytes()) | |
| 101 | .expect("Failed to write to stdin"); | |
| 102 | stdin.flush().expect("Failed to flush stdin"); | |
| 103 | drop(stdin); | |
| 104 | let out = formatter | |
| 105 | .wait_with_output() | |
| 106 | .expect("Failed to execute 'deno fmt' on output"); | |
| 107 | result = String::from_utf8(out.stdout).unwrap(); | |
| 108 | } | |
| 76 | 109 | |
| 77 | 110 | if stdout { |
| 78 | 111 | println!("{}", result); |
crates/js_ast/src/lib.rs+16-28| ... | ... | @@ -2,7 +2,7 @@ use std::collections::HashMap; |
| 2 | 2 | |
| 3 | 3 | use miette::Result; |
| 4 | 4 | |
| 5 | use ast::Ast; | |
| 5 | use ast::{Ast, Module}; | |
| 6 | 6 | |
| 7 | 7 | mod types; |
| 8 | 8 | pub use types::{JsExpression, JsStatement}; |
| ... | ... | @@ -55,9 +55,10 @@ impl TranslateContext { |
| 55 | 55 | .last_mut() |
| 56 | 56 | .unwrap() |
| 57 | 57 | .statements |
| 58 | .push(JsStatement::VariableDeclaration { | |
| 58 | .push(JsStatement::Declaration { | |
| 59 | 59 | name: format!("${}", self.internal_counter), |
| 60 | 60 | value, |
| 61 | is_constant: false, | |
| 61 | 62 | }); |
| 62 | 63 | self.internal_counter += 1; |
| 63 | 64 | JsExpression::Identifier(format!("${}", self.internal_counter - 1)) |
| ... | ... | @@ -169,28 +170,18 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression |
| 169 | 170 | |
| 170 | 171 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { |
| 171 | 172 | match ast { |
| 172 | Ast::ConstantDeclaration { | |
| 173 | Ast::Declaration { | |
| 173 | 174 | name, |
| 174 | return_type: _, | |
| 175 | value, | |
| 176 | } => { | |
| 177 | let handle = get_expression_handle(value, ctx); | |
| 178 | let rename = ctx.declare_variable(name.clone()); | |
| 179 | ctx.push(JsStatement::ConstantDeclaration { | |
| 180 | name: rename, | |
| 181 | value: handle, | |
| 182 | }); | |
| 183 | } | |
| 184 | Ast::VariableDeclaration { | |
| 185 | name, | |
| 186 | return_type: _, | |
| 175 | decl_type: _, | |
| 187 | 176 | value, |
| 177 | is_constant, | |
| 188 | 178 | } => { |
| 189 | 179 | let handle = get_expression_handle(value, ctx); |
| 190 | 180 | let rename = ctx.declare_variable(name.clone()); |
| 191 | ctx.push(JsStatement::VariableDeclaration { | |
| 181 | ctx.push(JsStatement::Declaration { | |
| 192 | 182 | name: rename, |
| 193 | 183 | value: handle, |
| 184 | is_constant: is_constant.clone(), | |
| 194 | 185 | }); |
| 195 | 186 | } |
| 196 | 187 | Ast::Function { |
| ... | ... | @@ -226,16 +217,13 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { |
| 226 | 217 | translate_statement(value, ctx); |
| 227 | 218 | // I'm sorry, I just don't know how to get the original name properly... my bad |
| 228 | 219 | match ctx.peek() { |
| 229 | JsStatement::Function { name, .. } | |
| 230 | | JsStatement::VariableDeclaration { name, .. } | |
| 231 | | JsStatement::ConstantDeclaration { name, .. } => { | |
| 220 | JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { | |
| 232 | 221 | let original_name = name.split("$").next().unwrap().to_string(); |
| 233 | ctx.push(JsStatement::Export(Box::new( | |
| 234 | JsStatement::ConstantDeclaration { | |
| 235 | name: original_name, | |
| 236 | value: JsExpression::Identifier(name.clone()), | |
| 237 | }, | |
| 238 | ))); | |
| 222 | ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { | |
| 223 | name: original_name, | |
| 224 | value: JsExpression::Identifier(name.clone()), | |
| 225 | is_constant: false, | |
| 226 | }))); | |
| 239 | 227 | } |
| 240 | 228 | _ => { |
| 241 | 229 | panic!("Cannot make non-function public"); |
| ... | ... | @@ -337,10 +325,10 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { |
| 337 | 325 | } |
| 338 | 326 | } |
| 339 | 327 | |
| 340 | pub fn translate(_ast: Vec<Ast>) -> Result<Vec<JsStatement>> { | |
| 328 | pub fn translate(module: &Module) -> Result<Vec<JsStatement>> { | |
| 341 | 329 | let mut ctx = TranslateContext::new(); |
| 342 | 330 | |
| 343 | for ast in _ast { | |
| 331 | for ast in module.value.iter() { | |
| 344 | 332 | translate_statement(&ast, &mut ctx); |
| 345 | 333 | } |
| 346 | 334 |
crates/js_ast/src/types.rs+2-5| ... | ... | @@ -33,13 +33,10 @@ pub enum JsStatement { |
| 33 | 33 | Expression(JsExpression), |
| 34 | 34 | Export(Box<JsStatement>), |
| 35 | 35 | Return(JsExpression), |
| 36 | VariableDeclaration { | |
| 37 | name: String, | |
| 38 | value: JsExpression, | |
| 39 | }, | |
| 40 | ConstantDeclaration { | |
| 36 | Declaration { | |
| 41 | 37 | name: String, |
| 42 | 38 | value: JsExpression, |
| 39 | is_constant: bool, | |
| 43 | 40 | }, |
| 44 | 41 | Function { |
| 45 | 42 | name: String, |
crates/js_printer/src/print_expression.rs+1-1| ... | ... | @@ -230,7 +230,7 @@ mod tests { |
| 230 | 230 | assert_eq!( |
| 231 | 231 | print_expression(&JsExpression::Function { |
| 232 | 232 | arguments: vec!["a".to_string(), "b".to_string()], |
| 233 | value: vec![JsStatement::VariableDeclaration { | |
| 233 | value: vec![JsStatement::Declaration { | |
| 234 | 234 | name: "a".to_string(), |
| 235 | 235 | value: JsExpression::Number(1.0) |
| 236 | 236 | }], |
crates/js_printer/src/print_statement.rs+15-6| ... | ... | @@ -7,11 +7,20 @@ pub fn print_statement(statement: &JsStatement) -> String { |
| 7 | 7 | JsStatement::Expression(value) => format!("{};", print_expression(value)), |
| 8 | 8 | JsStatement::Export(value) => format!("export {}", print_statement(value)), |
| 9 | 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)) | |
| 10 | JsStatement::Declaration { | |
| 11 | name, | |
| 12 | value, | |
| 13 | is_constant, | |
| 14 | } => { | |
| 15 | format!( | |
| 16 | "{} {}={};", | |
| 17 | match is_constant { | |
| 18 | true => "const", | |
| 19 | false => "let", | |
| 20 | }, | |
| 21 | name, | |
| 22 | print_expression(value) | |
| 23 | ) | |
| 15 | 24 | } |
| 16 | 25 | JsStatement::Function { |
| 17 | 26 | name, |
| ... | ... | @@ -66,7 +75,7 @@ mod tests { |
| 66 | 75 | #[test] |
| 67 | 76 | fn variable_declaration() { |
| 68 | 77 | assert_eq!( |
| 69 | print_statement(&JsStatement::VariableDeclaration { | |
| 78 | print_statement(&JsStatement::Declaration { | |
| 70 | 79 | name: "x".to_string(), |
| 71 | 80 | value: JsExpression::Number(1.0), |
| 72 | 81 | }), |
crates/tokenizer/src/lib.rs+36| ... | ... | @@ -43,6 +43,42 @@ pub enum Token { |
| 43 | 43 | Operator(Operator), |
| 44 | 44 | } |
| 45 | 45 | |
| 46 | impl std::fmt::Display for Token { | |
| 47 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | |
| 48 | let token = match self { | |
| 49 | Token::Identifier(_) => "an identifier".to_string(), | |
| 50 | Token::Number(n) => n.to_string(), | |
| 51 | Token::String(s) => format!("\"{}\"", s), | |
| 52 | Token::BuiltIn(_) => "a builtin identifier".to_string(), | |
| 53 | Token::Comment(_) => "a comment".to_string(), | |
| 54 | Token::DocComment(_) => "a documentation comment".to_string(), | |
| 55 | Token::True => "true".to_string(), | |
| 56 | Token::False => "false".to_string(), | |
| 57 | Token::Let => "let".to_string(), | |
| 58 | Token::Var => "var".to_string(), | |
| 59 | Token::If => "if".to_string(), | |
| 60 | Token::Else => "else".to_string(), | |
| 61 | Token::Return => "return".to_string(), | |
| 62 | Token::Pub => "pub".to_string(), | |
| 63 | Token::Function => "fn".to_string(), | |
| 64 | Token::RightArrow => "->".to_string(), | |
| 65 | Token::Comma => ",".to_string(), | |
| 66 | Token::OpenParanthesis => "(".to_string(), | |
| 67 | Token::ClosedParanthesis => ")".to_string(), | |
| 68 | Token::OpenBracket => "{".to_string(), | |
| 69 | Token::ClosedBracket => "}".to_string(), | |
| 70 | Token::Colon => ":".to_string(), | |
| 71 | Token::Equals => "=".to_string(), | |
| 72 | Token::Semicolon => ";".to_string(), | |
| 73 | Token::Operator(o) => o.clone().to_string(), | |
| 74 | }; | |
| 75 | ||
| 76 | write!(f, "{}", token) | |
| 77 | } | |
| 78 | } | |
| 79 | ||
| 80 | ||
| 81 | ||
| 46 | 82 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 47 | 83 | let mut tokens: Vec<Token> = Vec::new(); |
| 48 | 84 | let mut chars = content.chars().enumerate().peekable(); |
examples/basics/control.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | |
| 1 | let print = @raw("console.log"); | |
| 2 | 2 | |
| 3 | 3 | let x: bool = true; |
| 4 | 4 |
examples/basics/expressions.blight+1-1| ... | ... | @@ -7,5 +7,5 @@ let x: int = 5 + { |
| 7 | 7 | y |
| 8 | 8 | }; |
| 9 | 9 | |
| 10 | let print: any = @raw("console.log"); | |
| 10 | let print = @raw("console.log"); | |
| 11 | 11 | print(x); // 10 |
examples/basics/fib.blight+1-1| ... | ... | @@ -8,5 +8,5 @@ pub fn fib(n: int) -> int { |
| 8 | 8 | } |
| 9 | 9 | } |
| 10 | 10 | |
| 11 | let print: any = @raw("console.log"); | |
| 11 | let print = @raw("console.log"); | |
| 12 | 12 | print(fib(10)); |
examples/basics/functions.blight+1-1| ... | ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { |
| 4 | 4 | |
| 5 | 5 | let result: int = bingus(10, 25); |
| 6 | 6 | |
| 7 | let print: any = @raw("console.log"); | |
| 7 | let print = @raw("console.log"); | |
| 8 | 8 | print(result); |
examples/basics/operations.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | 1 | let a: int = 10 - 2 / 4 * 15; |
| 2 | 2 | |
| 3 | let print: any = @raw("console.log"); | |
| 3 | let print = @raw("console.log"); | |
| 4 | 4 | print(a); |
examples/basics/shadowing.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | |
| 1 | let print = @raw("console.log"); | |
| 2 | 2 | |
| 3 | 3 | let x: int = 0; |
| 4 | 4 | let x: int = 1; |
examples/basics/variables.blight+1-1| ... | ... | @@ -6,5 +6,5 @@ let a: int = 0; |
| 6 | 6 | let b: float = 0; |
| 7 | 7 | let c: bigint = 0; |
| 8 | 8 | |
| 9 | let print: any = @raw("console.log"); | |
| 9 | let print = @raw("console.log"); | |
| 10 | 10 | print(x); |
examples/basics/weird.blight+1-1| ... | ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { |
| 9 | 9 | x |
| 10 | 10 | } |
| 11 | 11 | |
| 12 | let print: any = @raw("console.log"); | |
| 12 | let print = @raw("console.log"); | |
| 13 | 13 | print(bingus(true)); |
| 14 | 14 | print(bingus(false)); |
examples/interop/deno.blight+1-1| ... | ... | @@ -1,4 +1,4 @@ |
| 1 | let writeTextFile: any = @raw("Deno.writeTextFile"); | |
| 1 | let writeTextFile = @raw("Deno.writeTextFile"); | |
| 2 | 2 | |
| 3 | 3 | fn x() -> string { |
| 4 | 4 | "hello" |