import { ExecutionEnvironment } from './env/env' import { IExpression, IParserConfig } from './main' import { StaticExpression } from './parser/expressions/static' import { StringExpression } from './parser/expressions/string' import { MoLangParser } from './parser/molang' export class MoLang { protected expressionCache: Record = {} protected totalCacheEntries = 0 protected executionEnvironment!: ExecutionEnvironment protected parser: MoLangParser constructor( env: Record = {}, protected config: Partial = {} ) { if (config.useOptimizer === undefined) this.config.useOptimizer = true if (config.useCache === undefined) this.config.useCache = true if (config.earlyReturnsSkipParsing === undefined) this.config.earlyReturnsSkipParsing = true if (config.earlyReturnsSkipTokenization === undefined) this.config.earlyReturnsSkipTokenization = true if (config.convertUndefined === undefined) this.config.convertUndefined = false this.parser = new MoLangParser({ ...this.config, tokenizer: undefined, }) this.updateExecutionEnv(env) } updateConfig(newConfig: Partial) { newConfig = Object.assign(this.config, newConfig) if (newConfig.tokenizer) this.parser.setTokenizer(newConfig.tokenizer) this.parser.updateConfig({ ...this.config, tokenizer: undefined }) this.executionEnvironment.updateConfig(newConfig) } updateExecutionEnv(env: Record, isFlat = false) { this.executionEnvironment = new ExecutionEnvironment(env, { useRadians: this.config.useRadians, convertUndefined: this.config.convertUndefined, isFlat, variableHandler: this.config.variableHandler, }) this.parser.setExecutionEnvironment(this.executionEnvironment) } /** * Clears the MoLang expression cache */ clearCache() { this.expressionCache = {} this.totalCacheEntries = 0 } /** * Execute the given MoLang string `expression` * @param expression The MoLang string to execute * * @returns The value the MoLang expression corresponds to */ execute(expression: string) { this.parser.setExecutionEnvironment(this.executionEnvironment) const abstractSyntaxTree = this.parse(expression) const result = abstractSyntaxTree.eval() if (result === undefined) return 0 if (typeof result === 'boolean') return Number(result) return result } /** * Execute the given MoLang string `expression` * In case of errors, return 0 * @param expression The MoLang string to execute * * @returns The value the MoLang expression corresponds to and 0 if the statement is invalid */ executeAndCatch(expression: string) { try { return this.execute(expression) } catch { return 0 } } /** * Parse the given MoLang string `expression` * @param expression The MoLang string to parse * * @returns An AST that corresponds to the MoLang expression */ parse(expression: string): IExpression { if (this.config.useCache ?? true) { const abstractSyntaxTree = this.expressionCache[expression] if (abstractSyntaxTree) return abstractSyntaxTree } this.parser.init(expression) let abstractSyntaxTree = this.parser.parseExpression() if ((this.config.useOptimizer ?? true) && abstractSyntaxTree.isStatic()) abstractSyntaxTree = new StaticExpression(abstractSyntaxTree.eval()) // console.log(JSON.stringify(abstractSyntaxTree, null, ' ')) if (this.config.useCache ?? true) { if (this.totalCacheEntries > (this.config.maxCacheSize || 256)) this.clearCache() this.expressionCache[expression] = abstractSyntaxTree this.totalCacheEntries++ } return abstractSyntaxTree } resolveStatic(ast: IExpression) { ast.walk((expr) => { if (expr instanceof StringExpression) return if (expr.isStatic()) return new StaticExpression(expr.eval()) }) } getParser() { return this.parser } }