25 files changed, 437 insertions(+), 104 deletions(-)
.gitignore+1| ... | @@ -1 +1,2 @@ | ... | @@ -1 +1,2 @@ |
| 1 | target | 1 | target |
| 2 | *.js |
Cargo.lock+12| ... | @@ -126,6 +126,17 @@ version = "1.0.0" | ... | @@ -126,6 +126,17 @@ version = "1.0.0" |
| 126 | source = "registry+https://github.com/rust-lang/crates.io-index" | 126 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 127 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | 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 | [[package]] | 140 | [[package]] |
| 130 | name = "clap" | 141 | name = "clap" |
| 131 | version = "4.4.14" | 142 | version = "4.4.14" |
| ... | @@ -171,6 +182,7 @@ name = "cli" | ... | @@ -171,6 +182,7 @@ name = "cli" |
| 171 | version = "0.1.0" | 182 | version = "0.1.0" |
| 172 | dependencies = [ | 183 | dependencies = [ |
| 173 | "ast", | 184 | "ast", |
| 185 | "checker", | ||
| 174 | "clap", | 186 | "clap", |
| 175 | "js_ast", | 187 | "js_ast", |
| 176 | "js_printer", | 188 | "js_printer", |
crates/ast/src/error.rs+4| ... | @@ -14,4 +14,8 @@ impl AstError { | ... | @@ -14,4 +14,8 @@ impl AstError { |
| 14 | error: String::from(error), | 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,9 +13,9 @@ use tokenizer::Token; |
| 13 | use parse_statement::parse_statement; | 13 | use parse_statement::parse_statement; |
| 14 | use types::ParserContext; | 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 | let mut ctx = ParserContext::new(tokens); | 19 | let mut ctx = ParserContext::new(tokens); |
| 20 | let mut ast: Vec<Ast> = Vec::new(); | 20 | let mut ast: Vec<Ast> = Vec::new(); |
| 21 | 21 | ||
| ... | @@ -23,5 +23,5 @@ pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { | ... | @@ -23,5 +23,5 @@ pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { |
| 23 | ast.push(parse_statement(&mut ctx)?); | 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,6 +2,8 @@ use miette::Result; |
| 2 | 2 | ||
| 3 | use tokenizer::Token; | 3 | use tokenizer::Token; |
| 4 | 4 | ||
| 5 | use crate::Type; | ||
| 6 | |||
| 5 | use super::error::AstError; | 7 | use super::error::AstError; |
| 6 | use super::parse_block::parse_block; | 8 | use super::parse_block::parse_block; |
| 7 | use super::parse_expression::parse_expression; | 9 | use super::parse_expression::parse_expression; |
| ... | @@ -38,41 +40,22 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -38,41 +40,22 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 38 | 40 | ||
| 39 | return Ok(Ast::Public(Box::new(inner))); | 41 | return Ok(Ast::Public(Box::new(inner))); |
| 40 | } | 42 | } |
| 41 | Token::Let => { | 43 | Token::Var | Token::Let => { |
| 42 | let name = match tokens.next() { | 44 | let is_constant = match token { |
| 43 | Some(Token::Identifier(name)) => name, | 45 | Token::Var => false, |
| 44 | _ => return Err(AstError::new("Expected identifier after var").into()), | 46 | Token::Let => true, |
| 45 | }; | 47 | _ => unreachable!(), |
| 46 | let return_type = match tokens.next() { | ||
| 47 | Some(Token::Colon) => parse_type(tokens)?, | ||
| 48 | _ => return Err(AstError::new("Expected colon after identifier").into()), | ||
| 49 | }; | ||
| 50 | let value = match tokens.next() { | ||
| 51 | Some(Token::Equals) => parse_expression(tokens)?, | ||
| 52 | _ => return Err(AstError::new("Expected equals after identifier").into()), | ||
| 53 | }; | ||
| 54 | match tokens.next() { | ||
| 55 | Some(Token::Semicolon) => (), | ||
| 56 | _ => { | ||
| 57 | return Err( | ||
| 58 | AstError::new("Expected semicolon after constant declaration").into(), | ||
| 59 | ) | ||
| 60 | } | ||
| 61 | }; | 48 | }; |
| 62 | Ok(Ast::ConstantDeclaration { | ||
| 63 | name: name.clone(), | ||
| 64 | return_type, | ||
| 65 | value: Box::new(value), | ||
| 66 | }) | ||
| 67 | } | ||
| 68 | Token::Var => { | ||
| 69 | let name = match tokens.next() { | 49 | let name = match tokens.next() { |
| 70 | Some(Token::Identifier(name)) => name, | 50 | Some(Token::Identifier(name)) => name, |
| 71 | _ => return Err(AstError::new("Expected identifier after var").into()), | 51 | _ => return Err(AstError::new("Expected identifier after var").into()), |
| 72 | }; | 52 | }; |
| 73 | let return_type = match tokens.next() { | 53 | let return_type = match tokens.peek() { |
| 74 | Some(Token::Colon) => parse_type(tokens)?, | 54 | Some(Token::Colon) => { |
| 75 | _ => return Err(AstError::new("Expected colon after identifier").into()), | 55 | tokens.next(); |
| 56 | parse_type(tokens)? | ||
| 57 | } | ||
| 58 | _ => Type::Infer, | ||
| 76 | }; | 59 | }; |
| 77 | let value = match tokens.next() { | 60 | let value = match tokens.next() { |
| 78 | Some(Token::Equals) => parse_expression(tokens)?, | 61 | Some(Token::Equals) => parse_expression(tokens)?, |
| ... | @@ -82,14 +65,15 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { | ... | @@ -82,14 +65,15 @@ pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> { |
| 82 | Some(Token::Semicolon) => (), | 65 | Some(Token::Semicolon) => (), |
| 83 | _ => { | 66 | _ => { |
| 84 | return Err( | 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 | name: name.clone(), | 73 | name: name.clone(), |
| 91 | return_type, | 74 | decl_type: return_type, |
| 92 | value: Box::new(value), | 75 | value: Box::new(value), |
| 76 | is_constant, | ||
| 93 | }) | 77 | }) |
| 94 | } | 78 | } |
| 95 | Token::Return => { | 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,12 +10,19 @@ pub fn parse_type(tokens: &mut ParserContext) -> Result<Type> { |
| 10 | Some(Token::Identifier(typ)) => match typ.as_str() { | 10 | Some(Token::Identifier(typ)) => match typ.as_str() { |
| 11 | "int" => Ok(Type::Int), | 11 | "int" => Ok(Type::Int), |
| 12 | "float" => Ok(Type::Float), | 12 | "float" => Ok(Type::Float), |
| 13 | "bigint" => Ok(Type::Bigint), | 13 | "bigint" => Ok(Type::BigInt), |
| 14 | "bool" => Ok(Type::Bool), | 14 | "bool" => Ok(Type::Bool), |
| 15 | "string" => Ok(Type::String), | 15 | "string" => Ok(Type::String), |
| 16 | "any" => Ok(Type::Any), | 16 | _ => { |
| 17 | _ => return Err(AstError::new("Expected type").into()), | 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,14 +41,62 @@ impl ParserContext { |
| 41 | } | 41 | } |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | #[derive(Debug, Clone)] | 44 | #[derive(Debug, Clone, PartialEq)] |
| 45 | pub enum Type { | 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 | Int, | 59 | Int, |
| 60 | /// 64 signed JS float | ||
| 47 | Float, | 61 | Float, |
| 48 | Bigint, | 62 | /// Boolean |
| 49 | Bool, | 63 | Bool, |
| 64 | /// JS BigInt | ||
| 65 | BigInt, | ||
| 66 | /// JS String | ||
| 50 | String, | 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 | #[derive(Debug, Clone)] | 102 | #[derive(Debug, Clone)] |
| ... | @@ -65,15 +113,11 @@ pub struct Argument { | ... | @@ -65,15 +113,11 @@ pub struct Argument { |
| 65 | 113 | ||
| 66 | #[derive(Debug, Clone)] | 114 | #[derive(Debug, Clone)] |
| 67 | pub enum Ast { | 115 | pub enum Ast { |
| 68 | ConstantDeclaration { | 116 | Declaration { |
| 69 | name: String, | ||
| 70 | return_type: Type, | ||
| 71 | value: Box<Ast>, | ||
| 72 | }, | ||
| 73 | VariableDeclaration { | ||
| 74 | name: String, | 117 | name: String, |
| 75 | return_type: Type, | 118 | decl_type: Type, |
| 76 | value: Box<Ast>, | 119 | value: Box<Ast>, |
| 120 | is_constant: bool, | ||
| 77 | }, | 121 | }, |
| 78 | Operation { | 122 | Operation { |
| 79 | operator: Operator, | 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,3 +16,4 @@ tokenizer = { path = "../tokenizer" } |
| 16 | ast = { path = "../ast" } | 16 | ast = { path = "../ast" } |
| 17 | js_ast = { path = "../js_ast" } | 17 | js_ast = { path = "../js_ast" } |
| 18 | js_printer = { path = "../js_printer" } | 18 | js_printer = { path = "../js_printer" } |
| 19 | checker = { path = "../checker" } |
crates/cli/src/main.rs+38-5| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | use std::path::PathBuf; | 1 | use std::{io::Write, path::PathBuf}; |
| 2 | 2 | ||
| 3 | use checker; | ||
| 3 | use clap::{Parser, Subcommand}; | 4 | use clap::{Parser, Subcommand}; |
| 4 | use miette::Result; | 5 | use miette::Result; |
| 5 | 6 | ||
| ... | @@ -29,6 +30,9 @@ enum Commands { | ... | @@ -29,6 +30,9 @@ enum Commands { |
| 29 | /// What javascript runtime to compile to | 30 | /// What javascript runtime to compile to |
| 30 | #[arg(short, long, default_value = "deno")] | 31 | #[arg(short, long, default_value = "deno")] |
| 31 | target: String, | 32 | target: String, |
| 33 | /// Format the output javascript? | ||
| 34 | #[arg(long, default_value_t = true)] | ||
| 35 | format: bool, | ||
| 32 | }, | 36 | }, |
| 33 | Run { | 37 | Run { |
| 34 | file: PathBuf, | 38 | file: PathBuf, |
| ... | @@ -51,11 +55,11 @@ pub fn transpile(content: String) -> Result<String> { | ... | @@ -51,11 +55,11 @@ pub fn transpile(content: String) -> Result<String> { |
| 51 | // 2. Parse the tokens into an AST | 55 | // 2. Parse the tokens into an AST |
| 52 | let blight_ast = ast::parse(tokens)?; | 56 | let blight_ast = ast::parse(tokens)?; |
| 53 | // 3. Type check the AST | 57 | // 3. Type check the AST |
| 54 | // type_check(tree.clone())?; | 58 | checker::check_module(&blight_ast)?; |
| 55 | // 4. Optimize the AST | 59 | // 4. Optimize the AST |
| 56 | // optimize(tree.clone())?; | 60 | // optimize(tree.clone())?; |
| 57 | // 5. Translate the AST into javascript | 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 | // 6. Print the JS AST | 63 | // 6. Print the JS AST |
| 60 | let javascript = js_printer::print(js_ast); | 64 | let javascript = js_printer::print(js_ast); |
| 61 | // 7. Return the javascript | 65 | // 7. Return the javascript |
| ... | @@ -70,9 +74,38 @@ fn main() -> Result<()> { | ... | @@ -70,9 +74,38 @@ fn main() -> Result<()> { |
| 70 | file, | 74 | file, |
| 71 | stdout, | 75 | stdout, |
| 72 | target: _, | 76 | target: _, |
| 77 | format, | ||
| 73 | } => { | 78 | } => { |
| 74 | let contents = std::fs::read_to_string(file.clone()).unwrap(); | 79 | let contents = match std::fs::read_to_string(file.clone()) { |
| 75 | let result = transpile(contents.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 | if stdout { | 110 | if stdout { |
| 78 | println!("{}", result); | 111 | println!("{}", result); |
crates/js_ast/src/lib.rs+16-28| ... | @@ -2,7 +2,7 @@ use std::collections::HashMap; | ... | @@ -2,7 +2,7 @@ use std::collections::HashMap; |
| 2 | 2 | ||
| 3 | use miette::Result; | 3 | use miette::Result; |
| 4 | 4 | ||
| 5 | use ast::Ast; | 5 | use ast::{Ast, Module}; |
| 6 | 6 | ||
| 7 | mod types; | 7 | mod types; |
| 8 | pub use types::{JsExpression, JsStatement}; | 8 | pub use types::{JsExpression, JsStatement}; |
| ... | @@ -55,9 +55,10 @@ impl TranslateContext { | ... | @@ -55,9 +55,10 @@ impl TranslateContext { |
| 55 | .last_mut() | 55 | .last_mut() |
| 56 | .unwrap() | 56 | .unwrap() |
| 57 | .statements | 57 | .statements |
| 58 | .push(JsStatement::VariableDeclaration { | 58 | .push(JsStatement::Declaration { |
| 59 | name: format!("${}", self.internal_counter), | 59 | name: format!("${}", self.internal_counter), |
| 60 | value, | 60 | value, |
| 61 | is_constant: false, | ||
| 61 | }); | 62 | }); |
| 62 | self.internal_counter += 1; | 63 | self.internal_counter += 1; |
| 63 | JsExpression::Identifier(format!("${}", self.internal_counter - 1)) | 64 | JsExpression::Identifier(format!("${}", self.internal_counter - 1)) |
| ... | @@ -169,28 +170,18 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression | ... | @@ -169,28 +170,18 @@ fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression |
| 169 | 170 | ||
| 170 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | 171 | fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { |
| 171 | match ast { | 172 | match ast { |
| 172 | Ast::ConstantDeclaration { | 173 | Ast::Declaration { |
| 173 | name, | 174 | name, |
| 174 | return_type: _, | 175 | decl_type: _, |
| 175 | value, | ||
| 176 | } => { | ||
| 177 | let handle = get_expression_handle(value, ctx); | ||
| 178 | let rename = ctx.declare_variable(name.clone()); | ||
| 179 | ctx.push(JsStatement::ConstantDeclaration { | ||
| 180 | name: rename, | ||
| 181 | value: handle, | ||
| 182 | }); | ||
| 183 | } | ||
| 184 | Ast::VariableDeclaration { | ||
| 185 | name, | ||
| 186 | return_type: _, | ||
| 187 | value, | 176 | value, |
| 177 | is_constant, | ||
| 188 | } => { | 178 | } => { |
| 189 | let handle = get_expression_handle(value, ctx); | 179 | let handle = get_expression_handle(value, ctx); |
| 190 | let rename = ctx.declare_variable(name.clone()); | 180 | let rename = ctx.declare_variable(name.clone()); |
| 191 | ctx.push(JsStatement::VariableDeclaration { | 181 | ctx.push(JsStatement::Declaration { |
| 192 | name: rename, | 182 | name: rename, |
| 193 | value: handle, | 183 | value: handle, |
| 184 | is_constant: is_constant.clone(), | ||
| 194 | }); | 185 | }); |
| 195 | } | 186 | } |
| 196 | Ast::Function { | 187 | Ast::Function { |
| ... | @@ -226,16 +217,13 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | ... | @@ -226,16 +217,13 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { |
| 226 | translate_statement(value, ctx); | 217 | translate_statement(value, ctx); |
| 227 | // I'm sorry, I just don't know how to get the original name properly... my bad | 218 | // I'm sorry, I just don't know how to get the original name properly... my bad |
| 228 | match ctx.peek() { | 219 | match ctx.peek() { |
| 229 | JsStatement::Function { name, .. } | 220 | JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => { |
| 230 | | JsStatement::VariableDeclaration { name, .. } | ||
| 231 | | JsStatement::ConstantDeclaration { name, .. } => { | ||
| 232 | let original_name = name.split("$").next().unwrap().to_string(); | 221 | let original_name = name.split("$").next().unwrap().to_string(); |
| 233 | ctx.push(JsStatement::Export(Box::new( | 222 | ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration { |
| 234 | JsStatement::ConstantDeclaration { | 223 | name: original_name, |
| 235 | name: original_name, | 224 | value: JsExpression::Identifier(name.clone()), |
| 236 | value: JsExpression::Identifier(name.clone()), | 225 | is_constant: false, |
| 237 | }, | 226 | }))); |
| 238 | ))); | ||
| 239 | } | 227 | } |
| 240 | _ => { | 228 | _ => { |
| 241 | panic!("Cannot make non-function public"); | 229 | panic!("Cannot make non-function public"); |
| ... | @@ -337,10 +325,10 @@ fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) { | ... | @@ -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 | let mut ctx = TranslateContext::new(); | 329 | let mut ctx = TranslateContext::new(); |
| 342 | 330 | ||
| 343 | for ast in _ast { | 331 | for ast in module.value.iter() { |
| 344 | translate_statement(&ast, &mut ctx); | 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,13 +33,10 @@ pub enum JsStatement { |
| 33 | Expression(JsExpression), | 33 | Expression(JsExpression), |
| 34 | Export(Box<JsStatement>), | 34 | Export(Box<JsStatement>), |
| 35 | Return(JsExpression), | 35 | Return(JsExpression), |
| 36 | VariableDeclaration { | 36 | Declaration { |
| 37 | name: String, | ||
| 38 | value: JsExpression, | ||
| 39 | }, | ||
| 40 | ConstantDeclaration { | ||
| 41 | name: String, | 37 | name: String, |
| 42 | value: JsExpression, | 38 | value: JsExpression, |
| 39 | is_constant: bool, | ||
| 43 | }, | 40 | }, |
| 44 | Function { | 41 | Function { |
| 45 | name: String, | 42 | name: String, |
crates/js_printer/src/print_expression.rs+1-1| ... | @@ -230,7 +230,7 @@ mod tests { | ... | @@ -230,7 +230,7 @@ mod tests { |
| 230 | assert_eq!( | 230 | assert_eq!( |
| 231 | print_expression(&JsExpression::Function { | 231 | print_expression(&JsExpression::Function { |
| 232 | arguments: vec!["a".to_string(), "b".to_string()], | 232 | arguments: vec!["a".to_string(), "b".to_string()], |
| 233 | value: vec![JsStatement::VariableDeclaration { | 233 | value: vec![JsStatement::Declaration { |
| 234 | name: "a".to_string(), | 234 | name: "a".to_string(), |
| 235 | value: JsExpression::Number(1.0) | 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,11 +7,20 @@ pub fn print_statement(statement: &JsStatement) -> String { |
| 7 | JsStatement::Expression(value) => format!("{};", print_expression(value)), | 7 | JsStatement::Expression(value) => format!("{};", print_expression(value)), |
| 8 | JsStatement::Export(value) => format!("export {}", print_statement(value)), | 8 | JsStatement::Export(value) => format!("export {}", print_statement(value)), |
| 9 | JsStatement::Return(value) => format!("return {};", print_expression(value)), | 9 | JsStatement::Return(value) => format!("return {};", print_expression(value)), |
| 10 | JsStatement::VariableDeclaration { name, value } => { | 10 | JsStatement::Declaration { |
| 11 | format!("let {}={};", name, print_expression(value)) | 11 | name, |
| 12 | } | 12 | value, |
| 13 | JsStatement::ConstantDeclaration { name, value } => { | 13 | is_constant, |
| 14 | format!("const {}={};", name, print_expression(value)) | 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 | JsStatement::Function { | 25 | JsStatement::Function { |
| 17 | name, | 26 | name, |
| ... | @@ -66,7 +75,7 @@ mod tests { | ... | @@ -66,7 +75,7 @@ mod tests { |
| 66 | #[test] | 75 | #[test] |
| 67 | fn variable_declaration() { | 76 | fn variable_declaration() { |
| 68 | assert_eq!( | 77 | assert_eq!( |
| 69 | print_statement(&JsStatement::VariableDeclaration { | 78 | print_statement(&JsStatement::Declaration { |
| 70 | name: "x".to_string(), | 79 | name: "x".to_string(), |
| 71 | value: JsExpression::Number(1.0), | 80 | value: JsExpression::Number(1.0), |
| 72 | }), | 81 | }), |
crates/tokenizer/src/lib.rs+36| ... | @@ -43,6 +43,42 @@ pub enum Token { | ... | @@ -43,6 +43,42 @@ pub enum Token { |
| 43 | Operator(Operator), | 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 | pub fn tokenize(content: String) -> Result<Vec<Token>> { | 82 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 47 | let mut tokens: Vec<Token> = Vec::new(); | 83 | let mut tokens: Vec<Token> = Vec::new(); |
| 48 | let mut chars = content.chars().enumerate().peekable(); | 84 | let mut chars = content.chars().enumerate().peekable(); |
examples/basics/control.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | 1 | let print = @raw("console.log"); |
| 2 | 2 | ||
| 3 | let x: bool = true; | 3 | let x: bool = true; |
| 4 | 4 |
examples/basics/expressions.blight+1-1| ... | @@ -7,5 +7,5 @@ let x: int = 5 + { | ... | @@ -7,5 +7,5 @@ let x: int = 5 + { |
| 7 | y | 7 | y |
| 8 | }; | 8 | }; |
| 9 | 9 | ||
| 10 | let print: any = @raw("console.log"); | 10 | let print = @raw("console.log"); |
| 11 | print(x); // 10 | 11 | print(x); // 10 |
examples/basics/fib.blight+1-1| ... | @@ -8,5 +8,5 @@ pub fn fib(n: int) -> int { | ... | @@ -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 | print(fib(10)); | 12 | print(fib(10)); |
examples/basics/functions.blight+1-1| ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { | ... | @@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int { |
| 4 | 4 | ||
| 5 | let result: int = bingus(10, 25); | 5 | let result: int = bingus(10, 25); |
| 6 | 6 | ||
| 7 | let print: any = @raw("console.log"); | 7 | let print = @raw("console.log"); |
| 8 | print(result); | 8 | print(result); |
examples/basics/operations.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let a: int = 10 - 2 / 4 * 15; | 1 | let a: int = 10 - 2 / 4 * 15; |
| 2 | 2 | ||
| 3 | let print: any = @raw("console.log"); | 3 | let print = @raw("console.log"); |
| 4 | print(a); | 4 | print(a); |
examples/basics/shadowing.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let print: any = @raw("console.log"); | 1 | let print = @raw("console.log"); |
| 2 | 2 | ||
| 3 | let x: int = 0; | 3 | let x: int = 0; |
| 4 | let x: int = 1; | 4 | let x: int = 1; |
examples/basics/variables.blight+1-1| ... | @@ -6,5 +6,5 @@ let a: int = 0; | ... | @@ -6,5 +6,5 @@ let a: int = 0; |
| 6 | let b: float = 0; | 6 | let b: float = 0; |
| 7 | let c: bigint = 0; | 7 | let c: bigint = 0; |
| 8 | 8 | ||
| 9 | let print: any = @raw("console.log"); | 9 | let print = @raw("console.log"); |
| 10 | print(x); | 10 | print(x); |
examples/basics/weird.blight+1-1| ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { | ... | @@ -9,6 +9,6 @@ fn bingus(y: bool) -> int { |
| 9 | x | 9 | x |
| 10 | } | 10 | } |
| 11 | 11 | ||
| 12 | let print: any = @raw("console.log"); | 12 | let print = @raw("console.log"); |
| 13 | print(bingus(true)); | 13 | print(bingus(true)); |
| 14 | print(bingus(false)); | 14 | print(bingus(false)); |
examples/interop/deno.blight+1-1| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | let writeTextFile: any = @raw("Deno.writeTextFile"); | 1 | let writeTextFile = @raw("Deno.writeTextFile"); |
| 2 | 2 | ||
| 3 | fn x() -> string { | 3 | fn x() -> string { |
| 4 | "hello" | 4 | "hello" |