142 lines
2.8 KiB
TypeScript
142 lines
2.8 KiB
TypeScript
|
|
|
|
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;
|
|
}
|