[rust] basic VariableDeclaration support

This commit is contained in:
Joe Savona
2023-07-06 09:24:50 +09:00
parent 2eb7c897f3
commit d99da01698
12 changed files with 222 additions and 82 deletions
+1
View File
@@ -587,6 +587,7 @@ name = "hir"
version = "0.1.0"
dependencies = [
"bumpalo",
"estree",
"indexmap 2.0.0",
"serde",
]
+79 -3
View File
@@ -1,8 +1,11 @@
use bumpalo::collections::{CollectIn, String};
use estree::{ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Statement};
use estree::{
ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Pattern, Statement,
VariableDeclarationKind,
};
use hir::{
ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionValue,
LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionKind,
InstructionValue, LValue, LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
};
use crate::builder::{Binding, Builder};
@@ -102,6 +105,39 @@ fn lower_statement<'a>(
Statement::EmptyStatement(_) => {
// no-op
}
Statement::VariableDeclaration(stmt) => {
let kind = match stmt.kind {
VariableDeclarationKind::Const => InstructionKind::Const,
VariableDeclarationKind::Let => InstructionKind::Let,
VariableDeclarationKind::Var => panic!("`var` declarations are not supported"),
};
for declaration in stmt.declarations {
if let Some(init) = declaration.init {
let value = lower_expression_to_temporary(env, builder, init);
lower_assignment(env, builder, kind, declaration.id, value);
} else {
if let Pattern::Identifier(id) = declaration.id {
// TODO: handle unbound variables
let binding = builder.resolve_binding(&id).unwrap();
let identifier = match binding {
Binding::Local(identifier) => identifier,
_ => panic!("Expected variable declaration to be a local binding"),
};
let place = Place {
effect: None,
identifier,
};
lower_value_to_temporary(
env,
builder,
InstructionValue::DeclareLocal(hir::DeclareLocal {
lvalue: LValue { place, kind },
}),
);
}
}
}
}
_ => todo!("Lower {stmt:#?}"),
}
Ok(())
@@ -168,6 +204,46 @@ fn lower_expression<'a>(
}
}
fn lower_assignment<'a>(
env: &'a Environment<'a>,
builder: &mut Builder<'a>,
kind: InstructionKind,
lvalue: Pattern,
value: Place<'a>,
) -> InstructionValue<'a> {
match lvalue {
Pattern::Identifier(lvalue) => {
let place = lower_identifier_for_assignment(env, builder, kind, *lvalue).unwrap();
let temporary = lower_value_to_temporary(
env,
builder,
InstructionValue::StoreLocal(hir::StoreLocal {
lvalue: LValue { place, kind },
value,
}),
);
InstructionValue::LoadLocal(LoadLocal { place: temporary })
}
_ => todo!("lower assignment for {:#?}", lvalue),
}
}
fn lower_identifier_for_assignment<'a>(
env: &'a Environment<'a>,
builder: &mut Builder<'a>,
kind: InstructionKind,
identifier: estree::Identifier,
) -> Option<Place<'a>> {
let binding = builder.resolve_binding(&identifier)?;
match binding {
Binding::Module(..) | Binding::Global => panic!("Cannot reassign a global"),
Binding::Local(id) => Some(Place {
identifier: id,
effect: None,
}),
}
}
/// Given an already lowered InstructionValue:
/// - if the instruction is a LoadLocal for a temporary location, avoid the indirection
/// and return the place that the LoadLocal loads from
+14 -36
View File
@@ -21,7 +21,7 @@ use indexmap::IndexMap;
/// generally involves driving calls to enter/exit blocks, resolve
/// labels and variables, and then calling `build()` when the HIR
/// is complete.
pub struct Builder<'a> {
pub(crate) struct Builder<'a> {
#[allow(dead_code)]
environment: &'a Environment<'a>,
@@ -32,8 +32,12 @@ pub struct Builder<'a> {
wip: WipBlock<'a>,
id_gen: InstructionIdGenerator,
}
bindings: HashMap<(bumpalo::collections::String<'a>, BindingId), Identifier<'a>>,
pub(crate) struct WipBlock<'a> {
pub id: BlockId,
pub kind: BlockKind,
pub instructions: Vec<'a, Instruction<'a>>,
}
impl<'a> Builder<'a> {
@@ -50,7 +54,6 @@ impl<'a> Builder<'a> {
entry,
wip: current,
id_gen: InstructionIdGenerator::new(),
bindings: Default::default(),
}
}
@@ -148,35 +151,16 @@ impl<'a> Builder<'a> {
) -> Option<Binding<'a>> {
identifier.binding.as_ref().map(|binding| match binding {
estree::Binding::Global => Binding::Global,
estree::Binding::Local(id) => {
Binding::Local(self.resolve_binding_identifier(&identifier.name, *id))
}
estree::Binding::Module(id) => {
Binding::Module(self.resolve_binding_identifier(&identifier.name, *id))
}
estree::Binding::Local(id) => Binding::Local(
self.environment
.resolve_binding_identifier(&identifier.name, *id),
),
estree::Binding::Module(id) => Binding::Module(
self.environment
.resolve_binding_identifier(&identifier.name, *id),
),
})
}
fn resolve_binding_identifier(&mut self, name: &str, binding_id: BindingId) -> Identifier<'a> {
let key_name = bumpalo::collections::String::from_str_in(name, &self.environment.allocator);
if let Some(identifier) = self.bindings.get(&(key_name.clone(), binding_id)) {
identifier.clone()
} else {
let id = self.environment.next_identifier_id();
let identifier = Identifier {
id,
name: Some(key_name.clone()),
data: Rc::new(RefCell::new(IdentifierData {
mutable_range: Default::default(),
scope: None,
type_: Type::Var(self.environment.next_type_var_id()),
})),
};
self.bindings
.insert((key_name, binding_id), identifier.clone());
identifier
}
}
}
pub(crate) enum Binding<'a> {
@@ -231,12 +215,6 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
hir.blocks = blocks;
}
pub(crate) struct WipBlock<'a> {
pub id: BlockId,
pub kind: BlockKind,
pub instructions: Vec<'a, Instruction<'a>>,
}
/// Prunes ForTerminal.update values (sets to None) if they are unreachable
fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
+34 -19
View File
@@ -7,7 +7,7 @@ use swc_core::common::source_map::Pos;
use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS};
use swc_core::ecma::ast::{
AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, ModuleItem, Pat, PatOrExpr,
Program, Stmt, UnaryOp, VarDeclKind, VarDeclOrExpr,
Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
};
use swc_core::ecma::parser::Syntax;
use swc_core::ecma::transforms::base::resolver;
@@ -95,7 +95,7 @@ fn convert_module_item(cx: &Context, item: &ModuleItem) -> estree::ModuleItem {
ModuleItem::Stmt(item) => {
estree::ModuleItem::Statement(Box::new(convert_statement(cx, item)))
}
_ => todo!("Convert {:#?}", item),
_ => todo!("translate module item {:#?}", item),
}
}
@@ -152,6 +152,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
range: convert_span(&item.function.span),
}))
}
Stmt::Decl(Decl::Var(item)) => {
estree::Statement::VariableDeclaration(Box::new(convert_variable_declaration(cx, item)))
}
Stmt::Block(item) => {
estree::Statement::BlockStatement(Box::new(convert_block_statement(cx, item)))
}
@@ -207,18 +210,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
}
VarDeclOrExpr::VarDecl(init) => {
assert_eq!(init.decls.len(), 1);
let decl = &init.decls[0];
estree::ForInit::VariableDeclaration(Box::new(estree::VariableDeclaration {
kind: convert_decl_kind(&init.kind),
declarations: vec![estree::VariableDeclarator {
id: convert_pattern(cx, &decl.name),
init: decl.init.as_ref().map(|init| convert_expression(cx, init)),
loc: None,
range: convert_span(&decl.span),
}],
loc: None,
range: convert_span(&init.span),
}))
estree::ForInit::VariableDeclaration(Box::new(convert_variable_declaration(
cx, init,
)))
}
}),
test: item.test.as_ref().map(|test| convert_expression(cx, test)),
@@ -249,7 +243,28 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
loc: None,
range: convert_span(&item.span),
})),
_ => todo!(),
_ => todo!("translate statement {:#?}", stmt),
}
}
fn convert_variable_declaration(cx: &Context, decl: &VarDecl) -> estree::VariableDeclaration {
estree::VariableDeclaration {
kind: convert_decl_kind(&decl.kind),
declarations: decl
.decls
.iter()
.map(|declarator| estree::VariableDeclarator {
id: convert_pattern(cx, &declarator.name),
init: declarator
.init
.as_ref()
.map(|init| convert_expression(cx, init)),
loc: None,
range: convert_span(&decl.span),
})
.collect(),
loc: None,
range: convert_span(&decl.span),
}
}
@@ -351,12 +366,12 @@ fn convert_expression(cx: &Context, expr: &Expr) -> estree::ExpressionLike {
range: convert_span(&expr.span),
}))
}
_ => todo!(),
_ => todo!("translate expression {:#?}", expr),
}
}
fn convert_assignment_target(_target: &PatOrExpr) -> estree::AssignmentTarget {
todo!()
todo!("translate assignment target")
}
fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
@@ -374,7 +389,7 @@ fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
fn convert_assignment_operator(op: AssignOp) -> estree::AssignmentOperator {
match op {
AssignOp::AddAssign => estree::AssignmentOperator::PlusEquals,
_ => todo!(),
_ => todo!("translate assignment operator"),
}
}
@@ -422,7 +437,7 @@ fn convert_pattern(cx: &Context, pat: &Pat) -> estree::Pattern {
loc: None,
range: convert_span(&pat.span),
})),
_ => todo!(),
_ => todo!("translate pattern {:#?}", pat),
}
}
+16 -16
View File
@@ -1038,6 +1038,22 @@ pub enum Binding {
Global,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[serde(transparent)]
pub struct BindingId(NonZeroU32);
impl BindingId {
pub fn new(id: NonZeroU32) -> Self {
Self(id)
}
}
impl From<BindingId> for u32 {
fn from(value: BindingId) -> Self {
value.0.into()
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MemberExpression {
pub object: ExpressionLike,
@@ -1253,22 +1269,6 @@ pub struct JSXText {
pub range: Option<SourceRange>,
}
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
#[serde(transparent)]
pub struct BindingId(NonZeroU32);
impl BindingId {
pub fn new(id: NonZeroU32) -> Self {
Self(id)
}
}
impl From<BindingId> for u32 {
fn from(value: BindingId) -> Self {
value.0.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -7,5 +7,9 @@ function id(x) {
// FOO;
Math;
id;
let y = true;
y;
let z;
z;
return x;
}
@@ -11,8 +11,6 @@ fn fixtures() {
let input = std::fs::read_to_string(path).unwrap();
let ast = parse(&input, path.to_str().unwrap()).unwrap();
println!("{:#?}", &ast);
let mut output = String::new();
for (ix, item) in ast.body.into_iter().enumerate() {
@@ -13,6 +13,10 @@ function id(x) {
// FOO;
Math;
id;
let y = true;
y;
let z;
z;
return x;
}
@@ -22,6 +26,11 @@ entry bb0
bb0
[0] unknown $0 = LoadGlobal Math
[1] unknown $2 = LoadGlobal id
[2] unknown $4 = LoadLocal unknown x$3
[3] Return unknown $4
[2] unknown $3 = true
[3] unknown $5 = StoreLocal Let unknown y$4 = unknown $3
[4] unknown $6 = LoadLocal unknown y$4
[5] unknown $8 = DeclareLocal Let unknown z$7
[6] unknown $9 = LoadLocal unknown z$7
[7] unknown $11 = LoadLocal unknown x$10
[8] Return unknown $11
+1
View File
@@ -7,5 +7,6 @@ edition = "2021"
[dependencies]
bumpalo = { version = "3.13.0", features = ["boxed", "collections"] }
estree = { path = "../estree" }
indexmap = "2.0.0"
serde = "1.0.164"
+33 -2
View File
@@ -1,8 +1,15 @@
use std::cell::Cell;
use std::{
cell::{Cell, RefCell},
collections::HashMap,
rc::Rc,
};
use bumpalo::Bump;
use estree::BindingId;
use crate::{BlockId, Features, IdentifierId, Registry, TypeVarId};
use crate::{
BlockId, Features, Identifier, IdentifierData, IdentifierId, Registry, Type, TypeVarId,
};
/// Stores all the contextual information about the top-level React function being
/// compiled. Environments may not be reused between React functions, but *are*
@@ -28,6 +35,8 @@ pub struct Environment<'a> {
next_identifier_id: Cell<IdentifierId>,
next_type_var_id: Cell<TypeVarId>,
bindings: Rc<RefCell<HashMap<(bumpalo::collections::String<'a>, BindingId), Identifier<'a>>>>,
}
impl<'a> Environment<'a> {
@@ -39,6 +48,7 @@ impl<'a> Environment<'a> {
next_block_id: Cell::new(BlockId(0)),
next_identifier_id: Cell::new(IdentifierId(0)),
next_type_var_id: Cell::new(TypeVarId(0)),
bindings: Default::default(),
}
}
@@ -67,4 +77,25 @@ impl<'a> Environment<'a> {
self.next_type_var_id.set(id.next());
id
}
pub fn resolve_binding_identifier(&self, name: &str, binding_id: BindingId) -> Identifier<'a> {
let key_name = bumpalo::collections::String::from_str_in(name, &self.allocator);
let mut bindings = self.bindings.borrow_mut();
if let Some(identifier) = bindings.get(&(key_name.clone(), binding_id)) {
identifier.clone()
} else {
let id = self.next_identifier_id();
let identifier = Identifier {
id,
name: Some(key_name.clone()),
data: Rc::new(RefCell::new(IdentifierData {
mutable_range: Default::default(),
scope: None,
type_: Type::Var(self.next_type_var_id()),
})),
};
bindings.insert((key_name, binding_id), identifier.clone());
identifier
}
}
}
@@ -145,6 +145,16 @@ pub enum InstructionKind {
Reassign,
}
impl Display for InstructionKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Const => f.write_str("Const"),
Self::Let => f.write_str("Let"),
Self::Reassign => f.write_str("Reassign"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum Effect {
/// This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction)
+19 -2
View File
@@ -1,8 +1,8 @@
use std::fmt::{Result, Write};
use crate::{
ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue,
Terminal, TerminalValue,
ArrayElement, BasicBlock, Function, Instruction, InstructionValue, LValue, Place,
PrimitiveValue, Terminal, TerminalValue,
};
/// Trait for HIR types to describe how they print themselves.
@@ -80,6 +80,16 @@ impl<'a> Print for InstructionValue<'a> {
PrimitiveValue::Undefined => write!(out, "<undefined>")?,
};
}
InstructionValue::StoreLocal(value) => {
write!(out, "StoreLocal ")?;
value.lvalue.print(out)?;
write!(out, " = ")?;
value.value.print(out)?;
}
InstructionValue::DeclareLocal(value) => {
write!(out, "DeclareLocal ")?;
value.lvalue.print(out)?;
}
_ => write!(out, "{:?}", self)?,
}
Ok(())
@@ -117,6 +127,13 @@ impl<'a> Print for Place<'a> {
}
}
impl<'a> Print for LValue<'a> {
fn print(&self, out: &mut impl Write) -> Result {
write!(out, "{} ", self.kind)?;
self.place.print(out)
}
}
impl<'a> Print for Terminal<'a> {
fn print(&self, out: &mut impl Write) -> Result {
write!(out, " {} ", self.id)?;