All files / transform/passes FoldBinaryIntegerExpression.js

6.67% Statements 2/30
0% Branches 0/19
33.33% Functions 1/3
10% Lines 2/20
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          1x                                                                                                 19x                                                                  
import Transformation from '../transformation';
import TokenType from '../../parser/vsltokentype';
import t from '../../parser/nodes';
 
/** @private */
const MAX_SAFE_LEN = Number.MAX_SAFE_INTEGER.toString().length - 1;
 
/** @private */
function isSafeInteger(node: Node): bool {
    return node instanceof t.Literal && node.type === TokenType.Integer &&
        node.literal.length <= MAX_SAFE_LEN;
}
 
/**
 * Transforms any binary expressions with raw `VSLNodeType.Integer`s ({@link VSLNodeType})
 * 
 * Note: This won't apply for complex intger types as those wouldn't be the same
 * AST node as {@link Literal} only applies to simple literals.
 * 
 * This will transform integers within the bounds of a private `MAX_SAFE_LEN` which
 * is 15 on most platforms, set to: `Number.MAX_SAFE_INTEGER.toString().length - 1`.
 * A number is checked on whether it's string length exceedes this range. Due to 
 * integer promotion, it is not exactly safe to rely on the bounded Integer type
 * and therefore string types are used.
 * 
 * Not all operations are supported as some operations may product output which
 * is larger than the applicable computation boundry as to avoid overflow or loss
 * of precision.
 * 
 * This supports the operators:
 *  - `+`
 *  - `-`
 *  - `*`
 *  - `/`
 *  - `%`
 *  - `&`
 *  - `|`
 *  - `^`
 *  - `>>`
 *  - `<<`
 * 
 * @example
 * BinaryExpression {
 *   lhs: Literal { literal: "1", type: Integer }, 
 *   rhs: Literal { literal: "1", type: Integer },
 *   op: "+"
 * }
 * 
 * =>
 * 
 * Literal { literal: "2", type: Integer }
 */
export default class FoldBinaryIntegerExpression extends Transformation {
    constructor() {
        super(t.BinaryExpression, "Optimize::FoldBinaryIntegerExpression");
    }
    
    modify(node: Node, tool: ASTTool) {
        if (isSafeInteger(node.lhs) && isSafeInteger(node.rhs)) {
            let replacement = null,
                lhs = +node.lhs.literal,
                rhs = +node.rhs.literal;
            
            switch (node.op.value) {
                case "+": replacement = lhs + rhs; break;
                case "-": replacement = lhs - rhs; break;
                case "*": replacement = lhs * rhs; break;
                case "/": replacement = lhs / rhs; break;
                case "%": replacement = lhs % rhs; break;
                
                case "&": replacement = lhs & rhs; break;
                case "|": replacement = lhs | rhs; break;
                case "^": replacement = lhs ^ rhs; break;
                
                case ">>": replacement = lhs >> rhs; break;
                case "<<": replacement = lhs << rhs; break;
            }
            
            if (replacement != null) {
                tool.replace(new t.Literal(
                    replacement.toString(),
                    TokenType.Integer,
                    node.position
                ));
            }
        }
    }
}