use ast::*;
use binjs_shared::{SharedString, VisitMe};
use EnrichError;
use std::cell::RefCell;
use std::rc::Rc;
type EnterResult = Result<VisitMe<PropertiesGuard>, EnrichError>;
type ExitResult<T> = Result<Option<T>, EnrichError>;
pub const DICTIONARY_NAME_PURE_DATA: &'static str = "pojo";
#[derive(Clone, Copy, Debug)]
enum Properties {
PureDataSoFar {
size: usize,
},
Complex,
}
impl Default for Properties {
fn default() -> Self {
Properties::Complex
}
}
pub struct InjectVisitor {
pure_data_threshold: usize,
stack: Rc<RefCell<Vec<Properties>>>,
}
struct PropertiesGuard {
stack: Rc<RefCell<Vec<Properties>>>,
}
impl PropertiesGuard {
fn new(stack: Rc<RefCell<Vec<Properties>>>, top: Properties) -> Self {
{
stack.borrow_mut().push(top);
}
PropertiesGuard { stack }
}
fn complex(visitor: &InjectVisitor, _path: &WalkPath) -> Self {
let top = Properties::Complex;
Self::new(visitor.stack.clone(), top)
}
fn pure_data(visitor: &InjectVisitor, size: usize, _path: &WalkPath) -> Self {
let top = Properties::PureDataSoFar { size };
Self::new(visitor.stack.clone(), top)
}
}
impl Drop for PropertiesGuard {
fn drop(&mut self) {
let mut stack = self.stack.borrow_mut();
let me = stack.pop().unwrap();
if stack.len() == 0 {
return;
}
let parent = stack.last_mut().unwrap();
let new_parent = match (&me, &*parent) {
(&Properties::Complex, _) | (_, &Properties::Complex) => Properties::Complex,
(
&Properties::PureDataSoFar { size: my_size },
&Properties::PureDataSoFar { size: parent_size },
) => Properties::PureDataSoFar {
size: my_size + parent_size,
},
};
*parent = new_parent
}
}
impl WalkGuard<InjectVisitor> for PropertiesGuard {
fn new(origin: &InjectVisitor, path: &WalkPath) -> Self {
PropertiesGuard::complex(origin, path)
}
}
impl InjectVisitor {
pub fn new(pure_data_threshold: usize) -> Self {
Self {
pure_data_threshold,
stack: Rc::new(RefCell::new(Vec::new())),
}
}
pub fn rewrite_script(
pure_data_threshold: usize,
script: &mut Script,
) -> Result<(), EnrichError> {
let mut visitor = Self::new(pure_data_threshold);
script.walk(&mut WalkPath::new(), &mut visitor)?;
Ok(())
}
fn properties(&self) -> Properties {
self.stack.borrow().last().unwrap().clone()
}
fn parent_properties(&self) -> Option<Properties> {
let stack = self.stack.borrow();
if stack.len() >= 2 {
Some(stack[stack.len() - 2].clone())
} else {
None
}
}
}
impl Visitor<EnrichError, PropertiesGuard> for InjectVisitor {
fn enter_literal<'a>(&mut self, path: &WalkPath, node: &mut ViewMutLiteral<'a>) -> EnterResult {
let _guard = if let ViewMutLiteral::LiteralInfinityExpression(_) = *node {
PropertiesGuard::complex(self, path)
} else {
PropertiesGuard::pure_data(self, 1, path)
};
Ok(VisitMe::DoneHere)
}
fn enter_property_name<'a>(
&mut self,
path: &WalkPath,
_node: &mut ViewMutPropertyName<'a>,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 0, path)))
}
fn enter_object_property<'a>(
&mut self,
path: &WalkPath,
_node: &mut ViewMutObjectProperty<'a>,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 0, path)))
}
fn enter_literal_property_name(
&mut self,
path: &WalkPath,
_node: &mut LiteralPropertyName,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 1, path)))
}
fn enter_object_expression(
&mut self,
path: &WalkPath,
_node: &mut ObjectExpression,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 1, path)))
}
fn enter_data_property(&mut self, path: &WalkPath, _node: &mut DataProperty) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 1, path)))
}
fn enter_expression_or_spread_element<'a>(
&mut self,
path: &WalkPath,
_node: &mut ViewMutExpressionOrSpreadElement<'a>,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 1, path)))
}
fn enter_array_expression(
&mut self,
path: &WalkPath,
_node: &mut ArrayExpression,
) -> EnterResult {
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 1, path)))
}
fn enter_expression<'a>(
&mut self,
path: &WalkPath,
node: &mut ViewMutExpression<'a>,
) -> EnterResult {
match *node {
ViewMutExpression::LiteralNumericExpression(_)
| ViewMutExpression::ObjectExpression(_)
| ViewMutExpression::LiteralStringExpression(_)
| ViewMutExpression::LiteralNullExpression(_)
| ViewMutExpression::LiteralBooleanExpression(_)
| ViewMutExpression::ArrayExpression(_) =>
{
Ok(VisitMe::HoldThis(PropertiesGuard::pure_data(self, 0, path)))
}
_ =>
{
Ok(VisitMe::HoldThis(PropertiesGuard::complex(self, path)))
}
}
}
fn exit_expression<'a>(
&mut self,
_path: &WalkPath,
node: &mut ViewMutExpression<'a>,
) -> ExitResult<Expression> {
if let Properties::PureDataSoFar { size } = self.properties() {
match self.parent_properties() {
None | Some(Properties::Complex) => {
if size >= self.pure_data_threshold {
return Ok(Some(
BinASTExpressionWithProbabilityTable {
table: SharedString::from_str(DICTIONARY_NAME_PURE_DATA),
expression: node.steal(),
}
.into(),
));
}
}
_ => {}
}
}
Ok(None)
}
}
pub struct CleanupVisitor;
impl CleanupVisitor {
pub fn rewrite_script(script: &mut Script) {
let mut visitor = Self;
script
.walk(&mut WalkPath::new(), &mut visitor)
.expect("Could not walk script");
}
}
impl Visitor<()> for CleanupVisitor {
fn exit_expression<'a>(
&mut self,
_path: &WalkPath,
node: &mut ViewMutExpression<'a>,
) -> Result<Option<Expression>, ()> {
if let ViewMutExpression::BinASTExpressionWithProbabilityTable(_) = *node {
if let Expression::BinASTExpressionWithProbabilityTable(scoped) = node.steal() {
Ok(Some(scoped.expression))
} else {
panic!();
}
} else {
Ok(None)
}
}
}
#[cfg(test)]
mod test {
use ast::*;
use binjs_shared::*;
use sublanguages::{CleanupVisitor, InjectVisitor, DICTIONARY_NAME_PURE_DATA};
fn check(threshold: usize, source: Script, expected: Script) {
let mut injected = source.clone();
InjectVisitor::rewrite_script(threshold, &mut injected).unwrap();
assert_eq!(injected, expected);
let mut cleaned = injected.clone();
CleanupVisitor::rewrite_script(&mut cleaned);
assert_eq!(source, cleaned);
}
#[test]
fn test_sublanguage_pure_data_positive() {
let source = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: ArrayExpression {
elements: vec![
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralNullExpression {}.into()),
Some(LiteralNumericExpression { value: 5. }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
],
}
.into(),
}
.into()],
};
let expected = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: BinASTExpressionWithProbabilityTable {
table: SharedString::from_str(DICTIONARY_NAME_PURE_DATA),
expression: ArrayExpression {
elements: vec![
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralNullExpression {}.into()),
Some(LiteralNumericExpression { value: 5. }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
],
}
.into(),
}
.into(),
}
.into()],
};
check(5, source, expected);
}
#[test]
fn test_sublanguage_pure_data_positive_object() {
let source = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: ObjectExpression {
properties: vec![
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("a"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("b"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("c"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("d"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
],
}
.into(),
}
.into()],
};
let expected = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: BinASTExpressionWithProbabilityTable {
table: SharedString::from_str(DICTIONARY_NAME_PURE_DATA),
expression: ObjectExpression {
properties: vec![
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("a"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("b"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("c"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("d"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
],
}
.into(),
}
.into(),
}
.into()],
};
check(5, source, expected);
}
#[test]
fn test_sublanguage_pure_data_negative_with_infinity() {
let source = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: ArrayExpression {
elements: vec![
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralInfinityExpression {}.into()),
Some(LiteralNumericExpression { value: 5. }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
],
}
.into(),
}
.into()],
};
check(5, source.clone(), source);
}
#[test]
fn test_sublanguage_pure_data_negative_with_this() {
let source = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: ArrayExpression {
elements: vec![
Some(LiteralBooleanExpression { value: true }.into()),
Some(ThisExpression {}.into()),
Some(LiteralNumericExpression { value: 5. }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
Some(LiteralBooleanExpression { value: true }.into()),
],
}
.into(),
}
.into()],
};
check(5, source.clone(), source);
}
#[test]
fn test_sublanguage_pure_data_negative_object_with_this() {
let source = Script {
scope: Default::default(),
directives: vec![],
statements: vec![ExpressionStatement {
expression: ObjectExpression {
properties: vec![
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("a"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("b"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("c"),
}
.into(),
expression: LiteralBooleanExpression { value: true }.into(),
}
.into(),
DataProperty {
name: LiteralPropertyName {
value: SharedString::from_str("d"),
}
.into(),
expression: ThisExpression {}.into(),
}
.into(),
],
}
.into(),
}
.into()],
};
check(5, source.clone(), source);
}
}