authorgravatar for 11367844+lino-levan@users.noreply.github.comLino Le Van <11367844+lino-levan@users.noreply.github.com> 2024-01-26 05:07:40+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-01-26 05:07:40+01:00
logfec1b3637ceb5db50f87fa1e78e0fa968191bf12
treed78076620f5b9a3a1d0219d8dad16bab5350195d
parent091dd237e9ee9de0d9e8f3b7a95ccbcffb618b68
parenta69bb8e40fafdd2249ca2dbac1f94ba1e79340ad

Merge pull request #1 from lino-levan/dave/typecheck

feat: rewrite the project in rust, add operator precedence and type checking

81 files changed, 4462 insertions(+), 2421 deletions(-)

.gitignore+1
......@@ -1 +1,2 @@
11target
2*.js
Cargo.lock+26-8
......@@ -70,9 +70,9 @@ name = "ast"
7070version = "0.1.0"
7171dependencies = [
7272 "miette",
73 "num",
7374 "thiserror",
7475 "tokenizer",
75 "util",
7676]
7777
7878[[package]]
......@@ -175,6 +175,8 @@ dependencies = [
175175 "js_ast",
176176 "js_printer",
177177 "miette",
178 "parser",
179 "sema",
178180 "thiserror",
179181 "tokenizer",
180182]
......@@ -237,8 +239,9 @@ dependencies = [
237239 "ast",
238240 "miette",
239241 "num",
242 "parser",
240243 "thiserror",
241 "util",
244 "tokenizer",
242245]
243246
244247[[package]]
......@@ -249,7 +252,6 @@ dependencies = [
249252 "miette",
250253 "num",
251254 "thiserror",
252 "util",
253255]
254256
255257[[package]]
......@@ -408,6 +410,17 @@ version = "3.5.0"
408410source = "registry+https://github.com/rust-lang/crates.io-index"
409411checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
410412
413[[package]]
414name = "parser"
415version = "0.1.0"
416dependencies = [
417 "ast",
418 "miette",
419 "num",
420 "thiserror",
421 "tokenizer",
422]
423
411424[[package]]
412425name = "proc-macro2"
413426version = "1.0.76"
......@@ -445,6 +458,16 @@ dependencies = [
445458 "windows-sys",
446459]
447460
461[[package]]
462name = "sema"
463version = "0.1.0"
464dependencies = [
465 "ast",
466 "miette",
467 "num",
468 "thiserror",
469]
470
448471[[package]]
449472name = "smawk"
450473version = "0.3.2"
......@@ -543,7 +566,6 @@ version = "0.1.0"
543566dependencies = [
544567 "miette",
545568 "thiserror",
546 "util",
547569]
548570
549571[[package]]
......@@ -570,10 +592,6 @@ version = "0.2.1"
570592source = "registry+https://github.com/rust-lang/crates.io-index"
571593checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
572594
573[[package]]
574name = "util"
575version = "0.1.0"
576
577595[[package]]
578596name = "winapi"
579597version = "0.3.9"
Cargo.toml+10-3
......@@ -1,14 +1,21 @@
11[workspace.package]
22version = "0.1.0"
3name = "blight"
43edition = "2023"
54license = "MIT"
6authors = ["Lino Le Van"]
5authors = ["Lino Le Van", "Dave Caruso"]
76repository = "https://github.com/lino-levan/blight"
87
98[workspace]
109resolver = "2"
11members = ["crates/cli", "crates/util", "crates/tokenizer", "crates/ast", "crates/js_ast", "crates/js_printer"]
10members = [
11 "crates/ast",
12 "crates/sema",
13 "crates/cli",
14 "crates/js_ast",
15 "crates/js_printer",
16 "crates/parser",
17 "crates/tokenizer",
18]
1219
1320[workspace.dependencies]
1421clap = { version = "4.4.12", features = ["derive"] }
crates/ast/Cargo.toml+2-1
......@@ -4,10 +4,11 @@ version = "0.1.0"
44edition = "2021"
55
66[lib]
7path = "./lib.rs"
78
89[dependencies]
910miette.workspace = true
1011thiserror.workspace = true
12num.workspace = true
1113
1214tokenizer = { path = "../tokenizer" }
13util = { path = "../util" }
crates/ast/ast.rs created+247
......@@ -0,0 +1,247 @@
1use tokenizer::Loc;
2
3use crate::{
4 types::{Literal, Type},
5 BinaryOperator, UnaryOperator,
6};
7
8#[derive(Debug, Clone)]
9pub enum BuiltinId {
10 Raw,
11}
12
13#[derive(Debug, Clone)]
14pub struct Module {
15 pub comment: Option<DocComment>,
16 pub statements: Vec<TopLevelStatement>,
17}
18
19#[derive(Debug, Clone)]
20pub struct Block {
21 pub statements: Vec<Statement>,
22 pub has_implicit_return: bool,
23}
24
25#[derive(Debug, Clone)]
26pub struct Param {
27 pub name: String,
28 pub param_type: Type,
29}
30
31#[derive(Debug, Clone)]
32
33pub struct VarDecl {
34 pub name: String,
35 pub data_type: Type,
36 pub init: Expr,
37 pub is_constant: bool,
38 pub is_pub: bool,
39 pub loc: Loc,
40 pub comment: Option<DocComment>,
41}
42
43#[derive(Debug, Clone)]
44pub struct FnDecl {
45 pub name: String,
46 pub return_type: Type,
47 pub params: Vec<Param>,
48 pub body: Expr,
49 pub is_pub: bool,
50 pub loc: Loc,
51 pub comment: Option<DocComment>,
52}
53
54#[derive(Debug, Clone)]
55pub struct Expr {
56 pub data: ExprData,
57 pub loc: Loc,
58 pub comment: Option<Comment>,
59}
60
61impl Expr {
62 pub fn new(data: ExprData, loc: Loc, comment: Option<Comment>) -> Self {
63 Self { data, loc, comment }
64 }
65}
66
67#[derive(Debug, Clone)]
68pub struct DocComment {
69 pub data: String,
70 pub loc: Loc,
71}
72
73#[derive(Debug, PartialEq, Clone)]
74pub struct Comment {
75 pub data: String,
76}
77
78impl Comment {
79 pub fn format(&self) -> String {
80 let mut lines = self.data.lines();
81 let first_line = lines.next().unwrap();
82 let mut result = format!("// {}", first_line);
83 for line in lines {
84 result.push_str(&format!("\n// {}", line));
85 }
86 result
87 }
88
89 pub fn to_doc_comment(self, loc: Loc) -> DocComment {
90 DocComment {
91 data: self.data,
92 loc,
93 }
94 }
95}
96
97impl Into<Comment> for DocComment {
98 fn into(self) -> Comment {
99 Comment { data: self.data }
100 }
101}
102
103impl DocComment {
104 pub fn format(&self) -> String {
105 let mut lines = self.data.lines();
106 let first_line = lines.next().unwrap();
107 let mut result = format!("/// {}", first_line);
108 for line in lines {
109 result.push_str(&format!("\n/// {}", line));
110 }
111 result
112 }
113}
114
115#[derive(Debug, Clone)]
116pub struct BlockWithLoc {
117 pub block: Block,
118 pub loc: Loc,
119}
120
121#[derive(Debug, Clone)]
122pub struct If {
123 pub condition: Box<Expr>,
124 pub yes: BlockWithLoc,
125 pub no: Option<BlockOrIf>,
126}
127
128#[derive(Debug, Clone)]
129pub enum BlockOrIf {
130 Block(BlockWithLoc),
131 If(Box<If>),
132}
133
134#[derive(Debug, Clone)]
135pub enum ExprData {
136 Block(Block),
137 Binary {
138 op: BinaryOperator,
139 left: Box<Expr>,
140 right: Box<Expr>,
141 },
142 Unary {
143 op: UnaryOperator,
144 value: Box<Expr>,
145 },
146 Fn {
147 name: String,
148 params: Vec<Param>,
149 return_type: Type,
150 body: Box<Expr>,
151 },
152 Call {
153 base: Box<Expr>,
154 arguments: Vec<Expr>,
155 },
156 Field {
157 base: Box<Expr>,
158 field: Box<Expr>,
159 },
160 Builtin {
161 id: BuiltinId,
162 arguments: Vec<Expr>,
163 },
164 If(If),
165 Return(Box<Expr>),
166 Literal(Literal),
167 Identifier(String),
168}
169
170#[derive(Debug, Clone)]
171pub enum Statement {
172 Expr(Expr),
173 VarDecl(VarDecl),
174 FnDecl(FnDecl),
175 Comment(Comment),
176}
177
178#[derive(Debug, Clone)]
179pub enum TopLevelStatement {
180 Expr(Expr),
181 VarDecl(VarDecl),
182 FnDecl(FnDecl),
183 Comment(Comment),
184}
185
186impl BuiltinId {
187 pub fn display_name(&self) -> &'static str {
188 match self {
189 Self::Raw => "@raw",
190 }
191 }
192
193 pub fn from_str(name: &str) -> Option<Self> {
194 match name {
195 "@raw" => Some(Self::Raw),
196 _ => None,
197 }
198 }
199}
200
201impl Statement {
202 pub fn to_top_level(self) -> TopLevelStatement {
203 match self {
204 Statement::Expr(expr) => TopLevelStatement::Expr(expr),
205 Statement::VarDecl(decl) => TopLevelStatement::VarDecl(decl),
206 Statement::FnDecl(decl) => TopLevelStatement::FnDecl(decl),
207 Statement::Comment(comment) => TopLevelStatement::Comment(comment),
208 }
209 }
210
211 /// This is stupid function im sorry
212 pub fn require_semicolon(&self, semicolon_hint: bool) -> bool {
213 match &self {
214 Statement::Expr(_) => !semicolon_hint,
215 Statement::VarDecl(_) => true,
216 _ => false,
217 }
218 }
219
220 pub fn is_expression(&self) -> bool {
221 match &self {
222 Statement::Expr(_) => true,
223 _ => false,
224 }
225 }
226}
227
228impl TopLevelStatement {
229 /// This is stupid function im sorry
230 pub fn require_semicolon(&self, earlier_hint: bool) -> bool {
231 match &self {
232 TopLevelStatement::Expr(_) => !earlier_hint,
233 TopLevelStatement::VarDecl(_) => true,
234 TopLevelStatement::FnDecl(_) => false,
235 _ => false,
236 }
237 }
238
239 pub fn to_statement(self) -> Option<Statement> {
240 match self {
241 TopLevelStatement::Expr(expr) => Some(Statement::Expr(expr)),
242 TopLevelStatement::VarDecl(decl) => Some(Statement::VarDecl(decl)),
243 TopLevelStatement::FnDecl(decl) => Some(Statement::FnDecl(decl)),
244 TopLevelStatement::Comment(comment) => Some(Statement::Comment(comment)),
245 }
246 }
247}
crates/ast/lib.rs created+7
......@@ -0,0 +1,7 @@
1mod ast;
2mod operators;
3mod types;
4
5pub use ast::*;
6pub use operators::*;
7pub use types::*;
crates/ast/operators.rs created+315
......@@ -0,0 +1,315 @@
1use tokenizer::Token;
2
3use crate::Literal;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum BinaryOperator {
7 Add,
8 Sub,
9 Mul,
10 Div,
11 Pow,
12 Eq,
13 Gt,
14 Lt,
15 Gte,
16 Lte,
17 Lshift,
18 Rshift,
19 RshiftUnsigned,
20 BitAnd,
21 BitOr,
22 BitXor,
23 Mod,
24 NotEq,
25 And,
26 Or,
27
28 Assign,
29 AssignAdd,
30 AssignSub,
31 AssignMul,
32 AssignDiv,
33 AssignPow,
34 AssignLshift,
35 AssignRshift,
36 AssignRshiftUnsigned,
37 AssignBitAnd,
38 AssignBitOr,
39 AssignBitXor,
40 AssignMod,
41 AssignAnd,
42 AssignOr,
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub enum UnaryOperator {
47 Neg,
48 Not,
49 BitNot,
50}
51
52impl BinaryOperator {
53 pub fn to_str(&self) -> &'static str {
54 match self {
55 Self::Add => "+",
56 Self::Sub => "-",
57 Self::Div => "/",
58 Self::Mul => "*",
59 Self::Pow => "**",
60 Self::Eq => "==",
61 Self::Gt => ">",
62 Self::Lt => "<",
63 Self::Gte => ">=",
64 Self::Lte => "<=",
65 Self::Lshift => "<<",
66 Self::Rshift => ">>",
67 Self::RshiftUnsigned => ">>>",
68 Self::BitAnd => "&",
69 Self::BitOr => "|",
70 Self::BitXor => "^",
71 Self::Mod => "%",
72 Self::NotEq => "!=",
73 Self::And => "&&",
74 Self::Or => "||",
75 Self::AssignAdd => "+=",
76 Self::AssignSub => "-=",
77 Self::AssignDiv => "/=",
78 Self::AssignMul => "*=",
79 Self::AssignPow => "**=",
80 Self::Assign => "=",
81 Self::AssignLshift => "<<=",
82 Self::AssignRshift => ">>=",
83 Self::AssignRshiftUnsigned => ">>>=",
84 Self::AssignBitAnd => "&=",
85 Self::AssignBitOr => "|=",
86 Self::AssignBitXor => "^=",
87 Self::AssignMod => "%=",
88 Self::AssignAnd => "&&=",
89 Self::AssignOr => "||=",
90 }
91 }
92
93 pub fn to_string(&self) -> String {
94 String::from(self.to_str())
95 }
96
97 pub fn from_token(token: &Token) -> Option<BinaryOperator> {
98 match token {
99 Token::And => Some(Self::BitAnd),
100 Token::AndAnd => Some(Self::And),
101 Token::BangEqual => Some(Self::NotEq),
102 Token::Caret => Some(Self::BitXor),
103 Token::EqualEqual => Some(Self::Eq),
104 Token::Gt => Some(Self::Gt),
105 Token::GtEqual => Some(Self::Gte),
106 Token::GtGt => Some(Self::Rshift),
107 Token::GtGtGt => Some(Self::RshiftUnsigned),
108 Token::Lt => Some(Self::Lt),
109 Token::LtEqual => Some(Self::Lte),
110 Token::LtLt => Some(Self::Lshift),
111 Token::Minus => Some(Self::Sub),
112 Token::Or => Some(Self::BitOr),
113 Token::OrOr => Some(Self::Or),
114 Token::Percent => Some(Self::Mod),
115 Token::Plus => Some(Self::Add),
116 Token::Slash => Some(Self::Div),
117 Token::Star => Some(Self::Mul),
118 Token::StarStar => Some(Self::Pow),
119 _ => None,
120 }
121 }
122
123 pub fn level(&self) -> OperatorLevel {
124 OperatorLevel::from(self)
125 }
126
127 /// Error case of this means 'types do not allow this operation'
128 pub fn perform(&self, left: Literal, right: Literal) -> Result<Literal, ()> {
129 match self {
130 BinaryOperator::Eq => Ok(Literal::Bool(left == right)),
131 BinaryOperator::NotEq => Ok(Literal::Bool(left != right)),
132
133 BinaryOperator::Or | BinaryOperator::And => match (left, right) {
134 (Literal::Bool(a), Literal::Bool(b)) => Ok(Literal::Bool(match self {
135 BinaryOperator::Or => a || b,
136 BinaryOperator::And => a && b,
137 _ => unreachable!(),
138 })),
139 _ => Err(()),
140 },
141
142 BinaryOperator::Add
143 | BinaryOperator::Sub
144 | BinaryOperator::Mul
145 | BinaryOperator::Div
146 | BinaryOperator::Pow
147 | BinaryOperator::Gt
148 | BinaryOperator::Lt
149 | BinaryOperator::Gte
150 | BinaryOperator::Lte
151 | BinaryOperator::Lshift
152 | BinaryOperator::Rshift
153 | BinaryOperator::RshiftUnsigned
154 | BinaryOperator::BitAnd
155 | BinaryOperator::BitOr
156 | BinaryOperator::BitXor
157 | BinaryOperator::Mod => match (left, right) {
158 (Literal::Number(a), Literal::Number(b)) => Ok(match self {
159 BinaryOperator::Add => Literal::Number(a + b),
160 BinaryOperator::Sub => Literal::Number(a - b),
161 BinaryOperator::Mul => Literal::Number(a * b),
162 BinaryOperator::Div => Literal::Number(a / b),
163 BinaryOperator::Pow => Literal::Number(a.powf(b)),
164 BinaryOperator::Gt => Literal::Bool(a > b),
165 BinaryOperator::Lt => Literal::Bool(a < b),
166 BinaryOperator::Gte => Literal::Bool(a >= b),
167 BinaryOperator::Lte => Literal::Bool(a <= b),
168 BinaryOperator::Lshift => Literal::Number(((a as i32) << (b as i32)).into()),
169 BinaryOperator::Rshift => Literal::Number(((a as i32) >> (b as i32)).into()),
170 BinaryOperator::RshiftUnsigned => {
171 Literal::Number(((a as u32) >> (b as u32)).into())
172 }
173 BinaryOperator::BitAnd => Literal::Number(((a as i32) & (b as i32)).into()),
174 BinaryOperator::BitOr => Literal::Number(((a as i32) | (b as i32)).into()),
175 BinaryOperator::BitXor => Literal::Number(((a as i32) ^ (b as i32)).into()),
176 BinaryOperator::Mod => Literal::Number(a % b),
177 _ => unreachable!(),
178 }),
179 _ => Err(()),
180 },
181
182 // Impossible condition doing assign-add to a literal
183 _ => Err(()),
184 }
185 }
186}
187
188impl UnaryOperator {
189 pub fn to_str(&self) -> &'static str {
190 match self {
191 Self::Neg => "-",
192 Self::Not => "!",
193 Self::BitNot => "~",
194 }
195 }
196
197 pub fn to_string(&self) -> String {
198 String::from(self.to_str())
199 }
200
201 pub fn perform(self, value: Literal) -> Result<Literal, ()> {
202 match self {
203 UnaryOperator::Neg => match value {
204 Literal::Number(a) => Ok(Literal::Number(-a)),
205 _ => Err(()),
206 },
207 UnaryOperator::Not => match value {
208 Literal::Bool(a) => Ok(Literal::Bool(!a)),
209 _ => Err(()),
210 },
211 UnaryOperator::BitNot => match value {
212 Literal::Number(a) => Ok(Literal::Number((!(a as i32)).into())),
213 _ => Err(()),
214 },
215 }
216 }
217}
218
219/// Taken from from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table
220///
221/// Blight doesn't have all of these operators, but the
222/// printer uses it to determine where to put parens.
223#[derive(PartialEq, PartialOrd, Debug)]
224pub enum OperatorLevel {
225 Accessor, // e. e[] e()
226 New, // new
227 Postfix, // expr++ expr--
228 Prefix, // ++e, --e, !e, ~e, +e, -e, typeof, void, delete
229
230 // these are the binary operators
231 Exponentiation, // **
232 Multiplicative, // *, /, %
233 Additive, // +, -
234 BitwiseShift, // <<, >>, >>>
235 Relational, // <, <=, >, >=
236 Equality, // ==, !=
237 BitwiseAnd, // &
238 BitwiseXor, // ^
239 BitwiseOr, // |
240 LogicalAnd, // &&
241 LogicalOr, // ||, ??
242
243 AssignmentAndOther, // =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |=
244 Comma, // ,
245}
246
247pub enum Associativity {
248 LeftToRight,
249 RightToLeft,
250}
251
252impl OperatorLevel {
253 pub fn associativity(&self) -> Associativity {
254 use Associativity::*;
255 use OperatorLevel::*;
256
257 match self {
258 Accessor => LeftToRight,
259 Exponentiation => RightToLeft,
260 Multiplicative => LeftToRight,
261 Additive => LeftToRight,
262 BitwiseShift => LeftToRight,
263 Relational => LeftToRight,
264 Equality => LeftToRight,
265 BitwiseAnd => LeftToRight,
266 BitwiseXor => LeftToRight,
267 BitwiseOr => LeftToRight,
268 LogicalAnd => LeftToRight,
269 LogicalOr => LeftToRight,
270 AssignmentAndOther => RightToLeft,
271 Comma => LeftToRight,
272
273 // These do not make sense to have an associativity
274 // You will likely not run into this if you do
275 // BinaryOperator::level(o).associativity()
276 New | Postfix | Prefix => unreachable!("{:?} does not have an associativity", self),
277 }
278 }
279}
280
281impl From<&BinaryOperator> for OperatorLevel {
282 fn from(operator: &BinaryOperator) -> Self {
283 use BinaryOperator::*;
284 use OperatorLevel::*;
285
286 match operator {
287 Add | Sub => Additive,
288 Mul | Div | Mod => Multiplicative,
289 Pow => Exponentiation,
290 Gt | Lt | Gte | Lte => Relational,
291 Lshift | Rshift | RshiftUnsigned => BitwiseShift,
292 BitAnd => BitwiseAnd,
293 BitOr => BitwiseOr,
294 BitXor => BitwiseXor,
295 Eq | NotEq => Equality,
296 And => LogicalAnd,
297 Or => LogicalOr,
298
299 Assign | AssignAdd | AssignSub | AssignMul | AssignDiv | AssignPow | AssignLshift
300 | AssignRshift | AssignRshiftUnsigned | AssignBitAnd | AssignBitOr | AssignBitXor
301 | AssignMod | AssignAnd | AssignOr => AssignmentAndOther,
302 }
303 }
304}
305
306impl From<UnaryOperator> for OperatorLevel {
307 fn from(operator: UnaryOperator) -> Self {
308 use OperatorLevel::*;
309 use UnaryOperator::*;
310
311 match operator {
312 Neg | Not | BitNot => Prefix,
313 }
314 }
315}
crates/ast/src/error.rs deleted-17
......@@ -1,17 +0,0 @@
1use miette::Diagnostic;
2use thiserror::Error;
3
4#[derive(Error, Diagnostic, Debug)]
5#[error("AstError")]
6pub struct AstError {
7 #[help]
8 error: String,
9}
10
11impl AstError {
12 pub fn new(error: &'static str) -> AstError {
13 AstError {
14 error: String::from(error),
15 }
16 }
17}
crates/ast/src/lib.rs deleted-27
......@@ -1,27 +0,0 @@
1use miette::Result;
2
3mod error;
4mod parse_block;
5mod parse_expression;
6mod parse_function_call;
7mod parse_statement;
8mod parse_type;
9mod types;
10
11use tokenizer::Token;
12
13use parse_statement::parse_statement;
14use types::ParserContext;
15
16pub use types::{Ast, Block, Type};
17
18pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> {
19 let mut ctx = ParserContext::new(tokens);
20 let mut ast: Vec<Ast> = Vec::new();
21
22 while !ctx.empty() {
23 ast.push(parse_statement(&mut ctx)?);
24 }
25
26 Ok(ast)
27}
crates/ast/src/parse_block.rs deleted-55
......@@ -1,55 +0,0 @@
1use miette::Result;
2
3use tokenizer::Token;
4
5use super::error::AstError;
6use super::parse_expression::parse_expression;
7use super::parse_statement::parse_statement;
8use super::types::Block;
9use super::types::ParserContext;
10
11pub fn parse_block(tokens: &mut ParserContext) -> Result<Block> {
12 match tokens.next() {
13 Some(Token::OpenBracket) => (),
14 _ => return Err(AstError::new("Expected { when parsing block").into()),
15 }
16
17 let mut value = Vec::new();
18 let mut implicit_return = false;
19
20 loop {
21 match tokens.peek() {
22 Some(Token::ClosedBracket) => {
23 tokens.next();
24 break;
25 }
26 _ => (),
27 }
28
29 let current_cursor = tokens.cursor;
30 let result = parse_statement(tokens);
31
32 match result {
33 Ok(ast) => value.push(ast),
34 Err(_) => {
35 tokens.cursor = current_cursor;
36 value.push(parse_expression(tokens)?);
37 implicit_return = true;
38 match tokens.peek() {
39 Some(Token::ClosedBracket) => (),
40 _ => {
41 return Err(AstError::new(
42 "Expected closed bracket after expression in function body",
43 )
44 .into());
45 }
46 }
47 }
48 }
49 }
50
51 Ok(Block {
52 value,
53 implicit_return,
54 })
55}
crates/ast/src/parse_expression.rs deleted-105
......@@ -1,105 +0,0 @@
1use miette::Result;
2
3use tokenizer::Token;
4
5use super::error::AstError;
6use super::parse_block::parse_block;
7use super::parse_function_call::parse_function_call;
8use super::types::{Ast, Block, ParserContext};
9
10pub fn parse_expression(tokens: &mut ParserContext) -> Result<Ast> {
11 let left = match tokens.next() {
12 Some(token) => match token {
13 Token::Number(number) => Ast::Number(number),
14 Token::String(string) => Ast::String(string.clone()),
15 Token::BuiltIn(name) => {
16 match tokens.peek() {
17 Some(Token::OpenParanthesis) => {
18 // FRICK we consumed the open paranthesis
19 tokens.back();
20 parse_function_call(tokens)?
21 }
22 _ => Ast::BuiltIn(name.clone()),
23 }
24 }
25 Token::True => Ast::Bool(true),
26 Token::False => Ast::Bool(false),
27 Token::OpenBracket => {
28 // FRICK we consumed the open bracket
29 // we need to put the cursor back
30 tokens.back();
31 Ast::Block(parse_block(tokens)?)
32 }
33 Token::If => {
34 let condition = parse_expression(tokens)?;
35
36 let value = parse_block(tokens)?;
37
38 let mut else_value: Option<Block> = None;
39
40 match tokens.peek() {
41 Some(Token::Else) => {
42 tokens.next();
43 match tokens.peek() {
44 Some(Token::If) => {
45 let else_expression = parse_expression(tokens)?;
46 else_value = Some(Block {
47 value: vec![else_expression],
48 implicit_return: true,
49 });
50 }
51 Some(Token::OpenBracket) => {
52 else_value = Some(parse_block(tokens)?);
53 }
54 _ => {
55 return Err(AstError::new(
56 "Expected if or open bracket after else in if statement",
57 )
58 .into())
59 }
60 }
61 }
62 _ => (),
63 }
64
65 return Ok(Ast::If {
66 condition: Box::new(condition),
67 value,
68 else_value,
69 });
70 }
71 Token::Identifier(name) => {
72 match tokens.peek() {
73 Some(Token::OpenParanthesis) => {
74 // FRICK we consumed the open paranthesis
75 tokens.back();
76 parse_function_call(tokens)?
77 }
78 _ => Ast::Identifier(name.clone()),
79 }
80 }
81 _ => {
82 println!("token: {:?}", token);
83 return Err(AstError::new("Expected expression, but did not get it").into());
84 }
85 },
86 None => return Err(AstError::new("Expected expression, but got nothing instead").into()),
87 };
88
89 match tokens.peek() {
90 Some(token) => match token {
91 Token::Operator(operator) => {
92 tokens.next();
93 return Ok(Ast::Operation {
94 operator: operator.clone(),
95 left: Box::new(left),
96 right: Box::new(parse_expression(tokens)?),
97 });
98 }
99 _ => (),
100 },
101 _ => (),
102 }
103
104 Ok(left)
105}
crates/ast/src/parse_function_call.rs deleted-56
......@@ -1,56 +0,0 @@
1use miette::Result;
2
3use tokenizer::Token;
4
5use super::error::AstError;
6use super::parse_expression::parse_expression;
7use super::types::{Ast, ParserContext};
8
9/// Parses a function call AS AN EXPRESSION
10/// This does not consume the semicolon at the end of the function call
11/// if it is a statement.
12pub fn parse_function_call(tokens: &mut ParserContext) -> Result<Ast> {
13 let identifier = match tokens.next() {
14 Some(Token::Identifier(identifier)) => Ast::Identifier(identifier),
15 Some(Token::BuiltIn(identifier)) => Ast::BuiltIn(identifier),
16 _ => return Err(AstError::new("Expected identifier").into()),
17 };
18
19 match tokens.peek() {
20 Some(Token::OpenParanthesis) => {
21 tokens.next();
22
23 let mut arguments = Vec::new();
24
25 loop {
26 match tokens.peek() {
27 Some(Token::ClosedParanthesis) => {
28 tokens.next();
29 break;
30 }
31 _ => (),
32 }
33 arguments.push(parse_expression(tokens)?);
34
35 match tokens.peek() {
36 Some(Token::Comma) => {
37 tokens.next();
38 }
39 Some(Token::ClosedParanthesis) => (),
40 _ => {
41 return Err(AstError::new(
42 "Expected ) or , but found something else instead.",
43 )
44 .into())
45 }
46 }
47 }
48
49 Ok(Ast::FunctionCall {
50 name: Box::new(identifier),
51 arguments,
52 })
53 }
54 _ => Err(AstError::new("Unexpected identifier").into()),
55 }
56}
crates/ast/src/parse_statement.rs deleted-261
......@@ -1,261 +0,0 @@
1use miette::Result;
2
3use tokenizer::Token;
4
5use super::error::AstError;
6use super::parse_block::parse_block;
7use super::parse_expression::parse_expression;
8use super::parse_function_call::parse_function_call;
9use super::parse_type::parse_type;
10use super::types::{Argument, Ast, Block, ParserContext};
11
12/// Precondition: There is at least one token in the iterator
13pub fn parse_statement(tokens: &mut ParserContext) -> Result<Ast> {
14 let token = tokens.next().unwrap();
15 match token {
16 Token::BuiltIn(name) => {
17 match tokens.peek() {
18 Some(Token::OpenParanthesis) => {
19 // Okay, so we messed up. We shouldn't have consumed that token.
20 tokens.back();
21 parse_function_call(tokens)
22 }
23 _ => Ok(Ast::BuiltIn(name)),
24 }
25 }
26 Token::OpenBracket => {
27 // FRICK, we have to backtrack
28 tokens.back();
29 Ok(Ast::Block(parse_block(tokens)?))
30 }
31 Token::Pub => {
32 match tokens.peek() {
33 Some(Token::Let) | Some(Token::Var) | Some(Token::Function) => (),
34 _ => return Err(AstError::new("Expected let, var, or fn after pub").into()),
35 }
36
37 let inner = parse_statement(tokens)?;
38
39 return Ok(Ast::Public(Box::new(inner)));
40 }
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 }
61 };
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() {
70 Some(Token::Identifier(name)) => name,
71 _ => return Err(AstError::new("Expected identifier after var").into()),
72 };
73 let return_type = match tokens.next() {
74 Some(Token::Colon) => parse_type(tokens)?,
75 _ => return Err(AstError::new("Expected colon after identifier").into()),
76 };
77 let value = match tokens.next() {
78 Some(Token::Equals) => parse_expression(tokens)?,
79 _ => return Err(AstError::new("Expected equals after identifier").into()),
80 };
81 match tokens.next() {
82 Some(Token::Semicolon) => (),
83 _ => {
84 return Err(
85 AstError::new("Expected semicolon after variable declaration").into(),
86 )
87 }
88 };
89 Ok(Ast::VariableDeclaration {
90 name: name.clone(),
91 return_type,
92 value: Box::new(value),
93 })
94 }
95 Token::Return => {
96 let expression = parse_expression(tokens)?;
97
98 match tokens.next() {
99 Some(Token::Semicolon) => (),
100 _ => return Err(AstError::new("Expected semicolon after return statement").into()),
101 };
102
103 Ok(Ast::Return(Box::new(expression)))
104 }
105 Token::Function => {
106 let name = match tokens.next() {
107 Some(Token::Identifier(name)) => name.clone(),
108 _ => return Err(AstError::new("Expected identifier after fn").into()),
109 };
110
111 match tokens.next() {
112 Some(Token::OpenParanthesis) => (),
113 _ => {
114 return Err(AstError::new(
115 "Expected open paranthesis after identifier in function",
116 )
117 .into())
118 }
119 };
120
121 let mut arguments = Vec::new();
122
123 loop {
124 match tokens.peek() {
125 Some(Token::ClosedParanthesis) => {
126 tokens.next();
127 break;
128 }
129 _ => (),
130 }
131
132 let argument_name = match tokens.next() {
133 Some(Token::Identifier(name)) => name,
134 _ => {
135 return Err(AstError::new(
136 "Expected identifier for argument name in function",
137 )
138 .into())
139 }
140 };
141
142 let return_type = match tokens.next() {
143 Some(Token::Colon) => parse_type(tokens)?,
144 _ => return Err(AstError::new("Expected colon after identifier").into()),
145 };
146
147 match tokens.peek() {
148 Some(Token::Comma) => {
149 tokens.next();
150 }
151 _ => (),
152 }
153
154 arguments.push(Argument {
155 name: argument_name.clone(),
156 return_type,
157 })
158 }
159
160 let return_type = match tokens.next() {
161 Some(Token::RightArrow) => parse_type(tokens)?,
162 _ => {
163 return Err(AstError::new(
164 "Expected right arrow after argument list in function",
165 )
166 .into())
167 }
168 };
169
170 match tokens.peek() {
171 Some(Token::OpenBracket) => (),
172 _ => {
173 return Err(AstError::new(
174 "Expected open bracket after type in function declaration",
175 )
176 .into())
177 }
178 };
179
180 let block = parse_block(tokens)?;
181
182 return Ok(Ast::Function {
183 name,
184 arguments,
185 return_type,
186 block,
187 });
188 }
189 Token::If => {
190 let condition = parse_expression(tokens)?;
191
192 let value = parse_block(tokens)?;
193
194 let mut else_value: Option<Block> = None;
195
196 match tokens.peek() {
197 Some(Token::Else) => {
198 tokens.next();
199 match tokens.peek() {
200 Some(Token::If) => {
201 let else_if = parse_statement(tokens)?;
202 else_value = Some(Block {
203 value: vec![else_if],
204 implicit_return: false,
205 });
206 }
207 Some(Token::OpenBracket) => {
208 else_value = Some(parse_block(tokens)?);
209 }
210 _ => {
211 return Err(AstError::new(
212 "Expected if or open bracket after else in if statement",
213 )
214 .into())
215 }
216 }
217 }
218 _ => (),
219 }
220
221 return Ok(Ast::If {
222 condition: Box::new(condition),
223 value,
224 else_value,
225 });
226 }
227 Token::Identifier(_) => match tokens.peek() {
228 Some(Token::OpenParanthesis) => {
229 // We need to give the identifier back to the parser
230 tokens.back();
231 let result = parse_function_call(tokens);
232 match tokens.next() {
233 Some(Token::Semicolon) => (),
234 _ => {
235 return Err(AstError::new(
236 "Expected semicolon after function call statement",
237 )
238 .into())
239 }
240 };
241 result
242 }
243 _ => Err(AstError::new("Unexpected identifier").into()),
244 },
245 Token::Comment(comment) => Ok(Ast::Comment(comment.clone())),
246 Token::DocComment(comment) => {
247 let mut concatted_comments = comment.clone();
248 loop {
249 match tokens.peek() {
250 Some(Token::DocComment(comment)) => {
251 concatted_comments.push_str(format!("\n{}", comment).as_str());
252 tokens.next();
253 }
254 _ => break,
255 }
256 }
257 Ok(Ast::DocComment(concatted_comments))
258 }
259 _ => return Err(AstError::new("Unexpected token").into()),
260 }
261}
crates/ast/src/parse_type.rs deleted-21
......@@ -1,21 +0,0 @@
1use miette::Result;
2
3use tokenizer::Token;
4
5use super::error::AstError;
6use super::types::{ParserContext, Type};
7
8pub fn parse_type(tokens: &mut ParserContext) -> Result<Type> {
9 match tokens.next() {
10 Some(Token::Identifier(typ)) => match typ.as_str() {
11 "int" => Ok(Type::Int),
12 "float" => Ok(Type::Float),
13 "bigint" => Ok(Type::Bigint),
14 "bool" => Ok(Type::Bool),
15 "string" => Ok(Type::String),
16 "any" => Ok(Type::Any),
17 _ => return Err(AstError::new("Expected type").into()),
18 },
19 _ => return Err(AstError::new("Expected type").into()),
20 }
21}
crates/ast/src/types.rs deleted-109
......@@ -1,109 +0,0 @@
1// TODO(lino-levan): Split up AST into expressions, statements, etc.
2
3use tokenizer::Token;
4use util::Operator;
5
6#[derive(Debug)]
7pub struct ParserContext {
8 pub tokens: Vec<Token>,
9 pub cursor: usize,
10}
11
12impl ParserContext {
13 pub fn new(tokens: Vec<Token>) -> ParserContext {
14 ParserContext { tokens, cursor: 0 }
15 }
16
17 pub fn empty(&self) -> bool {
18 self.cursor == self.tokens.len()
19 }
20
21 pub fn back(&mut self) {
22 self.cursor -= 1;
23 }
24
25 pub fn next(&mut self) -> Option<Token> {
26 if self.cursor == self.tokens.len() {
27 return None;
28 }
29
30 let token = self.tokens[self.cursor].clone();
31 self.cursor += 1;
32 Some(token)
33 }
34
35 pub fn peek(&self) -> Option<Token> {
36 if self.cursor == self.tokens.len() {
37 return None;
38 }
39
40 Some(self.tokens[self.cursor].clone())
41 }
42}
43
44#[derive(Debug, Clone)]
45pub enum Type {
46 Int,
47 Float,
48 Bigint,
49 Bool,
50 String,
51 Any,
52}
53
54#[derive(Debug, Clone)]
55pub struct Block {
56 pub value: Vec<Ast>,
57 pub implicit_return: bool,
58}
59
60#[derive(Debug, Clone)]
61pub struct Argument {
62 pub name: String,
63 pub return_type: Type,
64}
65
66#[derive(Debug, Clone)]
67pub enum Ast {
68 ConstantDeclaration {
69 name: String,
70 return_type: Type,
71 value: Box<Ast>,
72 },
73 VariableDeclaration {
74 name: String,
75 return_type: Type,
76 value: Box<Ast>,
77 },
78 Operation {
79 operator: Operator,
80 left: Box<Ast>,
81 right: Box<Ast>,
82 },
83 Function {
84 name: String,
85 arguments: Vec<Argument>,
86 return_type: Type,
87 block: Block,
88 },
89 FunctionCall {
90 /// Identifier or BuiltIn
91 name: Box<Ast>,
92 arguments: Vec<Ast>,
93 },
94 If {
95 condition: Box<Ast>,
96 value: Block,
97 else_value: Option<Block>,
98 },
99 Return(Box<Ast>),
100 Public(Box<Ast>),
101 Block(Block),
102 Number(f64),
103 Bool(bool),
104 String(String),
105 Identifier(String),
106 BuiltIn(String),
107 Comment(String),
108 DocComment(String),
109}
crates/ast/types.rs created+208
......@@ -0,0 +1,208 @@
1use miette::{Diagnostic, Result};
2use thiserror::Error;
3
4/// A value that is known at compile-time
5#[derive(Debug, Clone, PartialEq)]
6pub enum Literal {
7 Number(f64),
8 Bool(bool),
9 String(String),
10 Void,
11}
12
13impl std::fmt::Display for Literal {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 match self {
16 Literal::Number(value) => write!(f, "{}", value),
17 Literal::Bool(value) => write!(f, "{}", value),
18 Literal::String(value) => write!(f, "{}", value),
19 Literal::Void => write!(f, "void"),
20 }
21 }
22}
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum Type {
26 /// Variables without an explicit type are assigned this until
27 /// the type inferencer figures out what this is. There is not a
28 /// keyword for this.
29 Infer,
30 /// The type assigned to literals. This allows integer literals
31 /// to coerce to integer or float depending on the context, but
32 /// prevents implicit casting of runtime values.
33 Literal(Literal),
34 /// Return value of `@raw`. All type checks are disabled.
35 Any,
36 /// The type of `void`. This is the return type of functions
37 /// that do not return a value.
38 Void,
39 /// noreturn is the type of 'return', aka a value that never exists.
40 /// unlike void, this includes functions that do not return at all.
41 NoReturn,
42 /// 32 signed integer
43 Int,
44 /// 64 signed JS float
45 Float,
46 /// Boolean
47 Bool,
48 /// JS BigInt
49 BigInt,
50 /// JS String
51 String,
52 /// A union of multiple possible types. Might be removed
53 Union(Vec<Type>),
54}
55
56pub trait TypeCoerceable {
57 fn to_type(&self) -> Option<Type>;
58}
59
60impl TypeCoerceable for Literal {
61 fn to_type(&self) -> Option<Type> {
62 Some(match self {
63 Literal::Number(_) => Type::Float,
64 Literal::Bool(_) => Type::Bool,
65 Literal::String(_) => Type::String,
66 Literal::Void => Type::Void,
67 })
68 }
69}
70
71impl TypeCoerceable for Type {
72 fn to_type(&self) -> Option<Type> {
73 Some(self.clone())
74 }
75}
76
77impl std::fmt::Display for Type {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 match self {
80 Type::Infer => write!(f, "infered type"),
81 Type::Any => write!(f, "opaque js value"),
82 Type::Literal(value) => write!(f, "{}", value),
83 Type::Int => write!(f, "int"),
84 Type::Float => write!(f, "float"),
85 Type::BigInt => write!(f, "bigint"),
86 Type::Bool => write!(f, "bool"),
87 Type::String => write!(f, "string"),
88 Type::NoReturn => write!(f, "noreturn"),
89 Type::Void => write!(f, "void"),
90 Type::Union(of) => {
91 for (i, t) in of.iter().enumerate() {
92 if i != 0 {
93 write!(f, " | ")?;
94 }
95 write!(f, "{}", t)?;
96 }
97 Ok(())
98 }
99 }
100 }
101}
102
103pub fn merge_types(a: Type, b: Type) -> Result<Type> {
104 match a {
105 Type::Infer => return Ok(b),
106 Type::Any => return Ok(Type::Any),
107 _ => {}
108 }
109
110 match b {
111 Type::Infer => return Ok(a),
112 Type::Any => return Ok(Type::Any),
113 _ => {}
114 }
115
116 if a == b {
117 return Ok(a);
118 }
119
120 Err(TypeError::new(format!("TODO: Implement type merging for {} and {}", a, b)).into())
121}
122
123// pub fn coerce_type(target: Type, source: Type) -> Result<Type> {
124// match source {
125// Type::Any => return Ok(target),
126// _ => {}
127// }
128// match target {
129// Type::Infer => Ok(source),
130// Type::Any => Ok(Type::Any),
131
132// Type::Literal(_) => unreachable!(),
133
134// Type::Int => match source {
135// Type::Int => Ok(Type::Int),
136// Type::Float => Ok(Type::Int),
137// Type::Literal(value) => {
138// if value.fract() != 0.0 {
139// Err(
140// TypeError::new(format!("Numeric Literal '{}' is not an integer", value))
141// .into(),
142// )
143// } else if value > i32::MAX.into() || value < i32::MIN.into() {
144// Err(TypeError::new(format!(
145// "Numeric Literal '{}' is out of range for an integer ({}..{})",
146// value,
147// i32::MIN,
148// i32::MAX
149// ))
150// .into())
151// } else {
152// Ok(Type::Int)
153// }
154// }
155// t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()),
156// },
157
158// Type::BigInt => match source {
159// Type::Int => Ok(Type::Int),
160// Type::Float => Ok(Type::Int),
161// Type::NumberLiteral(value) => {
162// if value.fract() != 0.0 {
163// Err(
164// TypeError::new(format!("Numeric Literal '{}' is not an integer", value))
165// .into(),
166// )
167// } else {
168// Ok(Type::BigInt)
169// }
170// }
171// t => Err(TypeError::new(format!("Cannot coerce {} to int", t)).into()),
172// },
173
174// Type::Float => match source {
175// Type::Int => Ok(Type::Float),
176// Type::Float => Ok(Type::Float),
177// Type::NumberLiteral(value) => {
178// if value.fract() == 0.0 && value <= f64::MAX.into() && value >= f64::MIN.into() {
179// Ok(Type::Float)
180// } else {
181// Err(TypeError::new(format!("Numeric Literal")).into())
182// }
183// }
184// _ => Err(TypeError::new(format!("Cannot coerce int to float")).into()),
185// },
186
187// other => {
188// if other == source {
189// Ok(other)
190// } else {
191// Err(TypeError::new(format!("Cannot coerce {} to {}", source, other)).into())
192// }
193// }
194// }
195// }
196
197#[derive(Error, Diagnostic, Debug)]
198#[error("TypeError")]
199pub struct TypeError {
200 #[help]
201 error: String,
202}
203
204impl TypeError {
205 pub fn new(error: String) -> Self {
206 Self { error }
207 }
208}
crates/cli/Cargo.lock deleted-532
......@@ -1,532 +0,0 @@
1# This file is automatically @generated by Cargo.
2# It is not intended for manual editing.
3version = 3
4
5[[package]]
6name = "addr2line"
7version = "0.21.0"
8source = "registry+https://github.com/rust-lang/crates.io-index"
9checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
10dependencies = [
11 "gimli",
12]
13
14[[package]]
15name = "adler"
16version = "1.0.2"
17source = "registry+https://github.com/rust-lang/crates.io-index"
18checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
19
20[[package]]
21name = "anstream"
22version = "0.6.5"
23source = "registry+https://github.com/rust-lang/crates.io-index"
24checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6"
25dependencies = [
26 "anstyle",
27 "anstyle-parse",
28 "anstyle-query",
29 "anstyle-wincon",
30 "colorchoice",
31 "utf8parse",
32]
33
34[[package]]
35name = "anstyle"
36version = "1.0.4"
37source = "registry+https://github.com/rust-lang/crates.io-index"
38checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
39
40[[package]]
41name = "anstyle-parse"
42version = "0.2.3"
43source = "registry+https://github.com/rust-lang/crates.io-index"
44checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
45dependencies = [
46 "utf8parse",
47]
48
49[[package]]
50name = "anstyle-query"
51version = "1.0.2"
52source = "registry+https://github.com/rust-lang/crates.io-index"
53checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
54dependencies = [
55 "windows-sys",
56]
57
58[[package]]
59name = "anstyle-wincon"
60version = "3.0.2"
61source = "registry+https://github.com/rust-lang/crates.io-index"
62checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
63dependencies = [
64 "anstyle",
65 "windows-sys",
66]
67
68[[package]]
69name = "backtrace"
70version = "0.3.69"
71source = "registry+https://github.com/rust-lang/crates.io-index"
72checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
73dependencies = [
74 "addr2line",
75 "cc",
76 "cfg-if",
77 "libc",
78 "miniz_oxide",
79 "object",
80 "rustc-demangle",
81]
82
83[[package]]
84name = "backtrace-ext"
85version = "0.2.1"
86source = "registry+https://github.com/rust-lang/crates.io-index"
87checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50"
88dependencies = [
89 "backtrace",
90]
91
92[[package]]
93name = "bitflags"
94version = "2.4.1"
95source = "registry+https://github.com/rust-lang/crates.io-index"
96checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
97
98[[package]]
99name = "blight"
100version = "0.1.0"
101dependencies = [
102 "clap",
103 "miette",
104 "thiserror",
105]
106
107[[package]]
108name = "cc"
109version = "1.0.83"
110source = "registry+https://github.com/rust-lang/crates.io-index"
111checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
112dependencies = [
113 "libc",
114]
115
116[[package]]
117name = "cfg-if"
118version = "1.0.0"
119source = "registry+https://github.com/rust-lang/crates.io-index"
120checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
121
122[[package]]
123name = "clap"
124version = "4.4.12"
125source = "registry+https://github.com/rust-lang/crates.io-index"
126checksum = "dcfab8ba68f3668e89f6ff60f5b205cea56aa7b769451a59f34b8682f51c056d"
127dependencies = [
128 "clap_builder",
129 "clap_derive",
130]
131
132[[package]]
133name = "clap_builder"
134version = "4.4.12"
135source = "registry+https://github.com/rust-lang/crates.io-index"
136checksum = "fb7fb5e4e979aec3be7791562fcba452f94ad85e954da024396433e0e25a79e9"
137dependencies = [
138 "anstream",
139 "anstyle",
140 "clap_lex",
141 "strsim",
142]
143
144[[package]]
145name = "clap_derive"
146version = "4.4.7"
147source = "registry+https://github.com/rust-lang/crates.io-index"
148checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
149dependencies = [
150 "heck",
151 "proc-macro2",
152 "quote",
153 "syn",
154]
155
156[[package]]
157name = "clap_lex"
158version = "0.6.0"
159source = "registry+https://github.com/rust-lang/crates.io-index"
160checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
161
162[[package]]
163name = "colorchoice"
164version = "1.0.0"
165source = "registry+https://github.com/rust-lang/crates.io-index"
166checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
167
168[[package]]
169name = "errno"
170version = "0.3.8"
171source = "registry+https://github.com/rust-lang/crates.io-index"
172checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
173dependencies = [
174 "libc",
175 "windows-sys",
176]
177
178[[package]]
179name = "gimli"
180version = "0.28.1"
181source = "registry+https://github.com/rust-lang/crates.io-index"
182checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
183
184[[package]]
185name = "heck"
186version = "0.4.1"
187source = "registry+https://github.com/rust-lang/crates.io-index"
188checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
189
190[[package]]
191name = "hermit-abi"
192version = "0.3.3"
193source = "registry+https://github.com/rust-lang/crates.io-index"
194checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
195
196[[package]]
197name = "is-terminal"
198version = "0.4.10"
199source = "registry+https://github.com/rust-lang/crates.io-index"
200checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
201dependencies = [
202 "hermit-abi",
203 "rustix",
204 "windows-sys",
205]
206
207[[package]]
208name = "is_ci"
209version = "1.1.1"
210source = "registry+https://github.com/rust-lang/crates.io-index"
211checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb"
212
213[[package]]
214name = "libc"
215version = "0.2.151"
216source = "registry+https://github.com/rust-lang/crates.io-index"
217checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
218
219[[package]]
220name = "linux-raw-sys"
221version = "0.4.12"
222source = "registry+https://github.com/rust-lang/crates.io-index"
223checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
224
225[[package]]
226name = "memchr"
227version = "2.7.1"
228source = "registry+https://github.com/rust-lang/crates.io-index"
229checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
230
231[[package]]
232name = "miette"
233version = "5.10.0"
234source = "registry+https://github.com/rust-lang/crates.io-index"
235checksum = "59bb584eaeeab6bd0226ccf3509a69d7936d148cf3d036ad350abe35e8c6856e"
236dependencies = [
237 "backtrace",
238 "backtrace-ext",
239 "is-terminal",
240 "miette-derive",
241 "once_cell",
242 "owo-colors",
243 "supports-color",
244 "supports-hyperlinks",
245 "supports-unicode",
246 "terminal_size",
247 "textwrap",
248 "thiserror",
249 "unicode-width",
250]
251
252[[package]]
253name = "miette-derive"
254version = "5.10.0"
255source = "registry+https://github.com/rust-lang/crates.io-index"
256checksum = "49e7bc1560b95a3c4a25d03de42fe76ca718ab92d1a22a55b9b4cf67b3ae635c"
257dependencies = [
258 "proc-macro2",
259 "quote",
260 "syn",
261]
262
263[[package]]
264name = "miniz_oxide"
265version = "0.7.1"
266source = "registry+https://github.com/rust-lang/crates.io-index"
267checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
268dependencies = [
269 "adler",
270]
271
272[[package]]
273name = "object"
274version = "0.32.2"
275source = "registry+https://github.com/rust-lang/crates.io-index"
276checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
277dependencies = [
278 "memchr",
279]
280
281[[package]]
282name = "once_cell"
283version = "1.19.0"
284source = "registry+https://github.com/rust-lang/crates.io-index"
285checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
286
287[[package]]
288name = "owo-colors"
289version = "3.5.0"
290source = "registry+https://github.com/rust-lang/crates.io-index"
291checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
292
293[[package]]
294name = "proc-macro2"
295version = "1.0.73"
296source = "registry+https://github.com/rust-lang/crates.io-index"
297checksum = "2dd5e8a1f1029c43224ad5898e50140c2aebb1705f19e67c918ebf5b9e797fe1"
298dependencies = [
299 "unicode-ident",
300]
301
302[[package]]
303name = "quote"
304version = "1.0.34"
305source = "registry+https://github.com/rust-lang/crates.io-index"
306checksum = "22a37c9326af5ed140c86a46655b5278de879853be5573c01df185b6f49a580a"
307dependencies = [
308 "proc-macro2",
309]
310
311[[package]]
312name = "rustc-demangle"
313version = "0.1.23"
314source = "registry+https://github.com/rust-lang/crates.io-index"
315checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
316
317[[package]]
318name = "rustix"
319version = "0.38.28"
320source = "registry+https://github.com/rust-lang/crates.io-index"
321checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
322dependencies = [
323 "bitflags",
324 "errno",
325 "libc",
326 "linux-raw-sys",
327 "windows-sys",
328]
329
330[[package]]
331name = "smawk"
332version = "0.3.2"
333source = "registry+https://github.com/rust-lang/crates.io-index"
334checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
335
336[[package]]
337name = "strsim"
338version = "0.10.0"
339source = "registry+https://github.com/rust-lang/crates.io-index"
340checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
341
342[[package]]
343name = "supports-color"
344version = "2.1.0"
345source = "registry+https://github.com/rust-lang/crates.io-index"
346checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89"
347dependencies = [
348 "is-terminal",
349 "is_ci",
350]
351
352[[package]]
353name = "supports-hyperlinks"
354version = "2.1.0"
355source = "registry+https://github.com/rust-lang/crates.io-index"
356checksum = "f84231692eb0d4d41e4cdd0cabfdd2e6cd9e255e65f80c9aa7c98dd502b4233d"
357dependencies = [
358 "is-terminal",
359]
360
361[[package]]
362name = "supports-unicode"
363version = "2.0.0"
364source = "registry+https://github.com/rust-lang/crates.io-index"
365checksum = "4b6c2cb240ab5dd21ed4906895ee23fe5a48acdbd15a3ce388e7b62a9b66baf7"
366dependencies = [
367 "is-terminal",
368]
369
370[[package]]
371name = "syn"
372version = "2.0.44"
373source = "registry+https://github.com/rust-lang/crates.io-index"
374checksum = "92d27c2c202598d05175a6dd3af46824b7f747f8d8e9b14c623f19fa5069735d"
375dependencies = [
376 "proc-macro2",
377 "quote",
378 "unicode-ident",
379]
380
381[[package]]
382name = "terminal_size"
383version = "0.1.17"
384source = "registry+https://github.com/rust-lang/crates.io-index"
385checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
386dependencies = [
387 "libc",
388 "winapi",
389]
390
391[[package]]
392name = "textwrap"
393version = "0.15.2"
394source = "registry+https://github.com/rust-lang/crates.io-index"
395checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d"
396dependencies = [
397 "smawk",
398 "unicode-linebreak",
399 "unicode-width",
400]
401
402[[package]]
403name = "thiserror"
404version = "1.0.53"
405source = "registry+https://github.com/rust-lang/crates.io-index"
406checksum = "b2cd5904763bad08ad5513ddbb12cf2ae273ca53fa9f68e843e236ec6dfccc09"
407dependencies = [
408 "thiserror-impl",
409]
410
411[[package]]
412name = "thiserror-impl"
413version = "1.0.53"
414source = "registry+https://github.com/rust-lang/crates.io-index"
415checksum = "3dcf4a824cce0aeacd6f38ae6f24234c8e80d68632338ebaa1443b5df9e29e19"
416dependencies = [
417 "proc-macro2",
418 "quote",
419 "syn",
420]
421
422[[package]]
423name = "unicode-ident"
424version = "1.0.12"
425source = "registry+https://github.com/rust-lang/crates.io-index"
426checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
427
428[[package]]
429name = "unicode-linebreak"
430version = "0.1.5"
431source = "registry+https://github.com/rust-lang/crates.io-index"
432checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
433
434[[package]]
435name = "unicode-width"
436version = "0.1.11"
437source = "registry+https://github.com/rust-lang/crates.io-index"
438checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
439
440[[package]]
441name = "utf8parse"
442version = "0.2.1"
443source = "registry+https://github.com/rust-lang/crates.io-index"
444checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
445
446[[package]]
447name = "winapi"
448version = "0.3.9"
449source = "registry+https://github.com/rust-lang/crates.io-index"
450checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
451dependencies = [
452 "winapi-i686-pc-windows-gnu",
453 "winapi-x86_64-pc-windows-gnu",
454]
455
456[[package]]
457name = "winapi-i686-pc-windows-gnu"
458version = "0.4.0"
459source = "registry+https://github.com/rust-lang/crates.io-index"
460checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
461
462[[package]]
463name = "winapi-x86_64-pc-windows-gnu"
464version = "0.4.0"
465source = "registry+https://github.com/rust-lang/crates.io-index"
466checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
467
468[[package]]
469name = "windows-sys"
470version = "0.52.0"
471source = "registry+https://github.com/rust-lang/crates.io-index"
472checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
473dependencies = [
474 "windows-targets",
475]
476
477[[package]]
478name = "windows-targets"
479version = "0.52.0"
480source = "registry+https://github.com/rust-lang/crates.io-index"
481checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
482dependencies = [
483 "windows_aarch64_gnullvm",
484 "windows_aarch64_msvc",
485 "windows_i686_gnu",
486 "windows_i686_msvc",
487 "windows_x86_64_gnu",
488 "windows_x86_64_gnullvm",
489 "windows_x86_64_msvc",
490]
491
492[[package]]
493name = "windows_aarch64_gnullvm"
494version = "0.52.0"
495source = "registry+https://github.com/rust-lang/crates.io-index"
496checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
497
498[[package]]
499name = "windows_aarch64_msvc"
500version = "0.52.0"
501source = "registry+https://github.com/rust-lang/crates.io-index"
502checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
503
504[[package]]
505name = "windows_i686_gnu"
506version = "0.52.0"
507source = "registry+https://github.com/rust-lang/crates.io-index"
508checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
509
510[[package]]
511name = "windows_i686_msvc"
512version = "0.52.0"
513source = "registry+https://github.com/rust-lang/crates.io-index"
514checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
515
516[[package]]
517name = "windows_x86_64_gnu"
518version = "0.52.0"
519source = "registry+https://github.com/rust-lang/crates.io-index"
520checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
521
522[[package]]
523name = "windows_x86_64_gnullvm"
524version = "0.52.0"
525source = "registry+https://github.com/rust-lang/crates.io-index"
526checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
527
528[[package]]
529name = "windows_x86_64_msvc"
530version = "0.52.0"
531source = "registry+https://github.com/rust-lang/crates.io-index"
532checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
crates/cli/Cargo.toml+3-1
......@@ -5,7 +5,7 @@ edition = "2021"
55
66[[bin]]
77name = "blight"
8path = "./src/main.rs"
8path = "./main.rs"
99
1010[dependencies]
1111clap.workspace = true
......@@ -14,5 +14,7 @@ thiserror.workspace = true
1414
1515tokenizer = { path = "../tokenizer" }
1616ast = { path = "../ast" }
17parser = { path = "../parser" }
1718js_ast = { path = "../js_ast" }
1819js_printer = { path = "../js_printer" }
20sema = { path = "../sema" }
crates/cli/main.rs created+172
......@@ -0,0 +1,172 @@
1use std::{io::Write, path::PathBuf};
2
3use clap::{Parser, Subcommand};
4use miette::{diagnostic, Result};
5
6use js_printer;
7use tokenizer;
8
9#[derive(Parser)]
10#[command(author, version, about, long_about = None)]
11struct Cli {
12 /// Turn debugging information on
13 #[arg(short, long, action = clap::ArgAction::Count)]
14 debug: u8,
15
16 #[command(subcommand)]
17 command: Commands,
18}
19
20#[derive(Subcommand)]
21enum Commands {
22 /// Compiles a file into javascript
23 Compile {
24 file: PathBuf,
25 // Whether to output to stdout instead of a file
26 #[arg(long, default_value_t = false)]
27 stdout: bool,
28 /// What javascript runtime to compile to
29 #[arg(short, long, default_value = "deno")]
30 target: String,
31 /// Format the output javascript?
32 #[arg(long, default_value_t = true)]
33 format: bool,
34 },
35 Run {
36 file: PathBuf,
37 /// What javascript runtime to compile to and use
38 #[arg(short, long, default_value = "deno")]
39 target: String,
40 },
41 Astdump {
42 file: PathBuf,
43 },
44}
45
46fn get_runtime(runtime: &String) -> String {
47 match std::env::var("USER") {
48 Ok(user) if user == "dave" => String::from("bun"),
49 _ => runtime.clone(),
50 }
51}
52
53pub fn transpile(content: String, writer: &mut dyn Write) -> Result<()> {
54 // 1. Tokenize the input
55 let tokens = tokenizer::tokenize(content)?;
56 // 2. Parse the tokens into an AST
57 let mut blight_ast = parser::parse(tokens)?;
58 // 3. Semantic analysis.
59 sema::analyze_module(&mut blight_ast)?;
60 // 4. Optimize the AST
61 // optimize::optimize_module(&mut blight_ast);
62 // 5. Translate the AST into javascript
63 let js_ast = js_ast::blight_to_js(blight_ast);
64 // 6. Print the JS AST
65 // TODO(@paperdave): use std::io::Write instead of a string
66 js_printer::Print::print(&js_ast, writer)
67 .map_err(|e| diagnostic!("Error while printing: {}", e))?;
68 Ok(())
69}
70
71pub fn transpile_to_string(content: String) -> Result<String> {
72 let mut buffer = Vec::new();
73 transpile(content, &mut buffer)?;
74 Ok(String::from_utf8(buffer).unwrap())
75}
76
77pub fn astdump(content: String) -> Result<String> {
78 // 1. Tokenize the input
79 let tokens = tokenizer::tokenize(content)?;
80 // 2. Parse the tokens into an AST
81 let blight_ast = parser::parse(tokens)?;
82 return Ok(format!("{:#?}", blight_ast));
83}
84
85fn main() -> Result<()> {
86 let cli = Cli::parse();
87
88 match cli.command {
89 Commands::Compile {
90 file,
91 stdout,
92 target: _,
93 format,
94 } => {
95 let contents = match std::fs::read_to_string(file.clone()) {
96 Ok(contents) => contents,
97 Err(e) => {
98 eprintln!("Failed to read {}: {}", file.display(), e);
99 std::process::exit(1);
100 }
101 };
102
103 let mut result = transpile_to_string(contents.clone())?;
104
105 if format {
106 let mut formatter = std::process::Command::new("deno")
107 .stdin(std::process::Stdio::piped())
108 .stdout(std::process::Stdio::piped())
109 .stderr(std::process::Stdio::inherit())
110 .arg("fmt")
111 .arg("-")
112 .spawn()
113 .expect("Failed to execute 'deno fmt' on output");
114 let mut stdin = formatter.stdin.take().expect("Failed to open stdin");
115 stdin
116 .write_all(result.as_bytes())
117 .expect("Failed to write to stdin");
118 stdin.flush().expect("Failed to flush stdin");
119 drop(stdin);
120 let out = formatter
121 .wait_with_output()
122 .expect("Failed to execute 'deno fmt' on output");
123 result = String::from_utf8(out.stdout).unwrap();
124 }
125
126 if stdout {
127 println!("{}", result);
128 } else {
129 std::fs::write(file.with_extension("js"), result).unwrap();
130 }
131 }
132 Commands::Run { file, target } => {
133 let contents = std::fs::read_to_string(file.clone()).unwrap();
134 let result = transpile_to_string(contents.clone())?;
135 let mut temp_file = std::env::temp_dir();
136 temp_file.push("temp.js");
137 std::fs::write(temp_file.clone(), result).unwrap();
138
139 let rt = get_runtime(&target);
140
141 match rt.as_str() {
142 "deno" => std::process::Command::new("deno")
143 .arg("run")
144 .arg("-A")
145 .arg(temp_file)
146 .spawn()
147 .expect("Failed to execute process"),
148 "node" => std::process::Command::new("node")
149 .arg(temp_file)
150 .spawn()
151 .expect("Failed to execute process"),
152 "bun" => std::process::Command::new("bun")
153 .arg("run")
154 .arg(temp_file)
155 .spawn()
156 .expect("Failed to execute process"),
157 _ => {
158 panic!("Unknown runtime: {}", target);
159 }
160 }
161 .wait()
162 .unwrap();
163 }
164 Commands::Astdump { file } => {
165 let contents = std::fs::read_to_string(file.clone()).unwrap();
166 let result = astdump(contents.clone())?;
167 println!("{}", result);
168 }
169 }
170
171 Ok(())
172}
crates/cli/src/main.rs deleted-118
......@@ -1,118 +0,0 @@
1use std::path::PathBuf;
2
3use clap::{Parser, Subcommand};
4use miette::Result;
5
6use ast;
7use js_printer;
8use tokenizer;
9
10#[derive(Parser)]
11#[command(author, version, about, long_about = None)]
12struct Cli {
13 /// Turn debugging information on
14 #[arg(short, long, action = clap::ArgAction::Count)]
15 debug: u8,
16
17 #[command(subcommand)]
18 command: Commands,
19}
20
21#[derive(Subcommand)]
22enum Commands {
23 /// Compiles a file into javascript
24 Compile {
25 file: PathBuf,
26 // Whether to output to stdout instead of a file
27 #[arg(long, default_value_t = false)]
28 stdout: bool,
29 /// What javascript runtime to compile to
30 #[arg(short, long, default_value = "deno")]
31 target: String,
32 },
33 Run {
34 file: PathBuf,
35 /// What javascript runtime to compile to and use
36 #[arg(short, long, default_value = "deno")]
37 target: String,
38 },
39}
40
41fn get_runtime(runtime: &String) -> String {
42 match std::env::var("USER") {
43 Ok(user) if user == "dave" => String::from("bun"),
44 _ => runtime.clone(),
45 }
46}
47
48pub fn transpile(content: String) -> Result<String> {
49 // 1. Tokenize the input
50 let tokens = tokenizer::tokenize(content)?;
51 // 2. Parse the tokens into an AST
52 let blight_ast = ast::parse(tokens)?;
53 // 3. Type check the AST
54 // type_check(tree.clone())?;
55 // 4. Optimize the AST
56 // optimize(tree.clone())?;
57 // 5. Translate the AST into javascript
58 let js_ast = js_ast::translate(blight_ast)?;
59 // 6. Print the JS AST
60 let javascript = js_printer::print(js_ast);
61 // 7. Return the javascript
62 return Ok(javascript);
63}
64
65fn main() -> Result<()> {
66 let cli = Cli::parse();
67
68 match cli.command {
69 Commands::Compile {
70 file,
71 stdout,
72 target: _,
73 } => {
74 let contents = std::fs::read_to_string(file.clone()).unwrap();
75 let result = transpile(contents.clone())?;
76
77 if stdout {
78 println!("{}", result);
79 } else {
80 std::fs::write(file.with_extension("js"), result).unwrap();
81 }
82 }
83 Commands::Run { file, target } => {
84 let contents = std::fs::read_to_string(file.clone()).unwrap();
85 let result = transpile(contents.clone())?;
86 let mut temp_file = std::env::temp_dir();
87 temp_file.push("temp.js");
88 std::fs::write(temp_file.clone(), result).unwrap();
89
90 let rt = get_runtime(&target);
91
92 match rt.as_str() {
93 "deno" => std::process::Command::new("deno")
94 .arg("run")
95 .arg("-A")
96 .arg(temp_file)
97 .spawn()
98 .expect("Failed to execute process"),
99 "node" => std::process::Command::new("node")
100 .arg(temp_file)
101 .spawn()
102 .expect("Failed to execute process"),
103 "bun" => std::process::Command::new("bun")
104 .arg("run")
105 .arg(temp_file)
106 .spawn()
107 .expect("Failed to execute process"),
108 _ => {
109 panic!("Unknown runtime: {}", target);
110 }
111 }
112 .wait()
113 .unwrap();
114 }
115 }
116
117 Ok(())
118}
crates/js_ast/Cargo.toml+3-1
......@@ -4,6 +4,7 @@ version = "0.1.0"
44edition = "2021"
55
66[lib]
7path = "./lib.rs"
78
89[dependencies]
910miette.workspace = true
......@@ -11,4 +12,5 @@ thiserror.workspace = true
1112num.workspace = true
1213
1314ast = { path = "../ast" }
14util = { path = "../util" }
15tokenizer = { path = "../tokenizer" } # TODO: move Loc out of the tokenizer
16parser = { path = "../parser" } # TODO: move Loc out of the tokenizer
crates/js_ast/convert/block_convert_context.rs created+244
......@@ -0,0 +1,244 @@
1use crate::js_ast::*;
2use ast::*;
3use std::collections::{HashMap, HashSet};
4use std::vec;
5use tokenizer::Loc;
6
7use super::tree_shake_statement_list;
8
9/// A block convert context keeps track of state while converting non-valid JS expressions to JS
10pub struct BlockConvertContext {
11 /// Statements in this block context
12 /// This may be the merged contents of multiple blocks
13 statements: Vec<JsStatement>,
14 ids: HashSet<String>,
15 next_id: u32,
16 scopes: Vec<VirtualScope>,
17 /// If the last item in `statements` is actually valid
18 has_last_statement: bool,
19}
20
21struct VirtualScope {
22 renames: HashMap<String, String>,
23}
24
25pub struct TempId {
26 unmapped_name: String,
27}
28
29pub struct StatementHandle {
30 index: Option<u32>,
31}
32
33impl StatementHandle {
34 pub fn delete(self, ctx: &mut BlockConvertContext) {
35 if let Some(index) = self.index {
36 ctx.statements.remove(index as usize);
37 }
38 }
39}
40
41impl TempId {
42 pub fn get(&self, hoist_context: &BlockConvertContext) -> String {
43 hoist_context.remap_identifier(&self.unmapped_name)
44 }
45}
46
47impl BlockConvertContext {
48 pub fn new() -> Self {
49 Self {
50 statements: Vec::new(),
51 ids: HashSet::new(),
52 next_id: 0,
53 scopes: vec![VirtualScope {
54 renames: HashMap::new(),
55 }],
56 has_last_statement: false,
57 }
58 }
59
60 pub fn new_child(&self) -> Self {
61 Self {
62 statements: Vec::new(),
63 ids: self.ids.clone(),
64 next_id: 0,
65 scopes: vec![VirtualScope {
66 renames: HashMap::new(),
67 }],
68 has_last_statement: false,
69 }
70 }
71
72 /// A virtual scope is a scope that exists in blight syntax
73 /// but may or may not exist in blight. With these it is important to
74 pub fn push_virtual_scope(&mut self) {
75 self.scopes.push(VirtualScope {
76 renames: HashMap::new(),
77 });
78 }
79
80 /// See `push_virtual_scope` for more information on what this is.
81 pub fn pop_virtual_scope(&mut self) {
82 self.scopes.pop();
83 debug_assert!(self.scopes.len() > 0);
84 }
85
86 pub fn generate_temp_id(&mut self, basename: &str) -> TempId {
87 self.next_id += 1;
88 loop {
89 let name = format!("{}{}", basename, self.next_id);
90 if !self.ids.contains(&name) {
91 self.ids.insert(name.clone());
92 return TempId {
93 unmapped_name: name,
94 };
95 }
96 self.next_id += 1;
97 }
98 }
99
100 /// This does some extra optimizations:
101 /// - `let a = b;` will not be emitted but instad write a variable alias
102 pub fn add_statement(&mut self, statement: JsStatement) -> StatementHandle {
103 match statement.data {
104 JsStatementData::Declaration(JSDeclaration {
105 name,
106 init:
107 Some(JsExpr {
108 data: JsExprData::Identifier(init_name),
109 ..
110 }),
111 is_constant: true,
112 }) => {
113 self.scopes
114 .last_mut()
115 .unwrap()
116 .renames
117 .insert(name, init_name);
118 self.has_last_statement = false;
119 return StatementHandle { index: None };
120 }
121 _ => {}
122 }
123 self.statements.push(statement);
124 self.has_last_statement = true;
125 StatementHandle {
126 index: Some(self.statements.len() as u32 - 1),
127 }
128 }
129
130 /// Get the last statement if it exists.
131 /// If the statement was optimized away, this returns None
132 pub fn last_statement(&self) -> Option<&JsStatement> {
133 if self.has_last_statement {
134 Some(self.statements.last().unwrap())
135 } else {
136 None
137 }
138 }
139
140 pub fn generate_id(&mut self, basename: &String) -> TempId {
141 let mut id = 0u32;
142 loop {
143 let name = match id {
144 0 => basename.clone(),
145 _ => format!("{}{}", basename, id),
146 };
147 if !self.ids.contains(&name) {
148 self.ids.insert(name.clone());
149 return TempId {
150 unmapped_name: name,
151 };
152 }
153 id += 1;
154 }
155 }
156
157 /// Given a blight identifier, remap it to a new identifier.
158 pub fn remap_identifier(&self, name: &String) -> String {
159 for scope in self.scopes.iter().rev() {
160 if let Some(remapped) = scope.renames.get(name) {
161 return remapped.clone();
162 }
163 }
164 name.clone()
165 }
166
167 /// Declare an identifier in the current scope.
168 pub fn declare_identifier(&mut self, name: String) -> TempId {
169 let remapped = self.generate_id(&name);
170 let scope = self.scopes.last_mut().unwrap();
171 self.ids.insert(remapped.unmapped_name.clone());
172 scope.renames.insert(name, remapped.unmapped_name.clone());
173 remapped
174 }
175
176 pub fn take_last_statement(&mut self) -> Option<JsStatement> {
177 if !self.has_last_statement {
178 return None;
179 }
180 self.has_last_statement = false;
181 Some(self.statements.pop().unwrap())
182 }
183
184 pub fn take_last_statement_if_expr(&mut self) -> Option<JsExpr> {
185 if !self.has_last_statement {
186 return None;
187 }
188 match self.statements.last().unwrap().data {
189 JsStatementData::Expression(_) => {
190 self.has_last_statement = false;
191 match self.statements.pop().unwrap().data {
192 JsStatementData::Expression(expr) => Some(expr),
193 _ => unreachable!(),
194 }
195 }
196 _ => return None,
197 }
198 }
199}
200
201pub fn new_js_assignment_statement(expr: JsExpr, target: String, loc: Loc) -> JsStatement {
202 JsStatement::new(
203 JsStatementData::Expression(new_js_assignment_expr(expr, target, loc.clone())),
204 loc,
205 None,
206 )
207}
208
209pub fn new_js_assignment_expr(expr: JsExpr, target: String, loc: Loc) -> JsExpr {
210 JsExpr::new(
211 JsExprData::Binary {
212 op: BinaryOperator::Assign,
213 left: Box::new(JsExpr::new(
214 JsExprData::Identifier(target),
215 loc.clone(),
216 None,
217 )),
218 right: Box::new(expr),
219 },
220 loc,
221 None,
222 )
223}
224
225impl Into<JsBlock> for BlockConvertContext {
226 fn into(self) -> JsBlock {
227 JsBlock::new(self.into(), Loc::EMPTY)
228 }
229}
230
231impl Into<JsModule> for BlockConvertContext {
232 fn into(self) -> JsModule {
233 JsModule {
234 statements: self.into(),
235 }
236 }
237}
238
239impl Into<Vec<JsStatement>> for BlockConvertContext {
240 fn into(mut self) -> Vec<JsStatement> {
241 tree_shake_statement_list(&mut self.statements);
242 self.statements
243 }
244}
crates/js_ast/convert/convert_valid_block.rs created+37
......@@ -0,0 +1,37 @@
1use super::convert_valid_statement_to_expr;
2use super::BlockConvertContext;
3use super::IsValidJSExpression;
4use crate::js_ast::*;
5use ast::*;
6use tokenizer::Loc;
7
8pub fn convert_valid_block(ctx: &mut BlockConvertContext, block: Block, loc: Loc) -> JsExpr {
9 debug_assert!(
10 block.is_valid_js_expression(),
11 "must call convert_valid_block with valid js expression"
12 );
13
14 let len = block.statements.len();
15 let loc = loc;
16 let block_expr = match len {
17 0 => JsExpr::new(JsExprData::Null, loc.clone(), None),
18 // a block expression with a single expression is just that expression
19 1 => convert_valid_statement_to_expr(ctx, block.statements.into_iter().next().unwrap()),
20 // Since blocks are not valid expressions in JS, we create a
21 // comma expression from a block with more than one statement.
22 2.. => {
23 let mut items = Vec::new();
24 for statement in block.statements.into_iter() {
25 items.push(convert_valid_statement_to_expr(ctx, statement));
26 }
27 JsExpr::new(JsExprData::Comma { items }, loc.clone(), None)
28 }
29 };
30
31 // a block without implicit return is to be type void, so we wrap in a void expression
32 if !block.has_implicit_return && len > 0 {
33 JsExpr::new(JsExprData::Void(Box::new(block_expr)), loc, None)
34 } else {
35 block_expr
36 }
37}
crates/js_ast/convert/convert_valid_expr.rs created+115
......@@ -0,0 +1,115 @@
1use super::convert_valid_block;
2use super::BlockConvertContext;
3use crate::convert::convert_valid_if;
4use crate::convert::process_function::process_function_to_arrow;
5use crate::convert::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8
9/// Translate a blight ast expression that is a valid JS expression, to a JS expression
10/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
11pub fn convert_valid_expr(ctx: &mut BlockConvertContext, expr: Expr) -> JsExpr {
12 // Assert may be skipped in release builds as all failures are
13 // annotated with a release-checked panic
14 debug_assert!(
15 expr.is_valid_js_expression(),
16 "must call convert_valid_expr with valid js expression (use hoist instead)"
17 );
18
19 let loc = expr.loc;
20 let comment = expr.comment;
21
22 match expr.data {
23 ExprData::Identifier(str) => JsExpr::new(
24 JsExprData::Identifier(ctx.remap_identifier(&str)),
25 loc,
26 comment,
27 ),
28
29 ExprData::Literal(Literal::Bool(value)) => {
30 JsExpr::new(JsExprData::Bool(value), loc, comment)
31 }
32 ExprData::Literal(Literal::Number(value)) => {
33 JsExpr::new(JsExprData::Number(value), loc, comment)
34 }
35 ExprData::Literal(Literal::String(value)) => {
36 JsExpr::new(JsExprData::String(value), loc, comment)
37 }
38 ExprData::Literal(Literal::Void) => JsExpr::new(JsExprData::Null, loc, comment),
39
40 ExprData::Block(block) => convert_valid_block(ctx, block, loc),
41
42 ExprData::Binary { op, left, right } => {
43 let left = convert_valid_expr(ctx, *left);
44 let right = convert_valid_expr(ctx, *right);
45 JsExpr::new(
46 JsExprData::Binary {
47 op,
48 left: Box::new(left),
49 right: Box::new(right),
50 },
51 loc,
52 comment,
53 )
54 }
55 ExprData::Unary { op, value } => {
56 let value = convert_valid_expr(ctx, *value);
57 JsExpr::new(
58 JsExprData::Unary {
59 op,
60 value: Box::new(value),
61 },
62 loc,
63 comment,
64 )
65 }
66 ExprData::Fn { body, params, .. } => {
67 process_function_to_arrow(ctx, *body, params, loc, comment)
68 }
69 ExprData::Builtin { id, arguments } => match id {
70 BuiltinId::Raw => {
71 let first_arg = arguments.iter().next().unwrap();
72 // TODO(@paperdave): this string clone is stupid
73 let str = match first_arg.data.clone() {
74 ExprData::Literal(Literal::String(s)) => s,
75 _ => panic!("raw expects a string"),
76 };
77 JsExpr::new(JsExprData::Raw(str), loc, comment)
78 }
79 },
80 ExprData::If(expr) => convert_valid_if(ctx, expr, loc, comment),
81
82 ExprData::Call { base, arguments } => {
83 let base = convert_valid_expr(ctx, *base);
84 let arguments = arguments
85 .into_iter()
86 .map(|x| convert_valid_expr(ctx, x))
87 .collect();
88 JsExpr::new(
89 JsExprData::Call {
90 base: Box::new(base),
91 arguments,
92 },
93 loc,
94 comment,
95 )
96 }
97
98 ExprData::Field { base, field } => JsExpr::new(
99 match field.data {
100 ExprData::Literal(Literal::String(name)) => JsExprData::Dot {
101 base: Box::new(convert_valid_expr(ctx, *base)),
102 name,
103 },
104 _ => JsExprData::Index {
105 base: Box::new(convert_valid_expr(ctx, *base)),
106 index: Box::new(convert_valid_expr(ctx, *field)),
107 },
108 },
109 loc,
110 comment,
111 ),
112
113 ExprData::Return(_) => unreachable!("convert_valid_expr called on return"),
114 }
115}
crates/js_ast/convert/convert_valid_if.rs created+38
......@@ -0,0 +1,38 @@
1use super::convert_valid_block;
2use super::convert_valid_expr;
3use super::BlockConvertContext;
4use crate::convert::IsValidJSExpression;
5use crate::js_ast::*;
6use ast::*;
7use tokenizer::Loc;
8
9/// Convert a valid blight if statement to a JS expression
10pub fn convert_valid_if(
11 ctx: &mut BlockConvertContext,
12 expr: If,
13 loc: Loc,
14 comment: Option<Comment>,
15) -> JsExpr {
16 debug_assert!(
17 expr.is_valid_js_expression(),
18 "convert_valid_if on an invalid expression (use hoist instead)"
19 );
20 let condition = convert_valid_expr(ctx, *expr.condition);
21
22 let yes = convert_valid_block(ctx, expr.yes.block, expr.yes.loc);
23 let no = match expr.no {
24 Some(BlockOrIf::Block(b)) => Some(convert_valid_block(ctx, b.block, b.loc)),
25 Some(BlockOrIf::If(else_if)) => Some(convert_valid_if(ctx, *else_if, Loc::EMPTY, None)),
26 None => None,
27 };
28
29 JsExpr::new(
30 JsExprData::Ternary {
31 condition: Box::new(condition),
32 yes: Box::new(yes),
33 no: no.map(Box::new),
34 },
35 loc,
36 comment,
37 )
38}
crates/js_ast/convert/convert_valid_statement.rs created+32
......@@ -0,0 +1,32 @@
1use super::convert_valid_expr;
2use super::is_valid_js_expression::*;
3use super::BlockConvertContext;
4use crate::js_ast::*;
5use ast::*;
6
7/// Translate a blight ast statement that is a valid JS expression, to a JS expression
8/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
9pub fn convert_valid_statement_to_expr(
10 ctx: &mut BlockConvertContext,
11 statement: Statement,
12) -> JsExpr {
13 debug_assert!(
14 statement.is_valid_js_expression(),
15 "convert_valid_statement_to_expr on an invalid expression (use hoist instead)"
16 );
17
18 match statement {
19 ast::Statement::Expr(expr) => convert_valid_expr(ctx, expr),
20 _ => unreachable!("attempt to convert statement to js without hoisting"),
21 }
22}
23
24/// Translate a blight ast statement that is a valid JS expression, to a JS statement
25/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
26///
27/// TODO(@paperdave): Replace this with Into<JsStatement> for JsExpr
28pub fn convert_valid_statement(ctx: &mut BlockConvertContext, statement: Statement) -> JsStatement {
29 let expr = convert_valid_statement_to_expr(ctx, statement);
30 let loc = expr.loc.clone();
31 JsStatement::new(JsStatementData::Expression(expr), loc, None)
32}
crates/js_ast/convert/hoist_block_to_expr.rs created+78
......@@ -0,0 +1,78 @@
1use super::is_valid_js_expression::*;
2use super::process_statement;
3use super::BlockConvertContext;
4use crate::js_ast::*;
5use ast::*;
6use tokenizer::Loc;
7
8/// This block hoist is used when we have a block in an expression context.
9///
10/// All the expressions in the block are lifted above the currently
11/// processed expression, and then a temporary variable is returned.
12/// (or the last expression might get inlined as the return value)
13///
14/// A "virtual scope" is created for this block, which handles renaming
15/// situations like:
16///
17/// let x = 1;
18/// {
19/// let x = 1;
20/// }
21/// call(x)
22///
23/// The output JS of the above would be:
24///
25/// const x = 1;
26/// const x2 = 1;
27/// call(x)
28///
29/// For expressions that have branches, use `hoist_block_to_js_block`
30pub fn hoist_block_to_expr(
31 ctx: &mut BlockConvertContext,
32 block: Block,
33 loc: Loc,
34 comment: Option<Comment>,
35) -> Option<JsExpr> {
36 debug_assert!(
37 !block.is_valid_js_expression(),
38 "hoist_block on a valid expression, should call convert_valid_block instead"
39 );
40
41 let temporary = ctx.generate_temp_id("_block");
42
43 ctx.push_virtual_scope();
44
45 for statement in block.statements {
46 process_statement(ctx, statement);
47 }
48
49 ctx.pop_virtual_scope();
50
51 // If the last statement is an expression, we need
52 // to assign to the temporary variable.
53 let result = match ctx.last_statement().map(|x| &x.data) {
54 Some(JsStatementData::Expression(_)) if block.has_implicit_return => {
55 match ctx.take_last_statement().unwrap().data {
56 JsStatementData::Expression(expr) => expr,
57 _ => unreachable!(),
58 }
59 }
60 _ => JsExpr::new(JsExprData::Null, loc.clone(), None),
61 };
62
63 ctx.add_statement(JsStatement::new(
64 JsStatementData::Declaration(JSDeclaration {
65 name: temporary.get(&ctx),
66 init: Some(result),
67 is_constant: false,
68 }),
69 loc.clone(),
70 comment,
71 ));
72
73 Some(JsExpr::new(
74 JsExprData::Identifier(temporary.get(&ctx)),
75 loc,
76 None,
77 ))
78}
crates/js_ast/convert/hoist_block_to_js_block.rs created+68
......@@ -0,0 +1,68 @@
1use crate::js_ast::*;
2use ast::*;
3use tokenizer::Loc;
4
5use super::*;
6
7/// This block hoist is used when we actually need to create a JsBlock, which
8/// is needed if there is an invalid expression within a branch of code.
9///
10/// For example, `if` has two branches, the 'yes' and 'no' paths.
11/// When we cannot lower it as a ternary, we have to create two JsBlocks
12///
13/// The `target` param is a shared temporary variable that will be assigned
14/// to the result of the block.
15///
16/// Since blocks can be void or noreturn, this target is not always
17/// assigned to. The `did_assign` flag is used to indicate that.
18///
19/// For expressions that do not have branches, use `hoist_block_to_expr`
20///
21/// TODO(@paperdave): move `did_assign` into the return value
22pub fn hoist_block_to_js_block(
23 ctx: &mut BlockConvertContext,
24 block: Block,
25 loc: Loc,
26 target: &TempId,
27 did_assign: &mut bool,
28) -> JsBlock {
29 // A non-block will become a block with a single expression:
30 // _temp = expr;
31 //
32 // We will also apply this transformation to blocks that are valid
33 // expressions, because that is more concise.
34 if block.is_valid_js_expression() {
35 return JsBlock::new(
36 vec![new_js_assignment_statement(
37 convert_valid_block(ctx, block, loc.clone()),
38 target.get(ctx),
39 loc.clone(),
40 )],
41 loc,
42 );
43 }
44
45 let mut child = ctx.new_child();
46
47 for statement in block.statements {
48 if let None = process_statement(&mut child, statement) {
49 return child.into();
50 };
51 }
52
53 // If the last statement is an expression, we need
54 // to assign to the temporary variable.
55 match child.take_last_statement_if_expr() {
56 Some(expr) => {
57 *did_assign = true;
58 child.add_statement(new_js_assignment_statement(
59 expr,
60 target.get(ctx),
61 loc.clone(),
62 ));
63 }
64 _ => {}
65 }
66
67 child.into()
68}
crates/js_ast/convert/hoist_expr.rs created+132
......@@ -0,0 +1,132 @@
1use super::convert_valid_expr;
2use super::hoist_block_to_expr;
3use super::hoist_if_expr;
4use super::is_valid_js_expression::*;
5use super::BlockConvertContext;
6// use super::HasSideEffects;
7use crate::js_ast::*;
8use crate::util::is_js_identifier;
9use ast::*;
10
11/// Pushes a decl with a generated identifier to the hoisted result
12/// and returns an identifier expression for it.
13pub fn hoist_expr_if_needed(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
14 // if !expr.has_side_effects() {
15 // return Some(ctx.hoist_expression_contents(expr)?);
16 // }
17
18 let init = hoist_expr_contents(ctx, expr)?;
19
20 let id = ctx.generate_temp_id("_");
21 let loc = init.loc.clone();
22
23 ctx.add_statement(JsStatement::new(
24 JsStatementData::Declaration(JSDeclaration {
25 name: id.get(&ctx),
26 init: Some(init),
27 is_constant: true,
28 }),
29 loc.clone(),
30 None,
31 ));
32
33 Some(JsExpr::new(JsExprData::Identifier(id.get(&ctx)), loc, None))
34}
35
36/// Returning `None` from this means that the expression is noreturn, and that
37/// the rest of the statement never occurs and can be stripped.
38pub fn hoist_expr_contents(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
39 if expr.is_valid_js_expression() {
40 return Some(convert_valid_expr(ctx, expr));
41 }
42
43 match expr.data {
44 ExprData::Binary { op, left, right } => {
45 let left = hoist_expr_if_needed(ctx, *left)?;
46 let right = hoist_expr_if_needed(ctx, *right)?;
47
48 Some(JsExpr::new(
49 JsExprData::Binary {
50 op,
51 left: Box::new(left),
52 right: Box::new(right),
53 },
54 expr.loc,
55 expr.comment,
56 ))
57 }
58
59 ExprData::Unary { op, value } => {
60 let value = hoist_expr_if_needed(ctx, *value)?;
61 Some(JsExpr::new(
62 JsExprData::Unary {
63 op,
64 value: Box::new(value),
65 },
66 expr.loc,
67 expr.comment,
68 ))
69 }
70
71 ExprData::Field { base, field } => {
72 let base = hoist_expr_if_needed(ctx, *base)?;
73 let field = hoist_expr_if_needed(ctx, *field)?;
74
75 Some(JsExpr::new(
76 match field.data {
77 JsExprData::String(name) if is_js_identifier(name.as_str()) => {
78 JsExprData::Dot {
79 base: Box::new(base),
80 name,
81 }
82 }
83 _ => JsExprData::Index {
84 base: Box::new(base),
85 index: Box::new(field),
86 },
87 },
88 expr.loc,
89 expr.comment,
90 ))
91 }
92
93 ExprData::Block(block) => hoist_block_to_expr(ctx, block, expr.loc, expr.comment),
94
95 ExprData::If(_) => hoist_if_expr(ctx, expr),
96
97 ExprData::Return(expr) => {
98 let loc = expr.loc.clone();
99 let return_value = hoist_expr_if_needed(ctx, *expr)?;
100 ctx.add_statement(JsStatement::new(
101 JsStatementData::Return(return_value),
102 loc,
103 None,
104 ));
105 None
106 }
107
108 ExprData::Call { base, arguments } => {
109 let base = hoist_expr_if_needed(ctx, *base)?;
110 let arguments = arguments
111 .into_iter()
112 .map(|x| hoist_expr_if_needed(ctx, x))
113 .collect::<Option<Vec<_>>>()?;
114
115 Some(JsExpr::new(
116 JsExprData::Call {
117 base: Box::new(base),
118 arguments,
119 },
120 expr.loc,
121 expr.comment,
122 ))
123 }
124
125 ExprData::Fn { .. }
126 | ExprData::Builtin { .. }
127 | ExprData::Literal(_)
128 | ExprData::Identifier(_) => {
129 unreachable!("attempt to hoist an expression that does not require it")
130 }
131 }
132}
crates/js_ast/convert/hoist_if.rs created+113
......@@ -0,0 +1,113 @@
1use super::convert_valid_expr;
2use super::hoist_block_to_js_block;
3use super::hoist_expr_if_needed;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use super::TempId;
7use crate::js_ast::*;
8use ast::*;
9use tokenizer::Loc;
10
11pub fn hoist_if_expr(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
12 let data = match expr.data {
13 ExprData::If(data) => data,
14 _ => unreachable!("hoist_if_expr called on non-if expression"),
15 };
16
17 let temporary = ctx.generate_temp_id("_if");
18
19 let decl = ctx.add_statement(JsStatement::new(
20 JsStatementData::Declaration(JSDeclaration {
21 name: temporary.get(&ctx),
22 init: None,
23 is_constant: false,
24 }),
25 expr.loc.clone(),
26 None,
27 ));
28
29 let condition = hoist_expr_if_needed(ctx, *data.condition)?;
30 let mut did_assign = false;
31
32 let js_if = hoist_if_branch(
33 ctx,
34 data.yes,
35 data.no,
36 condition,
37 &mut did_assign,
38 &temporary,
39 );
40
41 ctx.add_statement(JsStatement::new(
42 JsStatementData::If(js_if),
43 expr.loc.clone(),
44 None,
45 ));
46
47 if did_assign {
48 Some(JsExpr::new(
49 JsExprData::Identifier(temporary.get(&ctx)),
50 expr.loc,
51 expr.comment,
52 ))
53 } else {
54 decl.delete(ctx);
55 Some(JsExpr::new(JsExprData::Null, expr.loc, expr.comment))
56 }
57}
58
59pub fn hoist_if_branch(
60 ctx: &mut BlockConvertContext,
61 yes: BlockWithLoc,
62 no: Option<BlockOrIf>,
63 condition: JsExpr,
64 did_assign: &mut bool,
65 temporary: &TempId,
66) -> JsIf {
67 let yes = hoist_block_to_js_block(ctx, yes.block, yes.loc, &temporary, did_assign);
68
69 let no = match no {
70 Some(BlockOrIf::Block(no)) => Some(JsElse::Block(hoist_block_to_js_block(
71 ctx, no.block, no.loc, &temporary, did_assign,
72 ))),
73 Some(BlockOrIf::If(else_if)) => {
74 // If the condition of this block has a side effect, we need to
75 // emit a block so that we can assign to the temporary variable.
76 //
77 // Otherwise, we can just emit the condition inline
78 if else_if.condition.is_valid_js_expression() {
79 let condition = convert_valid_expr(ctx, *else_if.condition.clone());
80 Some(JsElse::If(Box::new(hoist_if_branch(
81 ctx,
82 else_if.yes,
83 else_if.no,
84 condition,
85 did_assign,
86 temporary,
87 ))))
88 } else {
89 let mut temp_block = ctx.new_child();
90 let condition =
91 hoist_expr_if_needed(&mut temp_block, *else_if.condition.clone()).unwrap();
92
93 temp_block.add_statement(JsStatement::new(
94 JsStatementData::If(hoist_if_branch(
95 ctx,
96 else_if.yes,
97 else_if.no,
98 condition,
99 did_assign,
100 temporary,
101 )),
102 Loc::EMPTY,
103 None,
104 ));
105
106 Some(JsElse::Block(temp_block.into()))
107 }
108 }
109 None => None,
110 };
111
112 JsIf { condition, yes, no }
113}
crates/js_ast/convert/is_valid_js_expression.rs created+91
......@@ -0,0 +1,91 @@
1use ast::*;
2
3/// "Valid" in this context means that the expression does not contain JS statements
4/// Meaning conversion is a direct map from a blight as node into a JS one
5///
6/// A valid expression should not go through any hoist logic, but if an expression
7/// isn't valid, then it means that it must be split into multiple JS statements
8/// inside of a block.
9pub trait IsValidJSExpression {
10 /// Can this be lowered to a JS ast without hoisting items
11 /// ex: a `return` expression is not a valid JS expression
12 fn is_valid_js_expression(&self) -> bool;
13}
14
15impl IsValidJSExpression for ExprData {
16 fn is_valid_js_expression(&self) -> bool {
17 match self {
18 // A block containing just expressions can be an expression
19 ExprData::Block(block) => block.is_valid_js_expression(),
20 ExprData::If(expr) => expr.is_valid_js_expression(),
21 ExprData::Return(_) => false,
22 ExprData::Builtin { id, .. } => match id {
23 BuiltinId::Raw => true,
24 },
25 ExprData::Binary { left, right, .. } => {
26 left.is_valid_js_expression() && right.is_valid_js_expression()
27 }
28 ExprData::Unary { value, .. } => value.is_valid_js_expression(),
29 ExprData::Fn { .. } => true,
30 ExprData::Call { base, arguments } => {
31 base.is_valid_js_expression()
32 && arguments
33 .iter()
34 .all(|argument| argument.is_valid_js_expression())
35 }
36 ExprData::Field { base, field } => {
37 base.is_valid_js_expression() && field.is_valid_js_expression()
38 }
39 ExprData::Literal(_) | ExprData::Identifier(_) => true,
40 }
41 }
42}
43
44impl IsValidJSExpression for Block {
45 fn is_valid_js_expression(&self) -> bool {
46 self.statements
47 .iter()
48 .all(|statement| statement.is_valid_js_expression())
49 }
50}
51
52impl IsValidJSExpression for Expr {
53 fn is_valid_js_expression(&self) -> bool {
54 self.data.is_valid_js_expression()
55 }
56}
57
58impl IsValidJSExpression for If {
59 fn is_valid_js_expression(&self) -> bool {
60 self.condition.is_valid_js_expression()
61 && self.yes.is_valid_js_expression()
62 && match &self.no {
63 Some(no) => no.is_valid_js_expression(),
64 None => true,
65 }
66 }
67}
68
69impl IsValidJSExpression for BlockOrIf {
70 fn is_valid_js_expression(&self) -> bool {
71 match self {
72 BlockOrIf::Block(block) => block.is_valid_js_expression(),
73 BlockOrIf::If(if_) => if_.is_valid_js_expression(),
74 }
75 }
76}
77
78impl IsValidJSExpression for BlockWithLoc {
79 fn is_valid_js_expression(&self) -> bool {
80 self.block.is_valid_js_expression()
81 }
82}
83
84impl IsValidJSExpression for Statement {
85 fn is_valid_js_expression(&self) -> bool {
86 match self {
87 Statement::Expr(expr) => expr.data.is_valid_js_expression(),
88 _ => false,
89 }
90 }
91}
crates/js_ast/convert/mod.rs created+42
......@@ -0,0 +1,42 @@
1mod block_convert_context;
2mod convert_valid_block;
3mod convert_valid_expr;
4mod convert_valid_if;
5mod convert_valid_statement;
6mod hoist_block_to_expr;
7mod hoist_block_to_js_block;
8mod hoist_expr;
9mod hoist_if;
10mod is_valid_js_expression;
11mod process_function;
12mod process_statement;
13mod side_effects;
14mod tree_shake_statement_list;
15
16pub use block_convert_context::*;
17pub use convert_valid_block::*;
18pub use convert_valid_expr::*;
19pub use convert_valid_if::*;
20pub use convert_valid_statement::*;
21pub use hoist_block_to_expr::*;
22pub use hoist_block_to_js_block::*;
23pub use hoist_expr::*;
24pub use hoist_if::*;
25pub use is_valid_js_expression::*;
26pub use process_function::*;
27pub use process_statement::*;
28// pub use side_effects::*;
29pub use tree_shake_statement_list::*;
30
31use crate::js_ast::JsModule;
32use ast::Module;
33
34pub fn blight_to_js(blight: Module) -> JsModule {
35 let mut root_context = BlockConvertContext::new();
36
37 for statement in blight.statements {
38 process_top_level_statement(&mut root_context, statement).unwrap();
39 }
40
41 root_context.into()
42}
crates/js_ast/convert/process_function.rs created+63
......@@ -0,0 +1,63 @@
1use super::convert_valid_expr;
2use super::hoist_expr_if_needed;
3use super::process_statement;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8use tokenizer::Loc;
9
10pub fn process_function_to_arrow(
11 ctx: &mut BlockConvertContext,
12 body: Expr,
13 params: Vec<Param>,
14 loc: Loc,
15 comment: Option<Comment>,
16) -> JsExpr {
17 // A block that is a valid expression will just become
18 // that expression. We can convert multiple expressions
19 // into a comma expression.
20 let body = if body.is_valid_js_expression() {
21 JsArrowBody::Expr(convert_valid_expr(ctx, body))
22 } else {
23 let mut child = ctx.new_child();
24
25 match body.data {
26 ExprData::Block(block) => {
27 for statement in block.statements {
28 if let None = process_statement(&mut child, statement) {
29 break;
30 };
31 }
32
33 if let Some(expr) = child.take_last_statement_if_expr() {
34 child.add_statement(JsStatement::new(
35 JsStatementData::Return(expr),
36 loc.clone(),
37 None,
38 ));
39 }
40 }
41 _ => {
42 if let Some(expr) = hoist_expr_if_needed(&mut child, body) {
43 child.add_statement(JsStatement::new(
44 JsStatementData::Return(expr),
45 loc.clone(),
46 None,
47 ));
48 }
49 }
50 }
51
52 JsArrowBody::Block(child.into())
53 };
54
55 JsExpr::new(
56 JsExprData::ArrowFunction {
57 params: params.into_iter().map(|x| x.name).collect(),
58 body: Box::new(body),
59 },
60 loc.clone(),
61 comment.map(|x| x.into()),
62 )
63}
crates/js_ast/convert/process_statement.rs created+95
......@@ -0,0 +1,95 @@
1use super::convert_valid_statement;
2use super::hoist_expr_contents;
3use super::process_function_to_arrow;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8
9pub fn process_statement(ctx: &mut BlockConvertContext, statement: Statement) -> Option<()> {
10 if statement.is_valid_js_expression() {
11 let result = convert_valid_statement(ctx, statement);
12 ctx.add_statement(result);
13 return Some(());
14 }
15
16 match statement {
17 Statement::VarDecl(decl) => {
18 // visit init before name, this way allows for:
19 //
20 // let a = a;
21 //
22 // and `a` will be looked up before it is re-declared.
23 // we will also return in the noreturn case
24 let init = hoist_expr_contents(ctx, decl.init)?;
25
26 let name = ctx.declare_identifier(decl.name);
27
28 ctx.add_statement(JsStatement::new(
29 JsStatementData::Declaration(JSDeclaration {
30 name: name.get(&ctx),
31 init: Some(init),
32 is_constant: decl.is_constant,
33 }),
34 decl.loc,
35 decl.comment.map(|x| x.into()),
36 ));
37
38 Some(())
39 }
40 Statement::Expr(expr) => {
41 if let Some(expr) = hoist_expr_contents(ctx, expr) {
42 let loc = expr.loc.clone();
43 ctx.add_statement(JsStatement::new(
44 JsStatementData::Expression(expr),
45 loc,
46 None,
47 ));
48 Some(())
49 } else {
50 None
51 }
52 }
53 Statement::FnDecl(FnDecl {
54 name,
55 params,
56 body,
57 loc,
58 comment,
59 ..
60 }) => {
61 let name = ctx.declare_identifier(name);
62
63 let processed = Some(process_function_to_arrow(
64 ctx,
65 body,
66 params,
67 loc.clone(),
68 None,
69 ));
70
71 ctx.add_statement(JsStatement::new(
72 JsStatementData::Declaration(JSDeclaration {
73 name: name.get(&ctx),
74 init: processed,
75 is_constant: true,
76 }),
77 loc,
78 comment.map(|x| x.into()),
79 ));
80
81 Some(())
82 }
83 Statement::Comment(_) => Some(()),
84 }
85}
86
87pub fn process_top_level_statement(
88 ctx: &mut BlockConvertContext,
89 statement: TopLevelStatement,
90) -> Option<()> {
91 match statement.to_statement() {
92 Some(stmt) => process_statement(ctx, stmt),
93 None => todo!("process_top_level_statement"),
94 }
95}
crates/js_ast/convert/side_effects.rs created+91
......@@ -0,0 +1,91 @@
1use ast::*;
2
3pub trait HasSideEffects {
4 /// Can this item be removed from the AST without changing the program's behavior?
5 fn has_side_effects(&self) -> bool;
6}
7
8impl HasSideEffects for ExprData {
9 fn has_side_effects(&self) -> bool {
10 match self {
11 ExprData::Literal(_) => false,
12 ExprData::Identifier(_) => false,
13
14 ExprData::Block(block) => block.has_side_effects(),
15 ExprData::Binary { left, right, .. } => {
16 left.has_side_effects() || right.has_side_effects()
17 }
18 ExprData::Unary { value, .. } => value.has_side_effects(),
19 ExprData::If(ifexpr) => ifexpr.has_side_effects(),
20
21 // note: a function *declaration* has no side effects, but the call *can*
22 ExprData::Fn { .. } => false,
23
24 // TODO(@paperdave): implement detection of pure functions
25 ExprData::Call { base, arguments } => {
26 base.has_side_effects() || arguments.iter().any(|x| x.has_side_effects())
27 }
28
29 // note: we assume field access has side effects, ie: a getter doesn't throw
30 ExprData::Field { base, field } => base.has_side_effects() || field.has_side_effects(),
31
32 // note: we assume the code in @raw always has side effects
33 ExprData::Builtin { id, .. } => match id {
34 BuiltinId::Raw => true,
35 },
36
37 ExprData::Return(_) => true,
38 }
39 }
40}
41
42impl HasSideEffects for Statement {
43 fn has_side_effects(&self) -> bool {
44 match self {
45 Statement::Expr(expr) => expr.has_side_effects(),
46 Statement::VarDecl(decl) => decl.init.has_side_effects(),
47 Statement::Comment(_) => false,
48 Statement::FnDecl(_) => false,
49 }
50 }
51}
52
53impl HasSideEffects for Block {
54 fn has_side_effects(&self) -> bool {
55 self.statements
56 .iter()
57 .any(|statement| statement.has_side_effects())
58 }
59}
60
61impl HasSideEffects for Expr {
62 fn has_side_effects(&self) -> bool {
63 self.data.has_side_effects()
64 }
65}
66
67impl HasSideEffects for If {
68 fn has_side_effects(&self) -> bool {
69 self.condition.has_side_effects()
70 || self.yes.has_side_effects()
71 || match &self.no {
72 Some(no) => no.has_side_effects(),
73 None => false,
74 }
75 }
76}
77
78impl HasSideEffects for BlockOrIf {
79 fn has_side_effects(&self) -> bool {
80 match self {
81 BlockOrIf::Block(block) => block.has_side_effects(),
82 BlockOrIf::If(if_) => if_.has_side_effects(),
83 }
84 }
85}
86
87impl HasSideEffects for BlockWithLoc {
88 fn has_side_effects(&self) -> bool {
89 self.block.has_side_effects()
90 }
91}
crates/js_ast/convert/tree_shake_statement_list.rs created+31
......@@ -0,0 +1,31 @@
1use crate::js_ast::*;
2
3/// Removes statements that are not needed from a JS block. Very limited.
4///
5/// BlockConvertContext will automatically execute this when converting
6/// itself into a Statement list or other data structure.
7///
8/// What this does:
9/// - Remove discarded literals `null` -> ``
10pub fn tree_shake_statement_list(list: &mut Vec<JsStatement>) {
11 let mut set_i = 0;
12 for i in 0..list.len() {
13 let stmt = list.get(i).unwrap();
14 let should_keep = match &stmt.data {
15 JsStatementData::Expression(expr) => !expr.is_literal(),
16 JsStatementData::Return(_) => true,
17 JsStatementData::Export(_) => true,
18 JsStatementData::Declaration(_) => true,
19 JsStatementData::Function(_) => true,
20 JsStatementData::If(_) => true,
21 };
22
23 if should_keep {
24 if set_i != i {
25 list.swap(i, set_i)
26 }
27 set_i += 1;
28 }
29 }
30 list.truncate(set_i)
31}
crates/js_ast/js_ast.rs created+190
......@@ -0,0 +1,190 @@
1use ast::{BinaryOperator, Comment, UnaryOperator};
2use num::bigint::BigInt;
3use tokenizer::Loc;
4
5#[derive(Debug, PartialEq, Clone)]
6pub struct JsExpr {
7 pub data: JsExprData,
8 pub loc: Loc,
9 pub comment: Option<Comment>,
10}
11
12#[derive(Debug, PartialEq, Clone)]
13pub struct JsKV {
14 pub key: JsExpr,
15 pub value: JsExpr,
16}
17
18#[derive(Debug, PartialEq, Clone)]
19pub enum JsExprData {
20 /// node emitted by the '@raw' building
21 Raw(String),
22 // Litearls
23 Number(f64),
24 BigInt(BigInt),
25 String(String),
26 Bool(bool),
27 Array(Vec<JsExpr>),
28 Object(Vec<JsKV>),
29 Null,
30 Undefined,
31
32 Identifier(String),
33 Unary {
34 op: UnaryOperator,
35 value: Box<JsExpr>,
36 },
37 Binary {
38 op: BinaryOperator,
39 left: Box<JsExpr>,
40 right: Box<JsExpr>,
41 },
42 Dot {
43 base: Box<JsExpr>,
44 name: String,
45 },
46 Index {
47 base: Box<JsExpr>,
48 index: Box<JsExpr>,
49 },
50 Call {
51 base: Box<JsExpr>,
52 arguments: Vec<JsExpr>,
53 },
54 Function {
55 name: Option<String>,
56 params: Vec<String>,
57 body: Box<JsExpr>,
58 },
59 ArrowFunction {
60 params: Vec<String>,
61 body: Box<JsArrowBody>,
62 },
63 Ternary {
64 condition: Box<JsExpr>,
65 yes: Box<JsExpr>,
66 no: Option<Box<JsExpr>>,
67 },
68 Comma {
69 items: Vec<JsExpr>,
70 },
71 Void(Box<JsExpr>),
72}
73
74#[derive(Debug, PartialEq, Clone)]
75pub struct JsStatement {
76 pub data: JsStatementData,
77 pub loc: Loc,
78 pub comment: Option<Comment>,
79}
80
81#[derive(Debug, PartialEq, Clone)]
82pub struct JSFunctionDeclaration {
83 pub name: String,
84 pub params: Vec<String>,
85 pub block: JsBlock,
86}
87
88#[derive(Debug, PartialEq, Clone)]
89pub struct JSDeclaration {
90 pub name: String,
91 pub init: Option<JsExpr>,
92 pub is_constant: bool,
93}
94
95#[derive(Debug, PartialEq, Clone)]
96pub struct JsIf {
97 pub condition: JsExpr,
98 pub yes: JsBlock,
99 pub no: Option<JsElse>,
100}
101
102#[derive(Debug, PartialEq, Clone)]
103pub enum JsElse {
104 Expr(JsExpr),
105 Block(JsBlock),
106 If(Box<JsIf>),
107}
108
109#[derive(Debug, PartialEq, Clone)]
110pub enum JsStatementData {
111 Expression(JsExpr),
112 Return(JsExpr),
113 Export(JsExport),
114 Declaration(JSDeclaration),
115 Function(JSFunctionDeclaration),
116 If(JsIf),
117}
118
119#[derive(Debug, PartialEq, Clone)]
120pub enum JsExport {
121 Named(Vec<JSNamedExport>),
122 Function(Box<JSFunctionDeclaration>),
123 Declaration(Box<JSDeclaration>),
124 Default(JsExpr),
125}
126
127#[derive(Debug, PartialEq, Clone)]
128pub struct JSNamedExport {
129 pub identifier: String,
130 pub export_name: String,
131}
132
133#[derive(Debug, PartialEq, Clone)]
134pub struct JsBlock {
135 pub statements: Vec<JsStatement>,
136 pub loc: Loc,
137}
138
139impl JsExpr {
140 pub fn new(data: JsExprData, loc: Loc, comment: Option<Comment>) -> Self {
141 Self { data, loc, comment }
142 }
143
144 pub fn is_literal(&self) -> bool {
145 match &self.data {
146 JsExprData::Number(_) => true,
147 JsExprData::BigInt(_) => true,
148 JsExprData::String(_) => true,
149 JsExprData::Bool(_) => true,
150 JsExprData::Null => true,
151 JsExprData::Undefined => true,
152 _ => false,
153 }
154 }
155}
156
157impl JsExprData {
158 /// Helper for testing
159 pub fn str(value: &str) -> Self {
160 Self::String(value.to_string())
161 }
162
163 /// Helper for testing
164 pub fn str_id(value: &str) -> Self {
165 Self::Identifier(value.to_string())
166 }
167}
168
169impl JsBlock {
170 pub fn new(statements: Vec<JsStatement>, loc: Loc) -> Self {
171 Self { statements, loc }
172 }
173}
174
175impl JsStatement {
176 pub fn new(data: JsStatementData, loc: Loc, comment: Option<Comment>) -> Self {
177 Self { data, loc, comment }
178 }
179}
180
181#[derive(Debug, PartialEq, Clone)]
182pub enum JsArrowBody {
183 Block(JsBlock),
184 Expr(JsExpr),
185}
186
187#[derive(Debug, PartialEq, Clone)]
188pub struct JsModule {
189 pub statements: Vec<JsStatement>,
190}
crates/js_ast/lib.rs created+7
......@@ -0,0 +1,7 @@
1mod convert;
2mod js_ast;
3mod util;
4
5pub use convert::*;
6pub use js_ast::*;
7pub use util::*;
crates/js_ast/src/lib.rs deleted-348
......@@ -1,348 +0,0 @@
1use std::collections::HashMap;
2
3use miette::Result;
4
5use ast::Ast;
6
7mod types;
8pub use types::{JsExpression, JsStatement};
9
10struct StackContext {
11 statements: Vec<JsStatement>,
12 /// Variables that are declared in this context.
13 /// The number is the number of times the variable has been declared.
14 /// This is used to allow for shadowing
15 variable_map: HashMap<String, u32>,
16 block: u32,
17}
18
19impl StackContext {
20 fn new(block: u32) -> Self {
21 StackContext {
22 statements: Vec::new(),
23 variable_map: HashMap::new(),
24 block,
25 }
26 }
27}
28
29struct TranslateContext {
30 context: Vec<StackContext>,
31 internal_counter: u32,
32 block_counter: u32,
33}
34
35impl TranslateContext {
36 fn new() -> Self {
37 TranslateContext {
38 context: vec![StackContext::new(0)],
39 internal_counter: 0,
40 block_counter: 0,
41 }
42 }
43
44 fn push_context(self: &mut Self) {
45 self.block_counter += 1;
46 self.context.push(StackContext::new(self.block_counter));
47 }
48
49 fn pop_context(self: &mut Self) -> Vec<JsStatement> {
50 self.context.pop().unwrap().statements
51 }
52
53 fn push_internal_variable(self: &mut Self, value: JsExpression) -> JsExpression {
54 self.context
55 .last_mut()
56 .unwrap()
57 .statements
58 .push(JsStatement::VariableDeclaration {
59 name: format!("${}", self.internal_counter),
60 value,
61 });
62 self.internal_counter += 1;
63 JsExpression::Identifier(format!("${}", self.internal_counter - 1))
64 }
65
66 fn push(self: &mut Self, statement: JsStatement) {
67 self.context.last_mut().unwrap().statements.push(statement);
68 }
69
70 fn peek(self: &Self) -> &JsStatement {
71 self.context.last().unwrap().statements.last().unwrap()
72 }
73
74 fn declare_variable(self: &mut Self, name: String) -> String {
75 let context = self.context.last_mut().unwrap();
76 if let Some(value) = context.variable_map.get_mut(name.as_str()) {
77 *value += 1;
78 format!("{}${}${}", name, value, context.block)
79 } else {
80 context.variable_map.insert(name.to_string(), 0);
81 format!("{}$0${}", name, context.block)
82 }
83 }
84
85 fn get_identifier(self: &Self, name: String) -> String {
86 for context in self.context.iter().rev() {
87 if let Some(value) = context.variable_map.get(name.as_str()) {
88 return format!("{}${}${}", name, value, context.block);
89 }
90 }
91 panic!("Variable {} not found", name);
92 }
93}
94
95// basically, expressions are really hard to translate
96// in blight, everything is an expression
97// in js, very few things are expressions
98// here, we recursively expand out expressions into statements
99// we then store the result of all the expressions in a variable
100// and then return the name of that variable
101fn get_expression_handle(ast: &Ast, ctx: &mut TranslateContext) -> JsExpression {
102 match ast {
103 Ast::Number(value) => JsExpression::Number(*value),
104 Ast::String(value) => JsExpression::String(value.clone()),
105 Ast::Identifier(value) => JsExpression::Identifier(ctx.get_identifier(value.clone())),
106 Ast::Bool(value) => JsExpression::Bool(*value),
107 Ast::FunctionCall { name, arguments } => {
108 let mut handles = Vec::new();
109 for argument in arguments {
110 handles.push(get_expression_handle(argument, ctx));
111 }
112 match *name.clone() {
113 Ast::Identifier(name) => ctx.push_internal_variable(JsExpression::FunctionCall {
114 name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))),
115 arguments: handles,
116 }),
117 Ast::BuiltIn(name) => match name.as_str() {
118 "@raw" => {
119 if handles.len() != 1 {
120 panic!("@raw expects exactly one argument");
121 }
122 match handles.get(0).unwrap() {
123 JsExpression::String(value) => {
124 return JsExpression::Raw(value.clone());
125 }
126 _ => panic!("@raw expects a string argument"),
127 }
128 }
129 _ => panic!("Unknown built-in function {}", name),
130 },
131 _ => panic!("Function name must be an identifier"),
132 }
133 }
134 Ast::Operation {
135 operator,
136 left,
137 right,
138 } => {
139 let left_handle = get_expression_handle(left, ctx);
140 let right_handle = get_expression_handle(right, ctx);
141 JsExpression::Operation {
142 operator: operator.clone(),
143 left: Box::new(left_handle),
144 right: Box::new(right_handle),
145 }
146 }
147 Ast::Block(block) => {
148 if !block.implicit_return {
149 panic!("Expression blocks must have an implicit return");
150 }
151 ctx.push_context();
152 for (i, statement) in block.value.iter().enumerate() {
153 if i == block.value.len() - 1 {
154 break;
155 } else {
156 translate_statement(statement, ctx);
157 }
158 }
159 let result = get_expression_handle(block.value.last().unwrap(), ctx);
160 let value = ctx.pop_context();
161 value.iter().for_each(|x| ctx.push(x.clone()));
162 result
163 }
164 _ => {
165 panic!("unimplemented: {:?}", ast);
166 }
167 }
168}
169
170fn translate_statement(ast: &Ast, ctx: &mut TranslateContext) {
171 match ast {
172 Ast::ConstantDeclaration {
173 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: _,
187 value,
188 } => {
189 let handle = get_expression_handle(value, ctx);
190 let rename = ctx.declare_variable(name.clone());
191 ctx.push(JsStatement::VariableDeclaration {
192 name: rename,
193 value: handle,
194 });
195 }
196 Ast::Function {
197 name,
198 arguments,
199 return_type: _,
200 block,
201 } => {
202 let rename = ctx.declare_variable(name.clone());
203 let arguments = arguments
204 .iter()
205 .map(|x| ctx.declare_variable(x.name.clone()))
206 .collect();
207
208 ctx.push_context();
209 for (i, statement) in block.value.iter().enumerate() {
210 if block.implicit_return && i == block.value.len() - 1 {
211 let handle = get_expression_handle(statement, ctx);
212 ctx.push(JsStatement::Return(handle));
213 } else {
214 translate_statement(statement, ctx);
215 }
216 }
217 let value = ctx.pop_context();
218
219 ctx.push(JsStatement::Function {
220 name: rename,
221 arguments,
222 value,
223 });
224 }
225 Ast::Public(value) => {
226 translate_statement(value, ctx);
227 // I'm sorry, I just don't know how to get the original name properly... my bad
228 match ctx.peek() {
229 JsStatement::Function { name, .. }
230 | JsStatement::VariableDeclaration { name, .. }
231 | JsStatement::ConstantDeclaration { name, .. } => {
232 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 )));
239 }
240 _ => {
241 panic!("Cannot make non-function public");
242 }
243 }
244 }
245 Ast::Return(value) => {
246 let handle = get_expression_handle(value, ctx);
247 ctx.push(JsStatement::Return(handle));
248 }
249 Ast::Block(block) => {
250 ctx.push_context();
251 for (i, statement) in block.value.iter().enumerate() {
252 if block.implicit_return && i == block.value.len() - 1 {
253 let handle = get_expression_handle(statement, ctx);
254 ctx.push(JsStatement::Return(handle));
255 } else {
256 translate_statement(statement, ctx);
257 }
258 }
259 let value = ctx.pop_context();
260 value.iter().for_each(|x| ctx.push(x.clone()));
261 }
262 Ast::If {
263 condition,
264 value: block,
265 else_value,
266 } => {
267 let handle = get_expression_handle(condition, ctx);
268
269 ctx.push_context();
270 for (i, statement) in block.value.iter().enumerate() {
271 if block.implicit_return && i == block.value.len() - 1 {
272 let handle = get_expression_handle(statement, ctx);
273 ctx.push(JsStatement::Return(handle));
274 } else {
275 translate_statement(statement, ctx);
276 }
277 }
278 let value = ctx.pop_context();
279 let else_value = match else_value {
280 Some(block) => {
281 ctx.push_context();
282 for (i, statement) in block.value.iter().enumerate() {
283 if block.implicit_return && i == block.value.len() - 1 {
284 let handle = get_expression_handle(statement, ctx);
285 ctx.push(JsStatement::Return(handle));
286 } else {
287 translate_statement(statement, ctx);
288 }
289 }
290 Some(ctx.pop_context())
291 }
292 None => None,
293 };
294
295 ctx.push(JsStatement::If {
296 condition: handle,
297 value,
298 else_value,
299 });
300 }
301 Ast::FunctionCall { name, arguments } => {
302 let mut handles = Vec::new();
303 for argument in arguments {
304 handles.push(get_expression_handle(argument, ctx));
305 }
306 match *name.clone() {
307 Ast::Identifier(name) => {
308 ctx.push(JsStatement::Expression(JsExpression::FunctionCall {
309 name: Box::new(JsExpression::Identifier(ctx.get_identifier(name.clone()))),
310 arguments: handles,
311 }));
312 }
313 Ast::BuiltIn(name) => match name.as_str() {
314 "@raw" => {
315 if handles.len() != 1 {
316 panic!("@raw expects exactly one argument");
317 }
318 match handles.get(0).unwrap() {
319 JsExpression::String(value) => {
320 ctx.push(JsStatement::Expression(JsExpression::FunctionCall {
321 name: Box::new(JsExpression::Raw(value.clone())),
322 arguments: handles,
323 }));
324 }
325 _ => panic!("@raw expects a string argument"),
326 }
327 }
328 _ => panic!("Unknown built-in function {}", name),
329 },
330 _ => panic!("Function name must be an identifier"),
331 }
332 }
333 Ast::Comment(_) | Ast::DocComment(_) => {}
334 _ => {
335 panic!("unimplemented: {:?}", ast);
336 }
337 }
338}
339
340pub fn translate(_ast: Vec<Ast>) -> Result<Vec<JsStatement>> {
341 let mut ctx = TranslateContext::new();
342
343 for ast in _ast {
344 translate_statement(&ast, &mut ctx);
345 }
346
347 Ok(ctx.pop_context())
348}
crates/js_ast/src/types.rs deleted-54
......@@ -1,54 +0,0 @@
1use num::bigint::BigInt;
2use std::collections::HashMap;
3
4use util::Operator;
5
6#[derive(Debug, PartialEq, Clone)]
7pub enum JsExpression {
8 Number(f64),
9 BigInt(BigInt),
10 String(String),
11 Bool(bool),
12 Identifier(String),
13 Raw(String),
14 Array(Vec<JsExpression>),
15 Object(HashMap<String, JsExpression>),
16 Operation {
17 operator: Operator,
18 left: Box<JsExpression>,
19 right: Box<JsExpression>,
20 },
21 Function {
22 arguments: Vec<String>,
23 value: Vec<JsStatement>,
24 },
25 FunctionCall {
26 name: Box<JsExpression>,
27 arguments: Vec<JsExpression>,
28 },
29}
30
31#[derive(Debug, PartialEq, Clone)]
32pub enum JsStatement {
33 Expression(JsExpression),
34 Export(Box<JsStatement>),
35 Return(JsExpression),
36 VariableDeclaration {
37 name: String,
38 value: JsExpression,
39 },
40 ConstantDeclaration {
41 name: String,
42 value: JsExpression,
43 },
44 Function {
45 name: String,
46 arguments: Vec<String>,
47 value: Vec<JsStatement>,
48 },
49 If {
50 condition: JsExpression,
51 value: Vec<JsStatement>,
52 else_value: Option<Vec<JsStatement>>,
53 },
54}
crates/js_ast/util.rs created+17
......@@ -0,0 +1,17 @@
1/// TODO(@paperdave): handle the rest of the identifiers
2pub fn is_js_identifier(s: &str) -> bool {
3 if s.is_empty() {
4 return false;
5 }
6 let mut chars = s.chars();
7 let first = chars.next().unwrap();
8 if !first.is_ascii_alphabetic() && first != '_' {
9 return false;
10 }
11 for c in chars {
12 if !c.is_ascii_alphanumeric() && c != '_' {
13 return false;
14 }
15 }
16 true
17}
crates/js_printer/Cargo.toml+1-1
......@@ -4,6 +4,7 @@ version = "0.1.0"
44edition = "2021"
55
66[lib]
7path = "./lib.rs"
78
89[dependencies]
910miette.workspace = true
......@@ -11,4 +12,3 @@ thiserror.workspace = true
1112num.workspace = true
1213
1314js_ast = { path = "../js_ast" }
14util = { path = "../util" }
crates/js_printer/lib.rs created+372
......@@ -0,0 +1,372 @@
1mod printer_tests;
2
3use js_ast::*;
4use std::io::{Result, Write};
5
6pub trait Print {
7 fn print(&self, writer: &mut dyn Write) -> Result<()>;
8
9 fn print_to_string(&self) -> String {
10 let mut buffer = Vec::new();
11 self.print(&mut buffer).unwrap();
12 String::from_utf8(buffer).unwrap()
13 }
14}
15
16// the printer:
17
18fn print_module_clause_identifier(writer: &mut dyn Write, export_name: &str) -> Result<()> {
19 // TODO(@paperdave): Escape invalid identifiers
20 if js_ast::is_js_identifier(export_name) {
21 write!(writer, "{}", export_name)
22 } else {
23 todo!("call JSON.stringify on identifier")
24 }
25}
26
27fn print_js_string(writer: &mut dyn Write, string: &str) -> Result<()> {
28 write!(writer, "\"")?;
29 // TODO(@paperdave): Instead of writing each character, this should write
30 // chunks between the escapes instead of calling write! on each character individually.
31 for c in string.chars() {
32 match c {
33 '\n' => write!(writer, "\\n")?,
34 '\r' => write!(writer, "\\r")?,
35 '\t' => write!(writer, "\\t")?,
36 '"' => write!(writer, "\\\"")?,
37 '\\' => write!(writer, "\\\\")?,
38 '\u{0008}' => write!(writer, "\\b")?,
39 '\u{000C}' => write!(writer, "\\f")?,
40 '\u{0000}'..='\u{001F}' => write!(writer, "\\u{:04x}", c as u32)?,
41 // TODO(@paperdave): I think there are some larger code points that need to be escaped?
42 _ => write!(writer, "{}", c)?,
43 }
44 }
45 write!(writer, "\"")
46}
47
48impl Print for JsStatement {
49 fn print(&self, w: &mut dyn Write) -> Result<()> {
50 match &self.data {
51 js_ast::JsStatementData::Expression(expr) => {
52 expr.print(w)?;
53 write!(w, ";")
54 }
55 js_ast::JsStatementData::Return(expr) => {
56 write!(w, "return ")?;
57 expr.print(w)
58 }
59 js_ast::JsStatementData::Export(export) => export.print(w),
60 js_ast::JsStatementData::Declaration(decl) => decl.print(w),
61 js_ast::JsStatementData::Function(function) => function.print(w),
62 js_ast::JsStatementData::If(js_if) => js_if.print(w),
63 }
64 }
65}
66
67impl Print for JsExport {
68 fn print(&self, w: &mut dyn Write) -> Result<()> {
69 match self {
70 JsExport::Named(exports) => {
71 write!(w, "export{{")?;
72 for (i, export) in exports.iter().enumerate() {
73 if i > 0 {
74 write!(w, ",")?;
75 }
76 export.print(w)?;
77 }
78 Ok(())
79 }
80 JsExport::Function(function) => {
81 write!(w, "export ")?;
82 function.print(w)
83 }
84 JsExport::Declaration(declaration) => {
85 write!(w, "export ")?;
86 declaration.print(w)
87 }
88 JsExport::Default(expr) => {
89 write!(w, "export default ")?;
90 expr.print(w)
91 }
92 }
93 }
94}
95
96impl Print for JSNamedExport {
97 fn print(&self, writer: &mut dyn Write) -> Result<()> {
98 if self.export_name == self.identifier {
99 write!(writer, "{}", self.identifier)
100 } else {
101 write!(writer, "{} as ", self.identifier)?;
102 print_module_clause_identifier(writer, &self.export_name)
103 }
104 }
105}
106
107impl Print for JSDeclaration {
108 fn print(&self, writer: &mut dyn Write) -> Result<()> {
109 write!(
110 writer,
111 "{} {}",
112 if self.is_constant { "const" } else { "let" },
113 self.name,
114 )?;
115 if let Some(init) = &self.init {
116 write!(writer, " = ")?;
117 init.print(writer)?;
118 }
119 write!(writer, ";")?;
120 Ok(())
121 }
122}
123
124impl Print for JsExpr {
125 fn print(&self, writer: &mut dyn Write) -> Result<()> {
126 self.data.print(writer)
127 }
128}
129
130impl Print for JsExprData {
131 fn print(&self, writer: &mut dyn Write) -> Result<()> {
132 match &self {
133 JsExprData::Raw(data) => write!(writer, "{}", data),
134 JsExprData::Number(number) => write!(writer, "{}", number),
135 JsExprData::BigInt(bigint) => write!(writer, "{}", bigint),
136 JsExprData::String(string) => print_js_string(writer, string.as_str()),
137 JsExprData::Bool(bool) => write!(writer, "{}", bool),
138 JsExprData::Null => write!(writer, "null"),
139 JsExprData::Undefined => write!(writer, "undefined"),
140 JsExprData::Identifier(str) => write!(writer, "{}", str),
141 JsExprData::Array(items) => {
142 write!(writer, "[")?;
143 for (i, item) in items.iter().enumerate() {
144 if i > 0 {
145 write!(writer, ",")?;
146 }
147 item.print(writer)?;
148 }
149 write!(writer, "]")
150 }
151 JsExprData::Object(items) => {
152 write!(writer, "{{")?;
153 for (i, kv) in items.iter().enumerate() {
154 if i > 0 {
155 write!(writer, ",")?;
156 }
157
158 kv.print(writer)?;
159 }
160 write!(writer, "}}")
161 }
162 JsExprData::Unary { op, value } => {
163 write!(writer, "{}", op.to_str())?;
164 value.print(writer)
165 }
166 JsExprData::Binary { op, left, right } => {
167 // TODO: figure out when we dont need to wrap
168 let wrap = true;
169 if wrap {
170 write!(writer, "(")?;
171 }
172 left.print(writer)?;
173 write!(writer, "{}", op.to_str())?;
174 right.print(writer)?;
175 if wrap {
176 write!(writer, ")")?;
177 }
178 Ok(())
179 }
180 JsExprData::Dot { base, name } => {
181 base.print(writer)?;
182 write!(writer, ".{}", name)
183 }
184 JsExprData::Index { base, index } => {
185 base.print(writer)?;
186 write!(writer, "[")?;
187 index.print(writer)?;
188 write!(writer, "]")
189 }
190 JsExprData::Call { base, arguments } => {
191 base.print(writer)?;
192 write!(writer, "(")?;
193 for (i, argument) in arguments.iter().enumerate() {
194 if i > 0 {
195 write!(writer, ",")?;
196 }
197 argument.print(writer)?;
198 }
199 write!(writer, ")")
200 }
201 JsExprData::Function { body, name, params } => {
202 write!(writer, "function ")?;
203 if let Some(name) = name {
204 write!(writer, "{}", name)?;
205 }
206 write!(writer, "(")?;
207 for (i, param) in params.iter().enumerate() {
208 if i > 0 {
209 write!(writer, ",")?;
210 }
211 write!(writer, "{}", param)?;
212 }
213 write!(writer, ")")?;
214 body.print(writer)
215 }
216 JsExprData::ArrowFunction { body, params } => {
217 let print_parens = params.len() != 1;
218 if print_parens {
219 write!(writer, "(")?;
220 }
221 for (i, param) in params.iter().enumerate() {
222 if i > 0 {
223 write!(writer, ",")?;
224 }
225 write!(writer, "{}", param)?;
226 }
227 if print_parens {
228 write!(writer, ")")?;
229 }
230 write!(writer, "=>")?;
231 body.print(writer)
232 }
233 JsExprData::Ternary { condition, yes, no } => {
234 let wrap = false;
235 if wrap {
236 write!(writer, "(")?;
237 }
238 condition.print(writer)?;
239 write!(writer, "?")?;
240 yes.print(writer)?;
241 write!(writer, ":")?;
242 if let Some(no) = no {
243 no.print(writer)?;
244 } else {
245 write!(writer, "null")?;
246 }
247 if wrap {
248 write!(writer, ")")?;
249 }
250 Ok(())
251 }
252 JsExprData::Comma { items } => {
253 let wrap = true;
254 if wrap {
255 write!(writer, "(")?;
256 }
257 for (i, item) in items.iter().enumerate() {
258 if i > 0 {
259 write!(writer, ",")?;
260 }
261 item.print(writer)?;
262 }
263 if wrap {
264 write!(writer, ")")?;
265 }
266 Ok(())
267 }
268 JsExprData::Void(expr) => {
269 write!(writer, "void ")?;
270 expr.print(writer)
271 }
272 }
273 }
274}
275
276impl Print for JSFunctionDeclaration {
277 fn print(&self, writer: &mut dyn Write) -> Result<()> {
278 write!(writer, "function {}", self.name)?;
279 write!(writer, "(")?;
280 for (i, param) in self.params.iter().enumerate() {
281 if i > 0 {
282 write!(writer, ",")?;
283 }
284 write!(writer, "{}", param)?;
285 }
286 write!(writer, ")")?;
287 self.block.print(writer)
288 }
289}
290
291impl Print for JsBlock {
292 fn print(&self, writer: &mut dyn Write) -> Result<()> {
293 write!(writer, "{{")?;
294 for statement in self.statements.iter() {
295 statement.print(writer)?;
296 }
297 write!(writer, "}}")
298 }
299}
300
301impl Print for JsIf {
302 fn print(&self, writer: &mut dyn Write) -> Result<()> {
303 write!(writer, "if(")?;
304 self.condition.print(writer)?;
305 write!(writer, ")")?;
306 self.yes.print(writer)?;
307 if let Some(no) = &self.no {
308 no.print(writer)?;
309 }
310 Ok(())
311 }
312}
313
314impl Print for JsElse {
315 fn print(&self, writer: &mut dyn Write) -> Result<()> {
316 match self {
317 JsElse::If(js_if) => {
318 write!(writer, "else ")?;
319 js_if.print(writer)
320 }
321 JsElse::Block(block) => {
322 write!(writer, "else")?;
323 block.print(writer)
324 }
325 JsElse::Expr(expr) => {
326 write!(writer, "else ")?;
327 expr.print(writer)
328 }
329 }
330 }
331}
332
333impl Print for JsKV {
334 fn print(&self, writer: &mut dyn Write) -> Result<()> {
335 match &self.key.data {
336 JsExprData::String(string) => {
337 if is_js_identifier(string) {
338 write!(writer, "{}", string)?;
339 } else {
340 print_js_string(writer, string.as_str())?;
341 }
342 }
343 _ => {
344 write!(writer, "[")?;
345 self.key.print(writer)?;
346 write!(writer, "]")?;
347 }
348 }
349 write!(writer, ":")?;
350 self.key.print(writer)?;
351 write!(writer, ":")?;
352 self.value.print(writer)
353 }
354}
355
356impl Print for JsModule {
357 fn print(&self, writer: &mut dyn Write) -> Result<()> {
358 for statement in self.statements.iter() {
359 statement.print(writer)?;
360 }
361 Ok(())
362 }
363}
364
365impl Print for JsArrowBody {
366 fn print(&self, writer: &mut dyn Write) -> Result<()> {
367 match self {
368 JsArrowBody::Block(block) => block.print(writer),
369 JsArrowBody::Expr(expr) => expr.print(writer),
370 }
371 }
372}
crates/js_printer/printer_tests.rs created+53
......@@ -0,0 +1,53 @@
1#![cfg(test)]
2use crate::Print;
3
4use js_ast::JsExprData as E;
5use num::{bigint::Sign, BigInt};
6
7macro_rules! assert_printed {
8 ($value:expr, $expected:expr) => {
9 assert_eq!($value.print_to_string(), $expected);
10 };
11}
12
13#[test]
14fn number() {
15 assert_printed!(E::Number(1.0), "1");
16 assert_printed!(E::Number(1.5), "1.5");
17 assert_printed!(E::Number(1.5e10), "15000000000");
18 assert_printed!(E::Number(1.5e-10), "0.00000000015");
19 assert_printed!(E::Number(-1.0), "-1");
20 assert_printed!(E::Number(-1.5), "-1.5");
21 assert_printed!(E::Number(0f64), "0");
22}
23
24#[test]
25fn bigint() {
26 assert_printed!(E::BigInt(BigInt::new(Sign::Plus, vec![1])), "1n");
27 assert_printed!(
28 E::BigInt(BigInt::new(Sign::Plus, vec![10000000])),
29 "10000000n"
30 );
31 assert_printed!(
32 E::BigInt(BigInt::new(Sign::Minus, vec![10000000])),
33 "-10000000n"
34 );
35}
36
37#[test]
38fn boolean() {
39 assert_printed!(E::Bool(true), "true");
40 assert_printed!(E::Bool(false), "false");
41}
42
43#[test]
44fn string() {
45 assert_printed!(E::str(""), "\"\"");
46 assert_printed!(E::str("hello"), "\"hello\"");
47 assert_printed!(E::str("hello\nworld"), "\"hello\\nworld\"");
48}
49
50#[test]
51fn identifier() {
52 assert_printed!(E::str_id("hello"), "hello");
53}
crates/js_printer/src/lib.rs deleted-16
......@@ -1,16 +0,0 @@
1mod print_expression;
2mod print_statement;
3
4use print_statement::print_statement;
5
6use js_ast::JsStatement;
7
8pub fn print(forest: Vec<JsStatement>) -> String {
9 let mut result = String::new();
10
11 for tree in forest {
12 result.push_str(&print_statement(&tree));
13 }
14
15 result
16}
crates/js_printer/src/print_expression.rs deleted-266
......@@ -1,266 +0,0 @@
1use js_ast::JsExpression;
2
3use crate::print_statement::print_statement;
4
5pub fn print_expression(statement: &JsExpression) -> String {
6 match statement {
7 JsExpression::Raw(value) => format!("{}", value),
8 JsExpression::Number(value) => format!("{}", value),
9 JsExpression::BigInt(value) => format!("{}n", value),
10 JsExpression::String(value) => {
11 let mut result = String::new();
12 // TODO: This is an INCREDIBLY naive implementation of escaping.
13 // Also it's probably not even correct.
14 for char in value.chars() {
15 match char {
16 '\n' => result.push_str("\\n"),
17 '\r' => result.push_str("\\r"),
18 '\t' => result.push_str("\\t"),
19 '"' => result.push_str("\\\""),
20 '\\' => result.push_str("\\\\"),
21 _ => result.push(char),
22 }
23 }
24 format!("\"{}\"", result)
25 }
26 JsExpression::Bool(value) => format!("{}", value),
27 JsExpression::Identifier(value) => format!("{}", value),
28 JsExpression::Array(value) => format!(
29 "[{}]",
30 value
31 .iter()
32 .map(|x| print_expression(x))
33 .collect::<Vec<String>>()
34 .join(",")
35 ),
36 JsExpression::Object(value) => format!(
37 "{{{}}}",
38 value
39 .iter()
40 .map(|(k, v)| format!("\"{}\":{}", k, print_expression(v)))
41 .collect::<Vec<String>>()
42 .join(",")
43 ),
44 JsExpression::Operation {
45 operator,
46 left,
47 right,
48 } => format!(
49 "{}{}{}",
50 print_expression(left),
51 operator.clone().to_string(),
52 print_expression(right)
53 ),
54 JsExpression::Function { arguments, value } => format!(
55 "({})=>{{{}}}",
56 arguments.join(","),
57 value
58 .iter()
59 .map(|x| format!("{}", print_statement(x)))
60 .collect::<Vec<String>>()
61 .join("")
62 ),
63 JsExpression::FunctionCall { name, arguments } => format!(
64 "{}({})",
65 match *name.clone() {
66 JsExpression::Identifier(value) => value.clone(),
67 JsExpression::Raw(value) => value.clone(),
68 _ => panic!("Function call name must be an identifier"),
69 },
70 arguments
71 .iter()
72 .map(|x| print_expression(x))
73 .collect::<Vec<String>>()
74 .join(",")
75 ),
76 }
77}
78
79#[cfg(test)]
80mod tests {
81 use crate::print_expression::print_expression;
82 use js_ast::JsExpression;
83 use js_ast::JsStatement;
84 use num::{bigint::Sign, BigInt};
85 use std::collections::HashMap;
86 use util::Operator;
87
88 #[test]
89 fn number() {
90 assert_eq!(print_expression(&JsExpression::Number(1.0)), "1");
91 assert_eq!(print_expression(&JsExpression::Number(1.5)), "1.5");
92 assert_eq!(
93 print_expression(&JsExpression::Number(1.5e10)),
94 "15000000000"
95 );
96 assert_eq!(
97 print_expression(&JsExpression::Number(1.5e-10)),
98 "0.00000000015"
99 );
100 }
101
102 #[test]
103 fn bigint() {
104 assert_eq!(
105 print_expression(&JsExpression::BigInt(BigInt::new(Sign::Plus, vec![1]))),
106 "1n"
107 );
108 assert_eq!(
109 print_expression(&JsExpression::BigInt(BigInt::new(
110 Sign::Plus,
111 vec![10000000]
112 ))),
113 "10000000n"
114 );
115 assert_eq!(
116 print_expression(&JsExpression::BigInt(BigInt::new(
117 Sign::Minus,
118 vec![10000000]
119 ))),
120 "-10000000n"
121 );
122 }
123
124 #[test]
125 fn string() {
126 assert_eq!(
127 print_expression(&JsExpression::String("".to_string())),
128 "\"\""
129 );
130 assert_eq!(
131 print_expression(&JsExpression::String("hello".to_string())),
132 "\"hello\""
133 );
134 assert_eq!(
135 print_expression(&JsExpression::String("hello\nworld".to_string())),
136 "\"hello\\nworld\""
137 );
138 }
139
140 #[test]
141 fn bool() {
142 assert_eq!(print_expression(&JsExpression::Bool(true)), "true");
143 assert_eq!(print_expression(&JsExpression::Bool(false)), "false");
144 }
145
146 #[test]
147 fn identifier() {
148 assert_eq!(
149 print_expression(&JsExpression::Identifier("hello".to_string())),
150 "hello"
151 );
152 }
153
154 #[test]
155 fn array() {
156 assert_eq!(print_expression(&JsExpression::Array(vec![])), "[]");
157 assert_eq!(
158 print_expression(&JsExpression::Array(vec![JsExpression::Number(1.0)])),
159 "[1]"
160 );
161 assert_eq!(
162 print_expression(&JsExpression::Array(vec![
163 JsExpression::Number(1.0),
164 JsExpression::Number(2.0)
165 ])),
166 "[1,2]"
167 );
168 }
169
170 #[test]
171 fn object() {
172 assert_eq!(
173 print_expression(&JsExpression::Object(HashMap::new())),
174 "{}"
175 );
176 let mut map = HashMap::new();
177 map.insert("hello".to_string(), JsExpression::Number(1.0));
178 assert_eq!(
179 print_expression(&JsExpression::Object(map)),
180 "{\"hello\":1}"
181 );
182 }
183
184 #[test]
185 fn operation() {
186 assert_eq!(
187 print_expression(&JsExpression::Operation {
188 operator: Operator::Add,
189 left: Box::new(JsExpression::Number(1.0)),
190 right: Box::new(JsExpression::Number(2.0)),
191 }),
192 "1+2"
193 );
194 assert_eq!(
195 print_expression(&JsExpression::Operation {
196 operator: Operator::Subtract,
197 left: Box::new(JsExpression::Number(1.0)),
198 right: Box::new(JsExpression::Number(2.0)),
199 }),
200 "1-2"
201 );
202 }
203
204 #[test]
205 fn function() {
206 use crate::print_expression::print_expression;
207 use js_ast::JsExpression;
208
209 assert_eq!(
210 print_expression(&JsExpression::Function {
211 arguments: vec![],
212 value: vec![],
213 }),
214 "()=>{}"
215 );
216 assert_eq!(
217 print_expression(&JsExpression::Function {
218 arguments: vec!["a".to_string()],
219 value: vec![],
220 }),
221 "(a)=>{}"
222 );
223 assert_eq!(
224 print_expression(&JsExpression::Function {
225 arguments: vec!["a".to_string(), "b".to_string()],
226 value: vec![],
227 }),
228 "(a,b)=>{}"
229 );
230 assert_eq!(
231 print_expression(&JsExpression::Function {
232 arguments: vec!["a".to_string(), "b".to_string()],
233 value: vec![JsStatement::VariableDeclaration {
234 name: "a".to_string(),
235 value: JsExpression::Number(1.0)
236 }],
237 }),
238 "(a,b)=>{let a=1;}"
239 );
240 }
241
242 #[test]
243 fn function_call() {
244 assert_eq!(
245 print_expression(&JsExpression::FunctionCall {
246 name: Box::new(JsExpression::Identifier("hello".to_string())),
247 arguments: vec![],
248 }),
249 "hello()"
250 );
251 assert_eq!(
252 print_expression(&JsExpression::FunctionCall {
253 name: Box::new(JsExpression::Identifier("hello".to_string())),
254 arguments: vec![JsExpression::Number(1.0)],
255 }),
256 "hello(1)"
257 );
258 assert_eq!(
259 print_expression(&JsExpression::FunctionCall {
260 name: Box::new(JsExpression::Identifier("hello".to_string())),
261 arguments: vec![JsExpression::Number(1.0), JsExpression::Number(2.0)],
262 }),
263 "hello(1,2)"
264 );
265 }
266}
crates/js_printer/src/print_statement.rs deleted-145
......@@ -1,145 +0,0 @@
1use js_ast::JsStatement;
2
3use crate::print_expression::print_expression;
4
5pub fn print_statement(statement: &JsStatement) -> String {
6 match statement {
7 JsStatement::Expression(value) => format!("{};", print_expression(value)),
8 JsStatement::Export(value) => format!("export {}", print_statement(value)),
9 JsStatement::Return(value) => format!("return {};", print_expression(value)),
10 JsStatement::VariableDeclaration { name, value } => {
11 format!("let {}={};", name, print_expression(value))
12 }
13 JsStatement::ConstantDeclaration { name, value } => {
14 format!("const {}={};", name, print_expression(value))
15 }
16 JsStatement::Function {
17 name,
18 arguments,
19 value,
20 } => format!(
21 "function {}({}){{{}}}",
22 name,
23 arguments.join(","),
24 value
25 .iter()
26 .map(|x| format!("{}", print_statement(x)))
27 .collect::<Vec<String>>()
28 .join("")
29 ),
30 JsStatement::If {
31 condition,
32 value,
33 else_value,
34 } => {
35 let else_value = match else_value {
36 Some(value) => format!(
37 "else{{{}}}",
38 value
39 .iter()
40 .map(|x| format!("{}", print_statement(x)))
41 .collect::<Vec<String>>()
42 .join("")
43 ),
44 None => "".to_string(),
45 };
46 format!(
47 "if({}){{{}}}{}",
48 print_expression(condition),
49 value
50 .iter()
51 .map(|x| format!("{}", print_statement(x)))
52 .collect::<Vec<String>>()
53 .join(""),
54 else_value
55 )
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::print_statement::print_statement;
63 use js_ast::JsExpression;
64 use js_ast::JsStatement;
65
66 #[test]
67 fn variable_declaration() {
68 assert_eq!(
69 print_statement(&JsStatement::VariableDeclaration {
70 name: "x".to_string(),
71 value: JsExpression::Number(1.0),
72 }),
73 "let x=1;"
74 );
75 }
76
77 #[test]
78 fn constant_declaration() {
79 assert_eq!(
80 print_statement(&JsStatement::ConstantDeclaration {
81 name: "x".to_string(),
82 value: JsExpression::Number(1.0),
83 }),
84 "const x=1;"
85 );
86 }
87
88 #[test]
89 fn function_declaration() {
90 assert_eq!(
91 print_statement(&JsStatement::Function {
92 name: "x".to_string(),
93 arguments: vec!["y".to_string()],
94 value: vec![JsStatement::Expression(JsExpression::Number(1.0))],
95 }),
96 "function x(y){1;}"
97 );
98 }
99
100 #[test]
101 fn expression() {
102 assert_eq!(
103 print_statement(&JsStatement::Expression(JsExpression::Number(1.0))),
104 "1;"
105 );
106 }
107
108 #[test]
109 fn export() {
110 assert_eq!(
111 print_statement(&JsStatement::Export(Box::new(JsStatement::Expression(
112 JsExpression::Number(1.0)
113 )))),
114 "export 1;"
115 );
116 }
117
118 #[test]
119 fn return_statement() {
120 assert_eq!(
121 print_statement(&JsStatement::Return(JsExpression::Number(1.0))),
122 "return 1;"
123 );
124 }
125
126 #[test]
127 fn if_statement() {
128 assert_eq!(
129 print_statement(&JsStatement::If {
130 condition: JsExpression::Number(1.0),
131 value: vec![JsStatement::Expression(JsExpression::Number(2.0))],
132 else_value: None,
133 }),
134 "if(1){2;}"
135 );
136 assert_eq!(
137 print_statement(&JsStatement::If {
138 condition: JsExpression::Number(1.0),
139 value: vec![JsStatement::Expression(JsExpression::Number(2.0))],
140 else_value: Some(vec![JsStatement::Expression(JsExpression::Number(3.0))]),
141 }),
142 "if(1){2;}else{3;}"
143 );
144 }
145}
crates/parser/Cargo.toml created+15
......@@ -0,0 +1,15 @@
1[package]
2name = "parser"
3version = "0.1.0"
4edition = "2021"
5
6[lib]
7path = "./parser.rs"
8
9[dependencies]
10miette.workspace = true
11thiserror.workspace = true
12num.workspace = true
13
14ast = { path = "../ast" }
15tokenizer = { path = "../tokenizer" }
crates/parser/parse_block.rs created+64
......@@ -0,0 +1,64 @@
1use crate::{parse_statement::parse_statement, Parser};
2use ast::Block;
3use miette::Result;
4use tokenizer::Token;
5
6/// Precondition: The first token exists and is '{'
7pub fn parse_block(p: &mut Parser) -> Result<Block> {
8 match p.next() {
9 Some(Token::OpenBrace) => (),
10 _ => unreachable!(),
11 }
12
13 let mut statements = Vec::new();
14
15 'outer: loop {
16 let is_statement_like_expression = p
17 .peek()
18 .map_or(false, |x| x.is_start_of_statement_like_expression());
19
20 let statement = match parse_statement(p) {
21 Ok(statement) => statement,
22 Err(err) => {
23 return Err(err);
24 }
25 };
26 statements.push(statement.clone());
27
28 match p.peek() {
29 Some(Token::Semicolon) => loop {
30 p.next();
31 match p.peek() {
32 Some(Token::ClosedBrace) => {
33 p.next();
34 return Ok(Block {
35 statements,
36 has_implicit_return: false,
37 });
38 }
39 _ => continue 'outer,
40 }
41 },
42 Some(Token::ClosedBrace) => {
43 p.next();
44 if !statement.is_expression() {
45 return Err(p
46 .error("Expected semicolon or '}' after statement".to_string())
47 .into());
48 }
49
50 return Ok(Block {
51 statements,
52 has_implicit_return: true,
53 });
54 }
55 _ => {
56 if statement.require_semicolon(is_statement_like_expression) {
57 return Err(p
58 .error("Expected semicolon or '}' after statement".to_string())
59 .into());
60 }
61 }
62 };
63 }
64}
crates/parser/parse_call_arguments.rs created+36
......@@ -0,0 +1,36 @@
1use ast::Expr;
2use miette::Result;
3use tokenizer::Token;
4
5use crate::{parse_expr::parse_expr, Parser};
6
7/// Precondition: The '(' was already consumed
8pub fn parse_call_arguments(p: &mut Parser) -> Result<Vec<Expr>> {
9 let mut args = Vec::new();
10
11 if let Some(Token::ClosedParen) = p.peek() {
12 _ = p.next();
13 return Ok(args);
14 }
15
16 loop {
17 let arg = parse_expr(p)?;
18 args.push(arg);
19
20 match p.peek() {
21 Some(Token::Comma) => {
22 _ = p.next();
23 continue;
24 }
25 Some(Token::ClosedParen) => {
26 _ = p.next();
27 return Ok(args);
28 }
29 _ => {
30 return Err(p
31 .error("Expected ',' or ')' after argument".to_string())
32 .into())
33 }
34 }
35 }
36}
crates/parser/parse_declaration.rs created+50
......@@ -0,0 +1,50 @@
1use ast::{DocComment, Type, VarDecl};
2use miette::Result;
3use tokenizer::Token;
4
5use crate::parse_expr::parse_expr;
6use crate::parse_type::parse_type;
7use crate::Parser;
8
9/// Precondition: The first token exists and is either 'var' or 'let'
10pub fn parse_declaration(
11 p: &mut Parser,
12 is_pub: bool,
13 comment: Option<DocComment>,
14) -> Result<VarDecl> {
15 let loc = p.loc();
16 let is_constant = match p.next() {
17 Some(Token::Var) => false,
18 Some(Token::Let) => true,
19 _ => unreachable!(),
20 };
21 let name = match p.next() {
22 Some(Token::Identifier(name)) => name,
23 Some(tok) => return Err(p.error(format!("Unexpected {}", tok)).into()),
24 None => return Err(p.error(format!("Unexpected end of file")).into()),
25 };
26 let return_type = match p.peek() {
27 Some(Token::Colon) => {
28 p.next();
29 parse_type(p)?
30 }
31 _ => Type::Infer,
32 };
33 let init = match p.next() {
34 Some(Token::Equal) => parse_expr(p)?,
35 _ => {
36 return Err(p
37 .error("Variable must have an initializer".to_string())
38 .into())
39 }
40 };
41 Ok(VarDecl {
42 name: name.clone(),
43 data_type: return_type,
44 init,
45 is_constant,
46 is_pub,
47 comment,
48 loc,
49 })
50}
crates/parser/parse_doc_comment.rs created+15
......@@ -0,0 +1,15 @@
1use ast::DocComment;
2use tokenizer::Token;
3
4use crate::Parser;
5
6pub fn maybe_read_doc_comment(p: &mut Parser) -> Option<DocComment> {
7 match p.peek() {
8 Some(Token::DocComment(data)) => {
9 let loc = p.loc();
10 p.next();
11 Some(DocComment { data, loc })
12 }
13 _ => None,
14 }
15}
crates/parser/parse_expr.rs created+99
......@@ -0,0 +1,99 @@
1use ast::Associativity::*;
2use ast::{BinaryOperator, Expr, ExprData, OperatorLevel};
3use miette::Result;
4
5use crate::parse_expr_basic::parse_expr_basic;
6use crate::Parser;
7
8pub fn parse_expr(p: &mut Parser) -> Result<Expr> {
9 let mut expr = parse_expr_basic(p)?;
10
11 // Edge case for return. This has already parsed all the way to the end of the expression
12 if let ExprData::Return(_) = expr.data {
13 return Ok(expr);
14 }
15
16 // Parse operators
17 loop {
18 if let Some(token) = p.peek() {
19 if let Some(level) = BinaryOperator::from_token(&token).map(|o| o.level()) {
20 _ = p.next();
21 match level.associativity() {
22 RightToLeft => {
23 expr = parse_expr_binary(
24 p,
25 BinaryOperator::from_token(&token).unwrap(),
26 expr,
27 level,
28 )?;
29 }
30 LeftToRight => {
31 // TODO(@paperdave): TODO
32 expr = parse_expr_binary(
33 p,
34 BinaryOperator::from_token(&token).unwrap(),
35 expr,
36 level,
37 )?;
38 }
39 }
40 continue;
41 }
42 }
43 break;
44 }
45
46 Ok(expr)
47}
48
49/// Prerequisite: `p.peek()` is an operator
50fn parse_expr_binary(
51 p: &mut Parser,
52 op: BinaryOperator,
53 left: Expr,
54 level: OperatorLevel,
55) -> Result<Expr> {
56 let loc = p.loc();
57 let mut right = parse_expr_basic(p)?;
58
59 loop {
60 if let Some(token) = p.peek() {
61 if let Some(new_level) = BinaryOperator::from_token(&token).map(|o| o.level()) {
62 if new_level <= level {
63 _ = p.next();
64 match new_level.associativity() {
65 RightToLeft => {
66 right = parse_expr_binary(
67 p,
68 BinaryOperator::from_token(&token).unwrap(),
69 right,
70 new_level,
71 )?;
72 }
73 LeftToRight => {
74 // TODO(@paperdave): TODO
75 right = parse_expr_binary(
76 p,
77 BinaryOperator::from_token(&token).unwrap(),
78 right,
79 new_level,
80 )?;
81 }
82 }
83 continue;
84 }
85 }
86 }
87 break;
88 }
89
90 Ok(Expr::new(
91 ExprData::Binary {
92 op,
93 left: Box::new(left),
94 right: Box::new(right),
95 },
96 loc,
97 None,
98 ))
99}
crates/parser/parse_expr_basic.rs created+180
......@@ -0,0 +1,180 @@
1use ast::{BuiltinId, Expr, ExprData, Literal, UnaryOperator};
2use miette::Result;
3use tokenizer::Token;
4
5use crate::{
6 parse_block::parse_block, parse_call_arguments::parse_call_arguments, parse_expr::parse_expr,
7 parse_expr_if::parse_expr_if, Parser,
8};
9
10pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> {
11 let loc = p.loc();
12 let comments = p.take_comments();
13 let mut expr = match p.peek() {
14 Some(Token::Number(number)) => {
15 _ = p.next();
16 Expr::new(
17 ExprData::Literal(Literal::Number(number.clone())),
18 loc,
19 comments,
20 )
21 }
22 Some(Token::String(string)) => {
23 _ = p.next();
24 Expr::new(
25 ExprData::Literal(Literal::String(string.clone())),
26 loc,
27 comments,
28 )
29 }
30 Some(Token::True) => {
31 _ = p.next();
32 Expr::new(ExprData::Literal(Literal::Bool(true)), loc, comments)
33 }
34 Some(Token::False) => {
35 _ = p.next();
36 Expr::new(ExprData::Literal(Literal::Bool(false)), loc, comments)
37 }
38 Some(Token::Void) => {
39 _ = p.next();
40 Expr::new(ExprData::Literal(Literal::Void), loc, comments)
41 }
42 Some(Token::Identifier(name)) => {
43 let loc = p.loc();
44 _ = p.next();
45 if name.starts_with("@") {
46 if let Some(id) = BuiltinId::from_str(name.as_str()) {
47 if let Some(Token::OpenParen) = p.next() {
48 let arguments = parse_call_arguments(p)?;
49 Expr::new(ExprData::Builtin { id, arguments }, loc, comments)
50 } else {
51 return Err(p
52 .error_at(format!("Built-in function '{}' must be called", name), loc)
53 .into());
54 }
55 } else {
56 return Err(p
57 .error_at(format!("Non-existant built-in: '{}'", name), loc)
58 .into());
59 }
60 } else {
61 Expr::new(ExprData::Identifier(name), loc, comments)
62 }
63 }
64
65 Some(Token::Return) => {
66 if p.allow_return {
67 return Err(p
68 .error("'return' cannot be used at top level".to_string())
69 .into());
70 } else {
71 _ = p.next();
72 return Ok(Expr::new(
73 ExprData::Return(Box::new(parse_expr(p)?)),
74 loc,
75 comments,
76 ));
77 }
78 }
79
80 Some(Token::If) => Expr::new(ExprData::If(parse_expr_if(p)?), loc, comments),
81
82 Some(Token::OpenBrace) => Expr::new(ExprData::Block(parse_block(p)?), loc, comments),
83
84 Some(Token::OpenParen) => {
85 _ = p.next();
86 let expr = parse_expr(p)?;
87 match p.next() {
88 Some(Token::ClosedParen) => (),
89 Some(token) => {
90 return Err(p.error(format!("Expected ')', but got {:?}", token)).into())
91 }
92 None => return Err(p.error("Unexpected end of file".to_string()).into()),
93 }
94 expr
95 }
96
97 Some(t @ Token::Bang) | Some(t @ Token::Minus) | Some(t @ Token::Tilde) => {
98 _ = p.next();
99 Expr::new(
100 ExprData::Unary {
101 op: match t {
102 Token::Bang => UnaryOperator::Not,
103 Token::Minus => UnaryOperator::Neg,
104 Token::Tilde => UnaryOperator::BitNot,
105 _ => unreachable!(),
106 },
107 value: Box::new(parse_expr_basic(p)?),
108 },
109 loc,
110 comments,
111 )
112 }
113
114 Some(t) => return Err(p.error(format!("Unexpected '{}'", t)).into()),
115 None => return Err(p.error("Unexpected end of file".to_string()).into()),
116 };
117
118 // Parse function calls and field accesses
119 loop {
120 match p.peek() {
121 Some(Token::Dot) => {
122 let loc = p.loc();
123 _ = p.next();
124 let field = match p.peek() {
125 Some(Token::Identifier(name)) => {
126 let loc = p.loc();
127 p.next();
128 Expr::new(ExprData::Literal(Literal::String(name)), loc, None)
129 }
130 _ => {
131 return Err(p.error("Expected identifier after dot".to_string()).into());
132 }
133 };
134 expr = Expr::new(
135 ExprData::Field {
136 base: Box::new(expr),
137 field: Box::new(field),
138 },
139 loc,
140 None,
141 );
142 }
143 Some(Token::OpenParen) => {
144 let loc = p.loc();
145 _ = p.next();
146 let arguments = parse_call_arguments(p)?;
147 expr = Expr::new(
148 ExprData::Call {
149 base: Box::new(expr),
150 arguments,
151 },
152 loc,
153 None,
154 );
155 }
156 Some(Token::OpenBracket) => {
157 let loc = p.loc();
158 _ = p.next();
159 let index = parse_expr(p)?;
160 if let Some(Token::ClosedBracket) = p.next() {
161 expr = Expr::new(
162 ExprData::Field {
163 base: Box::new(expr),
164 field: Box::new(index),
165 },
166 loc,
167 None,
168 );
169 } else {
170 return Err(p
171 .error("Expected closing bracket after index".to_string())
172 .into());
173 }
174 }
175 _ => break,
176 }
177 }
178
179 Ok(expr)
180}
crates/parser/parse_expr_if.rs created+70
......@@ -0,0 +1,70 @@
1use ast::{BlockOrIf, BlockWithLoc, If};
2use miette::Result;
3use tokenizer::Token;
4
5use crate::parse_block::parse_block;
6use crate::parse_expr::parse_expr;
7use crate::Parser;
8
9/// Precondition: the first token exists and is 'if'
10pub fn parse_expr_if(p: &mut Parser) -> Result<If> {
11 match p.next() {
12 Some(Token::If) => (),
13 _ => unreachable!(),
14 }
15
16 match p.peek() {
17 Some(Token::OpenBrace) => {
18 return Err(p
19 .error(
20 "To use a block as the condition for an if statement, wrap it in parentheses"
21 .to_string(),
22 )
23 .into());
24 }
25 _ => (),
26 }
27
28 let condition = Box::new(parse_expr(p)?);
29
30 let yes = match p.peek() {
31 Some(Token::OpenBrace) => {
32 let loc = p.loc();
33 BlockWithLoc {
34 block: parse_block(p)?,
35 loc,
36 }
37 }
38 _ => {
39 return Err(p
40 .error("Expected a block after condition".to_string())
41 .into())
42 }
43 };
44
45 let no = match p.peek() {
46 Some(Token::Else) => {
47 _ = p.next();
48 match p.peek() {
49 Some(Token::OpenBrace) => {
50 let loc = p.loc();
51 // let comments = p.take_comments();
52 Some(BlockOrIf::Block(BlockWithLoc {
53 block: parse_block(p)?,
54 loc,
55 }))
56 }
57 Some(Token::If) => {
58 // TODO(@paperdave): Save location for the if
59 // let loc = p.loc();
60 // let comments = p.take_comments();
61 Some(BlockOrIf::If(Box::new(parse_expr_if(p)?)))
62 }
63 _ => return Err(p.error("Expected a block after else".to_string()).into()),
64 }
65 }
66 _ => None,
67 };
68
69 Ok(If { condition, yes, no })
70}
crates/parser/parse_fn_declaration.rs created+122
......@@ -0,0 +1,122 @@
1use ast::{DocComment, Expr, ExprData, FnDecl, Type};
2use miette::Result;
3use tokenizer::Token;
4
5use crate::parse_block::parse_block;
6use crate::parse_expr::parse_expr;
7use crate::parse_param::parse_param;
8use crate::parse_type::parse_type;
9use crate::Parser;
10
11/// Precondition:
12pub fn parse_fn_declaration(
13 p: &mut Parser,
14 is_pub: bool,
15 comment: Option<DocComment>,
16) -> Result<FnDecl> {
17 let loc = p.loc();
18 match p.next() {
19 Some(Token::Fn) => (),
20 _ => unreachable!(),
21 }
22
23 let name = match p.next() {
24 Some(Token::Identifier(name)) => name,
25 _ => return Err(p.error("Expected identifier after 'fn'".to_string()).into()),
26 };
27
28 match p.peek() {
29 Some(Token::OpenParen) => p.next(),
30 _ => {
31 return Err(p
32 .error("Expected '(' after function name".to_string())
33 .into())
34 }
35 };
36
37 let mut params = Vec::new();
38
39 loop {
40 match p.peek() {
41 Some(Token::ClosedParen) => {
42 _ = p.next();
43 break;
44 }
45 Some(Token::Identifier(_)) => {
46 params.push(parse_param(p)?);
47
48 match p.peek() {
49 Some(Token::Comma) => {
50 p.next();
51 if let Some(Token::ClosedParen) = p.peek() {
52 p.next();
53 break;
54 }
55 }
56 Some(Token::ClosedParen) => {
57 p.next();
58 break;
59 }
60 _ => {
61 let actual = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x));
62 return Err(p
63 .error(format!(
64 "Expected ',' or ')' after identifier, got {}",
65 actual
66 ))
67 .into());
68 }
69 };
70 }
71 _ => {
72 return Err(p
73 .error("Expected ')' or identifier after '('".to_string())
74 .into())
75 }
76 }
77 }
78
79 let return_type = match p.peek() {
80 Some(Token::RightArrow) => {
81 _ = p.next();
82 parse_type(p)?
83 }
84 _ => Type::Infer,
85 };
86
87 let was_allow_return = p.allow_return;
88 p.allow_return = true;
89
90 let body = match p.peek() {
91 Some(Token::OpenBrace) => {
92 let block_loc = p.loc();
93 let block = parse_block(p)?;
94 Expr::new(ExprData::Block(block), block_loc, None)
95 }
96 Some(Token::BigRightArrow) => {
97 _ = p.next();
98 parse_expr(p)?
99 }
100 _ => {
101 let actually = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x));
102 return Err(p
103 .error(format!(
104 "Expected a block or '=>' to follow function declaration, but got {}",
105 actually,
106 ))
107 .into());
108 }
109 };
110
111 p.allow_return = was_allow_return;
112
113 Ok(FnDecl {
114 name,
115 return_type,
116 params,
117 body,
118 is_pub,
119 comment,
120 loc,
121 })
122}
crates/parser/parse_module.rs created+44
......@@ -0,0 +1,44 @@
1use crate::parse_doc_comment::maybe_read_doc_comment;
2use crate::parse_top_level_statement::parse_top_level_statement;
3use crate::Parser;
4use ast::Module;
5use miette::Result;
6use tokenizer::Token;
7
8pub fn parse_module(p: &mut Parser) -> Result<Module> {
9 let mut statements = Vec::new();
10
11 let file_doc_comment = maybe_read_doc_comment(p);
12
13 loop {
14 let is_statement_like_expression = p
15 .peek()
16 .map_or(false, |x| x.is_start_of_statement_like_expression());
17
18 let statement = parse_top_level_statement(p)?;
19
20 match p.peek() {
21 Some(Token::Semicolon) => {
22 while let Some(Token::Semicolon) = p.peek() {
23 p.next();
24 }
25 }
26 _ => {
27 if statement.require_semicolon(is_statement_like_expression) {
28 return Err(p
29 .error("Expected semicolon after statement".to_string())
30 .into());
31 }
32 }
33 };
34
35 statements.push(statement);
36
37 if p.empty() {
38 return Ok(Module {
39 statements,
40 comment: file_doc_comment,
41 });
42 }
43 }
44}
crates/parser/parse_param.rs created+30
......@@ -0,0 +1,30 @@
1use ast::Param;
2use miette::Result;
3use tokenizer::Token;
4
5use crate::{parse_type::parse_type, Parser};
6
7/// Precondition: A token exists and is an identifier
8pub fn parse_param(p: &mut Parser) -> Result<Param> {
9 let name = match p.next() {
10 Some(Token::Identifier(name)) => name.clone(),
11 _ => unreachable!(),
12 };
13
14 let param_type = match p.peek() {
15 Some(Token::Colon) => {
16 _ = p.next();
17 parse_type(p)?
18 }
19 _ => {
20 return Err(p
21 .error("Function parameters require type annotations".to_string())
22 .into())
23 }
24 };
25
26 Ok(Param {
27 name: name.clone(),
28 param_type,
29 })
30}
crates/parser/parse_statement.rs created+66
......@@ -0,0 +1,66 @@
1use ast::Statement;
2use miette::Result;
3use tokenizer::Token;
4
5use crate::parse_declaration::parse_declaration;
6use crate::parse_doc_comment::maybe_read_doc_comment;
7use crate::parse_expr::parse_expr;
8use crate::parse_fn_declaration::parse_fn_declaration;
9use crate::Parser;
10
11pub fn parse_statement(p: &mut Parser) -> Result<Statement> {
12 if let Some(comments) = p.take_comments() {
13 return Ok(Statement::Comment(comments));
14 }
15
16 let m_doc_comment = maybe_read_doc_comment(p);
17 if let Some(doc_comment) = m_doc_comment.clone() {
18 if p.has_comments() {
19 // this implies that comments are immediately after the doc
20 return Err(p
21 .error_at(format!("Unattached doc comment"), doc_comment.loc)
22 .into());
23 }
24 }
25
26 match p.peek() {
27 Some(Token::DocComment(_)) => unreachable!(),
28 Some(Token::Pub) => {
29 _ = p.next();
30 match p.peek() {
31 Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration(
32 p,
33 true,
34 m_doc_comment,
35 )?)),
36 Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration(
37 p,
38 true,
39 m_doc_comment,
40 )?)),
41 Some(_) => Err(p.error("Expected a declaration".to_string()).into()),
42 None => Err(p.error("Unexpected end of file".to_string()).into()),
43 }
44 }
45 Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration(
46 p,
47 false,
48 m_doc_comment,
49 )?)),
50 Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration(
51 p,
52 false,
53 m_doc_comment,
54 )?)),
55 Some(_) => {
56 if let Some(doc_comment) = m_doc_comment {
57 Err(p
58 .error_at(format!("Unattached doc comment"), doc_comment.loc)
59 .into())
60 } else {
61 Ok(Statement::Expr(parse_expr(p)?))
62 }
63 }
64 None => Err(p.error("Unexpected end of file".to_string()).into()),
65 }
66}
crates/parser/parse_top_level_statement.rs created+10
......@@ -0,0 +1,10 @@
1use ast::TopLevelStatement;
2use miette::Result;
3
4use crate::{parse_statement::parse_statement, Parser};
5
6/// Precondition: the next token exists
7pub fn parse_top_level_statement(p: &mut Parser) -> Result<TopLevelStatement> {
8 // TODO: we will eventually have real statements that are exclusive to top-level
9 Ok(parse_statement(p)?.to_top_level())
10}
crates/parser/parse_type.rs created+26
......@@ -0,0 +1,26 @@
1use ast::Type;
2use miette::Result;
3
4use tokenizer::Token;
5
6use crate::Parser;
7
8pub fn parse_type(p: &mut Parser) -> Result<Type> {
9 match p.next() {
10 Some(Token::Identifier(id)) => match id.as_str() {
11 "int" => Ok(Type::Int),
12 "float" => Ok(Type::Float),
13 "bigint" => Ok(Type::BigInt),
14 "bool" => Ok(Type::Bool),
15 "string" => Ok(Type::String),
16 invalid_type => Err(p
17 .error(format!(
18 "Invalid type '{}' (TODO: add custom type support)",
19 invalid_type
20 ))
21 .into()),
22 },
23 Some(unexpected) => Err(p.error(format!("Expected type, got {}", unexpected)).into()),
24 None => Err(p.error("Expected type, got EOF".to_string()).into()),
25 }
26}
crates/parser/parser.rs created+144
......@@ -0,0 +1,144 @@
1mod parse_block;
2mod parse_call_arguments;
3mod parse_declaration;
4mod parse_doc_comment;
5mod parse_expr;
6mod parse_expr_basic;
7mod parse_expr_if;
8mod parse_fn_declaration;
9mod parse_module;
10mod parse_param;
11mod parse_statement;
12mod parse_top_level_statement;
13mod parse_type;
14
15use ast::Comment;
16use ast::Module;
17use miette::Diagnostic;
18use miette::Result;
19use parse_module::parse_module;
20use thiserror::Error;
21use tokenizer::tokenize;
22use tokenizer::{Loc, Token};
23
24pub fn parse(tokens: Vec<Token>) -> Result<Module> {
25 let mut p = Parser::new(tokens);
26 parse_module(&mut p)
27}
28
29pub fn parse_from_string(string: String) -> Result<Module> {
30 let tokens = tokenize(string)?;
31 parse(tokens)
32}
33
34#[derive(Error, Diagnostic, Debug)]
35#[error("ParseError")]
36pub struct ParseError {
37 #[help]
38 error: String,
39 loc: Option<Loc>,
40}
41
42impl ParseError {
43 pub fn new(error: String, loc: Option<Loc>) -> ParseError {
44 ParseError { error, loc }
45 }
46}
47
48#[derive(Debug)]
49pub struct Parser {
50 /// TODO(@paperdave): switch to streaming tokenizer + one that can
51 /// report line and column info
52 tokens: Vec<Token>,
53 cursor: usize,
54
55 /// Whether or not the parser is at the top level
56 pub allow_return: bool,
57
58 comments: Vec<String>,
59}
60
61impl Parser {
62 pub fn new(tokens: Vec<Token>) -> Parser {
63 Parser {
64 tokens,
65 cursor: 0,
66 allow_return: false,
67 comments: vec![],
68 }
69 }
70
71 pub fn empty(&self) -> bool {
72 self.cursor == self.tokens.len()
73 }
74
75 // TODO(@paperdave): fix parser token stuff. its very messy rn
76
77 pub fn next(&mut self) -> Option<Token> {
78 if self.cursor == self.tokens.len() {
79 return None;
80 }
81
82 loop {
83 match self.tokens[self.cursor].clone() {
84 Token::Comment(comment) => {
85 self.comments.push(comment);
86 self.cursor += 1;
87 continue;
88 }
89 token => {
90 self.cursor += 1;
91 return Some(token);
92 }
93 }
94 }
95 }
96
97 pub fn take_comments(&mut self) -> Option<Comment> {
98 if self.comments.len() == 0 {
99 return None;
100 }
101 let comments = self.comments.clone();
102 self.comments = vec![];
103 Some(Comment {
104 data: comments.join("\n"),
105 })
106 }
107
108 pub fn has_comments(&mut self) -> bool {
109 self.peek();
110 self.comments.len() > 0
111 }
112
113 pub fn peek(&mut self) -> Option<Token> {
114 loop {
115 if self.cursor == self.tokens.len() {
116 return None;
117 }
118
119 match self.tokens[self.cursor].clone() {
120 Token::Comment(comment) => {
121 self.comments.push(comment);
122 self.cursor += 1;
123 continue;
124 }
125 token => {
126 return Some(token);
127 }
128 }
129 }
130 }
131
132 pub fn error(&self, error: String) -> ParseError {
133 // TODO(@lino-levan): Add location information
134 ParseError::new(error, None)
135 }
136
137 fn loc(&self) -> Loc {
138 return Loc {};
139 }
140
141 fn error_at(&self, error: String, loc: Loc) -> ParseError {
142 ParseError::new(error, Some(loc))
143 }
144}
crates/sema/Cargo.toml created+14
......@@ -0,0 +1,14 @@
1[package]
2name = "sema"
3version = "0.1.0"
4edition = "2021"
5
6[lib]
7path = "./sema.rs"
8
9[dependencies]
10miette.workspace = true
11thiserror.workspace = true
12num.workspace = true
13
14ast = { path = "../ast" }
\ No newline at end of file
crates/sema/ast_type_coersion.rs created+54
......@@ -0,0 +1,54 @@
1// use ast::{BinaryOperator, ExprData, Type};
2
3impl TypeCoerceable for ExprData {
4 fn to_type(&self) -> Option<Type> {
5 match self {
6 ExprData::Block(block) => {
7 if (block.has_implicit_return) {
8 block.statements.last().unwrap().to_type()
9 } else {
10 Some(Type::Void)
11 }
12 }
13 ExprData::Binary { op, left, right } => {
14 // TODO: this likely has to do further type checking
15 let left_type = left.to_type();
16 match op {
17 BinaryOperator::Add => left_type,
18 BinaryOperator::Sub => left_type,
19 BinaryOperator::Mul => left_type,
20 BinaryOperator::Div => left_type,
21 BinaryOperator::Pow => left_type,
22 BinaryOperator::Eq => Some(Type::Bool),
23 BinaryOperator::Gt => Some(Type::Bool),
24 BinaryOperator::Lt => Some(Type::Bool),
25 BinaryOperator::Gte => Some(Type::Bool),
26 BinaryOperator::Lte => Some(Type::Bool),
27 BinaryOperator::Lshift => Some(Type::Int),
28 BinaryOperator::Rshift => Some(Type::Int),
29 BinaryOperator::BitAnd => Some(Type::Int),
30 BinaryOperator::BitOr => Some(Type::Int),
31 BinaryOperator::BitXor => Some(Type::Int),
32 BinaryOperator::Mod => Some(Type::Int),
33 BinaryOperator::NotEq => Some(Type::Int),
34
35 BinaryOperator::And => Some(Type::Bool),
36 BinaryOperator::Or => Some(Type::Bool),
37 }
38 }
39 ExprData::Unary { op, value } => todo!(),
40 ExprData::Function {
41 name,
42 params,
43 return_type,
44 block,
45 } => Some(return_type),
46 ExprData::Call { base, arguments } => None, // oh shit. yeah this trait isn't implementable
47 ExprData::Builtin { id, arguments } => todo!(),
48 ExprData::If(_) => todo!(),
49 ExprData::Return(_) => todo!(),
50 ExprData::Literal(_) => todo!(),
51 ExprData::Identifier(_) => todo!(),
52 }
53 }
54}
crates/sema/sema.rs created+98
......@@ -0,0 +1,98 @@
1use ast::Module;
2use miette::Result;
3
4pub fn analyze_module(_block: &mut Module) -> Result<()> {
5 //
6 Ok(())
7}
8
9// for statement in &block.statements {
10// check_expression(statement)?;
11// }
12
13// Ok(())
14// }
15
16// pub fn resolve_type(expr: &Ast) -> Result<Type> {
17// match expr {
18// Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())),
19// Ast::Declaration {
20// name,
21// decl_type,
22// value,
23// is_constant,
24// } => Ok(Type::NoReturn),
25// Ast::Binary {
26// operator,
27// left,
28// right,
29// } => merge_types(resolve_type(left)?, resolve_type(right)?),
30// Ast::Function {
31// name,
32// arguments,
33// return_type,
34// block,
35// } => todo!(),
36// Ast::FunctionCall { name, arguments } => todo!(),
37// Ast::If {
38// condition,
39// value,
40// else_value,
41// } => todo!(),
42// Ast::Return(_) => Ok(Type::NoReturn),
43// Ast::Public(_) => todo!(), // TODO: remove Public
44// Ast::Block(_) => todo!(),
45// Ast::Bool(_) => Ok(Type::Bool),
46// Ast::String(_) => Ok(Type::String),
47// Ast::Identifier(_) => todo!(),
48// Ast::BuiltIn(_) => todo!(),
49
50// Ast::Comment(_) => unreachable!(),
51// Ast::DocComment(_) => unreachable!(),
52// }
53// }
54
55// pub fn check_expression(expr: &Ast) -> Result<()> {
56// panic!()
57// match expr {
58// Ast::Declaration {
59// name,
60// decl_type: return_type,
61// value,
62// is_constant,
63// } => {
64// check_expression(value)?;
65// // coerce_type(return_type.clone(), resolve_type(value)?)?;
66// }
67// _ => {}
68// // Ast::Operation {
69// // operator,
70// // left,
71// // right,
72// // } => Ok(()),
73// // Ast::Function {
74// // name,
75// // arguments,
76// // return_type,
77// // block,
78// // } => Ok(()),
79// // Ast::FunctionCall { name, arguments } => Ok(()),
80// // Ast::If {
81// // condition,
82// // value,
83// // else_value,
84// // } => Ok(()),
85// // Ast::Return(_) => Ok(()),
86// // Ast::Public(_) => Ok(()),
87// // Ast::Block(_) => Ok(()),
88// // Ast::Number(_) => Ok(()),
89// // Ast::Bool(_) => Ok(()),
90// // Ast::String(_) => Ok(()),
91// // Ast::Identifier(_) => Ok(()),
92// // Ast::BuiltIn(_) => Ok(()),
93// // Ast::Comment(_) => Ok(()),
94// // Ast::DocComment(_) => Ok(()),
95// }
96
97// Ok(())
98// }
crates/sema/sema_scope.rs created
crates/tokenizer/Cargo.toml+1-2
......@@ -4,9 +4,8 @@ version = "0.1.0"
44edition = "2021"
55
66[lib]
7path="./tokenizer.rs"
78
89[dependencies]
910miette.workspace = true
1011thiserror.workspace = true
11
12util = { path = "../util" }
crates/tokenizer/src/lib.rs deleted-227
......@@ -1,227 +0,0 @@
1use miette::{Diagnostic, Result};
2use thiserror::Error;
3
4use util::Operator;
5
6#[derive(Error, Diagnostic, Debug)]
7#[error("UnknownCharacter")]
8struct UnknownCharacter {
9 #[help]
10 error: String,
11}
12
13#[derive(Debug, Clone)]
14pub enum Token {
15 // Literals
16 Identifier(String),
17 Number(f64),
18 String(String),
19 BuiltIn(String),
20 // Comments
21 Comment(String),
22 DocComment(String),
23 // Keywords
24 True,
25 False,
26 Let,
27 Var,
28 If,
29 Else,
30 Return,
31 Pub,
32 Function,
33 RightArrow,
34 Comma,
35 OpenParanthesis,
36 ClosedParanthesis,
37 OpenBracket,
38 ClosedBracket,
39 Colon,
40 Equals,
41 Semicolon,
42 // Operators
43 Operator(Operator),
44}
45
46pub fn tokenize(content: String) -> Result<Vec<Token>> {
47 let mut tokens: Vec<Token> = Vec::new();
48 let mut chars = content.chars().enumerate().peekable();
49
50 loop {
51 match chars.next() {
52 Some((_, c)) => match c {
53 'a'..='z' | 'A'..='Z' | '_' | '@' => {
54 let mut current_token = String::new();
55 current_token.push(c);
56 loop {
57 let c = chars.peek();
58 if c.is_none() {
59 break;
60 }
61 let (_, c) = c.unwrap();
62 match c {
63 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => {
64 current_token.push(*c);
65 chars.next();
66 }
67 _ => break,
68 }
69 }
70
71 match current_token.as_str() {
72 "var" => tokens.push(Token::Var),
73 "let" => tokens.push(Token::Let),
74 "if" => tokens.push(Token::If),
75 "else" => tokens.push(Token::Else),
76 "pub" => tokens.push(Token::Pub),
77 "fn" => tokens.push(Token::Function),
78 "return" => tokens.push(Token::Return),
79 "true" => tokens.push(Token::True),
80 "false" => tokens.push(Token::False),
81 _ => {
82 if current_token.starts_with('@') {
83 tokens.push(Token::BuiltIn(current_token));
84 } else {
85 tokens.push(Token::Identifier(current_token))
86 }
87 }
88 }
89 }
90 '"' => {
91 let mut current_token = String::new();
92 loop {
93 let c = chars.next();
94 if c.is_none() {
95 break;
96 }
97 let (_, c) = c.unwrap();
98 match c {
99 '"' => break,
100 _ => current_token.push(c),
101 }
102 }
103 tokens.push(Token::String(current_token));
104 }
105 '0'..='9' => {
106 let mut current_token = String::new();
107 current_token.push(c);
108 loop {
109 let c = chars.peek();
110 if c.is_none() {
111 break;
112 }
113 let (_, c) = c.unwrap();
114 match c {
115 '0'..='9' => {
116 current_token.push(*c);
117 chars.next();
118 }
119 _ => break,
120 }
121 }
122 match current_token.parse::<f64>() {
123 Ok(v) => tokens.push(Token::Number(v)),
124 Err(_) => {
125 return Err(UnknownCharacter {
126 error: format!("Unknown character: {}", current_token),
127 }
128 .into())
129 }
130 }
131 }
132 ':' => tokens.push(Token::Colon),
133 '=' => match chars.peek() {
134 Some((_, '=')) => {
135 chars.next();
136 tokens.push(Token::Operator(Operator::Equals));
137 }
138 _ => tokens.push(Token::Equals),
139 },
140 ';' => tokens.push(Token::Semicolon),
141 '>' => match chars.peek() {
142 Some((_, '=')) => {
143 chars.next();
144 tokens.push(Token::Operator(Operator::GreaterThanOrEquals));
145 }
146 _ => tokens.push(Token::Operator(Operator::GreaterThan)),
147 },
148 '<' => match chars.peek() {
149 Some((_, '=')) => {
150 chars.next();
151 tokens.push(Token::Operator(Operator::LessThanOrEquals));
152 }
153 _ => tokens.push(Token::Operator(Operator::LessThan)),
154 },
155 '+' => tokens.push(Token::Operator(Operator::Add)),
156 '-' => match chars.peek() {
157 Some((_, '>')) => {
158 chars.next();
159 tokens.push(Token::RightArrow);
160 }
161 _ => tokens.push(Token::Operator(Operator::Subtract)),
162 },
163 '*' => match chars.peek() {
164 Some((_, '*')) => {
165 chars.next();
166 tokens.push(Token::Operator(Operator::Power));
167 }
168 _ => tokens.push(Token::Operator(Operator::Multiply)),
169 },
170 '/' => {
171 match chars.peek() {
172 Some((_, '/')) => {
173 chars.next(); // Consume the second slash
174
175 let is_doc_comment = match chars.peek() {
176 Some((_, '/')) => {
177 chars.next(); // Consume the third slash
178 true
179 }
180 _ => false,
181 };
182
183 let mut comment = String::new();
184 loop {
185 let c = chars.next();
186 if c.is_none() {
187 break;
188 }
189 let (_, c) = c.unwrap();
190 match c {
191 '\n' => break,
192 _ => {
193 comment.push(c.clone());
194 }
195 }
196 }
197
198 comment = comment.trim().to_string();
199
200 if is_doc_comment {
201 tokens.push(Token::DocComment(comment));
202 } else {
203 tokens.push(Token::Comment(comment));
204 }
205 }
206 _ => tokens.push(Token::Operator(Operator::Divide)),
207 }
208 }
209 ',' => tokens.push(Token::Comma),
210 '(' => tokens.push(Token::OpenParanthesis),
211 ')' => tokens.push(Token::ClosedParanthesis),
212 '{' => tokens.push(Token::OpenBracket),
213 '}' => tokens.push(Token::ClosedBracket),
214 s if s.is_whitespace() => (),
215 _ => {
216 return Err(UnknownCharacter {
217 error: format!("Unsupported character: {}", c),
218 }
219 .into())
220 }
221 },
222 None => break,
223 }
224 }
225
226 Ok(tokens)
227}
crates/tokenizer/tokenizer.rs created+409
......@@ -0,0 +1,409 @@
1use miette::{Diagnostic, Result};
2use thiserror::Error;
3
4#[derive(Debug, PartialEq, Clone)]
5pub struct Loc {}
6
7impl Loc {
8 /// TODO(@paperdave): Remove this. every usage of this is incorrect.
9 pub const EMPTY: Loc = Loc {};
10
11 pub fn new() -> Self {
12 Self {}
13 }
14}
15
16#[derive(Error, Diagnostic, Debug)]
17#[error("UnknownCharacter")]
18struct UnknownCharacter {
19 #[help]
20 error: String,
21}
22
23#[derive(Debug, Clone)]
24pub enum Token {
25 // Literals
26 Identifier(String),
27 Number(f64),
28 String(String),
29 // Comments
30 Comment(String),
31 DocComment(String),
32 // Keywords
33 Else,
34 False,
35 Fn,
36 If,
37 Let,
38 Pub,
39 Return,
40 True,
41 Var,
42 Void,
43 // Symbols
44 And, // &
45 AndAnd, // &&
46 Bang, // !
47 BangEqual, // !=
48 BigRightArrow, // =>
49 Caret, // ^
50 ClosedBrace, // }
51 ClosedBracket, // ]
52 ClosedParen, // )
53 Colon, // :
54 Comma, // ,
55 Dot, // .
56 Equal, // =
57 EqualEqual, // ==
58 Gt, // >
59 GtEqual, // >=
60 GtGt, // >>
61 GtGtGt, // >>>
62 Lt, // <
63 LtEqual, // <=
64 LtLt, // <<
65 Minus, // -
66 OpenBrace, // {
67 OpenBracket, // [
68 OpenParen, // (
69 Or, // |
70 OrOr, // ||
71 Percent, // %
72 Plus, // +
73 RightArrow, // ->
74 Semicolon, // ;
75 Slash, // /
76 Star, // *
77 StarStar, // **
78 Tilde, // ~
79}
80
81impl Token {
82 pub fn is_start_of_statement_like_expression(&self) -> bool {
83 match &self {
84 Token::If | Token::OpenBrace => true,
85 _ => false,
86 }
87 }
88}
89
90impl std::fmt::Display for Token {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 let token = match self {
93 Token::Identifier(_) => "identifier".to_string(),
94
95 Token::Number(n) => n.to_string(),
96 Token::String(s) => format!("\"{}\"", s),
97 Token::Comment(_) => "comment".to_string(),
98 Token::DocComment(_) => "documentation comment".to_string(),
99
100 Token::Else => "else".to_string(),
101 Token::False => "false".to_string(),
102 Token::Fn => "fn".to_string(),
103 Token::If => "if".to_string(),
104 Token::Let => "let".to_string(),
105 Token::Pub => "pub".to_string(),
106 Token::Return => "return".to_string(),
107 Token::True => "true".to_string(),
108 Token::Var => "var".to_string(),
109 Token::Void => "void".to_string(),
110
111 Token::And => "&".to_string(),
112 Token::AndAnd => "&&".to_string(),
113 Token::Bang => "!".to_string(),
114 Token::BangEqual => "!=".to_string(),
115 Token::BigRightArrow => "=>".to_string(),
116 Token::Caret => "^".to_string(),
117 Token::ClosedBrace => "}".to_string(),
118 Token::ClosedBracket => "]".to_string(),
119 Token::ClosedParen => ")".to_string(),
120 Token::Colon => ":".to_string(),
121 Token::Comma => ",".to_string(),
122 Token::Dot => ".".to_string(),
123 Token::Equal => "=".to_string(),
124 Token::EqualEqual => "==".to_string(),
125 Token::Gt => ">".to_string(),
126 Token::GtEqual => ">=".to_string(),
127 Token::GtGt => ">>".to_string(),
128 Token::GtGtGt => ">>>".to_string(),
129 Token::Lt => "<".to_string(),
130 Token::LtEqual => "<=".to_string(),
131 Token::LtLt => "<<".to_string(),
132 Token::Minus => "-".to_string(),
133 Token::OpenBrace => "{".to_string(),
134 Token::OpenBracket => "[".to_string(),
135 Token::OpenParen => "(".to_string(),
136 Token::Or => "|".to_string(),
137 Token::OrOr => "||".to_string(),
138 Token::Percent => "%".to_string(),
139 Token::Plus => "+".to_string(),
140 Token::RightArrow => "->".to_string(),
141 Token::Semicolon => ";".to_string(),
142 Token::Slash => "/".to_string(),
143 Token::Star => "*".to_string(),
144 Token::StarStar => "**".to_string(),
145 Token::Tilde => "~".to_string(),
146 };
147
148 write!(f, "{}", token)
149 }
150}
151
152// TODO(@paperdave): rewrite this to not use a Vec<Token>. we probably dont need it. also store Loc
153pub fn tokenize(content: String) -> Result<Vec<Token>> {
154 let mut tokens: Vec<Token> = Vec::new();
155 let mut chars = content.chars().enumerate().peekable();
156
157 loop {
158 match chars.next() {
159 Some((_, c)) => match c {
160 'a'..='z' | 'A'..='Z' | '_' | '@' => {
161 let mut current_token = String::new();
162 current_token.push(c);
163 loop {
164 let c = chars.peek();
165 if c.is_none() {
166 break;
167 }
168 let (_, c) = c.unwrap();
169 match c {
170 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => {
171 current_token.push(*c);
172 chars.next();
173 }
174 _ => break,
175 }
176 }
177
178 match current_token.as_str() {
179 "else" => tokens.push(Token::Else),
180 "false" => tokens.push(Token::False),
181 "fn" => tokens.push(Token::Fn),
182 "if" => tokens.push(Token::If),
183 "let" => tokens.push(Token::Let),
184 "pub" => tokens.push(Token::Pub),
185 "return" => tokens.push(Token::Return),
186 "true" => tokens.push(Token::True),
187 "var" => tokens.push(Token::Var),
188 "void" => tokens.push(Token::Void),
189 _ => tokens.push(Token::Identifier(current_token)),
190 }
191 }
192 '"' => {
193 let mut current_token = String::new();
194 loop {
195 let c = chars.next();
196 if c.is_none() {
197 break;
198 }
199 let (_, c) = c.unwrap();
200 match c {
201 '"' => break,
202 _ => current_token.push(c),
203 }
204 }
205 tokens.push(Token::String(current_token));
206 }
207 '0'..='9' => {
208 let mut current_token = String::new();
209 current_token.push(c);
210 loop {
211 let c = chars.peek();
212 if c.is_none() {
213 break;
214 }
215 let (_, c) = c.unwrap();
216 match c {
217 '0'..='9' => {
218 current_token.push(*c);
219 chars.next();
220 }
221 _ => break,
222 }
223 }
224 match current_token.parse::<f64>() {
225 Ok(v) => tokens.push(Token::Number(v)),
226 Err(_) => {
227 return Err(UnknownCharacter {
228 error: format!("Unknown character: {}", current_token),
229 }
230 .into())
231 }
232 }
233 }
234 ':' => tokens.push(Token::Colon),
235 '=' => match chars.peek() {
236 Some((_, '=')) => {
237 chars.next();
238 tokens.push(Token::EqualEqual);
239 }
240 Some((_, '>')) => {
241 chars.next();
242 tokens.push(Token::BigRightArrow);
243 }
244 _ => tokens.push(Token::Equal),
245 },
246 ';' => tokens.push(Token::Semicolon),
247 '>' => match chars.peek() {
248 Some((_, '=')) => {
249 chars.next();
250 tokens.push(Token::GtEqual);
251 }
252 Some((_, '>')) => {
253 chars.next();
254 match chars.peek() {
255 Some((_, '>')) => {
256 chars.next();
257 tokens.push(Token::GtGtGt);
258 }
259 _ => tokens.push(Token::GtGt),
260 }
261 }
262 _ => tokens.push(Token::Gt),
263 },
264 '<' => match chars.peek() {
265 Some((_, '=')) => {
266 chars.next();
267 tokens.push(Token::LtEqual);
268 }
269 Some((_, '<')) => {
270 chars.next();
271 tokens.push(Token::LtLt);
272 }
273 _ => tokens.push(Token::Lt),
274 },
275 '+' => tokens.push(Token::Plus),
276 '-' => match chars.peek() {
277 Some((_, '>')) => {
278 chars.next();
279 tokens.push(Token::RightArrow);
280 }
281 _ => tokens.push(Token::Minus),
282 },
283 '*' => match chars.peek() {
284 Some((_, '*')) => {
285 chars.next();
286 tokens.push(Token::StarStar);
287 }
288 _ => tokens.push(Token::Star),
289 },
290 '/' => {
291 match chars.peek() {
292 Some((_, '/')) => {
293 chars.next(); // Consume the second slash
294
295 let is_doc_comment = match chars.peek() {
296 Some((_, '/')) => {
297 chars.next(); // Consume the third slash
298 true
299 }
300 _ => false,
301 };
302
303 let mut comment = String::new();
304 loop {
305 // skip leading whitespace
306 while let Some((_, c)) = chars.peek() {
307 if !c.is_whitespace() {
308 break;
309 }
310 chars.next();
311 }
312 // read the comment
313 while let Some((_, c)) = chars.next() {
314 if c == '\n' || c == '\r' {
315 break;
316 }
317 comment.push(c);
318 }
319 // trim trailing whitespace on line
320 comment = comment.trim_end().to_string();
321 // skip to next non-whitespace character
322 while let Some((_, c)) = chars.peek() {
323 if !c.is_whitespace() {
324 break;
325 }
326 chars.next();
327 }
328 // check if we've hit another comment
329 let mut tmp = chars.clone();
330
331 let is_another_comment = match tmp.peek() {
332 Some((_, '/')) => {
333 tmp.next(); // Consume the second slash
334 match tmp.peek() {
335 Some((_, '/')) => {
336 tmp.next(); // Consume the third slash
337 is_doc_comment
338 }
339 _ => !is_doc_comment,
340 }
341 }
342 _ => false,
343 };
344
345 if is_another_comment {
346 // apply our temporary iterator to the main iterator
347 comment.push('\n');
348 chars = tmp;
349 chars.next();
350 } else {
351 break;
352 }
353 }
354
355 if is_doc_comment {
356 tokens.push(Token::DocComment(comment));
357 } else {
358 tokens.push(Token::Comment(comment));
359 }
360 }
361 _ => tokens.push(Token::Slash),
362 }
363 }
364 ',' => tokens.push(Token::Comma),
365 '(' => tokens.push(Token::OpenParen),
366 ')' => tokens.push(Token::ClosedParen),
367 '{' => tokens.push(Token::OpenBrace),
368 '}' => tokens.push(Token::ClosedBrace),
369 '[' => tokens.push(Token::OpenBracket),
370 ']' => tokens.push(Token::ClosedBracket),
371 '&' => match chars.peek() {
372 Some((_, '&')) => {
373 chars.next();
374 tokens.push(Token::AndAnd);
375 }
376 _ => tokens.push(Token::And),
377 },
378 '|' => match chars.peek() {
379 Some((_, '|')) => {
380 chars.next();
381 tokens.push(Token::OrOr);
382 }
383 _ => tokens.push(Token::Or),
384 },
385 '!' => match chars.peek() {
386 Some((_, '=')) => {
387 chars.next();
388 tokens.push(Token::BangEqual);
389 }
390 _ => tokens.push(Token::Bang),
391 },
392 '^' => tokens.push(Token::Caret),
393 '%' => tokens.push(Token::Percent),
394 '~' => tokens.push(Token::Tilde),
395 '.' => tokens.push(Token::Dot),
396 s if s.is_whitespace() => (),
397 _ => {
398 return Err(UnknownCharacter {
399 error: format!("Unexpected '{}' while tokenizing", c),
400 }
401 .into())
402 }
403 },
404 None => break,
405 }
406 }
407
408 Ok(tokens)
409}
crates/util/Cargo.toml deleted-8
......@@ -1,8 +0,0 @@
1[package]
2name = "util"
3version = "0.1.0"
4edition = "2021"
5
6[lib]
7
8[dependencies]
crates/util/src/lib.rs deleted-30
......@@ -1,30 +0,0 @@
1#[derive(Debug, Clone, PartialEq)]
2pub enum Operator {
3 Add,
4 Subtract,
5 Multiply,
6 Divide,
7 Power,
8 Equals,
9 GreaterThan,
10 LessThan,
11 GreaterThanOrEquals,
12 LessThanOrEquals,
13}
14
15impl Operator {
16 pub fn to_string(self) -> String {
17 String::from(match self {
18 Self::Add => "+",
19 Self::Subtract => "-",
20 Self::Divide => "/",
21 Self::Multiply => "*",
22 Self::Power => "**",
23 Self::Equals => "===",
24 Self::GreaterThan => ">",
25 Self::LessThan => "<",
26 Self::GreaterThanOrEquals => ">=",
27 Self::LessThanOrEquals => "<=",
28 })
29 }
30}
examples/basics/control.blight+1-1
......@@ -1,4 +1,4 @@
1let print: any = @raw("console.log");
1let print = @raw("console.log");
22
33let x: bool = true;
44
examples/basics/expressions.blight+1-1
......@@ -7,5 +7,5 @@ let x: int = 5 + {
77 y
88};
99
10let print: any = @raw("console.log");
10let print = @raw("console.log");
1111print(x); // 10
examples/basics/fib.blight+1-1
......@@ -8,5 +8,5 @@ pub fn fib(n: int) -> int {
88 }
99}
1010
11let print: any = @raw("console.log");
11let print = @raw("console.log");
1212print(fib(10));
examples/basics/functions.blight+1-1
......@@ -4,5 +4,5 @@ fn bingus(x: int, y: int) -> int {
44
55let result: int = bingus(10, 25);
66
7let print: any = @raw("console.log");
7let print = @raw("console.log");
88print(result);
examples/basics/operations.blight+1-1
......@@ -1,4 +1,4 @@
11let a: int = 10 - 2 / 4 * 15;
22
3let print: any = @raw("console.log");
3let print = @raw("console.log");
44print(a);
examples/basics/shadowing.blight+1-1
......@@ -1,4 +1,4 @@
1let print: any = @raw("console.log");
1let print = @raw("console.log");
22
33let x: int = 0;
44let x: int = 1;
examples/basics/stupid.blight created+2
......@@ -0,0 +1,2 @@
1
2fn add(x: int, y: int) => x + y
examples/basics/variables.blight+1-1
......@@ -6,5 +6,5 @@ let a: int = 0;
66let b: float = 0;
77let c: bigint = 0;
88
9let print: any = @raw("console.log");
9let print = @raw("console.log");
1010print(x);
examples/basics/weird.blight+1-1
......@@ -9,6 +9,6 @@ fn bingus(y: bool) -> int {
99 x
1010}
1111
12let print: any = @raw("console.log");
12let print = @raw("console.log");
1313print(bingus(true));
1414print(bingus(false));
examples/interop/deno.blight+1-1
......@@ -1,4 +1,4 @@
1let writeTextFile: any = @raw("Deno.writeTextFile");
1let writeTextFile = @raw("Deno.writeTextFile");
22
33fn x() -> string {
44 "hello"