Quick actions

cmd+k|ctrl+k

Navigation

Languages

Calculator

Snippet info

Language

JavaScript

Visibility

public

Author

evine

Created

2020-11-05T21:54:25Z

Updated

2020-11-06T09:40:32Z

let fs = require("fs");

function start() {
    //let program = fs.readFileSync("/dev/stdin", "utf-8");
    let program = "- 40 + 2 * ( 3 - 20 )";
    
    let tokens = lex(program);
    //console.log(tokens);
    
    let ast = parse(tokens);
    //console.log(JSON.stringify(ast, null, 2));
    
    let output = run(ast);
    console.log(output);
}

function lex(program) {
    let result = [];
    
    let splitProgram = program
        .split(" ");
    
    for (let text of splitProgram) {
        let number = parseFloat(text);
        let token = {};
        if (!isNaN(number)) {
            token = {
                type: "number",
                value: number,
            };
        } else if (text == "+") {
            token = {
                type: "+",
            };
        } else if (text == "-") {
            token = {
                type: "-",
            };
        } else if (text == "*") {
            token = {
                type: "*",
            };
        } else if (text == "/") {
            token = {
                type: "/",
            };
        } else if (text == "(") {
            token = {
                type: "(",
            };
        } else if (text == ")") {
            token = {
                type: ")",
            };
        } else {
            console.log(`Lex: token "${text}" is not recognized`);
            throw "";
        }
        
        result.push(token);
    }
    
    
    result.push({
        type: "EOF",
    });
    
    return result;
}

function parse(tokens) {
    let i = 0;
    let stream = {
        pop: () => {
            let result = tokens[i];
            i += 1;
            return result;
        },
        peek: () => {
            let result = tokens[i];
            return result;
        },
        consume: () => {
            i += 1;
        },
    };
    
    let result = parseExpr(stream, 9999);
    
    return result;
}

function parseExpr(stream, prevPrecedence) {
    let t = stream.pop();
        
    let left;
    
    if (t.type == "number") {
        left = {
            type: t.type,
            value: t.value,
        };
    } else if (t.type == "-") {
        let expr = parseExpr(stream, 30);
        left = {
            type: "u-",
            expr: expr,
        };
    } else if (t.type == "(") {
        let expr = parseExpr(stream, 90);
        let closingToken = stream.pop();
        if(closingToken.type != ")") {
            console.log(`Parse: Expected ")", found: ${closingToken.type}`);
        }
        
        left = {
            type: "()",
            expr: expr,
        };
    } else {
        console.log(`Parse: Expected operand, found: ${t.type}`);
        throw "";
    }
    
    while(true) {
        let operator = stream.peek();
        if (isClosing(operator)) {
            return left;
        }
        
        let precedence = getPrecedence(operator);
        if (precedence >= prevPrecedence) {
            return left;
        }
        stream.consume();
        
        let right = parseExpr(stream, precedence);
        
        left = {
            type: operator.type,
            left: left,
            right: right,
        };
    }
}

function isClosing(operator) {
    switch(operator.type) {
        case ")": return true;
        case "EOF": return true;
    }
    
    return false;
}

function getPrecedence(operator) {
    switch(operator.type) {
        case "+": return 60;
        case "-": return 60;
        case "*": return 40;
        case "/": return 40;
    }
    
    console.log(`Parse: No precedence for operator "${operator.type}"`);
    throw "";
}

function run(ast) {
    let result = calculate(ast);
    
    return result;
}

function calculate(ast) {
    if (ast.type == "number") {
        return ast.value;
    } else if (ast.type == "+") {
        let left = calculate(ast.left);
        let right = calculate(ast.right);
        let result = left + right;
        return result;
    } else if (ast.type == "-") {
        let left = calculate(ast.left);
        let right = calculate(ast.right);
        let result = left - right;
        return result;
    } else if (ast.type == "*") {
        let left = calculate(ast.left);
        let right = calculate(ast.right);
        let result = left * right;
        return result;
    } else if (ast.type == "/") {
        let left = calculate(ast.left);
        let right = calculate(ast.right);
        let result = left / right;
        return result;
    } else if (ast.type == "()") {
        let result = calculate(ast.expr);
        return result;
    } else if (ast.type == "u-") {
        let expr = calculate(ast.expr);
        let result = -expr;
        return result;
    } else {
        console.log(`Run: Unxepected type: ${ast.type}`);
        throw "";
    }
}

start();
INFO