1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::collections::{HashMap, HashSet};

use num_traits::identities::{One, Zero};
use num_traits::sign::Signed;
use yolol_number::YololNumber;

use crate::ast::{InfixOp, PrefixOp, YololNode};
use crate::environment::Context;
use crate::graph::DepGraph;

/// Optimizes Yolol assign statements.
///
/// Optimization is idempotent when given the same context.
///
/// # Panics
///
/// Panics if any of the statements are malformed.
pub fn optimize(stmts: &[YololNode], context: &Context) -> Vec<YololNode> {
    let mut curr = stmts.to_vec();
    let mut next = reduce_constants(&curr);
    while curr != next {
        curr = next;
        next = reduce_constants(&curr);
    }
    eliminate_dead_code(&next, &context.exported())
}

fn reduce_constants(stmts: &[YololNode]) -> Vec<YololNode> {
    let mut reduced = Vec::new();
    let mut variables: HashMap<String, YololNode> = HashMap::new();
    for stmt in stmts.iter() {
        if let YololNode::AssignStmt { ident, expr } = stmt {
            variables.insert(ident.to_string(), *expr.clone());
            reduced.push(reduce_node(&variables, stmt));
        } else {
            panic!("expected Yolol statement, but got: {:?}", stmt);
        }
    }
    reduced
}

fn reduce_node(vars: &HashMap<String, YololNode>, node: &YololNode) -> YololNode {
    let zero = YololNumber::zero();
    let one = YololNumber::one();
    match node {
        YololNode::AssignStmt { ident, expr } => YololNode::AssignStmt {
            ident: ident.to_string(),
            expr: Box::new(reduce_node(vars, expr)),
        },
        YololNode::PrefixExpr { op, expr } => match (op, *expr.clone()) {
            // Fold literals
            (op, YololNode::Literal(y)) => match op {
                PrefixOp::Neg => YololNode::Literal(-y),
                PrefixOp::Not => YololNode::Literal(!y),
                PrefixOp::Abs => YololNode::Literal(y.abs()),
                PrefixOp::Sqrt if y >= zero => YololNode::Literal(y.sqrt()),
                PrefixOp::Sin => YololNode::Literal(y.sin()),
                PrefixOp::Cos => YololNode::Literal(y.cos()),
                PrefixOp::Tan if y.cos() != zero => YololNode::Literal(y.tan()),
                PrefixOp::Asin if y.abs() <= one => YololNode::Literal(y.asin()),
                PrefixOp::Acos if y.abs() <= one => YololNode::Literal(y.acos()),
                PrefixOp::Atan => YololNode::Literal(y.atan()),
                _ => node.clone(),
            },
            _ => YololNode::PrefixExpr {
                op: *op,
                expr: Box::new(reduce_node(vars, expr)),
            },
        },
        YololNode::InfixExpr { lhs, op, rhs } => match (*lhs.clone(), op, *rhs.clone()) {
            // Fold `0 + n` and `n + 0` to `n`
            (YololNode::Literal(y), InfixOp::Add, _) if y == zero => *rhs.clone(),
            (_, InfixOp::Add, YololNode::Literal(y)) if y == zero => *lhs.clone(),
            // Fold `n - 0` to `n`
            (_, InfixOp::Sub, YololNode::Literal(y)) if y == zero => *lhs.clone(),
            // Fold `0 * n` and `n * 0` to `0`
            (YololNode::Literal(y), InfixOp::Mul, _) if y == zero => *lhs.clone(),
            (_, InfixOp::Mul, YololNode::Literal(y)) if y == zero => *rhs.clone(),
            // Fold `1 * n` and `n * 1` to `n`
            (YololNode::Literal(y), InfixOp::Mul, _) if y == one => *rhs.clone(),
            (_, InfixOp::Mul, YololNode::Literal(y)) if y == one => *lhs.clone(),
            // Fold `n / 1` to `n`
            (_, InfixOp::Div, YololNode::Literal(y)) if y == one => *lhs.clone(),
            // Fold `1 ^ n` to `1`
            (YololNode::Literal(y), InfixOp::Exp, _) if y == one => *lhs.clone(),
            // Fold `n ^ 1` to `n`
            (_, InfixOp::Exp, YololNode::Literal(y)) if y == one => *lhs.clone(),
            // Fold literals
            (YololNode::Literal(y), op, YololNode::Literal(z)) => match op {
                InfixOp::Add => YololNode::Literal(y + z),
                InfixOp::Sub => YololNode::Literal(y - z),
                InfixOp::Mul => YololNode::Literal(y * z),
                InfixOp::Div if !z.is_zero() => YololNode::Literal(y / z),
                InfixOp::Mod if !z.is_zero() => YololNode::Literal(y % z),
                InfixOp::Exp if !(y.is_zero() && z.is_zero()) => YololNode::Literal(y.pow(z)),
                InfixOp::LessThan => YololNode::Literal(YololNumber::from(y < z)),
                InfixOp::LessEqual => YololNode::Literal(YololNumber::from(y <= z)),
                InfixOp::GreaterThan => YololNode::Literal(YololNumber::from(y > z)),
                InfixOp::GreaterEqual => YololNode::Literal(YololNumber::from(y >= z)),
                InfixOp::Equal => YololNode::Literal(YololNumber::from(y == z)),
                InfixOp::NotEqual => YololNode::Literal(YololNumber::from(y != z)),
                InfixOp::And => YololNode::Literal(YololNumber::from((y != zero) && (z != zero))),
                InfixOp::Or => YololNode::Literal(YololNumber::from((y != zero) || (z != zero))),
                _ => node.clone(),
            },
            _ => YololNode::InfixExpr {
                lhs: Box::new(reduce_node(vars, lhs)),
                op: *op,
                rhs: Box::new(reduce_node(vars, rhs)),
            },
        },
        // Propagate literals
        YololNode::Ident(s) => match vars.get(s) {
            Some(YololNode::Literal(y)) => YololNode::Literal(y.clone()),
            Some(node) => reduce_node(vars, node),
            // Ignore imported variable
            None => node.clone(),
        },
        YololNode::Literal(_) => node.clone(),
    }
}

fn eliminate_dead_code(stmts: &[YololNode], exported: &HashSet<String>) -> Vec<YololNode> {
    let graph = DepGraph::from_statements(stmts);
    let exported = graph.search_from(exported);
    let mut living = Vec::new();
    for stmt in stmts.iter() {
        if let YololNode::AssignStmt { ident, expr: _ } = stmt {
            if exported.contains(ident) {
                living.push(stmt.clone());
            }
        } else {
            panic!("expected Yolol statement, but got: {:?}", stmt)
        }
    }
    living
}