type TokenType = 'number' | 'operator' | 'parenthesis'; type TokenValue = number | '+' | '-' | '*' | '/' | '(' | ')'; type Token = { type: TokenType, value: TokenValue, } type OperatorChars = '+' | '-' | '*' | '/'; type OperatorToken = Token & { type: 'operator', value: OperatorChars, } const isOperatorChar = (char: string): char is OperatorChars => { return char === '+' || char === '-' || char === '*' || char === '/'; } type NumberToken = Token & { type: 'number', value: number, } type ParenthesisChars = '(' | ')'; type ParenthesisToken = Token & { type: 'parenthesis', value: ParenthesisChars, } const isParenthesisChar = (char: string): char is ParenthesisChars => { return char === '(' || char === ')'; } const tokenize = (expression: string): Token[] => { const tokens: Token[] = []; const chars = expression.split(''); let done = false; let i = 0; while (!done) { if (i >= chars.length) { done = true; continue; } const char = chars[i]; if (char === ' ') { i++; continue; } // parenthesis if (isParenthesisChar(char)) { tokens.push({ type: 'parenthesis', value: char }); i++; continue; } // operator if (isOperatorChar(char)) { tokens.push({ type: 'operator', value: char }); i++; continue; } // number if (char.match(/\d/)) { let number = ''; while (chars[i].match(/\d/)) { number += chars[i]; i++; } tokens.push({ type: 'number', value: parseInt(number) }); } else { throw new Error('Invalid character'); } } return tokens; } abstract class ASTNodeCommon { abstract readonly type: string; abstract get value(): number; } type ASTNode = NumberNode | OperatorNode | GroupNode | NegationNode; class NumberNode extends ASTNodeCommon { private _value: number; type = 'number'; constructor(public numValue: number) { super(); this._value = numValue; } get value() { return this._value; } } class OperatorNode extends ASTNodeCommon { type = 'operator'; constructor(public operator: OperatorChars, public left: ASTNode, public right: ASTNode) { super(); } get value() { const left = this.left.value; const right = this.right.value; switch (this.operator) { case '+': return left + right; case '-': return left - right; case '*': return left * right; case '/': return left / right; default: throw new Error('Invalid operator'); } } } class GroupNode extends ASTNodeCommon { type = 'group'; constructor(public value: ASTNode) { super(); } } type NegationNode = { type: 'negation', value: ASTNode, } export function calc(expression: string): number { // evaluate `expression` and return result return 0; }