authorchloe caruso <git@paperclover.net> 2024-01-22 00:32:12-08:00
committerchloe caruso <git@paperclover.net> 2024-01-22 00:32:12-08:00
log12864f7c49c005271c6c5610dfaa2cb71ff56ae7
treed4f9793c94bcab05e4855cf894dee62e17476bd3
parentb9cad6ad285b99192b686be1cea8b1b8cfcdd6c1

finish rewriting the project in rust


35 files changed, 2549 insertions(+), 1958 deletions(-)

Cargo.lock+1
...@@ -239,6 +239,7 @@ dependencies = [...@@ -239,6 +239,7 @@ dependencies = [
239 "ast",239 "ast",
240 "miette",240 "miette",
241 "num",241 "num",
242 "parser",
242 "thiserror",243 "thiserror",
243 "tokenizer",244 "tokenizer",
244]245]
Cargo.toml-1
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
1[workspace.package]1[workspace.package]
2version = "0.1.0"2version = "0.1.0"
3name = "blight"
4edition = "2023"3edition = "2023"
5license = "MIT"4license = "MIT"
6authors = ["Lino Le Van", "Dave Caruso"]5authors = ["Lino Le Van", "Dave Caruso"]
crates/ast/ast.rs+38-66
...@@ -74,6 +74,19 @@ impl Comment {...@@ -74,6 +74,19 @@ impl Comment {
74 }74 }
75 result75 result
76 }76 }
77
78 pub fn to_doc_comment(self, loc: Loc) -> DocComment {
79 DocComment {
80 data: self.data,
81 loc,
82 }
83 }
84}
85
86impl Into<Comment> for DocComment {
87 fn into(self) -> Comment {
88 Comment { data: self.data }
89 }
77}90}
7891
79impl DocComment {92impl DocComment {
...@@ -98,7 +111,13 @@ pub struct BlockWithLoc {...@@ -98,7 +111,13 @@ pub struct BlockWithLoc {
98pub struct If {111pub struct If {
99 pub condition: Box<Expr>,112 pub condition: Box<Expr>,
100 pub yes: BlockWithLoc,113 pub yes: BlockWithLoc,
101 pub no: Option<Box<Expr>>,114 pub no: Option<BlockOrIf>,
115}
116
117#[derive(Debug, Clone)]
118pub enum BlockOrIf {
119 Block(BlockWithLoc),
120 If(Box<If>),
102}121}
103122
104#[derive(Debug, Clone)]123#[derive(Debug, Clone)]
...@@ -166,67 +185,10 @@ impl BuiltinId {...@@ -166,67 +185,10 @@ impl BuiltinId {
166 }185 }
167}186}
168187
169impl ExprData {
170 pub fn is_valid_js_expression(&self) -> bool {
171 match self {
172 // A block containing just expressions can be an expression
173 ExprData::Block(block) => block.is_valid_js_expression(),
174 ExprData::If(expr) => expr.is_valid_js_expression(),
175 ExprData::Return(_) => false,
176 ExprData::Builtin { id, .. } => match id {
177 BuiltinId::Raw => true,
178 },
179 _ => true,
180 }
181 }
182}
183
184impl Block {
185 pub fn is_valid_js_expression(&self) -> bool {
186 self.statements
187 .iter()
188 .all(|statement| statement.is_valid_js_expression())
189 }
190}
191
192impl Expr {
193 pub fn is_valid_js_expression(&self) -> bool {
194 self.data.is_valid_js_expression()
195 }
196}
197
198impl If {
199 pub fn is_valid_js_expression(&self) -> bool {
200 self.condition.is_valid_js_expression()
201 && self.yes.is_valid_js_expression()
202 && match &self.no {
203 Some(no) => no.is_valid_js_expression(),
204 None => true,
205 }
206 }
207}
208
209impl BlockWithLoc {
210 pub fn is_valid_js_expression(&self) -> bool {
211 self.block.is_valid_js_expression()
212 }
213}
214
215impl Statement {188impl Statement {
216 pub fn requires_semicolon(&self) -> bool {189 pub fn is_expression(&self) -> bool {
217 match &self {190 match &self {
218 Statement::Expr(expr) => match expr.data {191 Statement::Expr(_) => true,
219 ExprData::Block(_) => false,
220 ExprData::If(_) => false,
221 _ => true,
222 },
223 _ => false,
224 }
225 }
226
227 pub fn is_valid_js_expression(&self) -> bool {
228 match self {
229 Statement::Expr(expr) => expr.data.is_valid_js_expression(),
230 _ => false,192 _ => false,
231 }193 }
232 }194 }
...@@ -238,16 +200,19 @@ impl Statement {...@@ -238,16 +200,19 @@ impl Statement {
238 Statement::Comment(comment) => TopLevelStatement::Comment(comment),200 Statement::Comment(comment) => TopLevelStatement::Comment(comment),
239 }201 }
240 }202 }
203
204 pub fn is_comment(&self) -> bool {
205 match self {
206 Statement::Comment(_) => true,
207 _ => false,
208 }
209 }
241}210}
242211
243impl TopLevelStatement {212impl TopLevelStatement {
244 pub fn requires_semicolon(&self) -> bool {213 pub fn is_expression(&self) -> bool {
245 match &self {214 match &self {
246 TopLevelStatement::Expr(expr) => match expr.data {215 TopLevelStatement::Expr(_) => true,
247 ExprData::Block(_) => false,
248 ExprData::If(_) => false,
249 _ => true,
250 },
251 _ => false,216 _ => false,
252 }217 }
253 }218 }
...@@ -259,4 +224,11 @@ impl TopLevelStatement {...@@ -259,4 +224,11 @@ impl TopLevelStatement {
259 TopLevelStatement::Comment(comment) => Statement::Comment(comment),224 TopLevelStatement::Comment(comment) => Statement::Comment(comment),
260 }225 }
261 }226 }
227
228 pub fn is_comment(&self) -> bool {
229 match self {
230 TopLevelStatement::Comment(_) => true,
231 _ => false,
232 }
233 }
262}234}
crates/ast/operators.rs+133-23
...@@ -24,6 +24,22 @@ pub enum BinaryOperator {...@@ -24,6 +24,22 @@ pub enum BinaryOperator {
24 NotEq,24 NotEq,
25 And,25 And,
26 Or,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,
27}43}
2844
29#[derive(Debug, Clone, PartialEq)]45#[derive(Debug, Clone, PartialEq)]
...@@ -34,8 +50,8 @@ pub enum UnaryOperator {...@@ -34,8 +50,8 @@ pub enum UnaryOperator {
34}50}
3551
36impl BinaryOperator {52impl BinaryOperator {
37 pub fn to_string(&self) -> String {53 pub fn to_str(&self) -> &'static str {
38 String::from(match self {54 match self {
39 Self::Add => "+",55 Self::Add => "+",
40 Self::Sub => "-",56 Self::Sub => "-",
41 Self::Div => "/",57 Self::Div => "/",
...@@ -56,7 +72,26 @@ impl BinaryOperator {...@@ -56,7 +72,26 @@ impl BinaryOperator {
56 Self::NotEq => "!=",72 Self::NotEq => "!=",
57 Self::And => "&&",73 Self::And => "&&",
58 Self::Or => "||",74 Self::Or => "||",
59 })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())
60 }95 }
6196
62 pub fn from_token(token: &Token) -> Option<BinaryOperator> {97 pub fn from_token(token: &Token) -> Option<BinaryOperator> {
...@@ -85,6 +120,10 @@ impl BinaryOperator {...@@ -85,6 +120,10 @@ impl BinaryOperator {
85 }120 }
86 }121 }
87122
123 pub fn level(&self) -> OperatorLevel {
124 OperatorLevel::from(self)
125 }
126
88 /// Error case of this means 'types do not allow this operation'127 /// Error case of this means 'types do not allow this operation'
89 pub fn perform(&self, left: Literal, right: Literal) -> Result<Literal, ()> {128 pub fn perform(&self, left: Literal, right: Literal) -> Result<Literal, ()> {
90 match self {129 match self {
...@@ -139,17 +178,24 @@ impl BinaryOperator {...@@ -139,17 +178,24 @@ impl BinaryOperator {
139 }),178 }),
140 _ => Err(()),179 _ => Err(()),
141 },180 },
181
182 // Impossible condition doing assign-add to a literal
183 _ => Err(()),
142 }184 }
143 }185 }
144}186}
145187
146impl UnaryOperator {188impl UnaryOperator {
147 pub fn to_string(self) -> String {189 pub fn to_str(&self) -> &'static str {
148 String::from(match self {190 match self {
149 Self::Neg => "-",191 Self::Neg => "-",
150 Self::Not => "!",192 Self::Not => "!",
151 Self::BitNot => "~",193 Self::BitNot => "~",
152 })194 }
195 }
196
197 pub fn to_string(&self) -> String {
198 String::from(self.to_str())
153 }199 }
154200
155 pub fn perform(self, value: Literal) -> Result<Literal, ()> {201 pub fn perform(self, value: Literal) -> Result<Literal, ()> {
...@@ -171,8 +217,17 @@ impl UnaryOperator {...@@ -171,8 +217,17 @@ impl UnaryOperator {
171}217}
172218
173/// Taken from from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table219/// Taken from from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table
174#[derive(PartialEq, PartialOrd)]220///
175pub enum BinaryOperatorLevel {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
176 Exponentiation, // **231 Exponentiation, // **
177 Multiplicative, // *, /, %232 Multiplicative, // *, /, %
178 Additive, // +, -233 Additive, // +, -
...@@ -184,22 +239,77 @@ pub enum BinaryOperatorLevel {...@@ -184,22 +239,77 @@ pub enum BinaryOperatorLevel {
184 BitwiseOr, // |239 BitwiseOr, // |
185 LogicalAnd, // &&240 LogicalAnd, // &&
186 LogicalOr, // ||, ??241 LogicalOr, // ||, ??
242
243 AssignmentAndOther, // =, +=, -=, *=, /=, %=, / <<=, >>=, >>>=, &=, ^=, |=
244 Comma, // ,
187}245}
188246
189pub fn get_binary_op_level(token: &Token) -> Option<BinaryOperatorLevel> {247pub enum Associativity {
190 use BinaryOperatorLevel::*;248 LeftToRight,
191 match token {249 RightToLeft,
192 Token::StarStar => Some(Exponentiation),250}
193 Token::Star | Token::Slash | Token::Percent => Some(Multiplicative),251
194 Token::Plus | Token::Minus => Some(Additive),252impl OperatorLevel {
195 Token::LtLt | Token::GtGt | Token::GtGtGt => Some(BitwiseShift),253 pub fn associativity(&self) -> Associativity {
196 Token::Gt | Token::GtEqual | Token::Lt | Token::LtEqual => Some(Relational),254 use Associativity::*;
197 Token::EqualEqual | Token::BangEqual => Some(Equality),255 use OperatorLevel::*;
198 Token::And => Some(BitwiseAnd),256
199 Token::Caret => Some(BitwiseXor),257 match self {
200 Token::Or => Some(BitwiseOr),258 Accessor => LeftToRight,
201 Token::AndAnd => Some(LogicalAnd),259 Exponentiation => RightToLeft,
202 Token::OrOr => Some(LogicalOr),260 Multiplicative => LeftToRight,
203 _ => None,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 }
204 }314 }
205}315}
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+1-1
...@@ -5,7 +5,7 @@ edition = "2021"...@@ -5,7 +5,7 @@ edition = "2021"
55
6[[bin]]6[[bin]]
7name = "blight"7name = "blight"
8path = "./src/main.rs"8path = "./main.rs"
99
10[dependencies]10[dependencies]
11clap.workspace = true11clap.workspace = true
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-165
...@@ -1,165 +0,0 @@
1use std::{io::Write, path::PathBuf};
2
3use clap::{Parser, Subcommand};
4use miette::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) -> Result<String> {
54 // 1. Tokenize the input
55 let tokens = tokenizer::tokenize(content)?;
56 // 2. Parse the tokens into an AST
57 let blight_ast = parser::parse(tokens)?;
58 // 3. Type check the AST
59 // sema::check_module(&blight_ast)?;
60 // 4. Optimize the AST
61 // optimize(tree.clone())?;
62 // 5. Translate the AST into javascript
63 let js_ast = js_ast::translate(&blight_ast)?;
64 // 6. Print the JS AST
65 let javascript = js_printer::print(js_ast);
66 // 7. Return the javascript
67 return Ok(javascript);
68}
69
70pub fn astdump(content: String) -> Result<String> {
71 // 1. Tokenize the input
72 let tokens = tokenizer::tokenize(content)?;
73 // 2. Parse the tokens into an AST
74 let blight_ast = parser::parse(tokens)?;
75 return Ok(format!("{:#?}", blight_ast));
76}
77
78fn main() -> Result<()> {
79 let cli = Cli::parse();
80
81 match cli.command {
82 Commands::Compile {
83 file,
84 stdout,
85 target: _,
86 format,
87 } => {
88 let contents = match std::fs::read_to_string(file.clone()) {
89 Ok(contents) => contents,
90 Err(e) => {
91 eprintln!("Failed to read {}: {}", file.display(), e);
92 std::process::exit(1);
93 }
94 };
95
96 let mut result = transpile(contents.clone())?;
97
98 if format {
99 let mut formatter = std::process::Command::new("deno")
100 .stdin(std::process::Stdio::piped())
101 .stdout(std::process::Stdio::piped())
102 .stderr(std::process::Stdio::inherit())
103 .arg("fmt")
104 .arg("-")
105 .spawn()
106 .expect("Failed to execute 'deno fmt' on output");
107 let mut stdin = formatter.stdin.take().expect("Failed to open stdin");
108 stdin
109 .write_all(result.as_bytes())
110 .expect("Failed to write to stdin");
111 stdin.flush().expect("Failed to flush stdin");
112 drop(stdin);
113 let out = formatter
114 .wait_with_output()
115 .expect("Failed to execute 'deno fmt' on output");
116 result = String::from_utf8(out.stdout).unwrap();
117 }
118
119 if stdout {
120 println!("{}", result);
121 } else {
122 std::fs::write(file.with_extension("js"), result).unwrap();
123 }
124 }
125 Commands::Run { file, target } => {
126 let contents = std::fs::read_to_string(file.clone()).unwrap();
127 let result = transpile(contents.clone())?;
128 let mut temp_file = std::env::temp_dir();
129 temp_file.push("temp.js");
130 std::fs::write(temp_file.clone(), result).unwrap();
131
132 let rt = get_runtime(&target);
133
134 match rt.as_str() {
135 "deno" => std::process::Command::new("deno")
136 .arg("run")
137 .arg("-A")
138 .arg(temp_file)
139 .spawn()
140 .expect("Failed to execute process"),
141 "node" => std::process::Command::new("node")
142 .arg(temp_file)
143 .spawn()
144 .expect("Failed to execute process"),
145 "bun" => std::process::Command::new("bun")
146 .arg("run")
147 .arg(temp_file)
148 .spawn()
149 .expect("Failed to execute process"),
150 _ => {
151 panic!("Unknown runtime: {}", target);
152 }
153 }
154 .wait()
155 .unwrap();
156 }
157 Commands::Astdump { file } => {
158 let contents = std::fs::read_to_string(file.clone()).unwrap();
159 let result = astdump(contents.clone())?;
160 println!("{}", result);
161 }
162 }
163
164 Ok(())
165}
crates/js_ast/Cargo.toml+1
...@@ -13,3 +13,4 @@ num.workspace = true...@@ -13,3 +13,4 @@ num.workspace = true
1313
14ast = { path = "../ast" }14ast = { path = "../ast" }
15tokenizer = { path = "../tokenizer" } # TODO: move Loc out of the tokenizer15tokenizer = { path = "../tokenizer" } # TODO: move Loc out of the tokenizer
16parser = { path = "../parser" } # TODO: move Loc out of the tokenizer
crates/js_ast/convert_valid_expr.rs created+204
...@@ -0,0 +1,204 @@
1//! "Valid" in this context means that the expression does not contain JS statements
2//! Meaning conversion is a direct map from a blight as node into a JS one
3//!
4//! See `is_valid_js_expression` for more details on what qualifies
5//!
6//! For other nodes, it must hoist statements to the nearest JS block,
7//! is done in the `hoist_*` functions in `host.rs`.
8
9use ast::{BlockOrIf, Comment, ExprData, If};
10use ast::{BlockWithLoc, BuiltinId, Expr, Literal, Statement};
11use tokenizer::Loc;
12
13use crate::is_valid_js_expression::IsValidJSExpression;
14use crate::js_ast::*;
15use crate::HoistContext;
16
17/// Translate a blight ast expression that is a valid JS expression, to a JS expression
18/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
19pub fn convert_valid_expr(ctx: &mut HoistContext, expr: Expr) -> JsExpr {
20 // Assert may be skipped in release builds as all failures are
21 // annotated with a release-checked panic
22 debug_assert!(
23 expr.is_valid_js_expression(),
24 "must call convert_valid_expr with valid js expression (use hoist instead)"
25 );
26
27 let loc = expr.loc;
28 let comment = expr.comment;
29
30 match expr.data {
31 ExprData::Identifier(str) => JsExpr::new(
32 JsExprData::Identifier(ctx.remap_identifier(&str)),
33 loc,
34 comment,
35 ),
36
37 ExprData::Literal(Literal::Bool(value)) => {
38 JsExpr::new(JsExprData::Bool(value), loc, comment)
39 }
40 ExprData::Literal(Literal::Number(value)) => {
41 JsExpr::new(JsExprData::Number(value), loc, comment)
42 }
43 ExprData::Literal(Literal::String(value)) => {
44 JsExpr::new(JsExprData::String(value), loc, comment)
45 }
46 ExprData::Literal(Literal::Void) => JsExpr::new(JsExprData::Null, loc, comment),
47
48 ExprData::Block(block) => convert_valid_block(ctx, BlockWithLoc { block, loc }),
49
50 ExprData::Binary { op, left, right } => {
51 let left = convert_valid_expr(ctx, *left);
52 let right = convert_valid_expr(ctx, *right);
53 JsExpr::new(
54 JsExprData::Binary {
55 op,
56 left: Box::new(left),
57 right: Box::new(right),
58 },
59 loc,
60 comment,
61 )
62 }
63 ExprData::Unary { op, value } => {
64 let value = convert_valid_expr(ctx, *value);
65 JsExpr::new(
66 JsExprData::Unary {
67 op,
68 value: Box::new(value),
69 },
70 loc,
71 comment,
72 )
73 }
74 ExprData::Fn { block, params, .. } => JsExpr::new(
75 JsExprData::ArrowFunction {
76 params: params.into_iter().map(|x| x.name).collect(),
77 body: Box::new(ctx.hoist_block_to_arrow_fn_body(BlockWithLoc {
78 block,
79 loc: loc.clone(),
80 })),
81 },
82 loc,
83 comment,
84 ),
85 ExprData::Builtin { id, arguments } => match id {
86 BuiltinId::Raw => {
87 let first_arg = arguments.iter().next().unwrap();
88 // TODO(@paperdave): this string clone is stupid
89 let str = match first_arg.data.clone() {
90 ExprData::Literal(Literal::String(s)) => s,
91 _ => panic!("raw expects a string"),
92 };
93 JsExpr::new(JsExprData::Raw(str), loc, comment)
94 }
95 },
96 ExprData::If(expr) => convert_valid_if(ctx, expr, loc, comment),
97
98 ExprData::Call { base, arguments } => {
99 let base = convert_valid_expr(ctx, *base);
100 let arguments = arguments
101 .into_iter()
102 .map(|x| convert_valid_expr(ctx, x))
103 .collect();
104 JsExpr::new(
105 JsExprData::Call {
106 base: Box::new(base),
107 arguments,
108 },
109 loc,
110 comment,
111 )
112 }
113
114 ExprData::Field { base, field } => JsExpr::new(
115 match field.data {
116 ExprData::Literal(Literal::String(name)) => JsExprData::Dot {
117 base: Box::new(convert_valid_expr(ctx, *base)),
118 name,
119 },
120 _ => JsExprData::Index {
121 base: Box::new(convert_valid_expr(ctx, *base)),
122 index: Box::new(convert_valid_expr(ctx, *field)),
123 },
124 },
125 loc,
126 comment,
127 ),
128
129 ExprData::Return(_) => unreachable!("convert_valid_expr does not support return"),
130 }
131}
132
133pub fn convert_valid_block(ctx: &mut HoistContext, b: BlockWithLoc) -> JsExpr {
134 debug_assert!(
135 b.is_valid_js_expression(),
136 "must call convert_valid_block with valid js expression"
137 );
138
139 let len = b.block.statements.len();
140 let loc = b.loc;
141 let block_expr = match len {
142 0 => JsExpr::new(JsExprData::Null, loc.clone(), None),
143 // a block expression with a single expression is just that expression
144 1 => convert_valid_statement_to_expr(ctx, b.block.statements.into_iter().next().unwrap()),
145 // Since blocks are not valid expressions in JS, we create a
146 // comma expression from a block with more than one statement.
147 2.. => {
148 let mut items = Vec::new();
149 for statement in b.block.statements.into_iter() {
150 items.push(convert_valid_statement_to_expr(ctx, statement));
151 }
152 JsExpr::new(JsExprData::Comma { items }, loc.clone(), None)
153 }
154 };
155
156 // a block without implicit return is to be type void, so we wrap in a void expression
157 if !b.block.has_implicit_return && len > 0 {
158 JsExpr::new(JsExprData::Void(Box::new(block_expr)), loc, None)
159 } else {
160 block_expr
161 }
162}
163
164pub fn convert_valid_statement_to_expr(ctx: &mut HoistContext, statement: Statement) -> JsExpr {
165 debug_assert!(
166 statement.is_valid_js_expression(),
167 "convert_valid_statement_to_expr on an invalid expression (use hoist instead)"
168 );
169
170 match statement {
171 ast::Statement::Expr(expr) => convert_valid_expr(ctx, expr),
172 _ => unreachable!("attempt to convert statement to js without hoisting"),
173 }
174}
175
176pub fn convert_valid_statement(ctx: &mut HoistContext, statement: Statement) -> JsStatement {
177 let expr = convert_valid_statement_to_expr(ctx, statement);
178 let loc = expr.loc.clone();
179 JsStatement::new(JsStatementData::Expression(expr), loc, None)
180}
181
182pub fn convert_valid_if(
183 ctx: &mut HoistContext,
184 expr: If,
185 loc: Loc,
186 comment: Option<Comment>,
187) -> JsExpr {
188 let condition = convert_valid_expr(ctx, *expr.condition);
189 let yes = convert_valid_block(ctx, expr.yes);
190 let no = match expr.no {
191 Some(BlockOrIf::Block(block)) => Some(convert_valid_block(ctx, block)),
192 Some(BlockOrIf::If(else_if)) => Some(convert_valid_if(ctx, *else_if, Loc::EMPTY, None)),
193 None => None,
194 };
195 JsExpr::new(
196 JsExprData::Ternary {
197 condition: Box::new(condition),
198 yes: Box::new(yes),
199 no: no.map(Box::new),
200 },
201 loc,
202 comment,
203 )
204}
crates/js_ast/has_side_effects.rs created+90
...@@ -0,0 +1,90 @@
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::Decl(decl) => decl.init.has_side_effects(),
47 Statement::Comment(_) => false,
48 }
49 }
50}
51
52impl HasSideEffects for Block {
53 fn has_side_effects(&self) -> bool {
54 self.statements
55 .iter()
56 .any(|statement| statement.has_side_effects())
57 }
58}
59
60impl HasSideEffects for Expr {
61 fn has_side_effects(&self) -> bool {
62 self.data.has_side_effects()
63 }
64}
65
66impl HasSideEffects for If {
67 fn has_side_effects(&self) -> bool {
68 self.condition.has_side_effects()
69 || self.yes.has_side_effects()
70 || match &self.no {
71 Some(no) => no.has_side_effects(),
72 None => false,
73 }
74 }
75}
76
77impl HasSideEffects for BlockOrIf {
78 fn has_side_effects(&self) -> bool {
79 match self {
80 BlockOrIf::Block(block) => block.has_side_effects(),
81 BlockOrIf::If(if_) => if_.has_side_effects(),
82 }
83 }
84}
85
86impl HasSideEffects for BlockWithLoc {
87 fn has_side_effects(&self) -> bool {
88 self.block.has_side_effects()
89 }
90}
crates/js_ast/hoist.rs created+614
...@@ -0,0 +1,614 @@
1use std::collections::{HashMap, HashSet};
2use std::vec;
3
4use ast::{BinaryOperator, BlockOrIf, BlockWithLoc, Expr, Statement};
5use tokenizer::Loc;
6
7use crate::*;
8
9pub struct HoistContext {
10 pub statements: Vec<JsStatement>,
11 pub ids: HashSet<String>,
12 next_id: u32,
13 scopes: Vec<HoistScope>,
14 has_last_statement: bool,
15}
16
17struct HoistScope {
18 renames: HashMap<String, String>,
19}
20
21pub struct TempId {
22 unmapped_name: String,
23}
24
25impl TempId {
26 pub fn get(&self, hoist_context: &HoistContext) -> String {
27 hoist_context.remap_identifier(&self.unmapped_name)
28 }
29}
30
31impl HoistContext {
32 pub fn new() -> Self {
33 Self {
34 statements: Vec::new(),
35 ids: HashSet::new(),
36 next_id: 0,
37 scopes: vec![HoistScope {
38 renames: HashMap::new(),
39 }],
40 has_last_statement: false,
41 }
42 }
43
44 pub fn new_child(&self) -> Self {
45 Self {
46 statements: Vec::new(),
47 ids: self.ids.clone(),
48 next_id: 0,
49 scopes: vec![HoistScope {
50 renames: HashMap::new(),
51 }],
52 has_last_statement: false,
53 }
54 }
55
56 /// A virtual scope is a scope that exists in blight syntax
57 /// but may or may not exist in blight. With these it is important to
58 pub fn push_virtual_scope(&mut self) {
59 self.scopes.push(HoistScope {
60 renames: HashMap::new(),
61 });
62 }
63
64 /// See `push_virtual_scope` for more information on what this is.
65 pub fn pop_virtual_scope(&mut self) {
66 self.scopes.pop();
67 }
68
69 pub fn generate_temp_id(&mut self, basename: &str) -> TempId {
70 self.next_id += 1;
71 loop {
72 let name = format!("{}{}", basename, self.next_id);
73 if !self.ids.contains(&name) {
74 self.ids.insert(name.clone());
75 return TempId {
76 unmapped_name: name,
77 };
78 }
79 self.next_id += 1;
80 }
81 }
82
83 /// This does some extra optimizations:
84 /// - `let a = b;` will not be emitted but instad write a variable alias
85 pub fn add_statement(&mut self, statement: JsStatement) {
86 match statement.data {
87 JsStatementData::Declaration(JSDeclaration {
88 name,
89 init:
90 Some(JsExpr {
91 data: JsExprData::Identifier(init_name),
92 ..
93 }),
94 is_constant: true,
95 }) => {
96 self.scopes
97 .last_mut()
98 .unwrap()
99 .renames
100 .insert(name, init_name);
101 self.has_last_statement = false;
102 return;
103 }
104 _ => {}
105 }
106 self.statements.push(statement);
107 self.has_last_statement = true;
108 }
109
110 /// Get the last statement if it exists.
111 /// If the statement was optimized away, this returns None
112 pub fn last_statement(&self) -> Option<&JsStatement> {
113 if self.has_last_statement {
114 Some(self.statements.last().unwrap())
115 } else {
116 None
117 }
118 }
119
120 pub fn generate_id(&mut self, basename: &String) -> TempId {
121 let mut id = 0u32;
122 loop {
123 let name = match id {
124 0 => basename.clone(),
125 _ => format!("{}{}", basename, id),
126 };
127 if !self.ids.contains(&name) {
128 self.ids.insert(name.clone());
129 return TempId {
130 unmapped_name: name,
131 };
132 }
133 id += 1;
134 }
135 }
136
137 /// Given a blight identifier, remap it to a new identifier.
138 pub fn remap_identifier(&self, name: &String) -> String {
139 for scope in self.scopes.iter().rev() {
140 if let Some(remapped) = scope.renames.get(name) {
141 return remapped.clone();
142 }
143 }
144 name.clone()
145 }
146
147 /// Declare an identifier in the current scope.
148 pub fn declare_identifier(&mut self, name: String) -> TempId {
149 let remapped = self.generate_id(&name);
150 let scope = self.scopes.last_mut().unwrap();
151 self.ids.insert(remapped.unmapped_name.clone());
152 scope.renames.insert(name, remapped.unmapped_name.clone());
153 remapped
154 }
155
156 pub fn process_statement(&mut self, statement: Statement) -> Option<()> {
157 if statement.is_valid_js_expression() {
158 let result = convert_valid_statement(self, statement);
159 if !result.is_literal() {
160 self.add_statement(result);
161 }
162 return Some(());
163 }
164
165 match statement {
166 Statement::Decl(decl) => {
167 // visit init before name, this way allows for:
168 //
169 // let a = a;
170 //
171 // and `a` will be looked up before it is re-declared.
172 if let Some(init) = self.hoist_expression_contents(decl.init) {
173 let name = self.declare_identifier(decl.name);
174
175 self.add_statement(JsStatement::new(
176 JsStatementData::Declaration(crate::JSDeclaration {
177 name: name.get(&self),
178 init: Some(init),
179 is_constant: decl.is_constant,
180 }),
181 decl.loc,
182 decl.comment.map(|x| x.into()),
183 ));
184 Some(())
185 } else {
186 None
187 }
188 }
189 Statement::Expr(expr) => {
190 if let Some(expr) = self.hoist_expression_contents(expr) {
191 let loc = expr.loc.clone();
192 if !expr.is_literal() {
193 self.add_statement(JsStatement::new(
194 JsStatementData::Expression(expr),
195 loc,
196 None,
197 ))
198 }
199 Some(())
200 } else {
201 None
202 }
203 }
204 Statement::Comment(_) => Some(()),
205 }
206 }
207
208 /// Pushes a decl with a generated identifier to the hoisted result
209 /// and returns an identifier expression for it.
210 pub fn hoist_expression_to_temporary(&mut self, expr: Expr) -> Option<JsExpr> {
211 // TODO
212 // if !expr.has_side_effects() {
213 // return Some(self.hoist_expression_contents(expr)?);
214 // }
215
216 let init = self.hoist_expression_contents(expr)?;
217
218 let id = self.generate_temp_id("_temp");
219 let loc = init.loc.clone();
220
221 self.add_statement(JsStatement::new(
222 JsStatementData::Declaration(JSDeclaration {
223 name: id.get(&self),
224 init: Some(init),
225 is_constant: true,
226 }),
227 loc.clone(),
228 None,
229 ));
230
231 Some(JsExpr::new(
232 JsExprData::Identifier(id.get(&self)),
233 loc,
234 None,
235 ))
236 }
237
238 // Returning `None` from this means that the expression is noreturn, and that
239 // the rest of the statement never occurs and can be stripped.
240 pub fn hoist_expression_contents(&mut self, expr: Expr) -> Option<JsExpr> {
241 if expr.is_valid_js_expression() {
242 return Some(convert_valid_expr(self, expr));
243 }
244
245 match expr.data {
246 ast::ExprData::Binary { op, left, right } => {
247 let left = self.hoist_expression_to_temporary(*left)?;
248 let right = self.hoist_expression_to_temporary(*right)?;
249
250 Some(JsExpr::new(
251 JsExprData::Binary {
252 op,
253 left: Box::new(left),
254 right: Box::new(right),
255 },
256 expr.loc,
257 expr.comment,
258 ))
259 }
260
261 ast::ExprData::Unary { op, value } => {
262 let value = self.hoist_expression_to_temporary(*value)?;
263 Some(JsExpr::new(
264 JsExprData::Unary {
265 op,
266 value: Box::new(value),
267 },
268 expr.loc,
269 expr.comment,
270 ))
271 }
272
273 ast::ExprData::Field { base, field } => {
274 let base = self.hoist_expression_to_temporary(*base)?;
275 let field = self.hoist_expression_to_temporary(*field)?;
276
277 Some(JsExpr::new(
278 match field.data {
279 JsExprData::String(name) if is_js_identifier(name.as_str()) => {
280 JsExprData::Dot {
281 base: Box::new(base),
282 name,
283 }
284 }
285 _ => JsExprData::Index {
286 base: Box::new(base),
287 index: Box::new(field),
288 },
289 },
290 expr.loc,
291 expr.comment,
292 ))
293 }
294
295 ast::ExprData::Block(block) => {
296 let temporary = self.generate_temp_id("_block");
297 let result = self.hoist_block_to_js_block_in_same_context(BlockWithLoc {
298 block,
299 loc: expr.loc.clone(),
300 });
301
302 self.add_statement(JsStatement::new(
303 JsStatementData::Declaration(crate::JSDeclaration {
304 name: temporary.get(&self),
305 init: Some(result),
306 is_constant: false,
307 }),
308 expr.loc.clone(),
309 None,
310 ));
311
312 Some(JsExpr::new(
313 JsExprData::Identifier(temporary.get(&self)),
314 expr.loc,
315 expr.comment,
316 ))
317 }
318
319 ast::ExprData::If(data) => {
320 let temporary = self.generate_temp_id("_if");
321
322 let decl_index = self.statements.len();
323 self.add_statement(JsStatement::new(
324 JsStatementData::Declaration(crate::JSDeclaration {
325 name: temporary.get(&self),
326 init: None,
327 is_constant: false,
328 }),
329 expr.loc.clone(),
330 None,
331 ));
332
333 let condition = self.hoist_expression_to_temporary(*data.condition)?;
334 let mut did_assign = false;
335
336 let js_if =
337 self.hoist_js_if(data.yes, data.no, condition, &mut did_assign, &temporary)?;
338
339 self.add_statement(JsStatement::new(
340 JsStatementData::If(js_if),
341 expr.loc.clone(),
342 None,
343 ));
344
345 if did_assign {
346 Some(JsExpr::new(
347 JsExprData::Identifier(temporary.get(&self)),
348 expr.loc,
349 expr.comment,
350 ))
351 } else {
352 self.statements.remove(decl_index);
353 Some(JsExpr::new(JsExprData::Null, expr.loc, expr.comment))
354 }
355 }
356
357 ast::ExprData::Return(expr) => {
358 let loc = expr.loc.clone();
359 let return_value = self.hoist_expression_to_temporary(*expr)?;
360 self.add_statement(JsStatement::new(
361 JsStatementData::Return(return_value),
362 loc,
363 None,
364 ));
365 None
366 }
367
368 ast::ExprData::Call { base, arguments } => {
369 let base = self.hoist_expression_to_temporary(*base)?;
370 let arguments = arguments
371 .into_iter()
372 .map(|x| self.hoist_expression_to_temporary(x))
373 .collect::<Option<Vec<_>>>()?;
374
375 Some(JsExpr::new(
376 JsExprData::Call {
377 base: Box::new(base),
378 arguments,
379 },
380 expr.loc,
381 expr.comment,
382 ))
383 }
384
385 ast::ExprData::Fn { .. }
386 | ast::ExprData::Builtin { .. }
387 | ast::ExprData::Literal(_)
388 | ast::ExprData::Identifier(_) => {
389 unreachable!("attempt to hoist an expression that does not require it")
390 }
391 }
392 }
393
394 pub fn hoist_block_to_js_block(
395 &mut self,
396 b: BlockWithLoc,
397 temporary: &String,
398 did_assign: &mut bool,
399 ) -> Option<JsBlock> {
400 let loc = b.loc.clone();
401
402 // A non-block will become a block with a single expression:
403 // _temp = expr;
404 //
405 // We will also apply this transformation to blocks that are valid
406 // expressions, because that is more concise.
407 if b.is_valid_js_expression() {
408 return Some(JsBlock::new(
409 vec![new_js_assignment_statement(
410 convert_valid_block(self, b),
411 temporary.clone(),
412 loc.clone(),
413 )],
414 loc,
415 ));
416 }
417
418 let mut child = self.new_child();
419
420 for statement in b.block.statements {
421 if let None = child.process_statement(statement) {
422 return None;
423 };
424 }
425
426 // If the last statement is an expression, we need
427 // to assign to the temporary variable.
428 match child.statements.last().map(|x| &x.data) {
429 Some(JsStatementData::Expression(_)) => {
430 *did_assign = true;
431
432 let last = child.statements.pop().unwrap();
433 let value = match last {
434 JsStatement {
435 data: JsStatementData::Expression(expr),
436 ..
437 } => expr,
438 _ => {
439 self.statements.push(last);
440 JsExpr::new(JsExprData::Null, loc.clone(), None)
441 }
442 };
443 child.statements.push(new_js_assignment_statement(
444 value,
445 temporary.clone(),
446 loc.clone(),
447 ));
448 }
449 _ => {}
450 }
451
452 Some(JsBlock::new(child.statements, loc))
453 }
454
455 pub fn hoist_block_to_arrow_fn_body(&mut self, b: BlockWithLoc) -> JsArrowBody {
456 let loc = b.loc.clone();
457
458 // A block that is a valid expression will just become
459 // that expression. We can convert multiple expressions
460 // into a comma expression.
461 if b.is_valid_js_expression() {
462 return JsArrowBody::Expr(convert_valid_block(self, b));
463 }
464
465 let mut child = self.new_child();
466
467 for statement in b.block.statements {
468 if let None = child.process_statement(statement) {
469 break;
470 };
471 }
472
473 // If the last statement is an expression,
474 // we need to return that expression.
475 match child.statements.last().map(|x| &x.data) {
476 Some(JsStatementData::Expression(_)) => {
477 let last = child.statements.pop().unwrap();
478 let value = match last {
479 JsStatement {
480 data: JsStatementData::Expression(expr),
481 ..
482 } => expr,
483 _ => {
484 self.statements.push(last);
485 JsExpr::new(JsExprData::Null, loc.clone(), None)
486 }
487 };
488 child.statements.push(JsStatement::new(
489 JsStatementData::Return(value),
490 loc.clone(),
491 None,
492 ));
493 }
494 _ => {}
495 }
496
497 JsArrowBody::Block(JsBlock::new(child.statements, loc))
498 }
499
500 pub fn hoist_block_to_js_block_in_same_context(&mut self, b: BlockWithLoc) -> JsExpr {
501 let loc = b.loc.clone();
502 self.push_virtual_scope();
503
504 debug_assert!(!b.block.is_valid_js_expression());
505
506 for statement in b.block.statements {
507 self.process_statement(statement);
508 }
509
510 self.pop_virtual_scope();
511
512 // If the last statement is an expression, we need
513 // to assign to the temporary variable.
514 match self.last_statement().map(|x| &x.data) {
515 Some(JsStatementData::Expression(_)) if b.block.has_implicit_return => {
516 let last = self.statements.pop().unwrap();
517 match last {
518 JsStatement {
519 data: JsStatementData::Expression(expr),
520 ..
521 } => expr,
522 _ => {
523 self.statements.push(last);
524 JsExpr::new(JsExprData::Null, loc.clone(), None)
525 }
526 }
527 }
528 _ => JsExpr::new(JsExprData::Null, loc.clone(), None),
529 }
530 }
531
532 pub fn hoist_js_if(
533 &mut self,
534 yes: BlockWithLoc,
535 no: Option<BlockOrIf>,
536 condition: JsExpr,
537 did_assign: &mut bool,
538 temporary: &TempId,
539 ) -> Option<JsIf> {
540 let yes = self.hoist_block_to_js_block(yes, &temporary.get(&self), did_assign)?;
541
542 let no = match no {
543 Some(BlockOrIf::Block(no)) => Some(JsElse::Block(self.hoist_block_to_js_block(
544 no,
545 &temporary.get(&self),
546 did_assign,
547 )?)),
548 Some(BlockOrIf::If(else_if)) => {
549 // If the condition of this block has a side effect, we need to
550 // emit a block so that we can assign to the temporary variable.
551 //
552 // Otherwise, we can just emit the condition inline
553 if else_if.condition.is_valid_js_expression() {
554 let condition = convert_valid_expr(self, *else_if.condition.clone());
555 Some(JsElse::If(Box::new(self.hoist_js_if(
556 else_if.yes,
557 else_if.no,
558 condition,
559 did_assign,
560 temporary,
561 )?)))
562 } else {
563 let mut temp_block = self.new_child();
564 let condition =
565 temp_block.hoist_expression_to_temporary(*else_if.condition.clone())?;
566
567 temp_block.add_statement(JsStatement::new(
568 JsStatementData::If(self.hoist_js_if(
569 else_if.yes,
570 else_if.no,
571 condition,
572 did_assign,
573 temporary,
574 )?),
575 Loc::EMPTY,
576 None,
577 ));
578
579 Some(JsElse::Block(JsBlock::new(
580 temp_block.statements,
581 Loc::EMPTY,
582 )))
583 }
584 }
585 None => None,
586 };
587
588 Some(JsIf { condition, yes, no })
589 }
590}
591
592pub fn new_js_assignment_statement(expr: JsExpr, target: String, loc: Loc) -> JsStatement {
593 JsStatement::new(
594 JsStatementData::Expression(new_js_assignment_expr(expr, target, loc.clone())),
595 loc,
596 None,
597 )
598}
599
600pub fn new_js_assignment_expr(expr: JsExpr, target: String, loc: Loc) -> JsExpr {
601 JsExpr::new(
602 JsExprData::Binary {
603 op: BinaryOperator::Assign,
604 left: Box::new(JsExpr::new(
605 JsExprData::Identifier(target),
606 loc.clone(),
607 None,
608 )),
609 right: Box::new(expr),
610 },
611 loc,
612 None,
613 )
614}
crates/js_ast/is_valid_js_expression.rs created+85
...@@ -0,0 +1,85 @@
1use ast::*;
2
3pub trait IsValidJSExpression {
4 /// Can this be lowered to a JS ast without hoisting items
5 /// ex: a `return` expression is not a valid JS expression
6 fn is_valid_js_expression(&self) -> bool;
7}
8
9impl IsValidJSExpression for ExprData {
10 fn is_valid_js_expression(&self) -> bool {
11 match self {
12 // A block containing just expressions can be an expression
13 ExprData::Block(block) => block.is_valid_js_expression(),
14 ExprData::If(expr) => expr.is_valid_js_expression(),
15 ExprData::Return(_) => false,
16 ExprData::Builtin { id, .. } => match id {
17 BuiltinId::Raw => true,
18 },
19 ExprData::Binary { left, right, .. } => {
20 left.is_valid_js_expression() && right.is_valid_js_expression()
21 }
22 ExprData::Unary { value, .. } => value.is_valid_js_expression(),
23 ExprData::Fn { .. } => true,
24 ExprData::Call { base, arguments } => {
25 base.is_valid_js_expression()
26 && arguments
27 .iter()
28 .all(|argument| argument.is_valid_js_expression())
29 }
30 ExprData::Field { base, field } => {
31 base.is_valid_js_expression() && field.is_valid_js_expression()
32 }
33 ExprData::Literal(_) | ExprData::Identifier(_) => true,
34 }
35 }
36}
37
38impl IsValidJSExpression for Block {
39 fn is_valid_js_expression(&self) -> bool {
40 self.statements
41 .iter()
42 .all(|statement| statement.is_valid_js_expression())
43 }
44}
45
46impl IsValidJSExpression for Expr {
47 fn is_valid_js_expression(&self) -> bool {
48 self.data.is_valid_js_expression()
49 }
50}
51
52impl IsValidJSExpression for If {
53 fn is_valid_js_expression(&self) -> bool {
54 self.condition.is_valid_js_expression()
55 && self.yes.is_valid_js_expression()
56 && match &self.no {
57 Some(no) => no.is_valid_js_expression(),
58 None => true,
59 }
60 }
61}
62
63impl IsValidJSExpression for BlockOrIf {
64 fn is_valid_js_expression(&self) -> bool {
65 match self {
66 BlockOrIf::Block(block) => block.is_valid_js_expression(),
67 BlockOrIf::If(if_) => if_.is_valid_js_expression(),
68 }
69 }
70}
71
72impl IsValidJSExpression for BlockWithLoc {
73 fn is_valid_js_expression(&self) -> bool {
74 self.block.is_valid_js_expression()
75 }
76}
77
78impl IsValidJSExpression for Statement {
79 fn is_valid_js_expression(&self) -> bool {
80 match self {
81 Statement::Expr(expr) => expr.data.is_valid_js_expression(),
82 _ => false,
83 }
84 }
85}
crates/js_ast/js_ast.rs+96-172
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1use ast::{BinaryOperator, BlockWithLoc, BuiltinId, Comment, Expr, UnaryOperator};1use ast::{BinaryOperator, Comment, UnaryOperator};
2use num::bigint::BigInt;2use num::bigint::BigInt;
3use tokenizer::Loc;3use tokenizer::Loc;
44
...@@ -31,11 +31,11 @@ pub enum JsExprData {...@@ -31,11 +31,11 @@ pub enum JsExprData {
3131
32 Identifier(String),32 Identifier(String),
33 Unary {33 Unary {
34 operator: UnaryOperator,34 op: UnaryOperator,
35 value: Box<JsExpr>,35 value: Box<JsExpr>,
36 },36 },
37 Binary {37 Binary {
38 operator: BinaryOperator,38 op: BinaryOperator,
39 left: Box<JsExpr>,39 left: Box<JsExpr>,
40 right: Box<JsExpr>,40 right: Box<JsExpr>,
41 },41 },
...@@ -52,22 +52,23 @@ pub enum JsExprData {...@@ -52,22 +52,23 @@ pub enum JsExprData {
52 arguments: Vec<JsExpr>,52 arguments: Vec<JsExpr>,
53 },53 },
54 Function {54 Function {
55 name: String,55 name: Option<String>,
56 params: Vec<String>,56 params: Vec<String>,
57 block: Vec<JsStatement>,57 body: Box<JsExpr>,
58 },
59 If {
60 condition: Box<JsExpr>,
61 yes: Vec<JsExpr>,
62 no: Option<Box<JsExpr>>,
63 },58 },
64 ArrowFunction {59 ArrowFunction {
65 params: Vec<String>,60 params: Vec<String>,
66 block: Vec<JsStatement>,61 body: Box<JsArrowBody>,
62 },
63 Ternary {
64 condition: Box<JsExpr>,
65 yes: Box<JsExpr>,
66 no: Option<Box<JsExpr>>,
67 },67 },
68 Comma {68 Comma {
69 items: Vec<JsExpr>,69 items: Vec<JsExpr>,
70 },70 },
71 Void(Box<JsExpr>),
71}72}
7273
73#[derive(Debug, PartialEq, Clone)]74#[derive(Debug, PartialEq, Clone)]
...@@ -77,33 +78,56 @@ pub struct JsStatement {...@@ -77,33 +78,56 @@ pub struct JsStatement {
77 pub comment: Option<Comment>,78 pub comment: Option<Comment>,
78}79}
7980
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
80#[derive(Debug, PartialEq, Clone)]109#[derive(Debug, PartialEq, Clone)]
81pub enum JsStatementData {110pub enum JsStatementData {
82 Expression(JsExpr),111 Expression(JsExpr),
83 Return(JsExpr),112 Return(JsExpr),
84 Export(JsExport),113 Export(JsExport),
85 Declaration {114 Declaration(JSDeclaration),
86 name: String,115 Function(JSFunctionDeclaration),
87 init: JsExpr,116 If(JsIf),
88 is_constant: bool,
89 },
90 Function {
91 name: String,
92 params: Vec<String>,
93 block: JsBlock,
94 },
95 If {
96 condition: JsExpr,
97 yes: JsBlock,
98 no: Option<JsBlock>,
99 },
100}117}
101118
102#[derive(Debug, PartialEq, Clone)]119#[derive(Debug, PartialEq, Clone)]
103pub enum JsExport {120pub enum JsExport {
104 Named(Vec<String>),121 Named(Vec<JSNamedExport>),
122 Function(Box<JSFunctionDeclaration>),
123 Declaration(Box<JSDeclaration>),
105 Default(JsExpr),124 Default(JsExpr),
106 Equals(JsExpr),125}
126
127#[derive(Debug, PartialEq, Clone)]
128pub struct JSNamedExport {
129 pub identifier: String,
130 pub export_name: String,
107}131}
108132
109#[derive(Debug, PartialEq, Clone)]133#[derive(Debug, PartialEq, Clone)]
...@@ -113,156 +137,38 @@ pub struct JsBlock {...@@ -113,156 +137,38 @@ pub struct JsBlock {
113}137}
114138
115impl JsExpr {139impl JsExpr {
116 pub fn from_blight(expr: &Expr) -> Option<JsExpr> {140 pub fn new(data: JsExprData, loc: Loc, comment: Option<Comment>) -> Self {
117 use ast::ExprData::*;141 Self { data, loc, comment }
118142 }
119 let loc = &expr.loc;
120 let comment = &expr.comment;
121
122 match &expr.data {
123 Block(block) => Self::comma_expr_from_blight_block(&BlockWithLoc {
124 block: block.clone(),
125 loc: loc.clone(),
126 }),
127 Binary { op, left, right } => {
128 let left = Self::from_blight(&*left)?;
129 let right = Self::from_blight(&*right)?;
130 Some(JsExpr::new(
131 JsExprData::Binary {
132 operator: op.clone(),
133 left: Box::new(left),
134 right: Box::new(right),
135 },
136 loc.clone(),
137 comment.clone(),
138 ))
139 }
140 Unary { op, value } => {
141 let value = Self::from_blight(&*value)?;
142 Some(JsExpr::new(
143 JsExprData::Unary {
144 operator: op.clone(),
145 value: Box::new(value),
146 },
147 loc.clone(),
148 comment.clone(),
149 ))
150 }
151 Fn {
152 name,
153 params,
154 return_type,
155 block,
156 } => {
157 let block = Self::comma_expr_from_blight_block(&BlockWithLoc {
158 block: block.clone(),
159 loc: loc.clone(),
160 })?;
161 Some(JsExpr::new(
162 JsExprData::Function {
163 name: name.clone(),
164 params: vec![],
165 block: vec![JsStatement::new(
166 JsStatementData::Expression(block),
167 loc.clone(),
168 comment.clone(),
169 )],
170 },
171 loc.clone(),
172 comment.clone(),
173 ))
174 }
175 Builtin { id, arguments } => match id {
176 BuiltinId::Raw => {
177 let mut args = arguments.iter();
178 let arg = args.next().unwrap();
179 let arg = match &arg.data {
180 Literal(ast::Literal::String(s)) => s,
181 _ => panic!("raw expects a string"),
182 };
183 Some(JsExpr::new(
184 JsExprData::Raw(arg.clone()),
185 loc.clone(),
186 comment.clone(),
187 ))
188 }
189 },
190 If(expr) => {
191 let condition = Self::from_blight(&expr.condition)?;
192 let yes = Self::comma_expr_from_blight_block(&expr.yes)?;
193 let no = match &expr.no {
194 Some(no) => Some(Self::from_blight(&no)?),
195 None => None,
196 };
197 Some(JsExpr::new(
198 JsExprData::If {
199 condition: Box::new(condition),
200 yes: vec![yes],
201 no: no.map(Box::new),
202 },
203 loc.clone(),
204 comment.clone(),
205 ))
206 }
207
208 Call { base, arguments } => todo!(),
209 Field { base, field } => todo!(),
210
211 Return(_) => None,
212143
213 Literal(_) => todo!(),144 pub fn is_literal(&self) -> bool {
214 Identifier(_) => todo!(),145 match &self.data {
215 // Literal(ast::Literal::Number(n)) => {146 JsExprData::Number(_) => true,
216 // Some(JsExpr::new(JsExprData::Number(n), loc.clone(), comment))147 JsExprData::BigInt(_) => true,
217 // }148 JsExprData::String(_) => true,
218 // Literal(ast::Literal::Bool(b)) => Some(JsExpr::new(JsExprData::Bool(b), loc, comment)),149 JsExprData::Bool(_) => true,
219 // Literal(ast::Literal::String(s)) => {150 JsExprData::Null => true,
220 // Some(JsExpr::new(JsExprData::String(s), loc, comment))151 JsExprData::Undefined => true,
221 // }152 _ => false,
222 // Literal(ast::Literal::Void) => Some(JsExpr::new(JsExprData::Null, loc, comment)),
223 // Identifier(id) => Some(JsExpr::new(JsExprData::Identifier(id), loc, comment)),
224 }153 }
225 }154 }
155}
226156
227 /// Since blocks are not valid expressions in JS, this creates a157impl JsExprData {
228 /// comma expression from a block with more than one statement.158 /// Helper for testing
229 ///159 pub fn str(value: &str) -> Self {
230 /// Not possible to create an ast => None160 Self::String(value.to_string())
231 pub fn comma_expr_from_blight_block(b: &ast::BlockWithLoc) -> Option<JsExpr> {
232 if !b.block.has_implicit_return {
233 return Some(JsExpr::new(JsExprData::Null, b.loc.clone(), None));
234 }
235 // a block expression with a single statement is just that statement
236 if b.block.statements.len() == 1 && b.block.has_implicit_return {
237 match b.block.statements.first().unwrap() {
238 ast::Statement::Expr(expr) => Self::from_blight(expr),
239 _ => return None,
240 };
241 }
242 let mut statements = Vec::new();
243 for statement in &b.block.statements {
244 match statement {
245 ast::Statement::Expr(expr) => {
246 if let Some(expr) = Self::from_blight(&expr) {
247 statements.push(expr);
248 }
249 }
250 _ => return None,
251 }
252 }
253 // a block without implicit return is to be type void, so we append null
254 if !b.block.has_implicit_return {
255 statements.push(JsExpr::new(JsExprData::Null, b.loc.clone(), None));
256 }
257 Some(JsExpr::new(
258 JsExprData::Comma { items: statements },
259 b.loc.clone(),
260 None,
261 ))
262 }161 }
263162
264 pub fn new(data: JsExprData, loc: Loc, comment: Option<Comment>) -> Self {163 /// Helper for testing
265 Self { data, loc, comment }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 }
266 }172 }
267}173}
268174
...@@ -270,4 +176,22 @@ impl JsStatement {...@@ -270,4 +176,22 @@ impl JsStatement {
270 pub fn new(data: JsStatementData, loc: Loc, comment: Option<Comment>) -> Self {176 pub fn new(data: JsStatementData, loc: Loc, comment: Option<Comment>) -> Self {
271 Self { data, loc, comment }177 Self { data, loc, comment }
272 }178 }
179
180 pub(crate) fn is_literal(&self) -> bool {
181 match &self.data {
182 JsStatementData::Expression(expr) => expr.is_literal(),
183 _ => false,
184 }
185 }
186}
187
188#[derive(Debug, PartialEq, Clone)]
189pub enum JsArrowBody {
190 Block(JsBlock),
191 Expr(JsExpr),
192}
193
194#[derive(Debug, PartialEq, Clone)]
195pub struct JsModule {
196 pub statements: Vec<JsStatement>,
273}197}
crates/js_ast/lib.rs+19-402
...@@ -1,411 +1,28 @@...@@ -1,411 +1,28 @@
1use std::collections::HashMap;1mod convert_valid_expr;
22mod has_side_effects;
3use ast::{BlockWithLoc, Expr, ExprData, Literal, Module, Statement};3mod hoist;
4pub use js_ast::*;4mod is_valid_js_expression;
5use miette::Result;
6use tokenizer::Loc;
7
8mod js_ast;5mod js_ast;
6mod util;
97
10struct StackContext {8use ast::Module;
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: JsExpr) -> JsExpr {
54 let name = format!("${}", self.internal_counter);
55 let loc = value.loc.clone();
56 self.context
57 .last_mut()
58 .unwrap()
59 .statements
60 .push(JsStatement::new(
61 JsStatementData::Declaration {
62 name: name.clone(),
63 init: value,
64 is_constant: false,
65 },
66 loc,
67 None,
68 ));
69 self.internal_counter += 1;
70 JsExpr::new(JsExprData::Identifier(name), Loc::EMPTY, None)
71 }
72
73 fn push(self: &mut Self, statement: JsStatement) {
74 self.context.last_mut().unwrap().statements.push(statement);
75 }
76
77 // fn peek(self: &Self) -> &JsStatement {
78 // self.context.last().unwrap().statements.last().unwrap()
79 // }
80
81 fn declare_variable(self: &mut Self, name: String) -> String {
82 let context = self.context.last_mut().unwrap();
83 if let Some(value) = context.variable_map.get_mut(name.as_str()) {
84 *value += 1;
85 format!("{}${}${}", name, value, context.block)
86 } else {
87 context.variable_map.insert(name.to_string(), 0);
88 format!("{}$0${}", name, context.block)
89 }
90 }
919
92 fn get_identifier(self: &Self, name: String) -> String {10pub use convert_valid_expr::*;
93 for context in self.context.iter().rev() {11pub use hoist::*;
94 if let Some(value) = context.variable_map.get(name.as_str()) {12pub use is_valid_js_expression::*;
95 return format!("{}${}${}", name, value, context.block);13pub use js_ast::*;
96 }14pub use util::*;
97 }
98 panic!("Variable {} not found", name);
99 }
100}
101
102fn get_block_handle(b: &ast::BlockWithLoc, ctx: &mut TranslateContext) -> JsExpr {
103 // :)
104 if b.is_valid_js_expression() {
105 return JsExpr::comma_expr_from_blight_block(b).unwrap();
106 }
107
108 ctx.push_context();
109 for (i, statement) in b.block.statements.iter().enumerate() {
110 if i == b.block.statements.len() - 1 {
111 break;
112 } else {
113 translate_statement(statement, ctx);
114 }
115 }
116 let result = match b.block.statements.last().unwrap() {
117 ast::Statement::Expr(expr) => get_expression_handle(expr, ctx),
118 stmt => {
119 translate_statement(stmt, ctx);
120 JsExpr::new(JsExprData::Null, Loc::EMPTY, None)
121 }
122 };
123 let value = ctx.pop_context();
124 value.iter().for_each(|x| ctx.push(x.clone()));
125 result
126}
127
128// basically, expressions are really hard to translate
129// in blight, everything is an expression
130// in js, very few things are expressions
131// here, we recursively expand out expressions into statements
132// we then store the result of all the expressions in a variable
133// and then return the name of that variable
134fn get_expression_handle(ast: &Expr, ctx: &mut TranslateContext) -> JsExpr {
135 match ast.data.clone() {
136 ExprData::Literal(Literal::String(value)) => JsExpr::new(
137 JsExprData::String(value),
138 ast.loc.clone(),
139 ast.comment.clone(),
140 ),
141 ExprData::Literal(Literal::Number(value)) => JsExpr::new(
142 JsExprData::Number(value),
143 ast.loc.clone(),
144 ast.comment.clone(),
145 ),
146 ExprData::Literal(Literal::Void) => {
147 JsExpr::new(JsExprData::Null, ast.loc.clone(), ast.comment.clone())
148 }
149 ExprData::Literal(Literal::Bool(b)) => {
150 JsExpr::new(JsExprData::Bool(b), ast.loc.clone(), ast.comment.clone())
151 }
152
153 ExprData::Identifier(value) => JsExpr::new(
154 JsExprData::Identifier(ctx.get_identifier(value.clone())),
155 ast.loc.clone(),
156 ast.comment.clone(),
157 ),
158 ExprData::Call { base, arguments } => {
159 let mut handles = Vec::new();
160 for argument in arguments {
161 handles.push(get_expression_handle(&argument, ctx));
162 }
163 let base = get_expression_handle(&base, ctx);
164 ctx.push_internal_variable(JsExpr::new(
165 JsExprData::Call {
166 base: Box::new(base),
167 arguments: handles,
168 },
169 ast.loc.clone(),
170 ast.comment.clone(),
171 ))
172 }
173 ExprData::Builtin { id, arguments } => match id {
174 ast::BuiltinId::Raw => {
175 if arguments.len() != 1 {
176 panic!("@raw expects exactly one argument");
177 }
178 match arguments.get(0).unwrap() {
179 Expr {
180 data: ExprData::Literal(Literal::String(value)),
181 ..
182 } => JsExpr::new(
183 JsExprData::Raw(value.clone()),
184 ast.loc.clone(),
185 ast.comment.clone(),
186 ),
187 _ => panic!("@raw expects a string literal"),
188 }
189 }
190 },
191 ExprData::Binary { op, left, right } => {
192 let left_handle = get_expression_handle(&left, ctx);
193 let right_handle = get_expression_handle(&right, ctx);
194 JsExpr::new(
195 JsExprData::Binary {
196 operator: op.clone(),
197 left: Box::new(left_handle),
198 right: Box::new(right_handle),
199 },
200 ast.loc.clone(),
201 ast.comment.clone(),
202 )
203 }
204
205 ExprData::Unary { op, value } => {
206 let value_handle = get_expression_handle(&value, ctx);
207 JsExpr::new(
208 JsExprData::Unary {
209 operator: op.clone(),
210 value: Box::new(value_handle),
211 },
212 ast.loc.clone(),
213 ast.comment.clone(),
214 )
215 }
216
217 ExprData::Field { base, field } => {
218 let base = Box::new(get_expression_handle(&base, ctx));
219
220 match field.data {
221 ExprData::Literal(Literal::String(name)) => JsExpr::new(
222 JsExprData::Dot { base, name },
223 ast.loc.clone(),
224 ast.comment.clone(),
225 ),
226 _ => JsExpr::new(
227 JsExprData::Index {
228 base,
229 index: Box::new(get_expression_handle(&field, ctx)),
230 },
231 ast.loc.clone(),
232 ast.comment.clone(),
233 ),
234 }
235 }
236
237 ExprData::Block(block) => get_block_handle(
238 &BlockWithLoc {
239 block,
240 loc: ast.loc.clone(),
241 },
242 ctx,
243 ),
244
245 ExprData::If(expr) => {
246 let handle = get_expression_handle(&expr.condition, ctx);
247
248 let yes = get_block_handle(&expr.yes, ctx);
249 let no = match expr.no {
250 Some(block) => Some(get_expression_handle(&block, ctx)),
251 None => None,
252 };
253
254 // ctx.push(JsStatement::new(
255 // JsStatementData::If {
256 // condition: handle,
257 // yes,
258 // no,
259 // },
260 // ast.loc,
261 // ast.comment,
262 // ));
263
264 // panic!()
265 handle
266 }
267 ExprData::Fn {
268 name,
269 params,
270 return_type,
271 block,
272 } => todo!(),
273 ExprData::Return(_) => todo!(),
274 }
275}
276
277fn translate_statement(ast: &Statement, ctx: &mut TranslateContext) {
278 match ast {
279 Statement::Decl(decl) => {
280 let handle = get_expression_handle(&decl.init, ctx);
281 let rename = ctx.declare_variable(decl.name.clone());
282 ctx.push(JsStatement::new(
283 JsStatementData::Declaration {
284 name: rename,
285 init: handle,
286 is_constant: decl.is_constant.clone(),
287 },
288 decl.loc.clone(),
289 None,
290 ));
291 }
292 Statement::Expr(expr) => {
293 let h = get_expression_handle(expr, ctx);
294 ctx.push(JsStatement::new(
295 JsStatementData::Expression(h),
296 expr.loc.clone(),
297 None,
298 ))
299 }
300 Statement::Comment(_) => {} // Ast::Function {
301 // name,
302 // arguments,
303 // return_type: _,
304 // block,
305 // } => {
306 // let rename = ctx.declare_variable(name.clone());
307 // let arguments = arguments
308 // .iter()
309 // .map(|x| ctx.declare_variable(x.name.clone()))
310 // .collect();
311
312 // ctx.push_context();
313 // for (i, statement) in block.value.iter().enumerate() {
314 // if block.implicit_return && i == block.value.len() - 1 {
315 // let handle = get_expression_handle(statement, ctx);
316 // ctx.push(JsStatement::Return(handle));
317 // } else {
318 // translate_statement(statement, ctx);
319 // }
320 // }
321 // let value = ctx.pop_context();
32215
323 // ctx.push(JsStatement::Function {16pub fn blight_to_js(blight: Module) -> JsModule {
324 // name: rename,17 let mut root_context = HoistContext::new();
325 // arguments,
326 // value,
327 // });
328 // }
329 // Ast::Public(value) => {
330 // translate_statement(value, ctx);
331 // // I'm sorry, I just don't know how to get the original name properly... my bad
332 // match ctx.peek() {
333 // JsStatement::Function { name, .. } | JsStatement::Declaration { name, .. } => {
334 // let original_name = name.split("$").next().unwrap().to_string();
335 // ctx.push(JsStatement::Export(Box::new(JsStatement::Declaration {
336 // name: original_name,
337 // value: JsExpr::Identifier(name.clone()),
338 // is_constant: true,
339 // })));
340 // }
341 // _ => {
342 // panic!("Cannot make non-function public");
343 // }
344 // }
345 // }
346 // Ast::Return(value) => {
347 // let handle = get_expression_handle(value, ctx);
348 // ctx.push(JsStatement::Return(handle));
349 // }
350 // Ast::Block(block) => {
351 // ctx.push_context();
352 // for (i, statement) in block.value.iter().enumerate() {
353 // if block.implicit_return && i == block.value.len() - 1 {
354 // let handle = get_expression_handle(statement, ctx);
355 // ctx.push(JsStatement::Return(handle));
356 // } else {
357 // translate_statement(statement, ctx);
358 // }
359 // }
360 // let value = ctx.pop_context();
361 // value.iter().for_each(|x| ctx.push(x.clone()));
362 // }
36318
364 // Ast::FunctionCall { name, arguments } => {19 for statement in blight.statements {
365 // let mut handles = Vec::new();20 if let None = root_context.process_statement(statement.to_statement()) {
366 // for argument in arguments {21 unreachable!("top level return is disallowed by the parser")
367 // handles.push(get_expression_handle(argument, ctx));22 };
368 // }
369 // match *name.clone() {
370 // Ast::Identifier(name) => {
371 // ctx.push(JsStatement::Expression(JsExpr::Call {
372 // name: Box::new(JsExpr::Identifier(ctx.get_identifier(name.clone()))),
373 // arguments: handles,
374 // }));
375 // }
376 // Ast::BuiltIn(name) => match name.as_str() {
377 // "@raw" => {
378 // if handles.len() != 1 {
379 // panic!("@raw expects exactly one argument");
380 // }
381 // match handles.get(0).unwrap() {
382 // JsExpr::String(value) => {
383 // ctx.push(JsStatement::Expression(JsExpr::Call {
384 // name: Box::new(JsExpr::Raw(value.clone())),
385 // arguments: handles,
386 // }));
387 // }
388 // _ => panic!("@raw expects a string argument"),
389 // }
390 // }
391 // _ => panic!("Unknown built-in function {}", name),
392 // },
393 // _ => panic!("Function name must be an identifier"),
394 // }
395 // }
396 // Ast::Comment(_) | Ast::DocComment(_) => {}
397 // _ => {
398 // panic!("unimplemented: {:?}", ast);
399 // }
400 }23 }
401}
40224
403pub fn translate(module: &Module) -> Result<Vec<JsStatement>> {25 JsModule {
404 let mut ctx = TranslateContext::new();26 statements: root_context.statements,
405
406 for ast in module.statements.iter() {
407 translate_statement(&ast.clone().to_statement(), &mut ctx);
408 }27 }
409
410 Ok(ctx.pop_context())
411}28}
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
...@@ -4,6 +4,7 @@ version = "0.1.0"...@@ -4,6 +4,7 @@ version = "0.1.0"
4edition = "2021"4edition = "2021"
55
6[lib]6[lib]
7path = "./lib.rs"
78
8[dependencies]9[dependencies]
9miette.workspace = true10miette.workspace = true
crates/js_printer/lib.rs created+358
...@@ -0,0 +1,358 @@
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 condition.print(writer)?;
235 write!(writer, "?")?;
236 yes.print(writer)?;
237 write!(writer, ":")?;
238 if let Some(no) = no {
239 no.print(writer)?;
240 } else {
241 write!(writer, "null")?;
242 }
243 Ok(())
244 }
245 JsExprData::Comma { items } => {
246 for (i, item) in items.iter().enumerate() {
247 if i > 0 {
248 write!(writer, ",")?;
249 }
250 item.print(writer)?;
251 }
252 Ok(())
253 }
254 JsExprData::Void(expr) => {
255 write!(writer, "void ")?;
256 expr.print(writer)
257 }
258 }
259 }
260}
261
262impl Print for JSFunctionDeclaration {
263 fn print(&self, writer: &mut dyn Write) -> Result<()> {
264 write!(writer, "function {}", self.name)?;
265 write!(writer, "(")?;
266 for (i, param) in self.params.iter().enumerate() {
267 if i > 0 {
268 write!(writer, ",")?;
269 }
270 write!(writer, "{}", param)?;
271 }
272 write!(writer, ")")?;
273 self.block.print(writer)
274 }
275}
276
277impl Print for JsBlock {
278 fn print(&self, writer: &mut dyn Write) -> Result<()> {
279 write!(writer, "{{")?;
280 for statement in self.statements.iter() {
281 statement.print(writer)?;
282 }
283 write!(writer, "}}")
284 }
285}
286
287impl Print for JsIf {
288 fn print(&self, writer: &mut dyn Write) -> Result<()> {
289 write!(writer, "if(")?;
290 self.condition.print(writer)?;
291 write!(writer, ")")?;
292 self.yes.print(writer)?;
293 if let Some(no) = &self.no {
294 no.print(writer)?;
295 }
296 Ok(())
297 }
298}
299
300impl Print for JsElse {
301 fn print(&self, writer: &mut dyn Write) -> Result<()> {
302 match self {
303 JsElse::If(js_if) => {
304 write!(writer, "else ")?;
305 js_if.print(writer)
306 }
307 JsElse::Block(block) => {
308 write!(writer, "else")?;
309 block.print(writer)
310 }
311 JsElse::Expr(expr) => {
312 write!(writer, "else ")?;
313 expr.print(writer)
314 }
315 }
316 }
317}
318
319impl Print for JsKV {
320 fn print(&self, writer: &mut dyn Write) -> Result<()> {
321 match &self.key.data {
322 JsExprData::String(string) => {
323 if is_js_identifier(string) {
324 write!(writer, "{}", string)?;
325 } else {
326 print_js_string(writer, string.as_str())?;
327 }
328 }
329 _ => {
330 write!(writer, "[")?;
331 self.key.print(writer)?;
332 write!(writer, "]")?;
333 }
334 }
335 write!(writer, ":")?;
336 self.key.print(writer)?;
337 write!(writer, ":")?;
338 self.value.print(writer)
339 }
340}
341
342impl Print for JsModule {
343 fn print(&self, writer: &mut dyn Write) -> Result<()> {
344 for statement in self.statements.iter() {
345 statement.print(writer)?;
346 }
347 Ok(())
348 }
349}
350
351impl Print for JsArrowBody {
352 fn print(&self, writer: &mut dyn Write) -> Result<()> {
353 match self {
354 JsArrowBody::Block(block) => block.print(writer),
355 JsArrowBody::Expr(expr) => expr.print(writer),
356 }
357 }
358}
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/print_expression.rs+9-5
...@@ -46,7 +46,7 @@ pub fn print_expression(expr: &JsExpr) -> String {...@@ -46,7 +46,7 @@ pub fn print_expression(expr: &JsExpr) -> String {
46 .join(",")46 .join(",")
47 ),47 ),
48 JsExprData::Binary {48 JsExprData::Binary {
49 operator,49 op: operator,
50 left,50 left,
51 right,51 right,
52 } => format!(52 } => format!(
...@@ -81,7 +81,10 @@ pub fn print_expression(expr: &JsExpr) -> String {...@@ -81,7 +81,10 @@ pub fn print_expression(expr: &JsExpr) -> String {
81 ),81 ),
82 JsExprData::Null => todo!(),82 JsExprData::Null => todo!(),
83 JsExprData::Undefined => todo!(),83 JsExprData::Undefined => todo!(),
84 JsExprData::Unary { operator, value } => todo!(),84 JsExprData::Unary {
85 op: operator,
86 value,
87 } => todo!(),
85 JsExprData::Dot { base, name } => todo!(),88 JsExprData::Dot { base, name } => todo!(),
86 JsExprData::Index { base, index } => todo!(),89 JsExprData::Index { base, index } => todo!(),
87 JsExprData::Function {90 JsExprData::Function {
...@@ -89,9 +92,10 @@ pub fn print_expression(expr: &JsExpr) -> String {...@@ -89,9 +92,10 @@ pub fn print_expression(expr: &JsExpr) -> String {
89 params,92 params,
90 block,93 block,
91 } => todo!(),94 } => todo!(),
92 JsExprData::If { condition, yes, no } => todo!(),95 JsExprData::Ternary { condition, yes, no } => todo!(),
93 JsExprData::ArrowFunction { params, block } => todo!(),96 JsExprData::ArrowFunction { params, block } => todo!(),
94 JsExprData::Comma { items } => todo!(),97 JsExprData::Comma { items } => todo!(),
98 JsExprData::Void(_) => todo!(),
95 }99 }
96}100}
97101
...@@ -191,7 +195,7 @@ mod tests {...@@ -191,7 +195,7 @@ mod tests {
191 fn operation() {195 fn operation() {
192 assert_eq!(196 assert_eq!(
193 print_expression(&JsExprData::Binary {197 print_expression(&JsExprData::Binary {
194 operator: AnyOperator::Add,198 op: AnyOperator::Add,
195 left: Box::new(JsExprData::Number(1.0)),199 left: Box::new(JsExprData::Number(1.0)),
196 right: Box::new(JsExprData::Number(2.0)),200 right: Box::new(JsExprData::Number(2.0)),
197 }),201 }),
...@@ -199,7 +203,7 @@ mod tests {...@@ -199,7 +203,7 @@ mod tests {
199 );203 );
200 assert_eq!(204 assert_eq!(
201 print_expression(&JsExprData::Binary {205 print_expression(&JsExprData::Binary {
202 operator: AnyOperator::Sub,206 op: AnyOperator::Sub,
203 left: Box::new(JsExprData::Number(1.0)),207 left: Box::new(JsExprData::Number(1.0)),
204 right: Box::new(JsExprData::Number(2.0)),208 right: Box::new(JsExprData::Number(2.0)),
205 }),209 }),
crates/js_printer/src/print_statement.rs+6-6
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1use js_ast::{JsStatement, JsStatementData};1use js_ast::{If, JSDeclaration, JSFunctionDeclaration, JsStatement, JsStatementData};
22
3use crate::print_expression::print_expression;3use crate::print_expression::print_expression;
44
...@@ -8,11 +8,11 @@ pub fn print_statement(statement: &JsStatement) -> String {...@@ -8,11 +8,11 @@ pub fn print_statement(statement: &JsStatement) -> String {
8 // JsStatementData::Export(value) => format!("export {}", print_statement(value)),8 // JsStatementData::Export(value) => format!("export {}", print_statement(value)),
9 JsStatementData::Export(value) => todo!(),9 JsStatementData::Export(value) => todo!(),
10 JsStatementData::Return(value) => format!("return {};", print_expression(&value)),10 JsStatementData::Return(value) => format!("return {};", print_expression(&value)),
11 JsStatementData::Declaration {11 JsStatementData::Declaration(Declaration {
12 name,12 name,
13 init,13 init,
14 is_constant,14 is_constant,
15 } => {15 }) => {
16 format!(16 format!(
17 "{} {}={};",17 "{} {}={};",
18 match is_constant {18 match is_constant {
...@@ -23,11 +23,11 @@ pub fn print_statement(statement: &JsStatement) -> String {...@@ -23,11 +23,11 @@ pub fn print_statement(statement: &JsStatement) -> String {
23 print_expression(&init)23 print_expression(&init)
24 )24 )
25 }25 }
26 JsStatementData::Function {26 JsStatementData::Function(JSFunctionDeclaration {
27 name,27 name,
28 block,28 block,
29 params,29 params,
30 } => format!(30 }) => format!(
31 "function {}({}){{{}}}",31 "function {}({}){{{}}}",
32 name,32 name,
33 params.join(","),33 params.join(","),
...@@ -38,7 +38,7 @@ pub fn print_statement(statement: &JsStatement) -> String {...@@ -38,7 +38,7 @@ pub fn print_statement(statement: &JsStatement) -> String {
38 .collect::<Vec<String>>()38 .collect::<Vec<String>>()
39 .join("")39 .join("")
40 ),40 ),
41 JsStatementData::If { condition, yes, no } => {41 JsStatementData::If(JsIf { condition, yes, no }) => {
42 let else_value = match no {42 let else_value = match no {
43 Some(value) => format!(43 Some(value) => format!(
44 "else{{{}}}",44 "else{{{}}}",
crates/parser/parse_block.rs+23-9
...@@ -15,7 +15,11 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {...@@ -15,7 +15,11 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
15 let was_top_level = p.is_top_level;15 let was_top_level = p.is_top_level;
16 p.is_top_level = false;16 p.is_top_level = false;
1717
18 loop {18 'outer: loop {
19 let is_statement_like_expression = p
20 .peek()
21 .map_or(false, |x| x.is_start_of_statement_like_expression());
22
19 let statement = match parse_statement(p) {23 let statement = match parse_statement(p) {
20 Ok(statement) => statement,24 Ok(statement) => statement,
21 Err(err) => {25 Err(err) => {
...@@ -25,8 +29,9 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {...@@ -25,8 +29,9 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
25 };29 };
26 statements.push(statement.clone());30 statements.push(statement.clone());
2731
28 match p.next() {32 match p.peek() {
29 Some(Token::Semicolon) => loop {33 Some(Token::Semicolon) => loop {
34 p.next();
30 match p.peek() {35 match p.peek() {
31 Some(Token::ClosedBrace) => {36 Some(Token::ClosedBrace) => {
32 p.next();37 p.next();
...@@ -36,14 +41,18 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {...@@ -36,14 +41,18 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
36 has_implicit_return: false,41 has_implicit_return: false,
37 });42 });
38 }43 }
39 _ => continue,44 _ => continue 'outer,
40 }45 }
41 },46 },
42 Some(Token::ClosedBrace) => {47 Some(Token::ClosedBrace) => {
43 if statement.requires_semicolon() {48 p.next();
49 if !statement.is_expression()
50 && !statement.is_comment()
51 && is_statement_like_expression
52 {
44 p.is_top_level = was_top_level;53 p.is_top_level = was_top_level;
45 return Err(p54 return Err(p
46 .error("Expected semicolon after this statement".to_string())55 .error("Expected semicolon or '}' after statement".to_string())
47 .into());56 .into());
48 }57 }
4958
...@@ -54,10 +63,15 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {...@@ -54,10 +63,15 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
54 });63 });
55 }64 }
56 _ => {65 _ => {
57 p.is_top_level = was_top_level;66 if !statement.is_expression()
58 return Err(p67 && !statement.is_comment()
59 .error("Expected semicolon or '}' after statement".to_string())68 && is_statement_like_expression
60 .into());69 {
70 p.is_top_level = was_top_level;
71 return Err(p
72 .error("Expected semicolon or '}' after statement".to_string())
73 .into());
74 }
61 }75 }
62 };76 };
63 }77 }
crates/parser/parse_call_arguments.rs+5
...@@ -8,6 +8,11 @@ use crate::{parse_expr::parse_expr, Parser};...@@ -8,6 +8,11 @@ use crate::{parse_expr::parse_expr, Parser};
8pub fn parse_call_arguments(p: &mut Parser) -> Result<Vec<Expr>> {8pub fn parse_call_arguments(p: &mut Parser) -> Result<Vec<Expr>> {
9 let mut args = Vec::new();9 let mut args = Vec::new();
1010
11 if let Some(Token::ClosedParen) = p.peek() {
12 _ = p.next();
13 return Ok(args);
14 }
15
11 loop {16 loop {
12 let arg = parse_expr(p)?;17 let arg = parse_expr(p)?;
13 args.push(arg);18 args.push(arg);
crates/parser/parse_expr.rs+44-17
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1use ast::{get_binary_op_level, BinaryOperator, BinaryOperatorLevel, Expr, ExprData};1use ast::Associativity::*;
2use ast::{BinaryOperator, Expr, ExprData, OperatorLevel};
2use miette::Result;3use miette::Result;
34
4use crate::parse_expr_basic::parse_expr_basic;5use crate::parse_expr_basic::parse_expr_basic;
...@@ -15,14 +16,27 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> {...@@ -15,14 +16,27 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> {
15 // Parse operators16 // Parse operators
16 loop {17 loop {
17 if let Some(token) = p.peek() {18 if let Some(token) = p.peek() {
18 if let Some(level) = get_binary_op_level(&token) {19 if let Some(level) = BinaryOperator::from_token(&token).map(|o| o.level()) {
19 _ = p.next();20 _ = p.next();
20 expr = parse_expr_binary(21 match level.associativity() {
21 p,22 RightToLeft => {
22 BinaryOperator::from_token(&token).unwrap(),23 expr = parse_expr_binary(
23 expr.clone(),24 p,
24 level,25 BinaryOperator::from_token(&token).unwrap(),
25 )?;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 }
26 continue;40 continue;
27 }41 }
28 }42 }
...@@ -37,22 +51,35 @@ fn parse_expr_binary(...@@ -37,22 +51,35 @@ fn parse_expr_binary(
37 p: &mut Parser,51 p: &mut Parser,
38 op: BinaryOperator,52 op: BinaryOperator,
39 left: Expr,53 left: Expr,
40 level: BinaryOperatorLevel,54 level: OperatorLevel,
41) -> Result<Expr> {55) -> Result<Expr> {
42 let loc = p.loc();56 let loc = p.loc();
43 let mut right = parse_expr_basic(p)?;57 let mut right = parse_expr_basic(p)?;
4458
45 loop {59 loop {
46 if let Some(token) = p.peek() {60 if let Some(token) = p.peek() {
47 if let Some(next_level) = get_binary_op_level(&token) {61 if let Some(new_level) = BinaryOperator::from_token(&token).map(|o| o.level()) {
48 if next_level <= level {62 if new_level <= level {
49 _ = p.next();63 _ = p.next();
50 right = parse_expr_binary(64 match new_level.associativity() {
51 p,65 RightToLeft => {
52 BinaryOperator::from_token(&token).unwrap(),66 right = parse_expr_binary(
53 right,67 p,
54 next_level,68 BinaryOperator::from_token(&token).unwrap(),
55 )?;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 }
56 continue;83 continue;
57 }84 }
58 }85 }
crates/parser/parse_expr_if.rs+9-13
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1use ast::{BlockWithLoc, Expr, ExprData, If};1use ast::{BlockOrIf, BlockWithLoc, If};
2use miette::Result;2use miette::Result;
3use tokenizer::Token;3use tokenizer::Token;
44
...@@ -48,21 +48,17 @@ pub fn parse_expr_if(p: &mut Parser) -> Result<If> {...@@ -48,21 +48,17 @@ pub fn parse_expr_if(p: &mut Parser) -> Result<If> {
48 match p.peek() {48 match p.peek() {
49 Some(Token::OpenBrace) => {49 Some(Token::OpenBrace) => {
50 let loc = p.loc();50 let loc = p.loc();
51 let comments = p.take_comments();51 // let comments = p.take_comments();
52 Some(Box::new(Expr::new(52 Some(BlockOrIf::Block(BlockWithLoc {
53 ExprData::Block(parse_block(p)?),53 block: parse_block(p)?,
54 loc,54 loc,
55 comments,55 }))
56 )))
57 }56 }
58 Some(Token::If) => {57 Some(Token::If) => {
59 let loc = p.loc();58 // TODO(@paperdave): Save location for the if
60 let comments = p.take_comments();59 // let loc = p.loc();
61 Some(Box::new(Expr::new(60 // let comments = p.take_comments();
62 ExprData::If(parse_expr_if(p)?),61 Some(BlockOrIf::If(Box::new(parse_expr_if(p)?)))
63 loc,
64 comments,
65 )))
66 }62 }
67 _ => return Err(p.error("Expected a block after else".to_string()).into()),63 _ => return Err(p.error("Expected a block after else".to_string()).into()),
68 }64 }
crates/parser/parse_fn_declaration.rs+9-2
...@@ -52,7 +52,10 @@ pub fn parse_fn_declaration(...@@ -52,7 +52,10 @@ pub fn parse_fn_declaration(
52 break;52 break;
53 }53 }
54 }54 }
55 Some(Token::ClosedParen) => break,55 Some(Token::ClosedParen) => {
56 p.next();
57 break;
58 }
56 _ => {59 _ => {
57 return Err(p60 return Err(p
58 .error("Expected ',' or ')' after identifier".to_string())61 .error("Expected ',' or ')' after identifier".to_string())
...@@ -108,8 +111,12 @@ pub fn parse_fn_declaration(...@@ -108,8 +111,12 @@ pub fn parse_fn_declaration(
108 loc: loc,111 loc: loc,
109 })112 })
110 } else {113 } else {
114 let actually = p.peek().map_or("EOF".to_string(), |x| x.to_string());
111 return Err(p115 return Err(p
112 .error("Expected a block to follow function declaration".to_string())116 .error(format!(
117 "Expected a block to follow function declaration, but got {}",
118 actually,
119 ))
113 .into());120 .into());
114 }121 }
115}122}
crates/parser/parse_module.rs+5-1
...@@ -11,6 +11,10 @@ pub fn parse_module(p: &mut Parser) -> Result<Module> {...@@ -11,6 +11,10 @@ pub fn parse_module(p: &mut Parser) -> Result<Module> {
11 let file_doc_comment = maybe_read_doc_comment(p);11 let file_doc_comment = maybe_read_doc_comment(p);
1212
13 loop {13 loop {
14 let is_statement_like_expression = p
15 .peek()
16 .map_or(false, |x| x.is_start_of_statement_like_expression());
17
14 let statement = parse_top_level_statement(p)?;18 let statement = parse_top_level_statement(p)?;
1519
16 match p.peek() {20 match p.peek() {
...@@ -20,7 +24,7 @@ pub fn parse_module(p: &mut Parser) -> Result<Module> {...@@ -20,7 +24,7 @@ pub fn parse_module(p: &mut Parser) -> Result<Module> {
20 }24 }
21 }25 }
22 _ => {26 _ => {
23 if statement.requires_semicolon() {27 if !statement.is_comment() && is_statement_like_expression {
24 return Err(p28 return Err(p
25 .error("Expected semicolon after statement".to_string())29 .error("Expected semicolon after statement".to_string())
26 .into());30 .into());
crates/sema/Cargo.toml+1
...@@ -4,6 +4,7 @@ version = "0.1.0"...@@ -4,6 +4,7 @@ version = "0.1.0"
4edition = "2021"4edition = "2021"
55
6[lib]6[lib]
7path = "./sema.rs"
78
8[dependencies]9[dependencies]
9miette.workspace = true10miette.workspace = true
crates/sema/ast_type_coersion.rs created+54
...@@ -0,0 +1,54 @@
1// use ast::{BinaryOperator, ExprData, Type};
2
3// impl 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/src/ast_type_coersion.rs deleted-54
...@@ -1,54 +0,0 @@
1// use ast::{BinaryOperator, ExprData, Type};
2
3// impl 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/src/lib.rs deleted-96
...@@ -1,96 +0,0 @@
1// use ast::{ Module, Type};
2// use miette::Result;
3
4// mod ast_type_coersion;
5
6// pub fn check_module(block: &Module) -> Result<()> {
7// for statement in &block.statements {
8// check_expression(statement)?;
9// }
10
11// Ok(())
12// }
13
14// pub fn resolve_type(expr: &Ast) -> Result<Type> {
15// match expr {
16// Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())),
17// Ast::Declaration {
18// name,
19// decl_type,
20// value,
21// is_constant,
22// } => Ok(Type::NoReturn),
23// Ast::Binary {
24// operator,
25// left,
26// right,
27// } => merge_types(resolve_type(left)?, resolve_type(right)?),
28// Ast::Function {
29// name,
30// arguments,
31// return_type,
32// block,
33// } => todo!(),
34// Ast::FunctionCall { name, arguments } => todo!(),
35// Ast::If {
36// condition,
37// value,
38// else_value,
39// } => todo!(),
40// Ast::Return(_) => Ok(Type::NoReturn),
41// Ast::Public(_) => todo!(), // TODO: remove Public
42// Ast::Block(_) => todo!(),
43// Ast::Bool(_) => Ok(Type::Bool),
44// Ast::String(_) => Ok(Type::String),
45// Ast::Identifier(_) => todo!(),
46// Ast::BuiltIn(_) => todo!(),
47
48// Ast::Comment(_) => unreachable!(),
49// Ast::DocComment(_) => unreachable!(),
50// }
51// }
52
53// pub fn check_expression(expr: &Ast) -> Result<()> {
54// panic!()
55// match expr {
56// Ast::Declaration {
57// name,
58// decl_type: return_type,
59// value,
60// is_constant,
61// } => {
62// check_expression(value)?;
63// // coerce_type(return_type.clone(), resolve_type(value)?)?;
64// }
65// _ => {}
66// // Ast::Operation {
67// // operator,
68// // left,
69// // right,
70// // } => Ok(()),
71// // Ast::Function {
72// // name,
73// // arguments,
74// // return_type,
75// // block,
76// // } => Ok(()),
77// // Ast::FunctionCall { name, arguments } => Ok(()),
78// // Ast::If {
79// // condition,
80// // value,
81// // else_value,
82// // } => Ok(()),
83// // Ast::Return(_) => Ok(()),
84// // Ast::Public(_) => Ok(()),
85// // Ast::Block(_) => Ok(()),
86// // Ast::Number(_) => Ok(()),
87// // Ast::Bool(_) => Ok(()),
88// // Ast::String(_) => Ok(()),
89// // Ast::Identifier(_) => Ok(()),
90// // Ast::BuiltIn(_) => Ok(()),
91// // Ast::Comment(_) => Ok(()),
92// // Ast::DocComment(_) => Ok(()),
93// }
94
95// Ok(())
96// }
crates/tokenizer/Cargo.toml+1
...@@ -4,6 +4,7 @@ version = "0.1.0"...@@ -4,6 +4,7 @@ version = "0.1.0"
4edition = "2021"4edition = "2021"
55
6[lib]6[lib]
7path="./tokenizer.rs"
78
8[dependencies]9[dependencies]
9miette.workspace = true10miette.workspace = true
crates/tokenizer/src/lib.rs deleted-393
...@@ -1,393 +0,0 @@
1use miette::{Diagnostic, Result};
2use thiserror::Error;
3
4#[derive(Debug, PartialEq, Clone)]
5pub struct Loc {}
6
7impl Loc {
8 pub const EMPTY: Loc = Loc {};
9
10 pub fn new() -> Self {
11 Self {}
12 }
13}
14
15#[derive(Error, Diagnostic, Debug)]
16#[error("UnknownCharacter")]
17struct UnknownCharacter {
18 #[help]
19 error: String,
20}
21
22#[derive(Debug, Clone)]
23pub enum Token {
24 // Literals
25 Identifier(String),
26 Number(f64),
27 String(String),
28 // Comments
29 Comment(String),
30 DocComment(String),
31 // Keywords
32 Else,
33 False,
34 Fn,
35 If,
36 Let,
37 Pub,
38 Return,
39 True,
40 Var,
41 Void,
42 // Symbols
43 And, // &
44 AndAnd, // &&
45 Bang, // !
46 BangEqual, // !=
47 Caret, // ^
48 ClosedBrace, // }
49 ClosedBracket, // ]
50 ClosedParen, // )
51 Colon, // :
52 Comma, // ,
53 Equal, // =
54 EqualEqual, // ==
55 Gt, // >
56 GtEqual, // >=
57 GtGt, // >>
58 GtGtGt, // >>>
59 Lt, // <
60 LtEqual, // <=
61 LtLt, // <<
62 Minus, // -
63 OpenBrace, // {
64 OpenBracket, // [
65 OpenParen, // (
66 Or, // |
67 OrOr, // ||
68 Percent, // %
69 Plus, // +
70 RightArrow, // ->
71 Semicolon, // ;
72 Slash, // /
73 Star, // *
74 StarStar, // **
75 Tilde, // ~
76 Dot, // .
77}
78
79impl std::fmt::Display for Token {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 let token = match self {
82 Token::Identifier(_) => "identifier".to_string(),
83
84 Token::Number(n) => n.to_string(),
85 Token::String(s) => format!("\"{}\"", s),
86 Token::Comment(_) => "comment".to_string(),
87 Token::DocComment(_) => "documentation comment".to_string(),
88
89 Token::Else => "else".to_string(),
90 Token::False => "false".to_string(),
91 Token::Fn => "fn".to_string(),
92 Token::If => "if".to_string(),
93 Token::Let => "let".to_string(),
94 Token::Pub => "pub".to_string(),
95 Token::Return => "return".to_string(),
96 Token::True => "true".to_string(),
97 Token::Var => "var".to_string(),
98 Token::Void => "void".to_string(),
99
100 Token::And => "&".to_string(),
101 Token::AndAnd => "&&".to_string(),
102 Token::Bang => "!".to_string(),
103 Token::BangEqual => "!=".to_string(),
104 Token::Caret => "^".to_string(),
105 Token::ClosedBrace => "}".to_string(),
106 Token::ClosedBracket => "]".to_string(),
107 Token::ClosedParen => ")".to_string(),
108 Token::Colon => ":".to_string(),
109 Token::Comma => ",".to_string(),
110 Token::Equal => "=".to_string(),
111 Token::EqualEqual => "==".to_string(),
112 Token::Gt => ">".to_string(),
113 Token::GtEqual => ">=".to_string(),
114 Token::GtGt => ">>".to_string(),
115 Token::Lt => "<".to_string(),
116 Token::LtEqual => "<=".to_string(),
117 Token::LtLt => "<<".to_string(),
118 Token::Minus => "-".to_string(),
119 Token::OpenBrace => "{".to_string(),
120 Token::OpenBracket => "[".to_string(),
121 Token::OpenParen => "(".to_string(),
122 Token::Or => "|".to_string(),
123 Token::OrOr => "||".to_string(),
124 Token::Percent => "%".to_string(),
125 Token::Plus => "+".to_string(),
126 Token::RightArrow => "->".to_string(),
127 Token::Semicolon => ";".to_string(),
128 Token::Slash => "/".to_string(),
129 Token::Star => "*".to_string(),
130 Token::StarStar => "**".to_string(),
131 Token::Tilde => "~".to_string(),
132 Token::Dot => ".".to_string(),
133 Token::GtGtGt => ">>>".to_string(),
134 };
135
136 write!(f, "{}", token)
137 }
138}
139
140// TODO(@paperdave): rewrite this to not use a Vec<Token>. we probably dont need it. also store Loc
141pub fn tokenize(content: String) -> Result<Vec<Token>> {
142 let mut tokens: Vec<Token> = Vec::new();
143 let mut chars = content.chars().enumerate().peekable();
144
145 loop {
146 match chars.next() {
147 Some((_, c)) => match c {
148 'a'..='z' | 'A'..='Z' | '_' | '@' => {
149 let mut current_token = String::new();
150 current_token.push(c);
151 loop {
152 let c = chars.peek();
153 if c.is_none() {
154 break;
155 }
156 let (_, c) = c.unwrap();
157 match c {
158 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => {
159 current_token.push(*c);
160 chars.next();
161 }
162 _ => break,
163 }
164 }
165
166 match current_token.as_str() {
167 "else" => tokens.push(Token::Else),
168 "false" => tokens.push(Token::False),
169 "fn" => tokens.push(Token::Fn),
170 "if" => tokens.push(Token::If),
171 "let" => tokens.push(Token::Let),
172 "pub" => tokens.push(Token::Pub),
173 "return" => tokens.push(Token::Return),
174 "true" => tokens.push(Token::True),
175 "var" => tokens.push(Token::Var),
176 "void" => tokens.push(Token::Void),
177 _ => tokens.push(Token::Identifier(current_token)),
178 }
179 }
180 '"' => {
181 let mut current_token = String::new();
182 loop {
183 let c = chars.next();
184 if c.is_none() {
185 break;
186 }
187 let (_, c) = c.unwrap();
188 match c {
189 '"' => break,
190 _ => current_token.push(c),
191 }
192 }
193 tokens.push(Token::String(current_token));
194 }
195 '0'..='9' => {
196 let mut current_token = String::new();
197 current_token.push(c);
198 loop {
199 let c = chars.peek();
200 if c.is_none() {
201 break;
202 }
203 let (_, c) = c.unwrap();
204 match c {
205 '0'..='9' => {
206 current_token.push(*c);
207 chars.next();
208 }
209 _ => break,
210 }
211 }
212 match current_token.parse::<f64>() {
213 Ok(v) => tokens.push(Token::Number(v)),
214 Err(_) => {
215 return Err(UnknownCharacter {
216 error: format!("Unknown character: {}", current_token),
217 }
218 .into())
219 }
220 }
221 }
222 ':' => tokens.push(Token::Colon),
223 '=' => match chars.peek() {
224 Some((_, '=')) => {
225 chars.next();
226 tokens.push(Token::EqualEqual);
227 }
228 _ => tokens.push(Token::Equal),
229 },
230 ';' => tokens.push(Token::Semicolon),
231 '>' => match chars.peek() {
232 Some((_, '=')) => {
233 chars.next();
234 tokens.push(Token::GtEqual);
235 }
236 Some((_, '>')) => {
237 chars.next();
238 match chars.peek() {
239 Some((_, '>')) => {
240 chars.next();
241 tokens.push(Token::GtGtGt);
242 }
243 _ => tokens.push(Token::GtGt),
244 }
245 }
246 _ => tokens.push(Token::Gt),
247 },
248 '<' => match chars.peek() {
249 Some((_, '=')) => {
250 chars.next();
251 tokens.push(Token::LtEqual);
252 }
253 Some((_, '<')) => {
254 chars.next();
255 tokens.push(Token::LtLt);
256 }
257 _ => tokens.push(Token::Lt),
258 },
259 '+' => tokens.push(Token::Plus),
260 '-' => match chars.peek() {
261 Some((_, '>')) => {
262 chars.next();
263 tokens.push(Token::RightArrow);
264 }
265 _ => tokens.push(Token::Minus),
266 },
267 '*' => match chars.peek() {
268 Some((_, '*')) => {
269 chars.next();
270 tokens.push(Token::StarStar);
271 }
272 _ => tokens.push(Token::Star),
273 },
274 '/' => {
275 match chars.peek() {
276 Some((_, '/')) => {
277 chars.next(); // Consume the second slash
278
279 let is_doc_comment = match chars.peek() {
280 Some((_, '/')) => {
281 chars.next(); // Consume the third slash
282 true
283 }
284 _ => false,
285 };
286
287 let mut comment = String::new();
288 loop {
289 // skip leading whitespace
290 while let Some((_, c)) = chars.peek() {
291 if !c.is_whitespace() {
292 break;
293 }
294 chars.next();
295 }
296 // read the comment
297 while let Some((_, c)) = chars.next() {
298 if c == '\n' || c == '\r' {
299 break;
300 }
301 comment.push(c);
302 }
303 // trim trailing whitespace on line
304 comment = comment.trim_end().to_string();
305 // skip to next non-whitespace character
306 while let Some((_, c)) = chars.peek() {
307 if !c.is_whitespace() {
308 break;
309 }
310 chars.next();
311 }
312 // check if we've hit another comment
313 let mut tmp = chars.clone();
314
315 let is_another_comment = match tmp.peek() {
316 Some((_, '/')) => {
317 tmp.next(); // Consume the second slash
318 match tmp.peek() {
319 Some((_, '/')) => {
320 tmp.next(); // Consume the third slash
321 is_doc_comment
322 }
323 _ => !is_doc_comment,
324 }
325 }
326 _ => false,
327 };
328
329 if is_another_comment {
330 // apply our temporary iterator to the main iterator
331 comment.push('\n');
332 chars = tmp;
333 chars.next();
334 } else {
335 break;
336 }
337 }
338
339 if is_doc_comment {
340 tokens.push(Token::DocComment(comment));
341 } else {
342 tokens.push(Token::Comment(comment));
343 }
344 }
345 _ => tokens.push(Token::Slash),
346 }
347 }
348 ',' => tokens.push(Token::Comma),
349 '(' => tokens.push(Token::OpenParen),
350 ')' => tokens.push(Token::ClosedParen),
351 '{' => tokens.push(Token::OpenBrace),
352 '}' => tokens.push(Token::ClosedBrace),
353 '[' => tokens.push(Token::OpenBracket),
354 ']' => tokens.push(Token::ClosedBracket),
355 '&' => match chars.peek() {
356 Some((_, '&')) => {
357 chars.next();
358 tokens.push(Token::AndAnd);
359 }
360 _ => tokens.push(Token::And),
361 },
362 '|' => match chars.peek() {
363 Some((_, '|')) => {
364 chars.next();
365 tokens.push(Token::OrOr);
366 }
367 _ => tokens.push(Token::Or),
368 },
369 '!' => match chars.peek() {
370 Some((_, '=')) => {
371 chars.next();
372 tokens.push(Token::BangEqual);
373 }
374 _ => tokens.push(Token::Bang),
375 },
376 '^' => tokens.push(Token::Caret),
377 '%' => tokens.push(Token::Percent),
378 '~' => tokens.push(Token::Tilde),
379 '.' => tokens.push(Token::Dot),
380 s if s.is_whitespace() => (),
381 _ => {
382 return Err(UnknownCharacter {
383 error: format!("Unexpected '{}' while tokenizing", c),
384 }
385 .into())
386 }
387 },
388 None => break,
389 }
390 }
391
392 Ok(tokens)
393}
crates/tokenizer/tokenizer.rs created+402
...@@ -0,0 +1,402 @@
1use miette::{Diagnostic, Result};
2use thiserror::Error;
3
4#[derive(Debug, PartialEq, Clone)]
5pub struct Loc {}
6
7impl Loc {
8 pub const EMPTY: Loc = Loc {};
9
10 pub fn new() -> Self {
11 Self {}
12 }
13}
14
15#[derive(Error, Diagnostic, Debug)]
16#[error("UnknownCharacter")]
17struct UnknownCharacter {
18 #[help]
19 error: String,
20}
21
22#[derive(Debug, Clone)]
23pub enum Token {
24 // Literals
25 Identifier(String),
26 Number(f64),
27 String(String),
28 // Comments
29 Comment(String),
30 DocComment(String),
31 // Keywords
32 Else,
33 False,
34 Fn,
35 If,
36 Let,
37 Pub,
38 Return,
39 True,
40 Var,
41 Void,
42 // Symbols
43 And, // &
44 AndAnd, // &&
45 Bang, // !
46 BangEqual, // !=
47 Caret, // ^
48 ClosedBrace, // }
49 ClosedBracket, // ]
50 ClosedParen, // )
51 Colon, // :
52 Comma, // ,
53 Equal, // =
54 EqualEqual, // ==
55 Gt, // >
56 GtEqual, // >=
57 GtGt, // >>
58 GtGtGt, // >>>
59 Lt, // <
60 LtEqual, // <=
61 LtLt, // <<
62 Minus, // -
63 OpenBrace, // {
64 OpenBracket, // [
65 OpenParen, // (
66 Or, // |
67 OrOr, // ||
68 Percent, // %
69 Plus, // +
70 RightArrow, // ->
71 Semicolon, // ;
72 Slash, // /
73 Star, // *
74 StarStar, // **
75 Tilde, // ~
76 Dot, // .
77}
78
79impl Token {
80 pub fn is_start_of_statement_like_expression(&self) -> bool {
81 match &self {
82 Token::If | Token::OpenBrace | Token::Fn => false,
83 _ => true,
84 }
85 }
86}
87
88impl std::fmt::Display for Token {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 let token = match self {
91 Token::Identifier(_) => "identifier".to_string(),
92
93 Token::Number(n) => n.to_string(),
94 Token::String(s) => format!("\"{}\"", s),
95 Token::Comment(_) => "comment".to_string(),
96 Token::DocComment(_) => "documentation comment".to_string(),
97
98 Token::Else => "else".to_string(),
99 Token::False => "false".to_string(),
100 Token::Fn => "fn".to_string(),
101 Token::If => "if".to_string(),
102 Token::Let => "let".to_string(),
103 Token::Pub => "pub".to_string(),
104 Token::Return => "return".to_string(),
105 Token::True => "true".to_string(),
106 Token::Var => "var".to_string(),
107 Token::Void => "void".to_string(),
108
109 Token::And => "&".to_string(),
110 Token::AndAnd => "&&".to_string(),
111 Token::Bang => "!".to_string(),
112 Token::BangEqual => "!=".to_string(),
113 Token::Caret => "^".to_string(),
114 Token::ClosedBrace => "}".to_string(),
115 Token::ClosedBracket => "]".to_string(),
116 Token::ClosedParen => ")".to_string(),
117 Token::Colon => ":".to_string(),
118 Token::Comma => ",".to_string(),
119 Token::Equal => "=".to_string(),
120 Token::EqualEqual => "==".to_string(),
121 Token::Gt => ">".to_string(),
122 Token::GtEqual => ">=".to_string(),
123 Token::GtGt => ">>".to_string(),
124 Token::Lt => "<".to_string(),
125 Token::LtEqual => "<=".to_string(),
126 Token::LtLt => "<<".to_string(),
127 Token::Minus => "-".to_string(),
128 Token::OpenBrace => "{".to_string(),
129 Token::OpenBracket => "[".to_string(),
130 Token::OpenParen => "(".to_string(),
131 Token::Or => "|".to_string(),
132 Token::OrOr => "||".to_string(),
133 Token::Percent => "%".to_string(),
134 Token::Plus => "+".to_string(),
135 Token::RightArrow => "->".to_string(),
136 Token::Semicolon => ";".to_string(),
137 Token::Slash => "/".to_string(),
138 Token::Star => "*".to_string(),
139 Token::StarStar => "**".to_string(),
140 Token::Tilde => "~".to_string(),
141 Token::Dot => ".".to_string(),
142 Token::GtGtGt => ">>>".to_string(),
143 };
144
145 write!(f, "{}", token)
146 }
147}
148
149// TODO(@paperdave): rewrite this to not use a Vec<Token>. we probably dont need it. also store Loc
150pub fn tokenize(content: String) -> Result<Vec<Token>> {
151 let mut tokens: Vec<Token> = Vec::new();
152 let mut chars = content.chars().enumerate().peekable();
153
154 loop {
155 match chars.next() {
156 Some((_, c)) => match c {
157 'a'..='z' | 'A'..='Z' | '_' | '@' => {
158 let mut current_token = String::new();
159 current_token.push(c);
160 loop {
161 let c = chars.peek();
162 if c.is_none() {
163 break;
164 }
165 let (_, c) = c.unwrap();
166 match c {
167 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => {
168 current_token.push(*c);
169 chars.next();
170 }
171 _ => break,
172 }
173 }
174
175 match current_token.as_str() {
176 "else" => tokens.push(Token::Else),
177 "false" => tokens.push(Token::False),
178 "fn" => tokens.push(Token::Fn),
179 "if" => tokens.push(Token::If),
180 "let" => tokens.push(Token::Let),
181 "pub" => tokens.push(Token::Pub),
182 "return" => tokens.push(Token::Return),
183 "true" => tokens.push(Token::True),
184 "var" => tokens.push(Token::Var),
185 "void" => tokens.push(Token::Void),
186 _ => tokens.push(Token::Identifier(current_token)),
187 }
188 }
189 '"' => {
190 let mut current_token = String::new();
191 loop {
192 let c = chars.next();
193 if c.is_none() {
194 break;
195 }
196 let (_, c) = c.unwrap();
197 match c {
198 '"' => break,
199 _ => current_token.push(c),
200 }
201 }
202 tokens.push(Token::String(current_token));
203 }
204 '0'..='9' => {
205 let mut current_token = String::new();
206 current_token.push(c);
207 loop {
208 let c = chars.peek();
209 if c.is_none() {
210 break;
211 }
212 let (_, c) = c.unwrap();
213 match c {
214 '0'..='9' => {
215 current_token.push(*c);
216 chars.next();
217 }
218 _ => break,
219 }
220 }
221 match current_token.parse::<f64>() {
222 Ok(v) => tokens.push(Token::Number(v)),
223 Err(_) => {
224 return Err(UnknownCharacter {
225 error: format!("Unknown character: {}", current_token),
226 }
227 .into())
228 }
229 }
230 }
231 ':' => tokens.push(Token::Colon),
232 '=' => match chars.peek() {
233 Some((_, '=')) => {
234 chars.next();
235 tokens.push(Token::EqualEqual);
236 }
237 _ => tokens.push(Token::Equal),
238 },
239 ';' => tokens.push(Token::Semicolon),
240 '>' => match chars.peek() {
241 Some((_, '=')) => {
242 chars.next();
243 tokens.push(Token::GtEqual);
244 }
245 Some((_, '>')) => {
246 chars.next();
247 match chars.peek() {
248 Some((_, '>')) => {
249 chars.next();
250 tokens.push(Token::GtGtGt);
251 }
252 _ => tokens.push(Token::GtGt),
253 }
254 }
255 _ => tokens.push(Token::Gt),
256 },
257 '<' => match chars.peek() {
258 Some((_, '=')) => {
259 chars.next();
260 tokens.push(Token::LtEqual);
261 }
262 Some((_, '<')) => {
263 chars.next();
264 tokens.push(Token::LtLt);
265 }
266 _ => tokens.push(Token::Lt),
267 },
268 '+' => tokens.push(Token::Plus),
269 '-' => match chars.peek() {
270 Some((_, '>')) => {
271 chars.next();
272 tokens.push(Token::RightArrow);
273 }
274 _ => tokens.push(Token::Minus),
275 },
276 '*' => match chars.peek() {
277 Some((_, '*')) => {
278 chars.next();
279 tokens.push(Token::StarStar);
280 }
281 _ => tokens.push(Token::Star),
282 },
283 '/' => {
284 match chars.peek() {
285 Some((_, '/')) => {
286 chars.next(); // Consume the second slash
287
288 let is_doc_comment = match chars.peek() {
289 Some((_, '/')) => {
290 chars.next(); // Consume the third slash
291 true
292 }
293 _ => false,
294 };
295
296 let mut comment = String::new();
297 loop {
298 // skip leading whitespace
299 while let Some((_, c)) = chars.peek() {
300 if !c.is_whitespace() {
301 break;
302 }
303 chars.next();
304 }
305 // read the comment
306 while let Some((_, c)) = chars.next() {
307 if c == '\n' || c == '\r' {
308 break;
309 }
310 comment.push(c);
311 }
312 // trim trailing whitespace on line
313 comment = comment.trim_end().to_string();
314 // skip to next non-whitespace character
315 while let Some((_, c)) = chars.peek() {
316 if !c.is_whitespace() {
317 break;
318 }
319 chars.next();
320 }
321 // check if we've hit another comment
322 let mut tmp = chars.clone();
323
324 let is_another_comment = match tmp.peek() {
325 Some((_, '/')) => {
326 tmp.next(); // Consume the second slash
327 match tmp.peek() {
328 Some((_, '/')) => {
329 tmp.next(); // Consume the third slash
330 is_doc_comment
331 }
332 _ => !is_doc_comment,
333 }
334 }
335 _ => false,
336 };
337
338 if is_another_comment {
339 // apply our temporary iterator to the main iterator
340 comment.push('\n');
341 chars = tmp;
342 chars.next();
343 } else {
344 break;
345 }
346 }
347
348 if is_doc_comment {
349 tokens.push(Token::DocComment(comment));
350 } else {
351 tokens.push(Token::Comment(comment));
352 }
353 }
354 _ => tokens.push(Token::Slash),
355 }
356 }
357 ',' => tokens.push(Token::Comma),
358 '(' => tokens.push(Token::OpenParen),
359 ')' => tokens.push(Token::ClosedParen),
360 '{' => tokens.push(Token::OpenBrace),
361 '}' => tokens.push(Token::ClosedBrace),
362 '[' => tokens.push(Token::OpenBracket),
363 ']' => tokens.push(Token::ClosedBracket),
364 '&' => match chars.peek() {
365 Some((_, '&')) => {
366 chars.next();
367 tokens.push(Token::AndAnd);
368 }
369 _ => tokens.push(Token::And),
370 },
371 '|' => match chars.peek() {
372 Some((_, '|')) => {
373 chars.next();
374 tokens.push(Token::OrOr);
375 }
376 _ => tokens.push(Token::Or),
377 },
378 '!' => match chars.peek() {
379 Some((_, '=')) => {
380 chars.next();
381 tokens.push(Token::BangEqual);
382 }
383 _ => tokens.push(Token::Bang),
384 },
385 '^' => tokens.push(Token::Caret),
386 '%' => tokens.push(Token::Percent),
387 '~' => tokens.push(Token::Tilde),
388 '.' => tokens.push(Token::Dot),
389 s if s.is_whitespace() => (),
390 _ => {
391 return Err(UnknownCharacter {
392 error: format!("Unexpected '{}' while tokenizing", c),
393 }
394 .into())
395 }
396 },
397 None => break,
398 }
399 }
400
401 Ok(tokens)
402}