use hermes_ast::node::{AssignmentExpression, BinaryExpression, Node};
use hermes_ast::node_child::NodeLabel;
pub(crate) const MAX_NESTED_ASSIGNMENTS: u32 = 30000;
pub(crate) const MAX_NESTED_BINARY: u32 = 30000;
pub(crate) trait OperatorExpr<'gc>: Sized {
fn cast(node: &'gc Node<'gc>) -> Option<&'gc Self>;
fn operator(&self) -> NodeLabel;
fn left(&self) -> &'gc Node<'gc>;
fn right(&self) -> &'gc Node<'gc>;
}
impl<'gc> OperatorExpr<'gc> for BinaryExpression<'gc> {
fn cast(node: &'gc Node<'gc>) -> Option<&'gc Self> {
node.as_binary_expression()
}
fn operator(&self) -> NodeLabel {
self.operator.get()
}
fn left(&self) -> &'gc Node<'gc> {
self.left
}
fn right(&self) -> &'gc Node<'gc> {
self.right
}
}
impl<'gc> OperatorExpr<'gc> for AssignmentExpression<'gc> {
fn cast(node: &'gc Node<'gc>) -> Option<&'gc Self> {
node.as_assignment_expression()
}
fn operator(&self) -> NodeLabel {
self.operator.get()
}
fn left(&self) -> &'gc Node<'gc> {
self.left
}
fn right(&self) -> &'gc Node<'gc> {
self.right
}
}
fn check_expr_operator<'gc, N: OperatorExpr<'gc>>(
e: &'gc Node<'gc>,
ops: &[NodeLabel],
) -> Option<&'gc N> {
let n = N::cast(e)?;
if ops.contains(&n.operator()) {
Some(n)
} else {
None
}
}
pub(crate) fn linearize_left<'gc, N: OperatorExpr<'gc>>(
e: &'gc N,
ops: &[NodeLabel],
) -> Vec<&'gc N> {
let mut e = e;
let mut vec = vec![e];
while let Some(left) = check_expr_operator::<N>(e.left(), ops) {
e = left;
vec.push(e);
}
vec.reverse();
vec
}
pub(crate) fn linearize_right<'gc, N: OperatorExpr<'gc>>(
e: &'gc N,
ops: &[NodeLabel],
) -> Vec<&'gc N> {
let mut e = e;
let mut vec = vec![e];
while let Some(right) = check_expr_operator::<N>(e.right(), ops) {
e = right;
vec.push(e);
}
vec
}
#[cfg(test)]
mod tests {
use super::*;
use hermes_ast::context::{Context, GCLock};
use hermes_ast::node_child::NodeMetadata;
use hermes_support::location::{SMLoc, SMRange, SourceId};
fn r() -> SMRange {
let l = SMLoc {
source: SourceId::from_index(0),
offset: 0,
};
SMRange { start: l, end: l }
}
fn num<'gc>(gc: &'gc GCLock, v: f64) -> &'gc Node<'gc> {
gc.alloc(Node::NumericLiteral(hermes_ast::node::NumericLiteral::new(
NodeMetadata::new(r()),
v,
)))
}
fn bin<'gc>(
gc: &'gc GCLock,
left: &'gc Node<'gc>,
right: &'gc Node<'gc>,
op: &[u8],
) -> &'gc Node<'gc> {
gc.alloc(Node::BinaryExpression(BinaryExpression::new(
NodeMetadata::new(r()),
left,
right,
gc.atom_bytes(op),
)))
}
fn assign<'gc>(
gc: &'gc GCLock,
left: &'gc Node<'gc>,
right: &'gc Node<'gc>,
op: &[u8],
) -> &'gc Node<'gc> {
gc.alloc(Node::AssignmentExpression(AssignmentExpression::new(
NodeMetadata::new(r()),
gc.atom_bytes(op),
left,
right,
)))
}
#[test]
fn linearize_left_collects_the_left_spine() {
let mut ctx = Context::new();
let gc = ctx.lock();
let one_plus_two = bin(&gc, num(&gc, 1.0), num(&gc, 2.0), b"+");
let minus_three = bin(&gc, one_plus_two, num(&gc, 3.0), b"-");
let plus_four = bin(&gc, minus_three, num(&gc, 4.0), b"+");
let ops = [gc.atom_bytes(b"+"), gc.atom_bytes(b"-")];
let list =
linearize_left(plus_four.as_binary_expression().unwrap(), &ops);
let expected = [one_plus_two, minus_three, plus_four];
assert_eq!(list.len(), expected.len());
for (got, want) in list.iter().zip(expected.iter()) {
assert!(std::ptr::eq(
*got,
want.as_binary_expression().unwrap()
));
}
let innermost = one_plus_two.as_binary_expression().unwrap();
assert!(std::ptr::eq(list[0].left(), innermost.left()));
}
#[test]
fn linearize_left_stops_at_a_foreign_operator() {
let mut ctx = Context::new();
let gc = ctx.lock();
let times = bin(&gc, num(&gc, 1.0), num(&gc, 2.0), b"*");
let plus = bin(&gc, times, num(&gc, 3.0), b"+");
let ops = [gc.atom_bytes(b"+"), gc.atom_bytes(b"-")];
let list = linearize_left(plus.as_binary_expression().unwrap(), &ops);
assert_eq!(list.len(), 1);
assert!(std::ptr::eq(list[0], plus.as_binary_expression().unwrap()));
}
#[test]
fn linearize_right_collects_the_right_spine() {
let mut ctx = Context::new();
let gc = ctx.lock();
let last = num(&gc, 1.0);
let inner = assign(&gc, num(&gc, 3.0), last, b"=");
let middle = assign(&gc, num(&gc, 2.0), inner, b"=");
let outer = assign(&gc, num(&gc, 4.0), middle, b"=");
let ops = [gc.atom_bytes(b"=")];
let list =
linearize_right(outer.as_assignment_expression().unwrap(), &ops);
let expected = [outer, middle, inner];
assert_eq!(list.len(), expected.len());
for (got, want) in list.iter().zip(expected.iter()) {
assert!(std::ptr::eq(
*got,
want.as_assignment_expression().unwrap()
));
}
assert!(std::ptr::eq(list.last().unwrap().right(), last));
}
#[test]
fn linearize_right_stops_at_a_foreign_operator() {
let mut ctx = Context::new();
let gc = ctx.lock();
let inner = assign(&gc, num(&gc, 1.0), num(&gc, 2.0), b"+=");
let outer = assign(&gc, num(&gc, 3.0), inner, b"=");
let ops = [gc.atom_bytes(b"=")];
let list =
linearize_right(outer.as_assignment_expression().unwrap(), &ops);
assert_eq!(list.len(), 1);
assert!(std::ptr::eq(
list[0],
outer.as_assignment_expression().unwrap()
));
}
}