import ExpressionType from '../enums/ExpressionType' import TokenType from '../enums/TokenType' import JSONArray from '../instances/JSONArray' import Delegate from './Delegate' import Expression from './Expression' import Token from './Token' /** * 表达式入口 */ export default class Program { // 源程序 public readonly SOURCE: string public readonly SOURCE_LENGTH: number /** * Token队列,用于回退 */ private readonly tokens: Token[] = [] /** * 字符串处理环节堆栈,$进入字符串处理环节,“{}”中间部分脱离字符串处理环节 */ private readonly inStrings: boolean[] = [] /** * 当前获取到的字符位置 */ private pos: number = 0 /** * 当前是否在字符串处理环节 */ private inString: boolean = false constructor(source: string) { this.SOURCE = source this.SOURCE_LENGTH = this.SOURCE.length } /** * 调用解析过程 */ public parse(): Delegate { // 执行完以下方法后就已经诞生了一个完整的执行流程,也就是被翻译成了一套完整的指令集。 return this.createDelegate(this.expression()) } public parseExpression(expression: Expression): Delegate { // 执行完以下方法后就已经诞生了一个完整的执行流程,也就是被翻译成了一套完整的指令集。 return this.createDelegate(expression) } /** * 编译,产生可执行单元Delegate */ public createDelegate(expression: Expression): Delegate { return new Delegate(expression, this.SOURCE) } /** * 获取完整表达式 */ public expression(): Expression { const expression = this.CommaExp() if (this.pos < this.SOURCE_LENGTH) { throw new Error(this.GetExceptionMessage('需要逗号结尾!')) } return expression } /** * 逗号表达式=赋值表达式(,赋值表达式)* */ private CommaExp(): Expression { const exps: Expression[] = [] const first: Expression = this.AssignExp() exps.push(first) let t: Token = this.GetToken() // 没有结束 while (t.getType() === TokenType.Oper && (t.getValue() === ',' || t.getValue() === ';' || t.getValue() === '\n')) { const r: Expression = this.AssignExp() exps.push(r) t = this.GetToken() } this.tokens.push(t) return Expression.Comma(exps, this.pos) } /** * 赋值表达式=对象属性=一般表达式|一般表达式 */ private AssignExp(): Expression { let t: Token = this.GetToken() // 把第一个Token的位置记录下来,方便后面回退 const firstPos: number = t.getStartPos() if (t.getType() !== TokenType.Identy) { this.pos = firstPos this.tokens.length = 0 // Clear the tokens array this.inString = false return this.Exp() } let objExp: Expression = Expression.Identity(t.getValue(), this.pos) objExp = this.ObjectPath(objExp) // 只能给对象属性或者变量赋值 if (objExp.type !== ExpressionType.Property && objExp.type !== ExpressionType.Identity) { this.pos = firstPos this.tokens.length = 0 this.inString = false return this.Exp() } t = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== '=') { this.pos = firstPos this.tokens.length = 0 this.inString = false return this.Exp() } const exp: Expression = this.Exp() // 如果是属性赋值 if (objExp.type === ExpressionType.Property) { // 从属性Expression中获取对象及属性名 const name: string = objExp.value objExp = objExp.children[0] as Expression return Expression.Assign(objExp, exp, name, this.pos) } else { // Identity 变量赋值 return Expression.Assign(null, exp, objExp.value, this.pos) } } private Exp(): Expression { const v: Expression = this.Logic() // 是':',表示条件,否则直接返回单结果 let t: Token = this.GetToken() if (t.getType() === TokenType.Oper && t.getValue() === ':') { // 第一项转换 const result: Expression = this.Logic() // 下一个是",",继续读取下一个条件结果串,由于是右结合,只能采用递归 t = this.GetToken() if (t.getType() === TokenType.Oper && t.getValue() === ',') { // 第二项转换 const sExp: Expression = this.Exp() // 返回 return Expression.Condition(v, result, sExp, this.SOURCE, this.pos) } else { throw new Error(this.GetExceptionMessage('必须有默认值!')) } } else { this.tokens.push(t) // Put the token back return v } } private Logic(): Expression { let t: Token = this.GetToken() // !表达式 const hasNot: boolean = t.getType() === TokenType.Oper && t.getValue() === '!' if (!hasNot) { this.tokens.push(t) // Put the token back if it's not '!' } let v: Expression = this.Compare() if (hasNot) { v = Expression.Not(v, this.SOURCE, this.pos) } t = this.GetToken() while (t.getType() === TokenType.Oper && (t.getValue() === '&&' || t.getValue() === '||')) { // 第二项转换 const exp: Expression = this.Logic() // 执行 if (t.getValue() === '&&') { v = Expression.And(v, exp, this.SOURCE, this.pos) } else { v = Expression.Or(v, exp, this.SOURCE, this.pos) } t = this.GetToken() } this.tokens.push(t) // Put the token back return v } private Compare(): Expression { const left: Expression = this.Math() const t: Token = this.GetToken() if (t.getType() === TokenType.Oper && ( t.getValue() === '>' || t.getValue() === '>=' || t.getValue() === '<' || t.getValue() === '<=' || t.getValue() === '=>')) { if (t.getValue() === '=>') { this.tokens.push(t) // Push back the token for lambda handling return this.Lambda() } const rExp: Expression = this.Math() switch (t.getValue()) { case '>': return Expression.GreaterThan(left, rExp, this.SOURCE, this.pos) case '>=': return Expression.GreaterThanOrEqual(left, rExp, this.SOURCE, this.pos) case '<': return Expression.LessThan(left, rExp, this.SOURCE, this.pos) case '<=': return Expression.LessThanOrEqual(left, rExp, this.SOURCE, this.pos) } } else if (t.getType() === TokenType.Oper && ( t.getValue() === '==' || t.getValue() === '!=')) { const rExp: Expression = this.Math() // 相等比较 if (t.getValue() === '==') { return Expression.Equal(left, rExp, this.SOURCE, this.pos) } else { return Expression.NotEqual(left, rExp, this.SOURCE, this.pos) } } // 返回当前的表达式结果 this.tokens.push(t) // Push the token back return left } private Math(): Expression { let v: Expression = this.Mul() let t: Token = this.GetToken() while (t.getType() === TokenType.Oper && (t.getValue() === '+' || t.getValue() === '-')) { // 转换操作数2为数字 const r: Expression = this.Mul() // 开始运算 if (t.getValue() === '+') { v = Expression.Add(v, r, this.SOURCE, this.pos) } else { v = Expression.Subtract(v, r, this.SOURCE, this.pos) } t = this.GetToken() } this.tokens.push(t) // Push the token back return v } private Lambda(): Expression { let t: Token = this.GetToken() if (t.getValue() !== '=>' || t.getValue() == null) { throw new Error(this.GetExceptionMessage('lambda必须以=>开始')) } t = this.GetToken() if (t.getValue() !== '{' || t.getValue() == null) { throw new Error(this.GetExceptionMessage('lambda必须以{开始')) } // 取lambda中的表达式 const exp: Expression = this.CommaExp() t = this.GetToken() if (t.getValue() !== '}' || t.getValue() == null) { throw new Error(this.GetExceptionMessage('lambda必须以}结束')) } return Expression.Lambda(exp, this.pos) } private Mul(): Expression { let v: Expression = this.UnarySub() let t: Token = this.GetToken() while (t.getType() === TokenType.Oper && (t.getValue() === '*' || t.getValue() === '/' || t.getValue() === '%')) { // Convert the second operand to a number const r: Expression = this.UnarySub() // Perform the operation if (t.getValue() === '*') { v = Expression.Multiply(v, r, this.SOURCE, this.pos) } else if (t.getValue() === '/') { v = Expression.Divide(v, r, this.SOURCE, this.pos) } else { v = Expression.Modulo(v, r, this.SOURCE, this.pos) } t = this.GetToken() } this.tokens.push(t) return v } private UnarySub(): Expression { const t: Token = this.GetToken() if (t.getType() === TokenType.Oper && t.getValue() === '-') { const r: Expression = this.Item() return Expression.Subtract(Expression.Constant(0, this.pos), r, this.SOURCE, this.pos) } this.tokens.push(t) // Push the token back into the queue return this.Item() } private Item(): Expression { const objName: string = '' // 获取对象表达式 let objExp: Expression = this.ItemHead(objName) // 获取对象路径表达式 objExp = this.ObjectPath(objExp) return objExp } private ItemHead(name: string): Expression { let t: Token = this.GetToken() if (t.getType() === TokenType.Oper && t.getValue() === '(') { const result: Expression = this.CommaExp() t = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== ')') { throw new Error(this.GetExceptionMessage('括号不匹配')) } return result } else if (t.getType() === TokenType.Oper && t.getValue() === '{') { // JSON object return this.Json() } else if (t.getType() === TokenType.Oper && t.getValue() === '[') { // JSON array return this.JsonArray() } else if (t.getType() === TokenType.Oper && (t.getValue() === '$' || t.getValue() === '"')) { // String concatenation sequence return this.StringUnion() } else if (t.getType() === TokenType.Int || t.getType() === TokenType.Double || t.getType() === TokenType.Bool) { return Expression.Constant(t.getValue(), this.pos) } else if (t.getType() === TokenType.Identy) { const objName: string = t.getValue() switch (objName) { case 'return': return Expression.Return(this.Exp(), this.pos) case 'try': this.tokens.push(t) // Revert the token back to the queue return this.Try() case 'throw': return Expression.Throw(this.Exp(), this.pos) case 'validate': return this.Validate() case 'assert': return Expression.Assert(this.Exp(), this.pos) case 'break': return Expression.Break(this.pos, undefined) case 'continue': return Expression.Continue(this.pos, undefined) } // Return the object name name += objName // Process the object name return this.ObjectName(objName) } else if (t.getType() === TokenType.Null) { return Expression.Constant(null, this.pos) } throw new Error(this.GetExceptionMessage(`单词类型错误:${t.getType()}`)) } /** * TryCatch := try {expression} (catch(Exception类 名){表达式})* finally{表达式}? */ private Try(): Expression { let t: Token = this.GetToken() if (t.getValue() !== 'try') { throw new Error(this.GetExceptionMessage('try-catch必须以try开始')) } t = this.GetToken() if (t.getValue() !== '{') { throw new Error(this.GetExceptionMessage('try块必须以{开始')) } // Get the expression inside the try block const tryExp: Expression = this.CommaExp() t = this.GetToken() if (t.getValue() !== '}') { throw new Error(this.GetExceptionMessage('try块必须以}结束')) } // Handle the catch part const catches: Expression[] = this.Catch() // Handle finally let finallyExp: Expression | undefined t = this.GetToken() if (t.getValue()) { if (t.getValue() === 'finally') { t = this.GetToken() if (t.getValue() !== '{') { throw new Error(this.GetExceptionMessage('finally块必须以{开始')) } // Get the expression inside the finally block finallyExp = this.CommaExp() t = this.GetToken() if (t.getValue() !== '}') { throw new Error(this.GetExceptionMessage('finally块必须以}结束')) } t = this.GetToken() } } this.tokens.push(t) // Return the exception handling expression return Expression.Try(tryExp, catches, this.pos, finallyExp) } /** * catch部分处理 */ private Catch(): Expression[] { const result: Expression[] = [] let t: Token = this.GetToken() while (t.getValue() === 'catch') { t = this.GetToken() if (t.getValue() !== '(') { throw new Error(this.GetExceptionMessage('catch块参数必须以(开始')) } // Get the class name t = this.GetToken() const className: string = t.getValue() // Get the variable name t = this.GetToken() const varName: string = t.getValue() t = this.GetToken() if (t.getValue() !== ')') { throw new Error(this.GetExceptionMessage('catch块参数必须以)结束')) } // Get the opening curly brace t = this.GetToken() if (t.getValue() !== '{') { throw new Error(this.GetExceptionMessage('catch块必须以{开始')) } // Get the expression inside the catch block const catchExp: Expression = this.CommaExp() result.push(Expression.Catch(className, varName, catchExp, this.pos)) // Get the closing curly brace t = this.GetToken() if (t.getValue() !== '}') { throw new Error(this.GetExceptionMessage('catch块必须以}结束')) } // Get the next token t = this.GetToken() } this.tokens.push(t) return result } /** * Validate block handling */ private Validate(): Expression { const t: Token = this.GetToken() if (t.getValue() !== '{') { throw new Error(this.GetExceptionMessage('validate只接收Json对象')) } // Get the expression in the validate block const exp: Expression = this.Json() // Return Validate processing expression return Expression.Validate(exp, this.pos) } /** * JSON Object ::= {} | {propertyName: propertyValue, propertyName: propertyValue} */ private Json(): Expression { const children: Expression[] = [] let t: Token = this.GetToken() // Empty object, return directly if (t.getType() === TokenType.Oper && t.getValue() === '}') { return Expression.Json(children, this.pos) } this.tokens.push(t) children.push(this.Attr()) t = this.GetToken() // If it's a comma, continue checking the next property while (t.getType() === TokenType.Oper && t.getValue() === ',') { const nextT: Token = this.GetToken() if (nextT.getType() === TokenType.Oper && nextT.getValue() === '}') { t = nextT break } this.tokens.push(nextT) children.push(this.Attr()) t = this.GetToken() } if (t.getType() !== TokenType.Oper || t.getValue() !== '}') { throw new Error(this.GetExceptionMessage('JSON对象构建错误')) } return Expression.Json(children, this.pos) } /** * JSON Array ::= [] */ private JsonArray(): Expression { let t: Token = this.GetToken() // Empty array, return directly if (t.getType() === TokenType.Oper && t.getValue() === ']') { // Return JSON array constant return Expression.Constant(new JSONArray(), this.pos) } // Loop to get array contents this.tokens.push(t) const ps: Expression[] = this.Params() t = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== ']') { throw new Error(this.GetExceptionMessage('JSON集合构建错误')) } return Expression.Array(ps, this.pos) } /** * Attribute-Value Pair ::= propertyName: propertyValue */ private Attr(): Expression { const name: Token = this.GetToken() let value: string if (name.getType() === TokenType.Identy) { value = name.getValue() } else if (name.getType() === TokenType.Oper && (name.getValue() === '$' || name.getValue() === '"')) { const expression: Expression = this.StringUnion().children[1] as Expression value = expression.value } else { throw new Error(this.GetExceptionMessage('JSON对象的key必须是属性名或字符串')) } const t: Token = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== ':') { throw new Error(this.GetExceptionMessage('JSON构建错误')) } const exp: Expression = this.Exp() return Expression.Attr(value, exp, this.pos) } /** * Parse string concatenation sequence */ private StringUnion(): Expression { let exp: Expression = Expression.Constant('', this.pos) let t: Token = this.GetToken() // Object sequence while ((t.getType() === TokenType.Oper && t.getValue() === '{') || t.getType() === TokenType.String) { // If it's a string, return the concatenated result if (t.getType() === TokenType.String) { exp = Expression.Concat(exp, Expression.Constant(t.getValue(), this.pos), this.SOURCE, this.pos) } else { // Handle content inside the {} const objExp: Expression = this.Exp() t = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== '}') { throw new Error(this.GetExceptionMessage('缺少\'}\'')) } // String concatenation exp = Expression.Concat(exp, objExp, this.SOURCE, this.pos) } t = this.GetToken() } this.tokens.push(t) return exp } /** * Get object and object expression by name */ private ObjectName(objName: string): Expression { return Expression.Identity(objName, this.pos) } /** * Object path parsing, handles method calls, properties, and array indices */ private ObjectPath(inExp: Expression): Expression { let objExp: Expression = this.ArrayIndex(inExp) let t: Token = this.GetToken() while (t.getType() === TokenType.Oper && t.getValue() === '.') { const nameToken: Token = this.GetToken() const n: Token = this.GetToken() if (n.getType() === TokenType.Oper && n.getValue() === '(') { const name: string = nameToken.getValue() if (name === 'each') { objExp = this.For(objExp) } else { objExp = this.MethodCall(name, objExp) } } else { this.tokens.push(n) const pi: string = nameToken.getValue() objExp = Expression.Property(objExp, pi, this.pos) objExp = this.ArrayIndex(objExp) } t = this.GetToken() } this.tokens.push(t) return objExp } /** * Array index parsing with optional condition */ private ArrayIndex(objExp: Expression): Expression { let n: Token = this.GetToken() if (n.getType() === TokenType.Oper && n.getValue() === '[') { const exp: Expression = this.Exp() n = this.GetToken() if (n.getType() !== TokenType.Oper || n.getValue() !== ']') { throw new Error(this.GetExceptionMessage('缺少\']\'')) } return Expression.ArrayIndex(objExp, exp, this.SOURCE, this.pos) } this.tokens.push(n) return objExp } /** * For loop handling */ private For(obj: Expression): Expression { const exp: Expression = this.CommaExp() const t: Token = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== ')') { throw new Error(this.GetExceptionMessage('函数调用括号不匹配')) } return Expression.For(obj, exp, this.SOURCE, this.pos) } /** * Method call handling with parameters */ private MethodCall(name: string, obj: Expression): Expression { let t: Token = this.GetToken() if (t.getType() === TokenType.Oper && t.getValue() === ')') { const ps: Expression[] = [] return Expression.Call(obj, name, ps, this.pos) } this.tokens.push(t) const ps: Expression[] = this.Params() t = this.GetToken() if (t.getType() !== TokenType.Oper || t.getValue() !== ')') { throw new Error(this.GetExceptionMessage('函数调用括号不匹配')) } return Expression.Call(obj, name, ps, this.pos) } /** * Function parameter list handling */ private Params(): Expression[] { const ps: Expression[] = [] let name: string = this.paramName() let exp: Expression = this.Exp() this.procParam(ps, exp, name) let t: Token = this.GetToken() while (t.getType() === TokenType.Oper && t.getValue() === ',') { name = this.paramName() exp = this.Exp() this.procParam(ps, exp, name) t = this.GetToken() } this.tokens.push(t) return ps } /** * Parse parameter name */ private paramName(): string { const t: Token = this.GetToken() if (t.getType() === TokenType.Identy) { const t1: Token = this.GetToken() if (t1.getValue() === ':') { return t.getValue() } else { this.tokens.push(t) this.tokens.push(t1) return '' } } else { this.tokens.push(t) return '' } } /** * Process parameters, especially if it contains the "data" object */ private procParam(ps: Expression[], exp: Expression, name: string): void { const delegate: Delegate = this.createDelegate(exp) if (delegate.containsKey('data')) { ps.push(Expression.Constant(delegate, this.pos)) } else { ps.push(Expression.Param(exp, name, this.pos)) } } /** * Get exception message with position information */ private GetExceptionMessage(msg: string): string { const result: string = `${this.SOURCE.substring(0, this.pos)} <- ${this.SOURCE.substring(this.pos)}` return `${msg}:\n${result}` } public GetToken(): Token { // If there are tokens in the queue, return the last one saved if (this.tokens.length > 0) { return this.tokens.shift()! } const sPos = this.pos let hasKeyword = this.pos < this.SOURCE_LENGTH let value: string | null = null if (hasKeyword) { value = this.SOURCE.charAt(this.pos) } // If it's '{', save the previous state and change to non-string state if (hasKeyword && value === '{') { this.inStrings.push(this.inString) this.inString = false const str = this.SOURCE.substring(this.pos, this.pos + 1) this.pos += 1 return new Token(TokenType.Oper, str, sPos) } // If in string state, process the string if (this.inString) { const startPos = this.pos while (hasKeyword && value !== '{' && value !== '}' && value !== '$' && value !== '\"') { if (value === '\\') { const nextStrValue = this.SOURCE.charAt(this.pos + 1) if (this.pos + 1 < this.SOURCE_LENGTH && ['{', '}', '\"', '$', '\\'].includes(nextStrValue)) { this.pos++ } else { break } } this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } if (!(hasKeyword && value === '{')) { this.inString = this.inStrings.pop()! } const t = new Token(TokenType.String, this.SOURCE.substring(startPos, this.pos) .replace('\\$', '$') .replace('\\{', '{') .replace('\\}', '}') .replace(/\\"/g, '"') .replace('\\\\', '\\'), sPos) if (hasKeyword && (value === '$' || value === '\"')) { this.pos++ } return t } // Skip whitespace and comments while (hasKeyword && (value === ' ' || value === '\n' || value === '\t' || (this.pos < this.SOURCE_LENGTH - 2 && value === '/' && this.SOURCE.charAt(this.pos + 1) === '/'))) { if (value !== ' ' && value !== '\n' && value !== '\t') { this.pos += 2 if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } while (hasKeyword && value !== '\n') { this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } } this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } // If we are at the end of the source, return an "End" token if (this.pos === this.SOURCE_LENGTH) { return new Token(TokenType.End, null, sPos) } if (value !== null) { // Handle number tokens if (value >= '0' && value <= '9') { const oldPos = this.pos while (hasKeyword && value >= '0' && value <= '9') { this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } if (hasKeyword && value === '.') { do { this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } while (hasKeyword && value >= '0' && value <= '9') const str = this.SOURCE.substring(oldPos, this.pos) return new Token(TokenType.Double, Number.parseFloat(str), sPos) } else { const str = this.SOURCE.substring(oldPos, this.pos) return new Token(TokenType.Int, Number.parseInt(str, 10), sPos) } } // Handle identifier tokens else if ((value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || value === '_' || /[\u4E00-\u9FA5]/.test(value)) { const oldPos = this.pos while (hasKeyword && ((value >= 'a' && value <= 'z') || (value >= 'A' && value <= 'Z') || (value >= '0' && value <= '9') || value === '_' || /[\u4E00-\u9FA5]/.test(value))) { this.pos++ if (this.pos < this.SOURCE_LENGTH) { value = this.SOURCE.charAt(this.pos) } else { hasKeyword = false } } const str = this.SOURCE.substring(oldPos, this.pos) switch (str) { case 'false': case 'true': return new Token(TokenType.Bool, Boolean(str), sPos) case 'null': return new Token(TokenType.Null, null, sPos) default: return new Token(TokenType.Identy, str, sPos) } } // Handle operators like &&, ||, and single-character operators else if ((value === '&' && this.SOURCE.charAt(this.pos + 1) === '&') || (value === '|' && this.SOURCE.charAt(this.pos + 1) === '|')) { const str = this.SOURCE.substring(this.pos, this.pos + 2) this.pos += 2 return new Token(TokenType.Oper, str, sPos) } // Handle other operators (+, -, *, /, etc.) else if (['+', '-', '*', '/', '%', '>', '<', '!'].includes(value)) { let str = '' if (this.SOURCE.charAt(this.pos + 1) === '=') { str = this.SOURCE.substring(this.pos, this.pos + 2) this.pos += 2 } else { str = this.SOURCE.substring(this.pos, this.pos + 1) this.pos += 1 } return new Token(TokenType.Oper, str, sPos) } // Handle '=' with different cases else if (value === '=') { let str = '' if (this.SOURCE.charAt(this.pos + 1) === '=' || this.SOURCE.charAt(this.pos + 1) === '>') { str = this.SOURCE.substring(this.pos, this.pos + 2) this.pos += 2 } else { str = this.SOURCE.substring(this.pos, this.pos + 1) this.pos += 1 } return new Token(TokenType.Oper, str, sPos) } // Single character operators like parentheses, commas, etc. else if ('()[],;.:@{}"$'.includes(value)) { if (value === '$' || value === '\"') { this.inStrings.push(false) this.inString = true } else if ((value === '{' || value === '}') && this.inStrings.length) { if (value === '{') { this.inStrings.push(false) this.inString = false } else { this.inString = this.inStrings.pop()! } } const str = this.SOURCE.substring(this.pos, this.pos + 1) this.pos += 1 return new Token(TokenType.Oper, str, sPos) } else { throw new Error(`Invalid token: '${value}'`) } } throw new Error('Unknown error while parsing.') } }