OverviewFilesCommitsIssuesDocsReleasesPeers

mirall-cli / node_modules / unbash / dist / arithmetic.js

461 lines · javascript ref:
import { CH_TAB, CH_NL, CH_SPACE, CH_BANG, CH_DOLLAR, CH_PERCENT, CH_AMP, CH_LPAREN, CH_RPAREN, CH_STAR, CH_PLUS, CH_COMMA, CH_DASH, CH_SLASH, CH_0, CH_9, CH_COLON, CH_LT, CH_EQ, CH_GT, CH_QUESTION, CH_A, CH_Z, CH_LBRACKET, CH_RBRACKET, CH_CARET, CH_UNDERSCORE, CH_a, CH_z, CH_LBRACE, CH_PIPE, CH_RBRACE, CH_TILDE, } from "./chars.js";
function opPrec(op) {
    switch (op) {
        case ",":
            return 1;
        case "=":
        case "+=":
        case "-=":
        case "*=":
        case "/=":
        case "%=":
        case "<<=":
        case ">>=":
        case "&=":
        case "|=":
        case "^=":
            return 2;
        case "||":
            return 4;
        case "&&":
            return 5;
        case "|":
            return 6;
        case "^":
            return 7;
        case "&":
            return 8;
        case "==":
        case "!=":
            return 9;
        case "<":
        case "<=":
        case ">":
        case ">=":
            return 10;
        case "<<":
        case ">>":
            return 11;
        case "+":
        case "-":
            return 12;
        case "*":
        case "/":
        case "%":
            return 13;
        case "**":
            return 14;
        default:
            return -1;
    }
}
function opRightAssoc(op) {
    switch (op) {
        case "=":
        case "+=":
        case "-=":
        case "*=":
        case "/=":
        case "%=":
        case "<<=":
        case ">>=":
        case "&=":
        case "|=":
        case "^=":
        case "**":
            return true;
        default:
            return false;
    }
}
let pendingArithCmdExps = null;
export function drainArithCmdExps() {
    const out = pendingArithCmdExps;
    pendingArithCmdExps = null;
    return out;
}
export function parseArithmeticExpression(src, offset = 0) {
    pendingArithCmdExps = null;
    let pos = 0;
    const len = src.length;
    function skipWS() {
        while (pos < len) {
            const c = src.charCodeAt(pos);
            if (c === CH_SPACE || c === CH_TAB || c === CH_NL)
                pos++;
            else
                break;
        }
    }
    function tryReadBinOp() {
        if (pos >= len)
            return null;
        const c = src.charCodeAt(pos);
        const nc = pos + 1 < len ? src.charCodeAt(pos + 1) : 0;
        const nnc = pos + 2 < len ? src.charCodeAt(pos + 2) : 0;
        switch (c) {
            case CH_COMMA:
                pos++;
                return ",";
            case CH_EQ:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "==";
                }
                pos++;
                return "=";
            case CH_BANG:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "!=";
                }
                return null; // unary
            case CH_LT:
                if (nc === CH_LT) {
                    if (nnc === CH_EQ) {
                        pos += 3;
                        return "<<=";
                    }
                    pos += 2;
                    return "<<";
                }
                if (nc === CH_EQ) {
                    pos += 2;
                    return "<=";
                }
                pos++;
                return "<";
            case CH_GT:
                if (nc === CH_GT) {
                    if (nnc === CH_EQ) {
                        pos += 3;
                        return ">>=";
                    }
                    pos += 2;
                    return ">>";
                }
                if (nc === CH_EQ) {
                    pos += 2;
                    return ">=";
                }
                pos++;
                return ">";
            case CH_PLUS:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "+=";
                }
                if (nc === CH_PLUS)
                    return null; // postfix/prefix, not binary
                pos++;
                return "+";
            case CH_DASH:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "-=";
                }
                if (nc === CH_DASH)
                    return null; // postfix/prefix, not binary
                pos++;
                return "-";
            case CH_STAR:
                if (nc === CH_STAR) {
                    pos += 2;
                    return "**";
                }
                if (nc === CH_EQ) {
                    pos += 2;
                    return "*=";
                }
                pos++;
                return "*";
            case CH_SLASH:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "/=";
                }
                pos++;
                return "/";
            case CH_PERCENT:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "%=";
                }
                pos++;
                return "%";
            case CH_PIPE:
                if (nc === CH_PIPE) {
                    pos += 2;
                    return "||";
                }
                if (nc === CH_EQ) {
                    pos += 2;
                    return "|=";
                }
                pos++;
                return "|";
            case CH_AMP:
                if (nc === CH_AMP) {
                    pos += 2;
                    return "&&";
                }
                if (nc === CH_EQ) {
                    pos += 2;
                    return "&=";
                }
                pos++;
                return "&";
            case CH_CARET:
                if (nc === CH_EQ) {
                    pos += 2;
                    return "^=";
                }
                pos++;
                return "^";
            case CH_QUESTION:
                pos++;
                return "?";
            default:
                return null;
        }
    }
    function parseBinExpr(minPrec) {
        let left = parseUnaryExpr();
        while (true) {
            skipWS();
            if (pos >= len)
                break;
            const saved = pos;
            const op = tryReadBinOp();
            if (!op)
                break;
            // Ternary
            if (op === "?") {
                if (3 < minPrec) {
                    pos = saved;
                    break;
                }
                const consequent = parseBinExpr(1);
                skipWS();
                if (pos < len && src.charCodeAt(pos) === CH_COLON)
                    pos++;
                const alternate = parseBinExpr(3);
                left = { type: "ArithmeticTernary", pos: left.pos, end: alternate.end, test: left, consequent, alternate };
                continue;
            }
            const prec = opPrec(op);
            if (prec < minPrec) {
                pos = saved;
                break;
            }
            const nextPrec = opRightAssoc(op) ? prec : prec + 1;
            const right = parseBinExpr(nextPrec);
            left = { type: "ArithmeticBinary", pos: left.pos, end: right.end, operator: op, left, right };
        }
        return left;
    }
    function parseUnaryExpr() {
        skipWS();
        if (pos >= len)
            return { type: "ArithmeticWord", pos: pos + offset, end: pos + offset, value: "" };
        const start = pos;
        const c = src.charCodeAt(pos);
        const nc = pos + 1 < len ? src.charCodeAt(pos + 1) : 0;
        // Prefix ++ --
        if (c === CH_PLUS && nc === CH_PLUS) {
            pos += 2;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "++", operand, prefix: true };
        }
        if (c === CH_DASH && nc === CH_DASH) {
            pos += 2;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "--", operand, prefix: true };
        }
        // Unary ! ~ + -
        if (c === CH_BANG) {
            pos++;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "!", operand, prefix: true };
        }
        if (c === CH_TILDE) {
            pos++;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "~", operand, prefix: true };
        }
        if (c === CH_PLUS && nc !== CH_PLUS && nc !== CH_EQ) {
            pos++;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "+", operand, prefix: true };
        }
        if (c === CH_DASH && nc !== CH_DASH && nc !== CH_EQ) {
            pos++;
            const operand = parseUnaryExpr();
            return { type: "ArithmeticUnary", pos: start + offset, end: operand.end, operator: "-", operand, prefix: true };
        }
        return parsePostfixExpr();
    }
    function parsePostfixExpr() {
        const operand = parseAtom();
        skipWS();
        if (pos + 1 < len) {
            const c = src.charCodeAt(pos);
            const nc = src.charCodeAt(pos + 1);
            if (c === CH_PLUS && nc === CH_PLUS) {
                pos += 2;
                return { type: "ArithmeticUnary", pos: operand.pos, end: pos + offset, operator: "++", operand, prefix: false };
            }
            if (c === CH_DASH && nc === CH_DASH) {
                pos += 2;
                return { type: "ArithmeticUnary", pos: operand.pos, end: pos + offset, operator: "--", operand, prefix: false };
            }
        }
        return operand;
    }
    function parseAtom() {
        skipWS();
        if (pos >= len)
            return { type: "ArithmeticWord", pos: pos + offset, end: pos + offset, value: "" };
        const c = src.charCodeAt(pos);
        // Parenthesized expression
        if (c === CH_LPAREN) {
            const start = pos;
            pos++;
            const expr = parseBinExpr(0);
            skipWS();
            if (pos < len && src.charCodeAt(pos) === CH_RPAREN)
                pos++;
            return { type: "ArithmeticGroup", pos: start + offset, end: pos + offset, expression: expr };
        }
        // Dollar expansion
        if (c === CH_DOLLAR) {
            return readDollarAtom();
        }
        // Number or variable name
        return readWordAtom();
    }
    function readDollarAtom() {
        const start = pos;
        pos++; // skip $
        if (pos >= len)
            return { type: "ArithmeticWord", pos: start + offset, end: pos + offset, value: "$" };
        const c = src.charCodeAt(pos);
        if (c === CH_LPAREN) {
            if (pos + 1 < len && src.charCodeAt(pos + 1) === CH_LPAREN) {
                // $(( nested arithmetic ))
                pos += 2;
                let depth = 1;
                while (pos < len && depth > 0) {
                    if (src.charCodeAt(pos) === CH_LPAREN && pos + 1 < len && src.charCodeAt(pos + 1) === CH_LPAREN) {
                        depth++;
                        pos += 2;
                    }
                    else if (src.charCodeAt(pos) === CH_RPAREN && pos + 1 < len && src.charCodeAt(pos + 1) === CH_RPAREN) {
                        depth--;
                        if (depth > 0)
                            pos += 2;
                        else
                            pos += 2;
                    }
                    else
                        pos++;
                }
            }
            else {
                // $( command substitution )
                pos++; // skip (
                let depth = 1;
                while (pos < len && depth > 0) {
                    const ch = src.charCodeAt(pos);
                    if (ch === CH_LPAREN)
                        depth++;
                    else if (ch === CH_RPAREN)
                        depth--;
                    pos++;
                }
                const text = src.slice(start, pos);
                const inner = text.slice(2, -1); // remove "$(" and ")"
                const node = {
                    type: "ArithmeticCommandExpansion",
                    pos: start + offset,
                    end: pos + offset,
                    text,
                    inner,
                    script: undefined,
                };
                (pendingArithCmdExps ??= []).push(node);
                return node;
            }
        }
        else if (c === CH_LBRACE) {
            // ${ parameter expansion }
            pos++;
            let depth = 1;
            while (pos < len && depth > 0) {
                const ch = src.charCodeAt(pos);
                if (ch === CH_LBRACE)
                    depth++;
                else if (ch === CH_RBRACE)
                    depth--;
                pos++;
            }
        }
        else {
            // $var
            while (pos < len) {
                const ch = src.charCodeAt(pos);
                if ((ch >= CH_a && ch <= CH_z) ||
                    (ch >= CH_A && ch <= CH_Z) ||
                    (ch >= CH_0 && ch <= CH_9) ||
                    ch === CH_UNDERSCORE)
                    pos++;
                else
                    break;
            }
        }
        return { type: "ArithmeticWord", pos: start + offset, end: pos + offset, value: src.slice(start, pos) };
    }
    function readWordAtom() {
        const start = pos;
        while (pos < len) {
            const c = src.charCodeAt(pos);
            if ((c >= CH_0 && c <= CH_9) ||
                (c >= CH_A && c <= CH_Z) ||
                (c >= CH_a && c <= CH_z) ||
                c === CH_UNDERSCORE ||
                c === 35 // # for base-N literals like 2#101
            ) {
                pos++;
            }
            else
                break;
        }
        // Array subscript: var[expr]
        if (pos > start && pos < len && src.charCodeAt(pos) === CH_LBRACKET) {
            pos++;
            let depth = 1;
            while (pos < len && depth > 0) {
                const c = src.charCodeAt(pos);
                if (c === CH_LBRACKET)
                    depth++;
                else if (c === CH_RBRACKET)
                    depth--;
                pos++;
            }
        }
        if (pos === start) {
            // Unknown character — advance to prevent infinite loop
            pos++;
            return { type: "ArithmeticWord", pos: start + offset, end: pos + offset, value: src.slice(start, pos) };
        }
        return { type: "ArithmeticWord", pos: start + offset, end: pos + offset, value: src.slice(start, pos) };
    }
    skipWS();
    if (pos >= len)
        return null;
    const result = parseBinExpr(0);
    // Check there's nothing important remaining
    skipWS();
    return result;
}