authorgravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-01-22 04:42:33-08:00
committergravatar for me@paperdave.netdave caruso <me@paperdave.net> 2024-01-22 04:42:33-08:00
logcd0400a1a24ca1bd96fb883589958dabece01154
treed78076620f5b9a3a1d0219d8dad16bab5350195d
parent42c364acbbbd54bf8afefa0ff82034928e83f06e
signature Commit is signed but in an unrecognized format.

organize the converter


33 files changed, 1397 insertions(+), 1568 deletions(-)

crates/ast/ast.rs+34-11
......@@ -30,7 +30,7 @@ pub struct Param {
3030
3131#[derive(Debug, Clone)]
3232
33pub struct Decl {
33pub struct VarDecl {
3434 pub name: String,
3535 pub data_type: Type,
3636 pub init: Expr,
......@@ -40,6 +40,17 @@ pub struct Decl {
4040 pub comment: Option<DocComment>,
4141}
4242
43#[derive(Debug, Clone)]
44pub struct FnDecl {
45 pub name: String,
46 pub return_type: Type,
47 pub params: Vec<Param>,
48 pub body: Expr,
49 pub is_pub: bool,
50 pub loc: Loc,
51 pub comment: Option<DocComment>,
52}
53
4354#[derive(Debug, Clone)]
4455pub struct Expr {
4556 pub data: ExprData,
......@@ -136,7 +147,7 @@ pub enum ExprData {
136147 name: String,
137148 params: Vec<Param>,
138149 return_type: Type,
139 block: Block,
150 body: Box<Expr>,
140151 },
141152 Call {
142153 base: Box<Expr>,
......@@ -159,14 +170,16 @@ pub enum ExprData {
159170#[derive(Debug, Clone)]
160171pub enum Statement {
161172 Expr(Expr),
162 Decl(Decl),
173 VarDecl(VarDecl),
174 FnDecl(FnDecl),
163175 Comment(Comment),
164176}
165177
166178#[derive(Debug, Clone)]
167179pub enum TopLevelStatement {
168180 Expr(Expr),
169 Decl(Decl),
181 VarDecl(VarDecl),
182 FnDecl(FnDecl),
170183 Comment(Comment),
171184}
172185
......@@ -189,7 +202,8 @@ impl Statement {
189202 pub fn to_top_level(self) -> TopLevelStatement {
190203 match self {
191204 Statement::Expr(expr) => TopLevelStatement::Expr(expr),
192 Statement::Decl(decl) => TopLevelStatement::Decl(decl),
205 Statement::VarDecl(decl) => TopLevelStatement::VarDecl(decl),
206 Statement::FnDecl(decl) => TopLevelStatement::FnDecl(decl),
193207 Statement::Comment(comment) => TopLevelStatement::Comment(comment),
194208 }
195209 }
......@@ -198,7 +212,14 @@ impl Statement {
198212 pub fn require_semicolon(&self, semicolon_hint: bool) -> bool {
199213 match &self {
200214 Statement::Expr(_) => !semicolon_hint,
201 Statement::Decl(_) => true,
215 Statement::VarDecl(_) => true,
216 _ => false,
217 }
218 }
219
220 pub fn is_expression(&self) -> bool {
221 match &self {
222 Statement::Expr(_) => true,
202223 _ => false,
203224 }
204225 }
......@@ -209,16 +230,18 @@ impl TopLevelStatement {
209230 pub fn require_semicolon(&self, earlier_hint: bool) -> bool {
210231 match &self {
211232 TopLevelStatement::Expr(_) => !earlier_hint,
212 TopLevelStatement::Decl(_) => true,
233 TopLevelStatement::VarDecl(_) => true,
234 TopLevelStatement::FnDecl(_) => false,
213235 _ => false,
214236 }
215237 }
216238
217 pub fn to_statement(self) -> Statement {
239 pub fn to_statement(self) -> Option<Statement> {
218240 match self {
219 TopLevelStatement::Expr(expr) => Statement::Expr(expr),
220 TopLevelStatement::Decl(decl) => Statement::Decl(decl),
221 TopLevelStatement::Comment(comment) => Statement::Comment(comment),
241 TopLevelStatement::Expr(expr) => Some(Statement::Expr(expr)),
242 TopLevelStatement::VarDecl(decl) => Some(Statement::VarDecl(decl)),
243 TopLevelStatement::FnDecl(decl) => Some(Statement::FnDecl(decl)),
244 TopLevelStatement::Comment(comment) => Some(Statement::Comment(comment)),
222245 }
223246 }
224247}
crates/js_ast/convert/block_convert_context.rs created+244
......@@ -0,0 +1,244 @@
1use crate::js_ast::*;
2use ast::*;
3use std::collections::{HashMap, HashSet};
4use std::vec;
5use tokenizer::Loc;
6
7use super::tree_shake_statement_list;
8
9/// A block convert context keeps track of state while converting non-valid JS expressions to JS
10pub struct BlockConvertContext {
11 /// Statements in this block context
12 /// This may be the merged contents of multiple blocks
13 statements: Vec<JsStatement>,
14 ids: HashSet<String>,
15 next_id: u32,
16 scopes: Vec<VirtualScope>,
17 /// If the last item in `statements` is actually valid
18 has_last_statement: bool,
19}
20
21struct VirtualScope {
22 renames: HashMap<String, String>,
23}
24
25pub struct TempId {
26 unmapped_name: String,
27}
28
29pub struct StatementHandle {
30 index: Option<u32>,
31}
32
33impl StatementHandle {
34 pub fn delete(self, ctx: &mut BlockConvertContext) {
35 if let Some(index) = self.index {
36 ctx.statements.remove(index as usize);
37 }
38 }
39}
40
41impl TempId {
42 pub fn get(&self, hoist_context: &BlockConvertContext) -> String {
43 hoist_context.remap_identifier(&self.unmapped_name)
44 }
45}
46
47impl BlockConvertContext {
48 pub fn new() -> Self {
49 Self {
50 statements: Vec::new(),
51 ids: HashSet::new(),
52 next_id: 0,
53 scopes: vec![VirtualScope {
54 renames: HashMap::new(),
55 }],
56 has_last_statement: false,
57 }
58 }
59
60 pub fn new_child(&self) -> Self {
61 Self {
62 statements: Vec::new(),
63 ids: self.ids.clone(),
64 next_id: 0,
65 scopes: vec![VirtualScope {
66 renames: HashMap::new(),
67 }],
68 has_last_statement: false,
69 }
70 }
71
72 /// A virtual scope is a scope that exists in blight syntax
73 /// but may or may not exist in blight. With these it is important to
74 pub fn push_virtual_scope(&mut self) {
75 self.scopes.push(VirtualScope {
76 renames: HashMap::new(),
77 });
78 }
79
80 /// See `push_virtual_scope` for more information on what this is.
81 pub fn pop_virtual_scope(&mut self) {
82 self.scopes.pop();
83 debug_assert!(self.scopes.len() > 0);
84 }
85
86 pub fn generate_temp_id(&mut self, basename: &str) -> TempId {
87 self.next_id += 1;
88 loop {
89 let name = format!("{}{}", basename, self.next_id);
90 if !self.ids.contains(&name) {
91 self.ids.insert(name.clone());
92 return TempId {
93 unmapped_name: name,
94 };
95 }
96 self.next_id += 1;
97 }
98 }
99
100 /// This does some extra optimizations:
101 /// - `let a = b;` will not be emitted but instad write a variable alias
102 pub fn add_statement(&mut self, statement: JsStatement) -> StatementHandle {
103 match statement.data {
104 JsStatementData::Declaration(JSDeclaration {
105 name,
106 init:
107 Some(JsExpr {
108 data: JsExprData::Identifier(init_name),
109 ..
110 }),
111 is_constant: true,
112 }) => {
113 self.scopes
114 .last_mut()
115 .unwrap()
116 .renames
117 .insert(name, init_name);
118 self.has_last_statement = false;
119 return StatementHandle { index: None };
120 }
121 _ => {}
122 }
123 self.statements.push(statement);
124 self.has_last_statement = true;
125 StatementHandle {
126 index: Some(self.statements.len() as u32 - 1),
127 }
128 }
129
130 /// Get the last statement if it exists.
131 /// If the statement was optimized away, this returns None
132 pub fn last_statement(&self) -> Option<&JsStatement> {
133 if self.has_last_statement {
134 Some(self.statements.last().unwrap())
135 } else {
136 None
137 }
138 }
139
140 pub fn generate_id(&mut self, basename: &String) -> TempId {
141 let mut id = 0u32;
142 loop {
143 let name = match id {
144 0 => basename.clone(),
145 _ => format!("{}{}", basename, id),
146 };
147 if !self.ids.contains(&name) {
148 self.ids.insert(name.clone());
149 return TempId {
150 unmapped_name: name,
151 };
152 }
153 id += 1;
154 }
155 }
156
157 /// Given a blight identifier, remap it to a new identifier.
158 pub fn remap_identifier(&self, name: &String) -> String {
159 for scope in self.scopes.iter().rev() {
160 if let Some(remapped) = scope.renames.get(name) {
161 return remapped.clone();
162 }
163 }
164 name.clone()
165 }
166
167 /// Declare an identifier in the current scope.
168 pub fn declare_identifier(&mut self, name: String) -> TempId {
169 let remapped = self.generate_id(&name);
170 let scope = self.scopes.last_mut().unwrap();
171 self.ids.insert(remapped.unmapped_name.clone());
172 scope.renames.insert(name, remapped.unmapped_name.clone());
173 remapped
174 }
175
176 pub fn take_last_statement(&mut self) -> Option<JsStatement> {
177 if !self.has_last_statement {
178 return None;
179 }
180 self.has_last_statement = false;
181 Some(self.statements.pop().unwrap())
182 }
183
184 pub fn take_last_statement_if_expr(&mut self) -> Option<JsExpr> {
185 if !self.has_last_statement {
186 return None;
187 }
188 match self.statements.last().unwrap().data {
189 JsStatementData::Expression(_) => {
190 self.has_last_statement = false;
191 match self.statements.pop().unwrap().data {
192 JsStatementData::Expression(expr) => Some(expr),
193 _ => unreachable!(),
194 }
195 }
196 _ => return None,
197 }
198 }
199}
200
201pub fn new_js_assignment_statement(expr: JsExpr, target: String, loc: Loc) -> JsStatement {
202 JsStatement::new(
203 JsStatementData::Expression(new_js_assignment_expr(expr, target, loc.clone())),
204 loc,
205 None,
206 )
207}
208
209pub fn new_js_assignment_expr(expr: JsExpr, target: String, loc: Loc) -> JsExpr {
210 JsExpr::new(
211 JsExprData::Binary {
212 op: BinaryOperator::Assign,
213 left: Box::new(JsExpr::new(
214 JsExprData::Identifier(target),
215 loc.clone(),
216 None,
217 )),
218 right: Box::new(expr),
219 },
220 loc,
221 None,
222 )
223}
224
225impl Into<JsBlock> for BlockConvertContext {
226 fn into(self) -> JsBlock {
227 JsBlock::new(self.into(), Loc::EMPTY)
228 }
229}
230
231impl Into<JsModule> for BlockConvertContext {
232 fn into(self) -> JsModule {
233 JsModule {
234 statements: self.into(),
235 }
236 }
237}
238
239impl Into<Vec<JsStatement>> for BlockConvertContext {
240 fn into(mut self) -> Vec<JsStatement> {
241 tree_shake_statement_list(&mut self.statements);
242 self.statements
243 }
244}
crates/js_ast/convert/convert_valid_block.rs created+37
......@@ -0,0 +1,37 @@
1use super::convert_valid_statement_to_expr;
2use super::BlockConvertContext;
3use super::IsValidJSExpression;
4use crate::js_ast::*;
5use ast::*;
6use tokenizer::Loc;
7
8pub fn convert_valid_block(ctx: &mut BlockConvertContext, block: Block, loc: Loc) -> JsExpr {
9 debug_assert!(
10 block.is_valid_js_expression(),
11 "must call convert_valid_block with valid js expression"
12 );
13
14 let len = block.statements.len();
15 let loc = loc;
16 let block_expr = match len {
17 0 => JsExpr::new(JsExprData::Null, loc.clone(), None),
18 // a block expression with a single expression is just that expression
19 1 => convert_valid_statement_to_expr(ctx, block.statements.into_iter().next().unwrap()),
20 // Since blocks are not valid expressions in JS, we create a
21 // comma expression from a block with more than one statement.
22 2.. => {
23 let mut items = Vec::new();
24 for statement in block.statements.into_iter() {
25 items.push(convert_valid_statement_to_expr(ctx, statement));
26 }
27 JsExpr::new(JsExprData::Comma { items }, loc.clone(), None)
28 }
29 };
30
31 // a block without implicit return is to be type void, so we wrap in a void expression
32 if !block.has_implicit_return && len > 0 {
33 JsExpr::new(JsExprData::Void(Box::new(block_expr)), loc, None)
34 } else {
35 block_expr
36 }
37}
crates/js_ast/convert/convert_valid_expr.rs created+115
......@@ -0,0 +1,115 @@
1use super::convert_valid_block;
2use super::BlockConvertContext;
3use crate::convert::convert_valid_if;
4use crate::convert::process_function::process_function_to_arrow;
5use crate::convert::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8
9/// Translate a blight ast expression that is a valid JS expression, to a JS expression
10/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
11pub fn convert_valid_expr(ctx: &mut BlockConvertContext, expr: Expr) -> JsExpr {
12 // Assert may be skipped in release builds as all failures are
13 // annotated with a release-checked panic
14 debug_assert!(
15 expr.is_valid_js_expression(),
16 "must call convert_valid_expr with valid js expression (use hoist instead)"
17 );
18
19 let loc = expr.loc;
20 let comment = expr.comment;
21
22 match expr.data {
23 ExprData::Identifier(str) => JsExpr::new(
24 JsExprData::Identifier(ctx.remap_identifier(&str)),
25 loc,
26 comment,
27 ),
28
29 ExprData::Literal(Literal::Bool(value)) => {
30 JsExpr::new(JsExprData::Bool(value), loc, comment)
31 }
32 ExprData::Literal(Literal::Number(value)) => {
33 JsExpr::new(JsExprData::Number(value), loc, comment)
34 }
35 ExprData::Literal(Literal::String(value)) => {
36 JsExpr::new(JsExprData::String(value), loc, comment)
37 }
38 ExprData::Literal(Literal::Void) => JsExpr::new(JsExprData::Null, loc, comment),
39
40 ExprData::Block(block) => convert_valid_block(ctx, block, loc),
41
42 ExprData::Binary { op, left, right } => {
43 let left = convert_valid_expr(ctx, *left);
44 let right = convert_valid_expr(ctx, *right);
45 JsExpr::new(
46 JsExprData::Binary {
47 op,
48 left: Box::new(left),
49 right: Box::new(right),
50 },
51 loc,
52 comment,
53 )
54 }
55 ExprData::Unary { op, value } => {
56 let value = convert_valid_expr(ctx, *value);
57 JsExpr::new(
58 JsExprData::Unary {
59 op,
60 value: Box::new(value),
61 },
62 loc,
63 comment,
64 )
65 }
66 ExprData::Fn { body, params, .. } => {
67 process_function_to_arrow(ctx, *body, params, loc, comment)
68 }
69 ExprData::Builtin { id, arguments } => match id {
70 BuiltinId::Raw => {
71 let first_arg = arguments.iter().next().unwrap();
72 // TODO(@paperdave): this string clone is stupid
73 let str = match first_arg.data.clone() {
74 ExprData::Literal(Literal::String(s)) => s,
75 _ => panic!("raw expects a string"),
76 };
77 JsExpr::new(JsExprData::Raw(str), loc, comment)
78 }
79 },
80 ExprData::If(expr) => convert_valid_if(ctx, expr, loc, comment),
81
82 ExprData::Call { base, arguments } => {
83 let base = convert_valid_expr(ctx, *base);
84 let arguments = arguments
85 .into_iter()
86 .map(|x| convert_valid_expr(ctx, x))
87 .collect();
88 JsExpr::new(
89 JsExprData::Call {
90 base: Box::new(base),
91 arguments,
92 },
93 loc,
94 comment,
95 )
96 }
97
98 ExprData::Field { base, field } => JsExpr::new(
99 match field.data {
100 ExprData::Literal(Literal::String(name)) => JsExprData::Dot {
101 base: Box::new(convert_valid_expr(ctx, *base)),
102 name,
103 },
104 _ => JsExprData::Index {
105 base: Box::new(convert_valid_expr(ctx, *base)),
106 index: Box::new(convert_valid_expr(ctx, *field)),
107 },
108 },
109 loc,
110 comment,
111 ),
112
113 ExprData::Return(_) => unreachable!("convert_valid_expr called on return"),
114 }
115}
crates/js_ast/convert/convert_valid_if.rs created+38
......@@ -0,0 +1,38 @@
1use super::convert_valid_block;
2use super::convert_valid_expr;
3use super::BlockConvertContext;
4use crate::convert::IsValidJSExpression;
5use crate::js_ast::*;
6use ast::*;
7use tokenizer::Loc;
8
9/// Convert a valid blight if statement to a JS expression
10pub fn convert_valid_if(
11 ctx: &mut BlockConvertContext,
12 expr: If,
13 loc: Loc,
14 comment: Option<Comment>,
15) -> JsExpr {
16 debug_assert!(
17 expr.is_valid_js_expression(),
18 "convert_valid_if on an invalid expression (use hoist instead)"
19 );
20 let condition = convert_valid_expr(ctx, *expr.condition);
21
22 let yes = convert_valid_block(ctx, expr.yes.block, expr.yes.loc);
23 let no = match expr.no {
24 Some(BlockOrIf::Block(b)) => Some(convert_valid_block(ctx, b.block, b.loc)),
25 Some(BlockOrIf::If(else_if)) => Some(convert_valid_if(ctx, *else_if, Loc::EMPTY, None)),
26 None => None,
27 };
28
29 JsExpr::new(
30 JsExprData::Ternary {
31 condition: Box::new(condition),
32 yes: Box::new(yes),
33 no: no.map(Box::new),
34 },
35 loc,
36 comment,
37 )
38}
crates/js_ast/convert/convert_valid_statement.rs created+32
......@@ -0,0 +1,32 @@
1use super::convert_valid_expr;
2use super::is_valid_js_expression::*;
3use super::BlockConvertContext;
4use crate::js_ast::*;
5use ast::*;
6
7/// Translate a blight ast statement that is a valid JS expression, to a JS expression
8/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
9pub fn convert_valid_statement_to_expr(
10 ctx: &mut BlockConvertContext,
11 statement: Statement,
12) -> JsExpr {
13 debug_assert!(
14 statement.is_valid_js_expression(),
15 "convert_valid_statement_to_expr on an invalid expression (use hoist instead)"
16 );
17
18 match statement {
19 ast::Statement::Expr(expr) => convert_valid_expr(ctx, expr),
20 _ => unreachable!("attempt to convert statement to js without hoisting"),
21 }
22}
23
24/// Translate a blight ast statement that is a valid JS expression, to a JS statement
25/// Precondition: it is a valid JS expression, check this with `is_valid_js_expression`
26///
27/// TODO(@paperdave): Replace this with Into<JsStatement> for JsExpr
28pub fn convert_valid_statement(ctx: &mut BlockConvertContext, statement: Statement) -> JsStatement {
29 let expr = convert_valid_statement_to_expr(ctx, statement);
30 let loc = expr.loc.clone();
31 JsStatement::new(JsStatementData::Expression(expr), loc, None)
32}
crates/js_ast/convert/hoist_block_to_expr.rs created+78
......@@ -0,0 +1,78 @@
1use super::is_valid_js_expression::*;
2use super::process_statement;
3use super::BlockConvertContext;
4use crate::js_ast::*;
5use ast::*;
6use tokenizer::Loc;
7
8/// This block hoist is used when we have a block in an expression context.
9///
10/// All the expressions in the block are lifted above the currently
11/// processed expression, and then a temporary variable is returned.
12/// (or the last expression might get inlined as the return value)
13///
14/// A "virtual scope" is created for this block, which handles renaming
15/// situations like:
16///
17/// let x = 1;
18/// {
19/// let x = 1;
20/// }
21/// call(x)
22///
23/// The output JS of the above would be:
24///
25/// const x = 1;
26/// const x2 = 1;
27/// call(x)
28///
29/// For expressions that have branches, use `hoist_block_to_js_block`
30pub fn hoist_block_to_expr(
31 ctx: &mut BlockConvertContext,
32 block: Block,
33 loc: Loc,
34 comment: Option<Comment>,
35) -> Option<JsExpr> {
36 debug_assert!(
37 !block.is_valid_js_expression(),
38 "hoist_block on a valid expression, should call convert_valid_block instead"
39 );
40
41 let temporary = ctx.generate_temp_id("_block");
42
43 ctx.push_virtual_scope();
44
45 for statement in block.statements {
46 process_statement(ctx, statement);
47 }
48
49 ctx.pop_virtual_scope();
50
51 // If the last statement is an expression, we need
52 // to assign to the temporary variable.
53 let result = match ctx.last_statement().map(|x| &x.data) {
54 Some(JsStatementData::Expression(_)) if block.has_implicit_return => {
55 match ctx.take_last_statement().unwrap().data {
56 JsStatementData::Expression(expr) => expr,
57 _ => unreachable!(),
58 }
59 }
60 _ => JsExpr::new(JsExprData::Null, loc.clone(), None),
61 };
62
63 ctx.add_statement(JsStatement::new(
64 JsStatementData::Declaration(JSDeclaration {
65 name: temporary.get(&ctx),
66 init: Some(result),
67 is_constant: false,
68 }),
69 loc.clone(),
70 comment,
71 ));
72
73 Some(JsExpr::new(
74 JsExprData::Identifier(temporary.get(&ctx)),
75 loc,
76 None,
77 ))
78}
crates/js_ast/convert/hoist_block_to_js_block.rs created+68
......@@ -0,0 +1,68 @@
1use crate::js_ast::*;
2use ast::*;
3use tokenizer::Loc;
4
5use super::*;
6
7/// This block hoist is used when we actually need to create a JsBlock, which
8/// is needed if there is an invalid expression within a branch of code.
9///
10/// For example, `if` has two branches, the 'yes' and 'no' paths.
11/// When we cannot lower it as a ternary, we have to create two JsBlocks
12///
13/// The `target` param is a shared temporary variable that will be assigned
14/// to the result of the block.
15///
16/// Since blocks can be void or noreturn, this target is not always
17/// assigned to. The `did_assign` flag is used to indicate that.
18///
19/// For expressions that do not have branches, use `hoist_block_to_expr`
20///
21/// TODO(@paperdave): move `did_assign` into the return value
22pub fn hoist_block_to_js_block(
23 ctx: &mut BlockConvertContext,
24 block: Block,
25 loc: Loc,
26 target: &TempId,
27 did_assign: &mut bool,
28) -> JsBlock {
29 // A non-block will become a block with a single expression:
30 // _temp = expr;
31 //
32 // We will also apply this transformation to blocks that are valid
33 // expressions, because that is more concise.
34 if block.is_valid_js_expression() {
35 return JsBlock::new(
36 vec![new_js_assignment_statement(
37 convert_valid_block(ctx, block, loc.clone()),
38 target.get(ctx),
39 loc.clone(),
40 )],
41 loc,
42 );
43 }
44
45 let mut child = ctx.new_child();
46
47 for statement in block.statements {
48 if let None = process_statement(&mut child, statement) {
49 return child.into();
50 };
51 }
52
53 // If the last statement is an expression, we need
54 // to assign to the temporary variable.
55 match child.take_last_statement_if_expr() {
56 Some(expr) => {
57 *did_assign = true;
58 child.add_statement(new_js_assignment_statement(
59 expr,
60 target.get(ctx),
61 loc.clone(),
62 ));
63 }
64 _ => {}
65 }
66
67 child.into()
68}
crates/js_ast/convert/hoist_expr.rs created+132
......@@ -0,0 +1,132 @@
1use super::convert_valid_expr;
2use super::hoist_block_to_expr;
3use super::hoist_if_expr;
4use super::is_valid_js_expression::*;
5use super::BlockConvertContext;
6// use super::HasSideEffects;
7use crate::js_ast::*;
8use crate::util::is_js_identifier;
9use ast::*;
10
11/// Pushes a decl with a generated identifier to the hoisted result
12/// and returns an identifier expression for it.
13pub fn hoist_expr_if_needed(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
14 // if !expr.has_side_effects() {
15 // return Some(ctx.hoist_expression_contents(expr)?);
16 // }
17
18 let init = hoist_expr_contents(ctx, expr)?;
19
20 let id = ctx.generate_temp_id("_");
21 let loc = init.loc.clone();
22
23 ctx.add_statement(JsStatement::new(
24 JsStatementData::Declaration(JSDeclaration {
25 name: id.get(&ctx),
26 init: Some(init),
27 is_constant: true,
28 }),
29 loc.clone(),
30 None,
31 ));
32
33 Some(JsExpr::new(JsExprData::Identifier(id.get(&ctx)), loc, None))
34}
35
36/// Returning `None` from this means that the expression is noreturn, and that
37/// the rest of the statement never occurs and can be stripped.
38pub fn hoist_expr_contents(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
39 if expr.is_valid_js_expression() {
40 return Some(convert_valid_expr(ctx, expr));
41 }
42
43 match expr.data {
44 ExprData::Binary { op, left, right } => {
45 let left = hoist_expr_if_needed(ctx, *left)?;
46 let right = hoist_expr_if_needed(ctx, *right)?;
47
48 Some(JsExpr::new(
49 JsExprData::Binary {
50 op,
51 left: Box::new(left),
52 right: Box::new(right),
53 },
54 expr.loc,
55 expr.comment,
56 ))
57 }
58
59 ExprData::Unary { op, value } => {
60 let value = hoist_expr_if_needed(ctx, *value)?;
61 Some(JsExpr::new(
62 JsExprData::Unary {
63 op,
64 value: Box::new(value),
65 },
66 expr.loc,
67 expr.comment,
68 ))
69 }
70
71 ExprData::Field { base, field } => {
72 let base = hoist_expr_if_needed(ctx, *base)?;
73 let field = hoist_expr_if_needed(ctx, *field)?;
74
75 Some(JsExpr::new(
76 match field.data {
77 JsExprData::String(name) if is_js_identifier(name.as_str()) => {
78 JsExprData::Dot {
79 base: Box::new(base),
80 name,
81 }
82 }
83 _ => JsExprData::Index {
84 base: Box::new(base),
85 index: Box::new(field),
86 },
87 },
88 expr.loc,
89 expr.comment,
90 ))
91 }
92
93 ExprData::Block(block) => hoist_block_to_expr(ctx, block, expr.loc, expr.comment),
94
95 ExprData::If(_) => hoist_if_expr(ctx, expr),
96
97 ExprData::Return(expr) => {
98 let loc = expr.loc.clone();
99 let return_value = hoist_expr_if_needed(ctx, *expr)?;
100 ctx.add_statement(JsStatement::new(
101 JsStatementData::Return(return_value),
102 loc,
103 None,
104 ));
105 None
106 }
107
108 ExprData::Call { base, arguments } => {
109 let base = hoist_expr_if_needed(ctx, *base)?;
110 let arguments = arguments
111 .into_iter()
112 .map(|x| hoist_expr_if_needed(ctx, x))
113 .collect::<Option<Vec<_>>>()?;
114
115 Some(JsExpr::new(
116 JsExprData::Call {
117 base: Box::new(base),
118 arguments,
119 },
120 expr.loc,
121 expr.comment,
122 ))
123 }
124
125 ExprData::Fn { .. }
126 | ExprData::Builtin { .. }
127 | ExprData::Literal(_)
128 | ExprData::Identifier(_) => {
129 unreachable!("attempt to hoist an expression that does not require it")
130 }
131 }
132}
crates/js_ast/convert/hoist_if.rs created+113
......@@ -0,0 +1,113 @@
1use super::convert_valid_expr;
2use super::hoist_block_to_js_block;
3use super::hoist_expr_if_needed;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use super::TempId;
7use crate::js_ast::*;
8use ast::*;
9use tokenizer::Loc;
10
11pub fn hoist_if_expr(ctx: &mut BlockConvertContext, expr: Expr) -> Option<JsExpr> {
12 let data = match expr.data {
13 ExprData::If(data) => data,
14 _ => unreachable!("hoist_if_expr called on non-if expression"),
15 };
16
17 let temporary = ctx.generate_temp_id("_if");
18
19 let decl = ctx.add_statement(JsStatement::new(
20 JsStatementData::Declaration(JSDeclaration {
21 name: temporary.get(&ctx),
22 init: None,
23 is_constant: false,
24 }),
25 expr.loc.clone(),
26 None,
27 ));
28
29 let condition = hoist_expr_if_needed(ctx, *data.condition)?;
30 let mut did_assign = false;
31
32 let js_if = hoist_if_branch(
33 ctx,
34 data.yes,
35 data.no,
36 condition,
37 &mut did_assign,
38 &temporary,
39 );
40
41 ctx.add_statement(JsStatement::new(
42 JsStatementData::If(js_if),
43 expr.loc.clone(),
44 None,
45 ));
46
47 if did_assign {
48 Some(JsExpr::new(
49 JsExprData::Identifier(temporary.get(&ctx)),
50 expr.loc,
51 expr.comment,
52 ))
53 } else {
54 decl.delete(ctx);
55 Some(JsExpr::new(JsExprData::Null, expr.loc, expr.comment))
56 }
57}
58
59pub fn hoist_if_branch(
60 ctx: &mut BlockConvertContext,
61 yes: BlockWithLoc,
62 no: Option<BlockOrIf>,
63 condition: JsExpr,
64 did_assign: &mut bool,
65 temporary: &TempId,
66) -> JsIf {
67 let yes = hoist_block_to_js_block(ctx, yes.block, yes.loc, &temporary, did_assign);
68
69 let no = match no {
70 Some(BlockOrIf::Block(no)) => Some(JsElse::Block(hoist_block_to_js_block(
71 ctx, no.block, no.loc, &temporary, did_assign,
72 ))),
73 Some(BlockOrIf::If(else_if)) => {
74 // If the condition of this block has a side effect, we need to
75 // emit a block so that we can assign to the temporary variable.
76 //
77 // Otherwise, we can just emit the condition inline
78 if else_if.condition.is_valid_js_expression() {
79 let condition = convert_valid_expr(ctx, *else_if.condition.clone());
80 Some(JsElse::If(Box::new(hoist_if_branch(
81 ctx,
82 else_if.yes,
83 else_if.no,
84 condition,
85 did_assign,
86 temporary,
87 ))))
88 } else {
89 let mut temp_block = ctx.new_child();
90 let condition =
91 hoist_expr_if_needed(&mut temp_block, *else_if.condition.clone()).unwrap();
92
93 temp_block.add_statement(JsStatement::new(
94 JsStatementData::If(hoist_if_branch(
95 ctx,
96 else_if.yes,
97 else_if.no,
98 condition,
99 did_assign,
100 temporary,
101 )),
102 Loc::EMPTY,
103 None,
104 ));
105
106 Some(JsElse::Block(temp_block.into()))
107 }
108 }
109 None => None,
110 };
111
112 JsIf { condition, yes, no }
113}
crates/js_ast/convert/is_valid_js_expression.rs created+91
......@@ -0,0 +1,91 @@
1use ast::*;
2
3/// "Valid" in this context means that the expression does not contain JS statements
4/// Meaning conversion is a direct map from a blight as node into a JS one
5///
6/// A valid expression should not go through any hoist logic, but if an expression
7/// isn't valid, then it means that it must be split into multiple JS statements
8/// inside of a block.
9pub trait IsValidJSExpression {
10 /// Can this be lowered to a JS ast without hoisting items
11 /// ex: a `return` expression is not a valid JS expression
12 fn is_valid_js_expression(&self) -> bool;
13}
14
15impl IsValidJSExpression for ExprData {
16 fn is_valid_js_expression(&self) -> bool {
17 match self {
18 // A block containing just expressions can be an expression
19 ExprData::Block(block) => block.is_valid_js_expression(),
20 ExprData::If(expr) => expr.is_valid_js_expression(),
21 ExprData::Return(_) => false,
22 ExprData::Builtin { id, .. } => match id {
23 BuiltinId::Raw => true,
24 },
25 ExprData::Binary { left, right, .. } => {
26 left.is_valid_js_expression() && right.is_valid_js_expression()
27 }
28 ExprData::Unary { value, .. } => value.is_valid_js_expression(),
29 ExprData::Fn { .. } => true,
30 ExprData::Call { base, arguments } => {
31 base.is_valid_js_expression()
32 && arguments
33 .iter()
34 .all(|argument| argument.is_valid_js_expression())
35 }
36 ExprData::Field { base, field } => {
37 base.is_valid_js_expression() && field.is_valid_js_expression()
38 }
39 ExprData::Literal(_) | ExprData::Identifier(_) => true,
40 }
41 }
42}
43
44impl IsValidJSExpression for Block {
45 fn is_valid_js_expression(&self) -> bool {
46 self.statements
47 .iter()
48 .all(|statement| statement.is_valid_js_expression())
49 }
50}
51
52impl IsValidJSExpression for Expr {
53 fn is_valid_js_expression(&self) -> bool {
54 self.data.is_valid_js_expression()
55 }
56}
57
58impl IsValidJSExpression for If {
59 fn is_valid_js_expression(&self) -> bool {
60 self.condition.is_valid_js_expression()
61 && self.yes.is_valid_js_expression()
62 && match &self.no {
63 Some(no) => no.is_valid_js_expression(),
64 None => true,
65 }
66 }
67}
68
69impl IsValidJSExpression for BlockOrIf {
70 fn is_valid_js_expression(&self) -> bool {
71 match self {
72 BlockOrIf::Block(block) => block.is_valid_js_expression(),
73 BlockOrIf::If(if_) => if_.is_valid_js_expression(),
74 }
75 }
76}
77
78impl IsValidJSExpression for BlockWithLoc {
79 fn is_valid_js_expression(&self) -> bool {
80 self.block.is_valid_js_expression()
81 }
82}
83
84impl IsValidJSExpression for Statement {
85 fn is_valid_js_expression(&self) -> bool {
86 match self {
87 Statement::Expr(expr) => expr.data.is_valid_js_expression(),
88 _ => false,
89 }
90 }
91}
crates/js_ast/convert/mod.rs created+42
......@@ -0,0 +1,42 @@
1mod block_convert_context;
2mod convert_valid_block;
3mod convert_valid_expr;
4mod convert_valid_if;
5mod convert_valid_statement;
6mod hoist_block_to_expr;
7mod hoist_block_to_js_block;
8mod hoist_expr;
9mod hoist_if;
10mod is_valid_js_expression;
11mod process_function;
12mod process_statement;
13mod side_effects;
14mod tree_shake_statement_list;
15
16pub use block_convert_context::*;
17pub use convert_valid_block::*;
18pub use convert_valid_expr::*;
19pub use convert_valid_if::*;
20pub use convert_valid_statement::*;
21pub use hoist_block_to_expr::*;
22pub use hoist_block_to_js_block::*;
23pub use hoist_expr::*;
24pub use hoist_if::*;
25pub use is_valid_js_expression::*;
26pub use process_function::*;
27pub use process_statement::*;
28// pub use side_effects::*;
29pub use tree_shake_statement_list::*;
30
31use crate::js_ast::JsModule;
32use ast::Module;
33
34pub fn blight_to_js(blight: Module) -> JsModule {
35 let mut root_context = BlockConvertContext::new();
36
37 for statement in blight.statements {
38 process_top_level_statement(&mut root_context, statement).unwrap();
39 }
40
41 root_context.into()
42}
crates/js_ast/convert/process_function.rs created+63
......@@ -0,0 +1,63 @@
1use super::convert_valid_expr;
2use super::hoist_expr_if_needed;
3use super::process_statement;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8use tokenizer::Loc;
9
10pub fn process_function_to_arrow(
11 ctx: &mut BlockConvertContext,
12 body: Expr,
13 params: Vec<Param>,
14 loc: Loc,
15 comment: Option<Comment>,
16) -> JsExpr {
17 // A block that is a valid expression will just become
18 // that expression. We can convert multiple expressions
19 // into a comma expression.
20 let body = if body.is_valid_js_expression() {
21 JsArrowBody::Expr(convert_valid_expr(ctx, body))
22 } else {
23 let mut child = ctx.new_child();
24
25 match body.data {
26 ExprData::Block(block) => {
27 for statement in block.statements {
28 if let None = process_statement(&mut child, statement) {
29 break;
30 };
31 }
32
33 if let Some(expr) = child.take_last_statement_if_expr() {
34 child.add_statement(JsStatement::new(
35 JsStatementData::Return(expr),
36 loc.clone(),
37 None,
38 ));
39 }
40 }
41 _ => {
42 if let Some(expr) = hoist_expr_if_needed(&mut child, body) {
43 child.add_statement(JsStatement::new(
44 JsStatementData::Return(expr),
45 loc.clone(),
46 None,
47 ));
48 }
49 }
50 }
51
52 JsArrowBody::Block(child.into())
53 };
54
55 JsExpr::new(
56 JsExprData::ArrowFunction {
57 params: params.into_iter().map(|x| x.name).collect(),
58 body: Box::new(body),
59 },
60 loc.clone(),
61 comment.map(|x| x.into()),
62 )
63}
crates/js_ast/convert/process_statement.rs created+95
......@@ -0,0 +1,95 @@
1use super::convert_valid_statement;
2use super::hoist_expr_contents;
3use super::process_function_to_arrow;
4use super::BlockConvertContext;
5use super::IsValidJSExpression;
6use crate::js_ast::*;
7use ast::*;
8
9pub fn process_statement(ctx: &mut BlockConvertContext, statement: Statement) -> Option<()> {
10 if statement.is_valid_js_expression() {
11 let result = convert_valid_statement(ctx, statement);
12 ctx.add_statement(result);
13 return Some(());
14 }
15
16 match statement {
17 Statement::VarDecl(decl) => {
18 // visit init before name, this way allows for:
19 //
20 // let a = a;
21 //
22 // and `a` will be looked up before it is re-declared.
23 // we will also return in the noreturn case
24 let init = hoist_expr_contents(ctx, decl.init)?;
25
26 let name = ctx.declare_identifier(decl.name);
27
28 ctx.add_statement(JsStatement::new(
29 JsStatementData::Declaration(JSDeclaration {
30 name: name.get(&ctx),
31 init: Some(init),
32 is_constant: decl.is_constant,
33 }),
34 decl.loc,
35 decl.comment.map(|x| x.into()),
36 ));
37
38 Some(())
39 }
40 Statement::Expr(expr) => {
41 if let Some(expr) = hoist_expr_contents(ctx, expr) {
42 let loc = expr.loc.clone();
43 ctx.add_statement(JsStatement::new(
44 JsStatementData::Expression(expr),
45 loc,
46 None,
47 ));
48 Some(())
49 } else {
50 None
51 }
52 }
53 Statement::FnDecl(FnDecl {
54 name,
55 params,
56 body,
57 loc,
58 comment,
59 ..
60 }) => {
61 let name = ctx.declare_identifier(name);
62
63 let processed = Some(process_function_to_arrow(
64 ctx,
65 body,
66 params,
67 loc.clone(),
68 None,
69 ));
70
71 ctx.add_statement(JsStatement::new(
72 JsStatementData::Declaration(JSDeclaration {
73 name: name.get(&ctx),
74 init: processed,
75 is_constant: true,
76 }),
77 loc,
78 comment.map(|x| x.into()),
79 ));
80
81 Some(())
82 }
83 Statement::Comment(_) => Some(()),
84 }
85}
86
87pub fn process_top_level_statement(
88 ctx: &mut BlockConvertContext,
89 statement: TopLevelStatement,
90) -> Option<()> {
91 match statement.to_statement() {
92 Some(stmt) => process_statement(ctx, stmt),
93 None => todo!("process_top_level_statement"),
94 }
95}
crates/js_ast/convert/side_effects.rs created+91
......@@ -0,0 +1,91 @@
1use ast::*;
2
3pub trait HasSideEffects {
4 /// Can this item be removed from the AST without changing the program's behavior?
5 fn has_side_effects(&self) -> bool;
6}
7
8impl HasSideEffects for ExprData {
9 fn has_side_effects(&self) -> bool {
10 match self {
11 ExprData::Literal(_) => false,
12 ExprData::Identifier(_) => false,
13
14 ExprData::Block(block) => block.has_side_effects(),
15 ExprData::Binary { left, right, .. } => {
16 left.has_side_effects() || right.has_side_effects()
17 }
18 ExprData::Unary { value, .. } => value.has_side_effects(),
19 ExprData::If(ifexpr) => ifexpr.has_side_effects(),
20
21 // note: a function *declaration* has no side effects, but the call *can*
22 ExprData::Fn { .. } => false,
23
24 // TODO(@paperdave): implement detection of pure functions
25 ExprData::Call { base, arguments } => {
26 base.has_side_effects() || arguments.iter().any(|x| x.has_side_effects())
27 }
28
29 // note: we assume field access has side effects, ie: a getter doesn't throw
30 ExprData::Field { base, field } => base.has_side_effects() || field.has_side_effects(),
31
32 // note: we assume the code in @raw always has side effects
33 ExprData::Builtin { id, .. } => match id {
34 BuiltinId::Raw => true,
35 },
36
37 ExprData::Return(_) => true,
38 }
39 }
40}
41
42impl HasSideEffects for Statement {
43 fn has_side_effects(&self) -> bool {
44 match self {
45 Statement::Expr(expr) => expr.has_side_effects(),
46 Statement::VarDecl(decl) => decl.init.has_side_effects(),
47 Statement::Comment(_) => false,
48 Statement::FnDecl(_) => false,
49 }
50 }
51}
52
53impl HasSideEffects for Block {
54 fn has_side_effects(&self) -> bool {
55 self.statements
56 .iter()
57 .any(|statement| statement.has_side_effects())
58 }
59}
60
61impl HasSideEffects for Expr {
62 fn has_side_effects(&self) -> bool {
63 self.data.has_side_effects()
64 }
65}
66
67impl HasSideEffects for If {
68 fn has_side_effects(&self) -> bool {
69 self.condition.has_side_effects()
70 || self.yes.has_side_effects()
71 || match &self.no {
72 Some(no) => no.has_side_effects(),
73 None => false,
74 }
75 }
76}
77
78impl HasSideEffects for BlockOrIf {
79 fn has_side_effects(&self) -> bool {
80 match self {
81 BlockOrIf::Block(block) => block.has_side_effects(),
82 BlockOrIf::If(if_) => if_.has_side_effects(),
83 }
84 }
85}
86
87impl HasSideEffects for BlockWithLoc {
88 fn has_side_effects(&self) -> bool {
89 self.block.has_side_effects()
90 }
91}
crates/js_ast/convert/tree_shake_statement_list.rs created+31
......@@ -0,0 +1,31 @@
1use crate::js_ast::*;
2
3/// Removes statements that are not needed from a JS block. Very limited.
4///
5/// BlockConvertContext will automatically execute this when converting
6/// itself into a Statement list or other data structure.
7///
8/// What this does:
9/// - Remove discarded literals `null` -> ``
10pub fn tree_shake_statement_list(list: &mut Vec<JsStatement>) {
11 let mut set_i = 0;
12 for i in 0..list.len() {
13 let stmt = list.get(i).unwrap();
14 let should_keep = match &stmt.data {
15 JsStatementData::Expression(expr) => !expr.is_literal(),
16 JsStatementData::Return(_) => true,
17 JsStatementData::Export(_) => true,
18 JsStatementData::Declaration(_) => true,
19 JsStatementData::Function(_) => true,
20 JsStatementData::If(_) => true,
21 };
22
23 if should_keep {
24 if set_i != i {
25 list.swap(i, set_i)
26 }
27 set_i += 1;
28 }
29 }
30 list.truncate(set_i)
31}
crates/js_ast/convert_valid_expr.rs deleted-204
......@@ -1,204 +0,0 @@
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 deleted-90
......@@ -1,90 +0,0 @@
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 deleted-638
......@@ -1,638 +0,0 @@
1use std::collections::{HashMap, HashSet};
2use std::vec;
3
4use ast::{BinaryOperator, BlockOrIf, BlockWithLoc, Expr, Statement};
5use has_side_effects::HasSideEffects;
6use tokenizer::Loc;
7
8use crate::*;
9
10pub struct HoistContext {
11 pub statements: Vec<JsStatement>,
12 pub ids: HashSet<String>,
13 next_id: u32,
14 scopes: Vec<HoistScope>,
15 has_last_statement: bool,
16}
17
18struct HoistScope {
19 renames: HashMap<String, String>,
20}
21
22pub struct TempId {
23 unmapped_name: String,
24}
25
26impl TempId {
27 pub fn get(&self, hoist_context: &HoistContext) -> String {
28 hoist_context.remap_identifier(&self.unmapped_name)
29 }
30}
31
32impl HoistContext {
33 pub fn new() -> Self {
34 Self {
35 statements: Vec::new(),
36 ids: HashSet::new(),
37 next_id: 0,
38 scopes: vec![HoistScope {
39 renames: HashMap::new(),
40 }],
41 has_last_statement: false,
42 }
43 }
44
45 pub fn new_child(&self) -> Self {
46 Self {
47 statements: Vec::new(),
48 ids: self.ids.clone(),
49 next_id: 0,
50 scopes: vec![HoistScope {
51 renames: HashMap::new(),
52 }],
53 has_last_statement: false,
54 }
55 }
56
57 /// A virtual scope is a scope that exists in blight syntax
58 /// but may or may not exist in blight. With these it is important to
59 pub fn push_virtual_scope(&mut self) {
60 self.scopes.push(HoistScope {
61 renames: HashMap::new(),
62 });
63 }
64
65 /// See `push_virtual_scope` for more information on what this is.
66 pub fn pop_virtual_scope(&mut self) {
67 self.scopes.pop();
68 }
69
70 pub fn generate_temp_id(&mut self, basename: &str) -> TempId {
71 self.next_id += 1;
72 loop {
73 let name = format!("{}{}", basename, self.next_id);
74 if !self.ids.contains(&name) {
75 self.ids.insert(name.clone());
76 return TempId {
77 unmapped_name: name,
78 };
79 }
80 self.next_id += 1;
81 }
82 }
83
84 /// This does some extra optimizations:
85 /// - `let a = b;` will not be emitted but instad write a variable alias
86 pub fn add_statement(&mut self, statement: JsStatement) {
87 match statement.data {
88 JsStatementData::Declaration(JSDeclaration {
89 name,
90 init:
91 Some(JsExpr {
92 data: JsExprData::Identifier(init_name),
93 ..
94 }),
95 is_constant: true,
96 }) => {
97 self.scopes
98 .last_mut()
99 .unwrap()
100 .renames
101 .insert(name, init_name);
102 self.has_last_statement = false;
103 return;
104 }
105 _ => {}
106 }
107 self.statements.push(statement);
108 self.has_last_statement = true;
109 }
110
111 /// Get the last statement if it exists.
112 /// If the statement was optimized away, this returns None
113 pub fn last_statement(&self) -> Option<&JsStatement> {
114 if self.has_last_statement {
115 Some(self.statements.last().unwrap())
116 } else {
117 None
118 }
119 }
120
121 pub fn generate_id(&mut self, basename: &String) -> TempId {
122 let mut id = 0u32;
123 loop {
124 let name = match id {
125 0 => basename.clone(),
126 _ => format!("{}{}", basename, id),
127 };
128 if !self.ids.contains(&name) {
129 self.ids.insert(name.clone());
130 return TempId {
131 unmapped_name: name,
132 };
133 }
134 id += 1;
135 }
136 }
137
138 /// Given a blight identifier, remap it to a new identifier.
139 pub fn remap_identifier(&self, name: &String) -> String {
140 for scope in self.scopes.iter().rev() {
141 if let Some(remapped) = scope.renames.get(name) {
142 return remapped.clone();
143 }
144 }
145 name.clone()
146 }
147
148 /// Declare an identifier in the current scope.
149 pub fn declare_identifier(&mut self, name: String) -> TempId {
150 let remapped = self.generate_id(&name);
151 let scope = self.scopes.last_mut().unwrap();
152 self.ids.insert(remapped.unmapped_name.clone());
153 scope.renames.insert(name, remapped.unmapped_name.clone());
154 remapped
155 }
156
157 pub fn process_statement(&mut self, statement: Statement) -> Option<()> {
158 if statement.is_valid_js_expression() {
159 let result = convert_valid_statement(self, statement);
160 self.add_statement(result);
161 return Some(());
162 }
163
164 match statement {
165 Statement::Decl(decl) => {
166 // visit init before name, this way allows for:
167 //
168 // let a = a;
169 //
170 // and `a` will be looked up before it is re-declared.
171 if let Some(init) = self.hoist_expression_contents(decl.init) {
172 let name = self.declare_identifier(decl.name);
173
174 self.add_statement(JsStatement::new(
175 JsStatementData::Declaration(crate::JSDeclaration {
176 name: name.get(&self),
177 init: Some(init),
178 is_constant: decl.is_constant,
179 }),
180 decl.loc,
181 decl.comment.map(|x| x.into()),
182 ));
183 Some(())
184 } else {
185 None
186 }
187 }
188 Statement::Expr(expr) => {
189 if let Some(expr) = self.hoist_expression_contents(expr) {
190 let loc = expr.loc.clone();
191 self.add_statement(JsStatement::new(
192 JsStatementData::Expression(expr),
193 loc,
194 None,
195 ));
196 Some(())
197 } else {
198 None
199 }
200 }
201 Statement::Comment(_) => Some(()),
202 }
203 }
204
205 /// Pushes a decl with a generated identifier to the hoisted result
206 /// and returns an identifier expression for it.
207 pub fn hoist_expression_to_temporary(&mut self, expr: Expr) -> Option<JsExpr> {
208 // TODO
209 if !expr.has_side_effects() {
210 return Some(self.hoist_expression_contents(expr)?);
211 }
212
213 let init = self.hoist_expression_contents(expr)?;
214
215 let id = self.generate_temp_id("_temp");
216 let loc = init.loc.clone();
217
218 self.add_statement(JsStatement::new(
219 JsStatementData::Declaration(JSDeclaration {
220 name: id.get(&self),
221 init: Some(init),
222 is_constant: true,
223 }),
224 loc.clone(),
225 None,
226 ));
227
228 Some(JsExpr::new(
229 JsExprData::Identifier(id.get(&self)),
230 loc,
231 None,
232 ))
233 }
234
235 // Returning `None` from this means that the expression is noreturn, and that
236 // the rest of the statement never occurs and can be stripped.
237 pub fn hoist_expression_contents(&mut self, expr: Expr) -> Option<JsExpr> {
238 if expr.is_valid_js_expression() {
239 return Some(convert_valid_expr(self, expr));
240 }
241
242 match expr.data {
243 ast::ExprData::Binary { op, left, right } => {
244 let left = self.hoist_expression_to_temporary(*left)?;
245 let right = self.hoist_expression_to_temporary(*right)?;
246
247 Some(JsExpr::new(
248 JsExprData::Binary {
249 op,
250 left: Box::new(left),
251 right: Box::new(right),
252 },
253 expr.loc,
254 expr.comment,
255 ))
256 }
257
258 ast::ExprData::Unary { op, value } => {
259 let value = self.hoist_expression_to_temporary(*value)?;
260 Some(JsExpr::new(
261 JsExprData::Unary {
262 op,
263 value: Box::new(value),
264 },
265 expr.loc,
266 expr.comment,
267 ))
268 }
269
270 ast::ExprData::Field { base, field } => {
271 let base = self.hoist_expression_to_temporary(*base)?;
272 let field = self.hoist_expression_to_temporary(*field)?;
273
274 Some(JsExpr::new(
275 match field.data {
276 JsExprData::String(name) if is_js_identifier(name.as_str()) => {
277 JsExprData::Dot {
278 base: Box::new(base),
279 name,
280 }
281 }
282 _ => JsExprData::Index {
283 base: Box::new(base),
284 index: Box::new(field),
285 },
286 },
287 expr.loc,
288 expr.comment,
289 ))
290 }
291
292 ast::ExprData::Block(block) => {
293 let temporary = self.generate_temp_id("_block");
294 let result = self.hoist_block_to_js_block_in_same_context(BlockWithLoc {
295 block,
296 loc: expr.loc.clone(),
297 });
298
299 self.add_statement(JsStatement::new(
300 JsStatementData::Declaration(crate::JSDeclaration {
301 name: temporary.get(&self),
302 init: Some(result),
303 is_constant: false,
304 }),
305 expr.loc.clone(),
306 None,
307 ));
308
309 Some(JsExpr::new(
310 JsExprData::Identifier(temporary.get(&self)),
311 expr.loc,
312 expr.comment,
313 ))
314 }
315
316 ast::ExprData::If(data) => {
317 let temporary = self.generate_temp_id("_if");
318
319 let decl_index = self.statements.len();
320 self.add_statement(JsStatement::new(
321 JsStatementData::Declaration(crate::JSDeclaration {
322 name: temporary.get(&self),
323 init: None,
324 is_constant: false,
325 }),
326 expr.loc.clone(),
327 None,
328 ));
329
330 let condition = self.hoist_expression_to_temporary(*data.condition)?;
331 let mut did_assign = false;
332
333 let js_if =
334 self.hoist_js_if(data.yes, data.no, condition, &mut did_assign, &temporary);
335
336 self.add_statement(JsStatement::new(
337 JsStatementData::If(js_if),
338 expr.loc.clone(),
339 None,
340 ));
341
342 if did_assign {
343 Some(JsExpr::new(
344 JsExprData::Identifier(temporary.get(&self)),
345 expr.loc,
346 expr.comment,
347 ))
348 } else {
349 self.statements.remove(decl_index);
350 Some(JsExpr::new(JsExprData::Null, expr.loc, expr.comment))
351 }
352 }
353
354 ast::ExprData::Return(expr) => {
355 let loc = expr.loc.clone();
356 let return_value = self.hoist_expression_to_temporary(*expr)?;
357 self.add_statement(JsStatement::new(
358 JsStatementData::Return(return_value),
359 loc,
360 None,
361 ));
362 None
363 }
364
365 ast::ExprData::Call { base, arguments } => {
366 let base = self.hoist_expression_to_temporary(*base)?;
367 let arguments = arguments
368 .into_iter()
369 .map(|x| self.hoist_expression_to_temporary(x))
370 .collect::<Option<Vec<_>>>()?;
371
372 Some(JsExpr::new(
373 JsExprData::Call {
374 base: Box::new(base),
375 arguments,
376 },
377 expr.loc,
378 expr.comment,
379 ))
380 }
381
382 ast::ExprData::Fn { .. }
383 | ast::ExprData::Builtin { .. }
384 | ast::ExprData::Literal(_)
385 | ast::ExprData::Identifier(_) => {
386 unreachable!("attempt to hoist an expression that does not require it")
387 }
388 }
389 }
390
391 pub fn hoist_block_to_js_block(
392 &mut self,
393 b: BlockWithLoc,
394 temporary: &String,
395 did_assign: &mut bool,
396 ) -> JsBlock {
397 let loc = b.loc.clone();
398
399 // A non-block will become a block with a single expression:
400 // _temp = expr;
401 //
402 // We will also apply this transformation to blocks that are valid
403 // expressions, because that is more concise.
404 if b.is_valid_js_expression() {
405 return JsBlock::new(
406 vec![new_js_assignment_statement(
407 convert_valid_block(self, b),
408 temporary.clone(),
409 loc.clone(),
410 )],
411 loc,
412 );
413 }
414
415 let mut child = self.new_child();
416
417 for statement in b.block.statements {
418 if let None = child.process_statement(statement) {
419 tree_shake_statement_list(&mut child.statements);
420 return JsBlock::new(child.statements, loc);
421 };
422 }
423
424 // If the last statement is an expression, we need
425 // to assign to the temporary variable.
426 match child.statements.last().map(|x| &x.data) {
427 Some(JsStatementData::Expression(_)) => {
428 *did_assign = true;
429
430 let last = child.statements.pop().unwrap();
431 let value = match last {
432 JsStatement {
433 data: JsStatementData::Expression(expr),
434 ..
435 } => expr,
436 _ => {
437 self.statements.push(last);
438 JsExpr::new(JsExprData::Null, loc.clone(), None)
439 }
440 };
441 child.statements.push(new_js_assignment_statement(
442 value,
443 temporary.clone(),
444 loc.clone(),
445 ));
446 }
447 _ => {}
448 }
449
450 tree_shake_statement_list(&mut child.statements);
451
452 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 tree_shake_statement_list(&mut child.statements);
498
499 JsArrowBody::Block(JsBlock::new(child.statements, loc))
500 }
501
502 pub fn hoist_block_to_js_block_in_same_context(&mut self, b: BlockWithLoc) -> JsExpr {
503 let loc = b.loc.clone();
504 self.push_virtual_scope();
505
506 debug_assert!(!b.block.is_valid_js_expression());
507
508 for statement in b.block.statements {
509 self.process_statement(statement);
510 }
511
512 self.pop_virtual_scope();
513
514 // If the last statement is an expression, we need
515 // to assign to the temporary variable.
516 match self.last_statement().map(|x| &x.data) {
517 Some(JsStatementData::Expression(_)) if b.block.has_implicit_return => {
518 match self.statements.pop().unwrap().data {
519 JsStatementData::Expression(expr) => expr,
520 _ => unreachable!(),
521 }
522 }
523 _ => JsExpr::new(JsExprData::Null, loc.clone(), None),
524 }
525 }
526
527 pub fn hoist_js_if(
528 &mut self,
529 yes: BlockWithLoc,
530 no: Option<BlockOrIf>,
531 condition: JsExpr,
532 did_assign: &mut bool,
533 temporary: &TempId,
534 ) -> JsIf {
535 let yes = self.hoist_block_to_js_block(yes, &temporary.get(&self), did_assign);
536
537 let no = match no {
538 Some(BlockOrIf::Block(no)) => Some(JsElse::Block(self.hoist_block_to_js_block(
539 no,
540 &temporary.get(&self),
541 did_assign,
542 ))),
543 Some(BlockOrIf::If(else_if)) => {
544 // If the condition of this block has a side effect, we need to
545 // emit a block so that we can assign to the temporary variable.
546 //
547 // Otherwise, we can just emit the condition inline
548 if else_if.condition.is_valid_js_expression() {
549 let condition = convert_valid_expr(self, *else_if.condition.clone());
550 Some(JsElse::If(Box::new(self.hoist_js_if(
551 else_if.yes,
552 else_if.no,
553 condition,
554 did_assign,
555 temporary,
556 ))))
557 } else {
558 let mut temp_block = self.new_child();
559 let condition = temp_block
560 .hoist_expression_to_temporary(*else_if.condition.clone())
561 .unwrap();
562
563 temp_block.add_statement(JsStatement::new(
564 JsStatementData::If(self.hoist_js_if(
565 else_if.yes,
566 else_if.no,
567 condition,
568 did_assign,
569 temporary,
570 )),
571 Loc::EMPTY,
572 None,
573 ));
574
575 tree_shake_statement_list(&mut temp_block.statements);
576
577 Some(JsElse::Block(JsBlock::new(
578 temp_block.statements,
579 Loc::EMPTY,
580 )))
581 }
582 }
583 None => None,
584 };
585
586 JsIf { condition, yes, no }
587 }
588}
589
590pub fn new_js_assignment_statement(expr: JsExpr, target: String, loc: Loc) -> JsStatement {
591 JsStatement::new(
592 JsStatementData::Expression(new_js_assignment_expr(expr, target, loc.clone())),
593 loc,
594 None,
595 )
596}
597
598pub fn new_js_assignment_expr(expr: JsExpr, target: String, loc: Loc) -> JsExpr {
599 JsExpr::new(
600 JsExprData::Binary {
601 op: BinaryOperator::Assign,
602 left: Box::new(JsExpr::new(
603 JsExprData::Identifier(target),
604 loc.clone(),
605 None,
606 )),
607 right: Box::new(expr),
608 },
609 loc,
610 None,
611 )
612}
613
614/// Remove
615/// - Discarded literals
616pub fn tree_shake_statement_list(list: &mut Vec<JsStatement>) {
617 let mut set_i = 0;
618 for i in 0..list.len() {
619 let stmt = list.get(i).unwrap();
620 let should_keep = match &stmt.data {
621 JsStatementData::Expression(expr) => !expr.is_literal(),
622 JsStatementData::Return(_) => true,
623 JsStatementData::Export(_) => true,
624 JsStatementData::Declaration(_) => true,
625 JsStatementData::Function(_) => true,
626 JsStatementData::If(_) => true,
627 //
628 };
629
630 if should_keep {
631 if set_i != i {
632 list.swap(i, set_i)
633 }
634 set_i += 1;
635 }
636 }
637 list.truncate(set_i)
638}
crates/js_ast/is_valid_js_expression.rs deleted-85
......@@ -1,85 +0,0 @@
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/lib.rs+2-23
......@@ -1,28 +1,7 @@
1mod convert_valid_expr;
2mod has_side_effects;
3mod hoist;
4mod is_valid_js_expression;
1mod convert;
52mod js_ast;
63mod util;
74
8use ast::Module;
9
10pub use convert_valid_expr::*;
11pub use hoist::*;
12pub use is_valid_js_expression::*;
5pub use convert::*;
136pub use js_ast::*;
147pub use util::*;
15
16pub fn blight_to_js(blight: Module) -> JsModule {
17 let mut root_context = HoistContext::new();
18
19 for statement in blight.statements {
20 if let None = root_context.process_statement(statement.to_statement()) {
21 unreachable!("top level return is disallowed by the parser")
22 };
23 }
24
25 JsModule {
26 statements: root_context.statements,
27 }
28}
crates/js_printer/lib.rs+14
......@@ -231,6 +231,10 @@ impl Print for JsExprData {
231231 body.print(writer)
232232 }
233233 JsExprData::Ternary { condition, yes, no } => {
234 let wrap = false;
235 if wrap {
236 write!(writer, "(")?;
237 }
234238 condition.print(writer)?;
235239 write!(writer, "?")?;
236240 yes.print(writer)?;
......@@ -240,15 +244,25 @@ impl Print for JsExprData {
240244 } else {
241245 write!(writer, "null")?;
242246 }
247 if wrap {
248 write!(writer, ")")?;
249 }
243250 Ok(())
244251 }
245252 JsExprData::Comma { items } => {
253 let wrap = true;
254 if wrap {
255 write!(writer, "(")?;
256 }
246257 for (i, item) in items.iter().enumerate() {
247258 if i > 0 {
248259 write!(writer, ",")?;
249260 }
250261 item.print(writer)?;
251262 }
263 if wrap {
264 write!(writer, ")")?;
265 }
252266 Ok(())
253267 }
254268 JsExprData::Void(expr) => {
crates/js_printer/src/lib.rs deleted-16
......@@ -1,16 +0,0 @@
1mod print_expression;
2mod print_statement;
3
4use print_statement::print_statement;
5
6use js_ast::JsStatement;
7
8pub fn print(forest: Vec<JsStatement>) -> String {
9 let mut result = String::new();
10
11 for tree in forest {
12 result.push_str(&print_statement(&tree));
13 }
14
15 result
16}
crates/js_printer/src/print_expression.rs deleted-276
......@@ -1,276 +0,0 @@
1use js_ast::{JsExpr, JsExprData};
2
3use crate::print_statement::print_statement;
4
5pub fn print_expression(expr: &JsExpr) -> String {
6 match &expr.data {
7 JsExprData::Raw(value) => format!("{}", value),
8 JsExprData::Number(value) => format!("{}", value),
9 JsExprData::BigInt(value) => format!("{}n", value),
10 JsExprData::String(value) => {
11 let mut result = String::new();
12 // TODO: This is an INCREDIBLY naive implementation of escaping.
13 // Also it's probably not even correct.
14 for char in value.chars() {
15 match char {
16 '\n' => result.push_str("\\n"),
17 '\r' => result.push_str("\\r"),
18 '\t' => result.push_str("\\t"),
19 '"' => result.push_str("\\\""),
20 '\\' => result.push_str("\\\\"),
21 _ => result.push(char),
22 }
23 }
24 format!("\"{}\"", result)
25 }
26 JsExprData::Bool(value) => format!("{}", value),
27 JsExprData::Identifier(value) => format!("{}", value),
28 JsExprData::Array(value) => format!(
29 "[{}]",
30 value
31 .iter()
32 .map(|x| print_expression(x))
33 .collect::<Vec<String>>()
34 .join(",")
35 ),
36 JsExprData::Object(value) => format!(
37 "{{{}}}",
38 value
39 .iter()
40 .map(|entry| format!(
41 "{}:{}",
42 print_expression(&entry.key),
43 print_expression(&entry.value)
44 ))
45 .collect::<Vec<String>>()
46 .join(",")
47 ),
48 JsExprData::Binary {
49 op: operator,
50 left,
51 right,
52 } => format!(
53 "{}{}{}",
54 print_expression(&left),
55 operator.clone().to_string(),
56 print_expression(&right)
57 ),
58 // JsExprData::Function {
59 // arguments,
60 // value,
61 // name: _,
62 // params: _,
63 // block,
64 // } => format!(
65 // "({})=>{{{}}}",
66 // arguments.join(","),
67 // value
68 // .iter()
69 // .map(|x| format!("{}", print_statement(x)))
70 // .collect::<Vec<String>>()
71 // .join("")
72 // ),
73 JsExprData::Call { base, arguments } => format!(
74 "{}({})",
75 print_expression(&base),
76 arguments
77 .iter()
78 .map(|x| print_expression(x))
79 .collect::<Vec<String>>()
80 .join(",")
81 ),
82 JsExprData::Null => todo!(),
83 JsExprData::Undefined => todo!(),
84 JsExprData::Unary {
85 op: operator,
86 value,
87 } => todo!(),
88 JsExprData::Dot { base, name } => todo!(),
89 JsExprData::Index { base, index } => todo!(),
90 JsExprData::Function {
91 name,
92 params,
93 block,
94 } => todo!(),
95 JsExprData::Ternary { condition, yes, no } => todo!(),
96 JsExprData::ArrowFunction { params, block } => todo!(),
97 JsExprData::Comma { items } => todo!(),
98 JsExprData::Void(_) => todo!(),
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use crate::print_expression::print_expression;
105 use js_ast::JsExprData;
106 use js_ast::JsStatement;
107 use num::{bigint::Sign, BigInt};
108 use std::collections::HashMap;
109
110 #[test]
111 fn number() {
112 assert_eq!(print_expression(&JsExprData::Number(1.0)), "1");
113 assert_eq!(print_expression(&JsExprData::Number(1.5)), "1.5");
114 assert_eq!(print_expression(&JsExprData::Number(1.5e10)), "15000000000");
115 assert_eq!(
116 print_expression(&JsExprData::Number(1.5e-10)),
117 "0.00000000015"
118 );
119 }
120
121 #[test]
122 fn bigint() {
123 assert_eq!(
124 print_expression(&JsExprData::BigInt(BigInt::new(Sign::Plus, vec![1]))),
125 "1n"
126 );
127 assert_eq!(
128 print_expression(&JsExprData::BigInt(BigInt::new(Sign::Plus, vec![10000000]))),
129 "10000000n"
130 );
131 assert_eq!(
132 print_expression(&JsExprData::BigInt(BigInt::new(
133 Sign::Minus,
134 vec![10000000]
135 ))),
136 "-10000000n"
137 );
138 }
139
140 #[test]
141 fn string() {
142 assert_eq!(
143 print_expression(&JsExprData::String("".to_string())),
144 "\"\""
145 );
146 assert_eq!(
147 print_expression(&JsExprData::String("hello".to_string())),
148 "\"hello\""
149 );
150 assert_eq!(
151 print_expression(&JsExprData::String("hello\nworld".to_string())),
152 "\"hello\\nworld\""
153 );
154 }
155
156 #[test]
157 fn bool() {
158 assert_eq!(print_expression(&JsExprData::Bool(true)), "true");
159 assert_eq!(print_expression(&JsExprData::Bool(false)), "false");
160 }
161
162 #[test]
163 fn identifier() {
164 assert_eq!(
165 print_expression(&JsExprData::Identifier("hello".to_string())),
166 "hello"
167 );
168 }
169
170 #[test]
171 fn array() {
172 assert_eq!(print_expression(&JsExprData::Array(vec![])), "[]");
173 assert_eq!(
174 print_expression(&JsExprData::Array(vec![JsExprData::Number(1.0)])),
175 "[1]"
176 );
177 assert_eq!(
178 print_expression(&JsExprData::Array(vec![
179 JsExprData::Number(1.0),
180 JsExprData::Number(2.0)
181 ])),
182 "[1,2]"
183 );
184 }
185
186 #[test]
187 fn object() {
188 assert_eq!(print_expression(&JsExprData::Object(HashMap::new())), "{}");
189 let mut map = HashMap::new();
190 map.insert("hello".to_string(), JsExprData::Number(1.0));
191 assert_eq!(print_expression(&JsExprData::Object(map)), "{\"hello\":1}");
192 }
193
194 #[test]
195 fn operation() {
196 assert_eq!(
197 print_expression(&JsExprData::Binary {
198 op: AnyOperator::Add,
199 left: Box::new(JsExprData::Number(1.0)),
200 right: Box::new(JsExprData::Number(2.0)),
201 }),
202 "1+2"
203 );
204 assert_eq!(
205 print_expression(&JsExprData::Binary {
206 op: AnyOperator::Sub,
207 left: Box::new(JsExprData::Number(1.0)),
208 right: Box::new(JsExprData::Number(2.0)),
209 }),
210 "1-2"
211 );
212 }
213
214 #[test]
215 fn function() {
216 use crate::print_expression::print_expression;
217 use js_ast::JsExprData;
218
219 assert_eq!(
220 print_expression(&JsExprData::Function {
221 arguments: vec![],
222 value: vec![],
223 }),
224 "()=>{}"
225 );
226 assert_eq!(
227 print_expression(&JsExprData::Function {
228 arguments: vec!["a".to_string()],
229 value: vec![],
230 }),
231 "(a)=>{}"
232 );
233 assert_eq!(
234 print_expression(&JsExprData::Function {
235 arguments: vec!["a".to_string(), "b".to_string()],
236 value: vec![],
237 }),
238 "(a,b)=>{}"
239 );
240 assert_eq!(
241 print_expression(&JsExprData::Function {
242 arguments: vec!["a".to_string(), "b".to_string()],
243 value: vec![JsStatement::Declaration {
244 name: "a".to_string(),
245 value: JsExprData::Number(1.0)
246 }],
247 }),
248 "(a,b)=>{let a=1;}"
249 );
250 }
251
252 #[test]
253 fn function_call() {
254 assert_eq!(
255 print_expression(&JsExprData::Call {
256 name: Box::new(JsExprData::Identifier("hello".to_string())),
257 arguments: vec![],
258 }),
259 "hello()"
260 );
261 assert_eq!(
262 print_expression(&JsExprData::Call {
263 name: Box::new(JsExprData::Identifier("hello".to_string())),
264 arguments: vec![JsExprData::Number(1.0)],
265 }),
266 "hello(1)"
267 );
268 assert_eq!(
269 print_expression(&JsExprData::Call {
270 name: Box::new(JsExprData::Identifier("hello".to_string())),
271 arguments: vec![JsExprData::Number(1.0), JsExprData::Number(2.0)],
272 }),
273 "hello(1,2)"
274 );
275 }
276}
crates/js_printer/src/print_statement.rs deleted-153
......@@ -1,153 +0,0 @@
1use js_ast::{If, JSDeclaration, JSFunctionDeclaration, JsStatement, JsStatementData};
2
3use crate::print_expression::print_expression;
4
5pub fn print_statement(statement: &JsStatement) -> String {
6 match &statement.data {
7 JsStatementData::Expression(value) => format!("{};", print_expression(&value)),
8 // JsStatementData::Export(value) => format!("export {}", print_statement(value)),
9 JsStatementData::Export(value) => todo!(),
10 JsStatementData::Return(value) => format!("return {};", print_expression(&value)),
11 JsStatementData::Declaration(Declaration {
12 name,
13 init,
14 is_constant,
15 }) => {
16 format!(
17 "{} {}={};",
18 match is_constant {
19 true => "const",
20 false => "let",
21 },
22 name,
23 print_expression(&init)
24 )
25 }
26 JsStatementData::Function(JSFunctionDeclaration {
27 name,
28 block,
29 params,
30 }) => format!(
31 "function {}({}){{{}}}",
32 name,
33 params.join(","),
34 block
35 .statements
36 .iter()
37 .map(|x| format!("{}", print_statement(x)))
38 .collect::<Vec<String>>()
39 .join("")
40 ),
41 JsStatementData::If(JsIf { condition, yes, no }) => {
42 let else_value = match no {
43 Some(value) => format!(
44 "else{{{}}}",
45 value
46 .statements
47 .iter()
48 .map(|x| format!("{}", print_statement(x)))
49 .collect::<Vec<String>>()
50 .join("")
51 ),
52 None => "".to_string(),
53 };
54 format!(
55 "if({}){{{}}}{}",
56 print_expression(&condition),
57 yes.statements
58 .iter()
59 .map(|x| format!("{}", print_statement(x)))
60 .collect::<Vec<String>>()
61 .join(""),
62 else_value
63 )
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use crate::print_statement::print_statement;
71 use js_ast::JsExpression;
72 use js_ast::JsStatement;
73
74 #[test]
75 fn variable_declaration() {
76 assert_eq!(
77 print_statement(&JsStatement::Declaration {
78 name: "x".to_string(),
79 value: JsExpression::Number(1.0),
80 }),
81 "let x=1;"
82 );
83 }
84
85 #[test]
86 fn constant_declaration() {
87 assert_eq!(
88 print_statement(&JsStatement::ConstantDeclaration {
89 name: "x".to_string(),
90 value: JsExpression::Number(1.0),
91 }),
92 "const x=1;"
93 );
94 }
95
96 #[test]
97 fn function_declaration() {
98 assert_eq!(
99 print_statement(&JsStatement::Function {
100 name: "x".to_string(),
101 arguments: vec!["y".to_string()],
102 value: vec![JsStatement::Expression(JsExpression::Number(1.0))],
103 }),
104 "function x(y){1;}"
105 );
106 }
107
108 #[test]
109 fn expression() {
110 assert_eq!(
111 print_statement(&JsStatement::Expression(JsExpression::Number(1.0))),
112 "1;"
113 );
114 }
115
116 #[test]
117 fn export() {
118 assert_eq!(
119 print_statement(&JsStatement::Export(Box::new(JsStatement::Expression(
120 JsExpression::Number(1.0)
121 )))),
122 "export 1;"
123 );
124 }
125
126 #[test]
127 fn return_statement() {
128 assert_eq!(
129 print_statement(&JsStatement::Return(JsExpression::Number(1.0))),
130 "return 1;"
131 );
132 }
133
134 #[test]
135 fn if_statement() {
136 assert_eq!(
137 print_statement(&JsStatement::If {
138 condition: JsExpression::Number(1.0),
139 value: vec![JsStatement::Expression(JsExpression::Number(2.0))],
140 else_value: None,
141 }),
142 "if(1){2;}"
143 );
144 assert_eq!(
145 print_statement(&JsStatement::If {
146 condition: JsExpression::Number(1.0),
147 value: vec![JsStatement::Expression(JsExpression::Number(2.0))],
148 else_value: Some(vec![JsStatement::Expression(JsExpression::Number(3.0))]),
149 }),
150 "if(1){2;}else{3;}"
151 );
152 }
153}
crates/parser/parse_block.rs+1-9
......@@ -12,9 +12,6 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
1212
1313 let mut statements = Vec::new();
1414
15 let was_top_level = p.is_top_level;
16 p.is_top_level = false;
17
1815 'outer: loop {
1916 let is_statement_like_expression = p
2017 .peek()
......@@ -23,7 +20,6 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
2320 let statement = match parse_statement(p) {
2421 Ok(statement) => statement,
2522 Err(err) => {
26 p.is_top_level = was_top_level;
2723 return Err(err);
2824 }
2925 };
......@@ -35,7 +31,6 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
3531 match p.peek() {
3632 Some(Token::ClosedBrace) => {
3733 p.next();
38 p.is_top_level = was_top_level;
3934 return Ok(Block {
4035 statements,
4136 has_implicit_return: false,
......@@ -46,14 +41,12 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
4641 },
4742 Some(Token::ClosedBrace) => {
4843 p.next();
49 if statement.require_semicolon(is_statement_like_expression) {
50 p.is_top_level = was_top_level;
44 if !statement.is_expression() {
5145 return Err(p
5246 .error("Expected semicolon or '}' after statement".to_string())
5347 .into());
5448 }
5549
56 p.is_top_level = was_top_level;
5750 return Ok(Block {
5851 statements,
5952 has_implicit_return: true,
......@@ -61,7 +54,6 @@ pub fn parse_block(p: &mut Parser) -> Result<Block> {
6154 }
6255 _ => {
6356 if statement.require_semicolon(is_statement_like_expression) {
64 p.is_top_level = was_top_level;
6557 return Err(p
6658 .error("Expected semicolon or '}' after statement".to_string())
6759 .into());
crates/parser/parse_declaration.rs+3-3
......@@ -1,4 +1,4 @@
1use ast::{Decl, DocComment, Type};
1use ast::{DocComment, Type, VarDecl};
22use miette::Result;
33use tokenizer::Token;
44
......@@ -11,7 +11,7 @@ pub fn parse_declaration(
1111 p: &mut Parser,
1212 is_pub: bool,
1313 comment: Option<DocComment>,
14) -> Result<Decl> {
14) -> Result<VarDecl> {
1515 let loc = p.loc();
1616 let is_constant = match p.next() {
1717 Some(Token::Var) => false,
......@@ -38,7 +38,7 @@ pub fn parse_declaration(
3838 .into())
3939 }
4040 };
41 Ok(Decl {
41 Ok(VarDecl {
4242 name: name.clone(),
4343 data_type: return_type,
4444 init,
crates/parser/parse_expr_basic.rs+1-1
......@@ -63,7 +63,7 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> {
6363 }
6464
6565 Some(Token::Return) => {
66 if p.is_top_level {
66 if p.allow_return {
6767 return Err(p
6868 .error("'return' cannot be used at top level".to_string())
6969 .into());
crates/parser/parse_fn_declaration.rs+44-44
......@@ -1,8 +1,9 @@
1use ast::{Decl, DocComment, Expr, ExprData, Type};
1use ast::{DocComment, Expr, ExprData, FnDecl, Type};
22use miette::Result;
33use tokenizer::Token;
44
55use crate::parse_block::parse_block;
6use crate::parse_expr::parse_expr;
67use crate::parse_param::parse_param;
78use crate::parse_type::parse_type;
89use crate::Parser;
......@@ -12,7 +13,7 @@ pub fn parse_fn_declaration(
1213 p: &mut Parser,
1314 is_pub: bool,
1415 comment: Option<DocComment>,
15) -> Result<Decl> {
16) -> Result<FnDecl> {
1617 let loc = p.loc();
1718 match p.next() {
1819 Some(Token::Fn) => (),
......@@ -57,9 +58,13 @@ pub fn parse_fn_declaration(
5758 break;
5859 }
5960 _ => {
61 let actual = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x));
6062 return Err(p
61 .error("Expected ',' or ')' after identifier".to_string())
62 .into())
63 .error(format!(
64 "Expected ',' or ')' after identifier, got {}",
65 actual
66 ))
67 .into());
6368 }
6469 };
6570 }
......@@ -69,19 +74,6 @@ pub fn parse_fn_declaration(
6974 .into())
7075 }
7176 }
72
73 match p.peek() {
74 Some(Token::Comma) => p.next(),
75 Some(Token::ClosedParen) => {
76 _ = p.next();
77 break;
78 }
79 _ => {
80 return Err(p
81 .error("Expected ',' or ')' after identifier".to_string())
82 .into())
83 }
84 };
8577 }
8678
8779 let return_type = match p.peek() {
......@@ -92,31 +84,39 @@ pub fn parse_fn_declaration(
9284 _ => Type::Infer,
9385 };
9486
95 if let Some(Token::OpenBrace) = p.peek() {
96 let block_loc = p.loc();
97 let block = parse_block(p)?;
98 let function_body = ExprData::Fn {
99 name: name.clone(),
100 params,
101 return_type,
102 block,
103 };
104 Ok(Decl {
105 name,
106 data_type: Type::Any,
107 init: Expr::new(function_body, block_loc, None),
108 is_constant: true,
109 is_pub,
110 comment,
111 loc: loc,
112 })
113 } else {
114 let actually = p.peek().map_or("EOF".to_string(), |x| x.to_string());
115 return Err(p
116 .error(format!(
117 "Expected a block to follow function declaration, but got {}",
118 actually,
119 ))
120 .into());
121 }
87 let was_allow_return = p.allow_return;
88 p.allow_return = true;
89
90 let body = match p.peek() {
91 Some(Token::OpenBrace) => {
92 let block_loc = p.loc();
93 let block = parse_block(p)?;
94 Expr::new(ExprData::Block(block), block_loc, None)
95 }
96 Some(Token::BigRightArrow) => {
97 _ = p.next();
98 parse_expr(p)?
99 }
100 _ => {
101 let actually = p.peek().map_or("EOF".to_string(), |x| format!("{:?}", x));
102 return Err(p
103 .error(format!(
104 "Expected a block or '=>' to follow function declaration, but got {}",
105 actually,
106 ))
107 .into());
108 }
109 };
110
111 p.allow_return = was_allow_return;
112
113 Ok(FnDecl {
114 name,
115 return_type,
116 params,
117 body,
118 is_pub,
119 comment,
120 loc,
121 })
122122}
crates/parser/parse_statement.rs+12-8
......@@ -28,10 +28,12 @@ pub fn parse_statement(p: &mut Parser) -> Result<Statement> {
2828 Some(Token::Pub) => {
2929 _ = p.next();
3030 match p.peek() {
31 Some(Token::Var) | Some(Token::Let) => {
32 Ok(Statement::Decl(parse_declaration(p, true, m_doc_comment)?))
33 }
34 Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(
31 Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration(
32 p,
33 true,
34 m_doc_comment,
35 )?)),
36 Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration(
3537 p,
3638 true,
3739 m_doc_comment,
......@@ -40,10 +42,12 @@ pub fn parse_statement(p: &mut Parser) -> Result<Statement> {
4042 None => Err(p.error("Unexpected end of file".to_string()).into()),
4143 }
4244 }
43 Some(Token::Var) | Some(Token::Let) => {
44 Ok(Statement::Decl(parse_declaration(p, false, m_doc_comment)?))
45 }
46 Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(
45 Some(Token::Var) | Some(Token::Let) => Ok(Statement::VarDecl(parse_declaration(
46 p,
47 false,
48 m_doc_comment,
49 )?)),
50 Some(Token::Fn) => Ok(Statement::FnDecl(parse_fn_declaration(
4751 p,
4852 false,
4953 m_doc_comment,
crates/parser/parser.rs+2-2
......@@ -53,7 +53,7 @@ pub struct Parser {
5353 cursor: usize,
5454
5555 /// Whether or not the parser is at the top level
56 pub is_top_level: bool,
56 pub allow_return: bool,
5757
5858 comments: Vec<String>,
5959}
......@@ -63,7 +63,7 @@ impl Parser {
6363 Parser {
6464 tokens,
6565 cursor: 0,
66 is_top_level: true,
66 allow_return: false,
6767 comments: vec![],
6868 }
6969 }
crates/tokenizer/tokenizer.rs+12-5
......@@ -5,6 +5,7 @@ use thiserror::Error;
55pub struct Loc {}
66
77impl Loc {
8 /// TODO(@paperdave): Remove this. every usage of this is incorrect.
89 pub const EMPTY: Loc = Loc {};
910
1011 pub fn new() -> Self {
......@@ -44,12 +45,14 @@ pub enum Token {
4445 AndAnd, // &&
4546 Bang, // !
4647 BangEqual, // !=
48 BigRightArrow, // =>
4749 Caret, // ^
4850 ClosedBrace, // }
4951 ClosedBracket, // ]
5052 ClosedParen, // )
5153 Colon, // :
5254 Comma, // ,
55 Dot, // .
5356 Equal, // =
5457 EqualEqual, // ==
5558 Gt, // >
......@@ -73,14 +76,13 @@ pub enum Token {
7376 Star, // *
7477 StarStar, // **
7578 Tilde, // ~
76 Dot, // .
7779}
7880
7981impl Token {
8082 pub fn is_start_of_statement_like_expression(&self) -> bool {
8183 match &self {
82 Token::If | Token::OpenBrace => false,
83 _ => true,
84 Token::If | Token::OpenBrace => true,
85 _ => false,
8486 }
8587 }
8688}
......@@ -110,17 +112,20 @@ impl std::fmt::Display for Token {
110112 Token::AndAnd => "&&".to_string(),
111113 Token::Bang => "!".to_string(),
112114 Token::BangEqual => "!=".to_string(),
115 Token::BigRightArrow => "=>".to_string(),
113116 Token::Caret => "^".to_string(),
114117 Token::ClosedBrace => "}".to_string(),
115118 Token::ClosedBracket => "]".to_string(),
116119 Token::ClosedParen => ")".to_string(),
117120 Token::Colon => ":".to_string(),
118121 Token::Comma => ",".to_string(),
122 Token::Dot => ".".to_string(),
119123 Token::Equal => "=".to_string(),
120124 Token::EqualEqual => "==".to_string(),
121125 Token::Gt => ">".to_string(),
122126 Token::GtEqual => ">=".to_string(),
123127 Token::GtGt => ">>".to_string(),
128 Token::GtGtGt => ">>>".to_string(),
124129 Token::Lt => "<".to_string(),
125130 Token::LtEqual => "<=".to_string(),
126131 Token::LtLt => "<<".to_string(),
......@@ -138,8 +143,6 @@ impl std::fmt::Display for Token {
138143 Token::Star => "*".to_string(),
139144 Token::StarStar => "**".to_string(),
140145 Token::Tilde => "~".to_string(),
141 Token::Dot => ".".to_string(),
142 Token::GtGtGt => ">>>".to_string(),
143146 };
144147
145148 write!(f, "{}", token)
......@@ -234,6 +237,10 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> {
234237 chars.next();
235238 tokens.push(Token::EqualEqual);
236239 }
240 Some((_, '>')) => {
241 chars.next();
242 tokens.push(Token::BigRightArrow);
243 }
237244 _ => tokens.push(Token::Equal),
238245 },
239246 ';' => tokens.push(Token::Semicolon),
examples/basics/stupid.blight created+2
......@@ -0,0 +1,2 @@
1
2fn add(x: int, y: int) => x + y