WIP: math parser
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user