From 857a252feaf9da3ac371d5dcd1ed3337bbad1049 Mon Sep 17 00:00:00 2001 From: Loosetooth Date: Mon, 3 Feb 2025 18:22:26 +0100 Subject: [PATCH] WIP: math parser --- .../solution.ts | 130 ++++++++++++++++-- 1 file changed, 118 insertions(+), 12 deletions(-) diff --git a/2kyu/evaluate-mathematical-expression/solution.ts b/2kyu/evaluate-mathematical-expression/solution.ts index 9c4fc80..05a06d8 100644 --- a/2kyu/evaluate-mathematical-expression/solution.ts +++ b/2kyu/evaluate-mathematical-expression/solution.ts @@ -1,27 +1,133 @@ -type Token = number | '+' | '-' | '*' | '/' | '(' | ')'; - +type TokenType = 'number' | 'operator' | 'parenthesis'; +type TokenValue = number | '+' | '-' | '*' | '/' | '(' | ')'; -type ASTNode = { - type: string, +type Token = { + type: TokenType, + value: TokenValue, } -type OperatorNode = { +type OperatorChars = '+' | '-' | '*' | '/'; +type OperatorToken = Token & { type: 'operator', - value: '+' | '-' | '*' | '/', - left: ASTNode, - right: ASTNode, + value: OperatorChars, } -type NumberNode = { +const isOperatorChar = (char: string): char is OperatorChars => { + return char === '+' || char === '-' || char === '*' || char === '/'; +} + +type NumberToken = Token & { type: 'number', value: number, } -type GroupNode = { - type: 'group', - value: ASTNode, +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 = {