mirror of
https://github.com/swc-project/swc.git
synced 2024-12-23 21:54:36 +03:00
[WIP] Working for simplfier
This commit is contained in:
parent
8d62017d88
commit
015ccb07f4
@ -6,6 +6,7 @@ authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
[dependencies]
|
||||
swc_ecma_ast = { path = "./ast" }
|
||||
swc_ecma_parser = { path = "./parser" }
|
||||
swc_ecma_transforms = { path = "./transforms" }
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -1,2 +1,3 @@
|
||||
pub extern crate swc_ecma_ast as ast;
|
||||
pub extern crate swc_ecma_parser as parser;
|
||||
pub extern crate swc_ecma_transforms as transforms;
|
||||
|
13
ecmascript/transforms/Cargo.toml
Normal file
13
ecmascript/transforms/Cargo.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "swc_ecma_transforms"
|
||||
version = "0.1.0"
|
||||
authors = ["강동윤 <kdy1@outlook.kr>"]
|
||||
|
||||
[dependencies]
|
||||
swc_atoms = { path = "../../atoms" }
|
||||
swc_common = { path = "../../common" }
|
||||
swc_ecma_ast = { path = "../ast" }
|
||||
|
||||
[dev-dependencies]
|
||||
swc_ecma_parser = { path = "../parser" }
|
||||
testing = { path = "../../testing" }
|
11
ecmascript/transforms/src/lib.rs
Normal file
11
ecmascript/transforms/src/lib.rs
Normal file
@ -0,0 +1,11 @@
|
||||
#![feature(box_patterns)]
|
||||
#![feature(box_syntax)]
|
||||
#![feature(specialization)]
|
||||
|
||||
#[macro_use]
|
||||
pub extern crate swc_atoms;
|
||||
pub extern crate swc_common;
|
||||
pub extern crate swc_ecma_ast as ast;
|
||||
|
||||
pub mod simplify;
|
||||
pub mod util;
|
176
ecmascript/transforms/src/simplify/expr.rs
Normal file
176
ecmascript/transforms/src/simplify/expr.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use super::Simplify;
|
||||
use ast::*;
|
||||
use ast::{Ident, Lit};
|
||||
use ast::ExprKind::*;
|
||||
use swc_common::fold::{FoldWith, Folder};
|
||||
use util::*;
|
||||
|
||||
impl Folder<ExprKind> for Simplify {
|
||||
fn fold(&mut self, expr: ExprKind) -> ExprKind {
|
||||
let expr = expr.fold_children(self);
|
||||
|
||||
match expr {
|
||||
// Do nothing for literals.
|
||||
Lit(_) => expr,
|
||||
|
||||
Unary { prefix, op, arg } => fold_unary(prefix, op, arg),
|
||||
Binary { left, op, right } => fold_bin(left, op, right),
|
||||
|
||||
Cond { test, cons, alt } => match test.as_bool() {
|
||||
(p, Known(val)) => {
|
||||
let expr_value = if val { cons } else { alt };
|
||||
if p.is_pure() {
|
||||
expr_value.node
|
||||
} else {
|
||||
Seq {
|
||||
exprs: vec![test, expr_value],
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => Cond { test, cons, alt },
|
||||
},
|
||||
|
||||
// Simplify sequence expression.
|
||||
Seq { exprs } => if exprs.len() == 1 {
|
||||
exprs.into_iter().next().unwrap().node
|
||||
} else {
|
||||
assert!(!exprs.is_empty(), "sequence expression should not be empty");
|
||||
//TODO: remove unused
|
||||
return Seq { exprs };
|
||||
},
|
||||
|
||||
// be conservative.
|
||||
_ => expr,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_bin(left: Box<Expr>, op: BinaryOp, right: Box<Expr>) -> ExprKind {
|
||||
let (left, right) = match op {
|
||||
BinaryOp::Add => return fold_add(left, right),
|
||||
BinaryOp::LogicalAnd | BinaryOp::LogicalOr => match left.as_bool() {
|
||||
(Pure, Known(val)) => {
|
||||
if op == BinaryOp::LogicalAnd {
|
||||
if val {
|
||||
// 1 && $right
|
||||
return right.node;
|
||||
} else {
|
||||
// 0 && $right
|
||||
return Lit(Lit::Bool(false));
|
||||
}
|
||||
} else {
|
||||
if val {
|
||||
// 1 || $right
|
||||
return left.node;
|
||||
} else {
|
||||
// 0 || $right
|
||||
return right.node;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (left, right),
|
||||
},
|
||||
BinaryOp::InstanceOf => (left, right),
|
||||
|
||||
BinaryOp::Sub | BinaryOp::Div | BinaryOp::Mod => {
|
||||
// Arithmetic operations
|
||||
|
||||
(left, right)
|
||||
}
|
||||
_ => (left, right),
|
||||
};
|
||||
|
||||
Binary { left, op, right }
|
||||
}
|
||||
|
||||
///See https://tc39.github.io/ecma262/#sec-addition-operator-plus
|
||||
fn fold_add(left: Box<Expr>, right: Box<Expr>) -> ExprKind {
|
||||
// It's string concatenation if either left or right is string.
|
||||
|
||||
Binary {
|
||||
left,
|
||||
op: BinaryOp::Add,
|
||||
right,
|
||||
}
|
||||
}
|
||||
|
||||
fn fold_unary(prefix: bool, op: UnaryOp, arg: Box<Expr>) -> ExprKind {
|
||||
let span = arg.span;
|
||||
|
||||
match op {
|
||||
UnaryOp::TypeOf => {
|
||||
let val = match arg.node {
|
||||
Function(..) => "function",
|
||||
Lit(Lit::Str(..)) => "string",
|
||||
Lit(Lit::Num(..)) => "number",
|
||||
Lit(Lit::Bool(..)) => "boolean",
|
||||
Lit(Lit::Null) | Object { .. } | Array { .. } => "object",
|
||||
Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Void,
|
||||
..
|
||||
}
|
||||
| Ident(Ident {
|
||||
sym: js_word!("undefined"),
|
||||
..
|
||||
}) => {
|
||||
// We can assume `undefined` is `undefined`,
|
||||
// because overriding `undefined` is always hard error in swc.
|
||||
"undefined"
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::TypeOf,
|
||||
arg,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return Lit(Lit::Str(val.into()));
|
||||
}
|
||||
UnaryOp::Bang => match arg.as_bool() {
|
||||
(p, Known(val)) => {
|
||||
let new = Lit(Lit::Bool(!val));
|
||||
return if p.is_pure() {
|
||||
new
|
||||
} else {
|
||||
Seq {
|
||||
exprs: vec![arg, box Expr { span, node: new }],
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
return Unary {
|
||||
op,
|
||||
arg,
|
||||
prefix: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
UnaryOp::Plus => {}
|
||||
UnaryOp::Minus => match arg.node {
|
||||
Ident(Ident {
|
||||
sym: js_word!("Infinity"),
|
||||
..
|
||||
}) => return Unary { prefix, op, arg },
|
||||
// "-NaN" is "NaN"
|
||||
Ident(Ident {
|
||||
sym: js_word!("NaN"),
|
||||
..
|
||||
}) => return arg.node,
|
||||
Lit(Lit::Num(Number(f))) => return Lit(Lit::Num(Number(-f))),
|
||||
_ => {
|
||||
|
||||
// TODO: Report that user is something bad (negating non-number value)
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Unary { prefix, op, arg }
|
||||
}
|
||||
|
||||
/// Try to fold arithmetic binary operators
|
||||
fn perform_arithmetic_op(op: BinaryOp, left: Box<Expr>, right: Box<Expr>) -> ExprKind {}
|
133
ecmascript/transforms/src/simplify/mod.rs
Normal file
133
ecmascript/transforms/src/simplify/mod.rs
Normal file
@ -0,0 +1,133 @@
|
||||
//! Ported from closure compiler.
|
||||
use ast::*;
|
||||
use swc_common::fold::{FoldWith, Folder};
|
||||
use util::*;
|
||||
|
||||
mod expr;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Simplify;
|
||||
|
||||
impl<T: StmtLike> Folder<Vec<T>> for Simplify
|
||||
where
|
||||
Self: Folder<T>,
|
||||
{
|
||||
fn fold(&mut self, stmts: Vec<T>) -> Vec<T> {
|
||||
let mut buf = Vec::with_capacity(stmts.len());
|
||||
|
||||
for stmt_like in stmts {
|
||||
let stmt_like = self.fold(stmt_like);
|
||||
let stmt_like = match stmt_like.try_into_stmt() {
|
||||
Ok(stmt) => {
|
||||
let span = stmt.span;
|
||||
let stmt = match stmt.node {
|
||||
// Remove empty statements.
|
||||
StmtKind::Empty => continue,
|
||||
|
||||
StmtKind::Throw { .. }
|
||||
| StmtKind::Return { .. }
|
||||
| StmtKind::Continue { .. }
|
||||
| StmtKind::Break { .. } => {
|
||||
let stmt_like = T::from_stmt(stmt);
|
||||
buf.push(stmt_like);
|
||||
return buf;
|
||||
}
|
||||
// Optimize if statement.
|
||||
StmtKind::If {
|
||||
test,
|
||||
consequent,
|
||||
alt,
|
||||
} => {
|
||||
// check if
|
||||
let node = match test.as_bool() {
|
||||
(Pure, Known(val)) => {
|
||||
if val {
|
||||
consequent.node
|
||||
} else {
|
||||
alt.map(|alt| alt.node).unwrap_or_else(|| StmtKind::Empty)
|
||||
}
|
||||
}
|
||||
// TODO: Impure
|
||||
_ => StmtKind::If {
|
||||
test,
|
||||
consequent,
|
||||
alt,
|
||||
},
|
||||
};
|
||||
Stmt { span, node }
|
||||
}
|
||||
_ => stmt,
|
||||
};
|
||||
|
||||
T::from_stmt(stmt)
|
||||
}
|
||||
Err(stmt_like) => stmt_like,
|
||||
};
|
||||
|
||||
buf.push(stmt_like);
|
||||
}
|
||||
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
impl Folder<StmtKind> for Simplify {
|
||||
fn fold(&mut self, stmt: StmtKind) -> StmtKind {
|
||||
let stmt = stmt.fold_children(self);
|
||||
|
||||
match stmt {
|
||||
// `1;` -> `;`
|
||||
StmtKind::Expr(box Expr { span, node }) => match node {
|
||||
ExprKind::Lit(Lit::Num(..))
|
||||
| ExprKind::Lit(Lit::Bool(..))
|
||||
| ExprKind::Lit(Lit::Regex(..)) => StmtKind::Empty,
|
||||
_ => StmtKind::Expr(box Expr { node, span }),
|
||||
},
|
||||
|
||||
StmtKind::Block(BlockStmt { span, stmts }) => {
|
||||
if stmts.len() == 0 {
|
||||
return StmtKind::Empty;
|
||||
} else if stmts.len() == 1 {
|
||||
// TODO: Check lexical variable
|
||||
return stmts.into_iter().next().unwrap().node;
|
||||
} else {
|
||||
StmtKind::Block(BlockStmt { span, stmts })
|
||||
}
|
||||
}
|
||||
|
||||
_ => stmt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StmtLike: Sized {
|
||||
fn try_into_stmt(self) -> Result<Stmt, Self>;
|
||||
fn from_stmt(stmt: Stmt) -> Self;
|
||||
}
|
||||
|
||||
impl StmtLike for Stmt {
|
||||
fn try_into_stmt(self) -> Result<Stmt, Self> {
|
||||
Ok(self)
|
||||
}
|
||||
fn from_stmt(stmt: Stmt) -> Self {
|
||||
stmt
|
||||
}
|
||||
}
|
||||
|
||||
impl StmtLike for ModuleItem {
|
||||
fn try_into_stmt(self) -> Result<Stmt, Self> {
|
||||
match self {
|
||||
ModuleItem::Stmt(stmt) => Ok(stmt),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
fn from_stmt(stmt: Stmt) -> Self {
|
||||
ModuleItem::Stmt(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
// impl Folder<Stmt> for Simplify {
|
||||
// fn fold(&mut self, stmt: Stmt) -> Stmt {
|
||||
// stmt.fold_children(&mut FoldConst)
|
||||
// }
|
||||
// }
|
336
ecmascript/transforms/src/util/mod.rs
Normal file
336
ecmascript/transforms/src/util/mod.rs
Normal file
@ -0,0 +1,336 @@
|
||||
pub use self::Purity::{MayBeImpure, Pure};
|
||||
pub use self::Value::{Known, Unknown};
|
||||
use ast::*;
|
||||
use std::borrow::Cow;
|
||||
use std::f64::{INFINITY, NAN};
|
||||
use std::num::FpCategory;
|
||||
use std::ops::{Add, Not};
|
||||
|
||||
pub trait ExprExt: Sized + AsRef<Expr> {
|
||||
///
|
||||
/// This method emulates the `Boolean()` JavaScript cast function.
|
||||
///Note: unlike getPureBooleanValue this function does not return `None`
|
||||
///for expressions with side-effects.
|
||||
fn as_bool(&self) -> (Purity, Bool) {
|
||||
let expr = self.as_ref();
|
||||
let val = match expr.node {
|
||||
ExprKind::Paren(ref e) => return e.as_bool(),
|
||||
ExprKind::Seq { ref exprs } => return exprs.last().unwrap().as_bool(),
|
||||
ExprKind::Assign { ref right, .. } => return right.as_bool(),
|
||||
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Bang,
|
||||
ref arg,
|
||||
} => {
|
||||
let (p, v) = arg.as_bool();
|
||||
return (p, !v);
|
||||
}
|
||||
|
||||
ExprKind::Binary {
|
||||
ref left,
|
||||
op: op @ BinaryOp::LogicalAnd,
|
||||
ref right,
|
||||
}
|
||||
| ExprKind::Binary {
|
||||
ref left,
|
||||
op: op @ BinaryOp::LogicalOr,
|
||||
ref right,
|
||||
} => {
|
||||
// TODO: Ignore purity if value cannot be reached.
|
||||
|
||||
let (lp, lv) = left.as_bool();
|
||||
let (rp, rv) = right.as_bool();
|
||||
|
||||
if lp + rp == Pure {
|
||||
return (Pure, lv.and(rv));
|
||||
}
|
||||
if op == BinaryOp::LogicalAnd {
|
||||
lv.and(rv)
|
||||
} else {
|
||||
lv.or(rv)
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Function(..)
|
||||
| ExprKind::Class(..)
|
||||
| ExprKind::New { .. }
|
||||
| ExprKind::Array { .. }
|
||||
| ExprKind::Object { .. } => Known(true),
|
||||
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Void,
|
||||
arg: _,
|
||||
} => Known(false),
|
||||
|
||||
ExprKind::Lit(ref lit) => {
|
||||
return (
|
||||
Pure,
|
||||
Known(match *lit {
|
||||
Lit::Num(Number(n)) => match n.classify() {
|
||||
FpCategory::Nan | FpCategory::Zero => false,
|
||||
_ => true,
|
||||
},
|
||||
Lit::Bool(b) => b,
|
||||
Lit::Str(ref s) => !s.is_empty(),
|
||||
Lit::Null => false,
|
||||
Lit::Regex(..) => true,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
//TODO?
|
||||
_ => Unknown,
|
||||
};
|
||||
|
||||
(MayBeImpure, val)
|
||||
}
|
||||
|
||||
/// Emulates javascript Number() cast function.
|
||||
fn as_number(&self) -> Value<f64> {
|
||||
let expr = self.as_ref();
|
||||
let v = match expr.node {
|
||||
ExprKind::Lit(ref l) => match *l {
|
||||
Lit::Bool(true) => 1.0,
|
||||
Lit::Bool(false) | Lit::Null => 0.0,
|
||||
Lit::Num(Number(n)) => n,
|
||||
Lit::Str(ref s) => return num_from_str(s),
|
||||
_ => return Unknown,
|
||||
},
|
||||
ExprKind::Ident(Ident { ref sym, .. }) => match &**sym {
|
||||
"undefined" | "NaN" => NAN,
|
||||
"Infinity" => INFINITY,
|
||||
_ => return Unknown,
|
||||
},
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Minus,
|
||||
arg:
|
||||
box Expr {
|
||||
span: _,
|
||||
node:
|
||||
ExprKind::Ident(Ident {
|
||||
sym: js_word!("Infinity"),
|
||||
..
|
||||
}),
|
||||
},
|
||||
} => -INFINITY,
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Bang,
|
||||
ref arg,
|
||||
} => match arg.as_bool() {
|
||||
(Pure, Known(v)) => {
|
||||
if v {
|
||||
0.0
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
_ => return Unknown,
|
||||
},
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Void,
|
||||
ref arg,
|
||||
} => {
|
||||
if arg.may_have_side_effects() {
|
||||
return Unknown;
|
||||
} else {
|
||||
NAN
|
||||
}
|
||||
}
|
||||
|
||||
ExprKind::Tpl(..) | ExprKind::Object { .. } | ExprKind::Array { .. } => {
|
||||
match self.as_string() {
|
||||
Some(ref s) => return num_from_str(s),
|
||||
None => return Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
_ => return Unknown,
|
||||
};
|
||||
|
||||
Known(v)
|
||||
}
|
||||
|
||||
fn as_string(&self) -> Option<Cow<str>> {
|
||||
let expr = self.as_ref();
|
||||
match expr.node {
|
||||
ExprKind::Lit(ref l) => match *l {
|
||||
Lit::Str(ref s) => Some(Cow::Borrowed(s)),
|
||||
Lit::Num(n) => Some(format!("{}", n).into()),
|
||||
Lit::Bool(true) => Some(Cow::Borrowed("true")),
|
||||
Lit::Bool(false) => Some(Cow::Borrowed("false")),
|
||||
Lit::Null => Some(Cow::Borrowed("null")),
|
||||
_ => None,
|
||||
},
|
||||
ExprKind::Tpl(_) => {
|
||||
// TODO:
|
||||
// Only convert a template literal if all its expressions can be converted.
|
||||
unimplemented!("TplLit.as_string()")
|
||||
}
|
||||
ExprKind::Ident(Ident { ref sym, .. }) => match &**sym {
|
||||
"undefined" | "Infinity" | "NaN" => Some(Cow::Borrowed(&**sym)),
|
||||
_ => None,
|
||||
},
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Void,
|
||||
..
|
||||
} => Some(Cow::Borrowed("undefined")),
|
||||
ExprKind::Unary {
|
||||
prefix: true,
|
||||
op: UnaryOp::Bang,
|
||||
ref arg,
|
||||
} => match arg.as_bool() {
|
||||
(Pure, Known(v)) => Some(Cow::Borrowed(if v { "false" } else { "true" })),
|
||||
_ => None,
|
||||
},
|
||||
ExprKind::Array { ref elems } => {
|
||||
let mut first = true;
|
||||
let mut buf = String::new();
|
||||
// null, undefined is "" in array literl.
|
||||
for elem in elems {
|
||||
let e = match *elem {
|
||||
Some(ref elem) => match *elem {
|
||||
ExprOrSpread::Expr(ref e) | ExprOrSpread::Spread(ref e) => match e.node
|
||||
{
|
||||
ExprKind::Lit(Lit::Null)
|
||||
| ExprKind::Ident(Ident {
|
||||
sym: js_word!("undefined"),
|
||||
..
|
||||
}) => Cow::Borrowed(""),
|
||||
_ => match e.as_string() {
|
||||
Some(s) => s,
|
||||
None => return None,
|
||||
},
|
||||
},
|
||||
},
|
||||
None => Cow::Borrowed(""),
|
||||
};
|
||||
buf.push_str(&e);
|
||||
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
buf.push(',');
|
||||
}
|
||||
}
|
||||
Some(buf.into())
|
||||
}
|
||||
ExprKind::Object { .. } => Some(Cow::Borrowed("[object Object]")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn num_from_str(s: &str) -> Value<f64> {
|
||||
if s.contains('\u{000b}') {
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
// TODO: Check if this is correct
|
||||
let s = s.trim();
|
||||
|
||||
if s.is_empty() {
|
||||
return Known(0.0);
|
||||
}
|
||||
|
||||
if s.starts_with("0x") || s.starts_with("0X") {
|
||||
return match s[2..4].parse() {
|
||||
Ok(n) => Known(n),
|
||||
Err(_) => Known(NAN),
|
||||
};
|
||||
}
|
||||
|
||||
if (s.starts_with('-') || s.starts_with('+'))
|
||||
&& (s[1..].starts_with("0x") || s[1..].starts_with("0X"))
|
||||
{
|
||||
// hex numbers with explicit signs vary between browsers.
|
||||
return Unknown;
|
||||
}
|
||||
|
||||
// Firefox and IE treat the "Infinity" differently. Firefox is case
|
||||
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
|
||||
match s {
|
||||
"infinity" | "+infinity" | "-infinity" => return Unknown,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Known(s.parse().ok().unwrap_or(NAN))
|
||||
}
|
||||
|
||||
impl<T: AsRef<Expr>> ExprExt for T {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Purity {
|
||||
MayBeImpure,
|
||||
Pure,
|
||||
}
|
||||
impl Purity {
|
||||
pub fn is_pure(self) -> bool {
|
||||
self == Pure
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Purity {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
match (self, rhs) {
|
||||
(Pure, Pure) => Pure,
|
||||
_ => MayBeImpure,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Value.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Value<T> {
|
||||
Known(T),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum Type {
|
||||
Str,
|
||||
Obj,
|
||||
Undetermined,
|
||||
}
|
||||
|
||||
pub type Bool = Value<bool>;
|
||||
|
||||
impl Value<bool> {
|
||||
pub fn and(self, other: Self) -> Self {
|
||||
match self {
|
||||
Known(true) => other,
|
||||
Known(false) => Known(false),
|
||||
Unknown => match other {
|
||||
Known(false) => Known(false),
|
||||
_ => Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or(self, other: Self) -> Self {
|
||||
match self {
|
||||
Known(true) => Known(true),
|
||||
Known(false) => other,
|
||||
Unknown => match other {
|
||||
Known(true) => Known(true),
|
||||
_ => Unknown,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for Value<bool> {
|
||||
type Output = Self;
|
||||
fn not(self) -> Self {
|
||||
match self {
|
||||
Value::Known(b) => Value::Known(!b),
|
||||
Value::Unknown => Value::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
62
ecmascript/transforms/tests/transforms.rs
Normal file
62
ecmascript/transforms/tests/transforms.rs
Normal file
@ -0,0 +1,62 @@
|
||||
extern crate swc_common;
|
||||
extern crate swc_ecma_ast as ast;
|
||||
extern crate swc_ecma_parser;
|
||||
extern crate swc_ecma_transforms as transforms;
|
||||
#[macro_use]
|
||||
extern crate testing;
|
||||
|
||||
use swc_common::fold::Folder;
|
||||
use swc_ecma_parser::lexer::Lexer;
|
||||
use swc_ecma_parser::parser::Parser;
|
||||
use testing::logger;
|
||||
use transforms::simplify::Simplify;
|
||||
|
||||
macro_rules! parse {
|
||||
($s:expr) => {{
|
||||
let l = logger();
|
||||
Parser::new_for_module(l.clone(), Lexer::new_from_str(l, $s))
|
||||
.parse_module()
|
||||
.unwrap_or_else(|err| panic!("failed to parse module: {:?}", err))
|
||||
}};
|
||||
}
|
||||
|
||||
fn fold<T, F>(t: T, mut f: F) -> T
|
||||
where
|
||||
F: Folder<T>,
|
||||
{
|
||||
f.fold(t)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_simple() {
|
||||
assert_eq_ignore_span!(fold(parse!("use(3 + 6)"), Simplify), parse!("use(9)"))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cond_simple() {
|
||||
assert_eq_ignore_span!(
|
||||
fold(parse!("use(true ? 3 : 6)"), Simplify),
|
||||
parse!("use(3)")
|
||||
);
|
||||
assert_eq_ignore_span!(
|
||||
fold(parse!("use(false ? 3 : 6)"), Simplify),
|
||||
parse!("use(6)")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cond_side_effect() {
|
||||
assert_eq_ignore_span!(
|
||||
fold(parse!("new UnknownClass() ? 3 : 6"), Simplify),
|
||||
parse!("new UnknownClass(), 3")
|
||||
);
|
||||
assert_eq_ignore_span!(
|
||||
fold(parse!("(void fn()) ? 3 : 6"), Simplify),
|
||||
parse!("(void fn()), 6")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oror_non_bool() {
|
||||
assert_eq_ignore_span!(fold(parse!("use(5 || 50)"), Simplify), parse!("use(5)"))
|
||||
}
|
Loading…
Reference in New Issue
Block a user