|
| 1 | +import { |
| 2 | + ArrowFuncDefNode, |
| 3 | + AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode, |
| 4 | + CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode |
| 5 | +} from '../common'; |
| 6 | +import { BlockContext, Scope } from './scope'; |
| 7 | + |
| 8 | +export class EvaluatorAsync { |
| 9 | + |
| 10 | + async evalBlockAsync(ast: AstBlock, blockContext: BlockContext): Promise<unknown> { |
| 11 | + let lastResult = null; |
| 12 | + |
| 13 | + for (let node of ast?.funcs || []) { |
| 14 | + const funcDef = node as FunctionDefNode; |
| 15 | + |
| 16 | + // a child scope needs to be created here |
| 17 | + const newScope = blockContext.blockScope; |
| 18 | + |
| 19 | + newScope.set(funcDef.funcAst.name, |
| 20 | + async (...args: unknown[]): Promise<unknown> => await this.jspyFuncInvokerAsync(funcDef, blockContext, ...args) |
| 21 | + ); |
| 22 | + } |
| 23 | + |
| 24 | + for (const node of ast.body) { |
| 25 | + if (node.type === 'comment') { continue; } |
| 26 | + |
| 27 | + lastResult = await this.evalNodeAsync(node, blockContext); |
| 28 | + if (blockContext.returnCalled) { |
| 29 | + const res = blockContext.returnObject; |
| 30 | + // stop processing return |
| 31 | + if (ast.type == 'func' || ast.type == 'module') { |
| 32 | + blockContext.returnCalled = false; |
| 33 | + blockContext.returnObject = null; |
| 34 | + } |
| 35 | + return res; |
| 36 | + } |
| 37 | + |
| 38 | + if (blockContext.continueCalled) { |
| 39 | + break; |
| 40 | + } |
| 41 | + if (blockContext.breakCalled) { |
| 42 | + break; |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + return lastResult; |
| 47 | + } |
| 48 | + |
| 49 | + private async jspyFuncInvokerAsync(funcDef: FuncDefNode, context: BlockContext, ...args: unknown[]): Promise<unknown> { |
| 50 | + |
| 51 | + const ast = Object.assign({}, funcDef.funcAst); |
| 52 | + ast.type = 'func'; |
| 53 | + |
| 54 | + const blockContext = { |
| 55 | + namelessFuncsCount: 0, |
| 56 | + blockScope: context.blockScope.clone() |
| 57 | + } as BlockContext; |
| 58 | + |
| 59 | + // set parameters into new scope, based incomming arguments |
| 60 | + for (let i = 0; i < args?.length || 0; i++) { |
| 61 | + if (i >= funcDef.params.length) { |
| 62 | + break; |
| 63 | + // throw new Error('Too much parameters provided'); |
| 64 | + } |
| 65 | + blockContext.blockScope.set(funcDef.params[i], args[i]); |
| 66 | + } |
| 67 | + |
| 68 | + return await this.evalBlockAsync(ast, blockContext); |
| 69 | + } |
| 70 | + |
| 71 | + private async invokeFunctionAsync(func: (...args: unknown[]) => unknown, fps: unknown[]): Promise<unknown> { |
| 72 | + if (fps.length === 0) { return await func(); } |
| 73 | + if (fps.length === 1) { return await func(fps[0]); } |
| 74 | + if (fps.length === 2) { return await func(fps[0], fps[1]); } |
| 75 | + if (fps.length === 3) { return await func(fps[0], fps[1], fps[2]); } |
| 76 | + if (fps.length === 4) { |
| 77 | + return await func(fps[0], fps[1], fps[2], fps[3]); |
| 78 | + } |
| 79 | + if (fps.length === 5) { |
| 80 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4]); |
| 81 | + } |
| 82 | + |
| 83 | + if (fps.length === 6) { |
| 84 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5]); |
| 85 | + } |
| 86 | + |
| 87 | + if (fps.length === 7) { |
| 88 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6]); |
| 89 | + } |
| 90 | + |
| 91 | + if (fps.length === 8) { |
| 92 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7]); |
| 93 | + } |
| 94 | + |
| 95 | + if (fps.length === 9) { |
| 96 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8]); |
| 97 | + } |
| 98 | + |
| 99 | + if (fps.length === 10) { |
| 100 | + return await func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8], fps[9]); |
| 101 | + } |
| 102 | + |
| 103 | + if (fps.length > 10) { |
| 104 | + throw Error('Function has too many parameters. Current limitation is 10'); |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + private async evalNodeAsync(node: AstNode, blockContext: BlockContext): Promise<unknown> { |
| 109 | + if (node.type === 'import') { |
| 110 | + // skip this for now. As modules are implemented externally |
| 111 | + return null; |
| 112 | + } |
| 113 | + |
| 114 | + if (node.type === 'comment') { |
| 115 | + return null; |
| 116 | + } |
| 117 | + |
| 118 | + if (node.type === 'if') { |
| 119 | + const ifNode = node as IfNode; |
| 120 | + if (await this.evalNodeAsync(ifNode.conditionNode, blockContext)) { |
| 121 | + await this.evalBlockAsync({ type: 'if', body: ifNode.ifBody } as AstBlock, blockContext); |
| 122 | + } else if (ifNode.elseBody) { |
| 123 | + await this.evalBlockAsync({ type: 'if', body: ifNode.elseBody } as AstBlock, blockContext); |
| 124 | + } |
| 125 | + |
| 126 | + return; |
| 127 | + } |
| 128 | + |
| 129 | + if (node.type === 'return') { |
| 130 | + const returnNode = node as ReturnNode; |
| 131 | + blockContext.returnCalled = true; |
| 132 | + blockContext.returnObject = returnNode.returnValue ? |
| 133 | + await this.evalNodeAsync(returnNode.returnValue, blockContext) |
| 134 | + : null; |
| 135 | + |
| 136 | + return blockContext.returnObject; |
| 137 | + } |
| 138 | + |
| 139 | + if (node.type === 'continue') { |
| 140 | + blockContext.continueCalled = true; |
| 141 | + return; |
| 142 | + } |
| 143 | + |
| 144 | + if (node.type === 'break') { |
| 145 | + blockContext.breakCalled = true; |
| 146 | + return; |
| 147 | + } |
| 148 | + |
| 149 | + if (node.type === 'for') { |
| 150 | + const forNode = node as ForNode; |
| 151 | + |
| 152 | + const array = await this.evalNodeAsync(forNode.sourceArray, blockContext) as unknown[] | string; |
| 153 | + |
| 154 | + for (let item of array) { |
| 155 | + blockContext.blockScope.set(forNode.itemVarName, item); |
| 156 | + await this.evalBlockAsync({ type: 'for', body: forNode.body } as AstBlock, blockContext); |
| 157 | + if (blockContext.continueCalled) { blockContext.continueCalled = false; } |
| 158 | + if (blockContext.breakCalled) { break; } |
| 159 | + } |
| 160 | + if (blockContext.breakCalled) { blockContext.breakCalled = false; } |
| 161 | + return; |
| 162 | + } |
| 163 | + |
| 164 | + if (node.type === 'while') { |
| 165 | + const whileNode = node as WhileNode; |
| 166 | + |
| 167 | + while (await this.evalNodeAsync(whileNode.condition, blockContext)) { |
| 168 | + await this.evalBlockAsync({ type: 'while', body: whileNode.body } as AstBlock, blockContext); |
| 169 | + |
| 170 | + if (blockContext.continueCalled) { blockContext.continueCalled = false; } |
| 171 | + if (blockContext.breakCalled) { break; } |
| 172 | + } |
| 173 | + if (blockContext.breakCalled) { blockContext.breakCalled = false; } |
| 174 | + |
| 175 | + return; |
| 176 | + } |
| 177 | + |
| 178 | + if (node.type === "const") { |
| 179 | + return (node as ConstNode).value; |
| 180 | + } |
| 181 | + |
| 182 | + if (node.type === "getSingleVar") { |
| 183 | + return blockContext.blockScope.get((node as GetSingleVarNode).name); |
| 184 | + } |
| 185 | + |
| 186 | + if (node.type === "binOp") { |
| 187 | + const binOpNode = (node as BinOpNode); |
| 188 | + var left = await this.evalNodeAsync(binOpNode.left, blockContext); |
| 189 | + var right = await this.evalNodeAsync(binOpNode.right, blockContext); |
| 190 | + return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive); |
| 191 | + } |
| 192 | + |
| 193 | + if (node.type === "arrowFuncDef") { |
| 194 | + const arrowFuncDef = node as ArrowFuncDefNode; |
| 195 | + |
| 196 | + return (...args: unknown[]): unknown => this.jspyFuncInvokerAsync(arrowFuncDef, blockContext, ...args); |
| 197 | + } |
| 198 | + |
| 199 | + if (node.type === "funcCall") { |
| 200 | + const funcCallNode = node as FunctionCallNode; |
| 201 | + const func = blockContext.blockScope.get(funcCallNode.name) as (...args: unknown[]) => unknown; |
| 202 | + const pms = [] |
| 203 | + for (let p of funcCallNode.paramNodes || []) { |
| 204 | + pms.push(await this.evalNodeAsync(p, blockContext)); |
| 205 | + } |
| 206 | + |
| 207 | + return await this.invokeFunctionAsync(func, pms); |
| 208 | + } |
| 209 | + |
| 210 | + if (node.type === "assign") { |
| 211 | + const assignNode = node as AssignNode; |
| 212 | + |
| 213 | + if (assignNode.target.type === 'getSingleVar') { |
| 214 | + const node = assignNode.target as SetSingleVarNode; |
| 215 | + blockContext.blockScope.set(node.name, await this.evalNodeAsync(assignNode.source, blockContext)); |
| 216 | + } else if (assignNode.target.type === 'dotObjectAccess') { |
| 217 | + const targetNode = assignNode.target as DotObjectAccessNode; |
| 218 | + |
| 219 | + // create a node for all but last property token |
| 220 | + // potentially it can go to parser |
| 221 | + const targetObjectNode = new DotObjectAccessNode(targetNode.nestedProps.slice(0, targetNode.nestedProps.length - 1)); |
| 222 | + const targetObject = await this.evalNodeAsync(targetObjectNode, blockContext) as Record<string, unknown>; |
| 223 | + |
| 224 | + // not sure nested properties should be GetSingleVarNode |
| 225 | + // can be factored in the parser |
| 226 | + const lastPropertyName = (targetNode.nestedProps[targetNode.nestedProps.length - 1] as GetSingleVarNode).name |
| 227 | + |
| 228 | + targetObject[lastPropertyName] = await this.evalNodeAsync(assignNode.source, blockContext); |
| 229 | + } else if (assignNode.target.type === 'bracketObjectAccess') { |
| 230 | + const targetNode = assignNode.target as BracketObjectAccessNode; |
| 231 | + const keyValue = await this.evalNodeAsync(targetNode.bracketBody, blockContext) as string | number; |
| 232 | + const targetObject = blockContext.blockScope.get(targetNode.propertyName as string) as Record<string, unknown>; |
| 233 | + |
| 234 | + targetObject[keyValue] = await this.evalNodeAsync(assignNode.source, blockContext); |
| 235 | + } else { |
| 236 | + throw Error('Not implemented Assign operation'); |
| 237 | + // get chaining calls |
| 238 | + } |
| 239 | + |
| 240 | + return null; |
| 241 | + } |
| 242 | + |
| 243 | + if (node.type === 'bracketObjectAccess') { |
| 244 | + const sbNode = node as BracketObjectAccessNode; |
| 245 | + const key = await this.evalNodeAsync(sbNode.bracketBody, blockContext) as string; |
| 246 | + const obj = blockContext.blockScope.get(sbNode.propertyName as string) as Record<string, unknown>; |
| 247 | + return (obj[key] === undefined) ? null : obj[key]; |
| 248 | + } |
| 249 | + |
| 250 | + if (node.type === "dotObjectAccess") { |
| 251 | + const dotObject = node as DotObjectAccessNode; |
| 252 | + |
| 253 | + let startObject = await this.evalNodeAsync(dotObject.nestedProps[0], blockContext) as any; |
| 254 | + for (let i = 1; i < dotObject.nestedProps.length; i++) { |
| 255 | + const nestedProp = dotObject.nestedProps[i]; |
| 256 | + |
| 257 | + if ((dotObject.nestedProps[i - 1] as any).nullCoelsing && !startObject) { |
| 258 | + startObject = {}; |
| 259 | + } |
| 260 | + |
| 261 | + if (nestedProp.type === 'getSingleVar') { |
| 262 | + startObject = startObject[(nestedProp as SetSingleVarNode).name] as unknown; |
| 263 | + } else if (nestedProp.type === 'bracketObjectAccess') { |
| 264 | + const node = nestedProp as BracketObjectAccessNode; |
| 265 | + startObject = startObject[node.propertyName] as unknown; |
| 266 | + startObject = startObject[await this.evalNodeAsync(node.bracketBody, blockContext) as string] as unknown; |
| 267 | + } else if (nestedProp.type === 'funcCall') { |
| 268 | + const funcCallNode = nestedProp as FunctionCallNode; |
| 269 | + const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown; |
| 270 | + |
| 271 | + const pms = [] |
| 272 | + for (let p of funcCallNode.paramNodes || []) { |
| 273 | + pms.push(await this.evalNodeAsync(p, blockContext)); |
| 274 | + } |
| 275 | + |
| 276 | + startObject = await this.invokeFunctionAsync(func.bind(startObject), pms); |
| 277 | + |
| 278 | + } else { |
| 279 | + throw Error("Can't resolve dotObjectAccess node") |
| 280 | + } |
| 281 | + } |
| 282 | + |
| 283 | + // no undefined values, make it rather null |
| 284 | + return (startObject === undefined) ? null : startObject; |
| 285 | + } |
| 286 | + |
| 287 | + if (node.type === 'createObject') { |
| 288 | + const createObjectNode = node as CreateObjectNode; |
| 289 | + const obj = {} as Record<string, unknown>; |
| 290 | + |
| 291 | + for (const p of createObjectNode.props) { |
| 292 | + obj[await this.evalNodeAsync(p.name, blockContext) as string] = await this.evalNodeAsync(p.value, blockContext); |
| 293 | + } |
| 294 | + |
| 295 | + return obj; |
| 296 | + } |
| 297 | + |
| 298 | + if (node.type === 'createArray') { |
| 299 | + const arrayNode = node as CreateArrayNode; |
| 300 | + const res = [] as unknown[]; |
| 301 | + |
| 302 | + for (const item of arrayNode.items) { |
| 303 | + res.push(await this.evalNodeAsync(item, blockContext)); |
| 304 | + } |
| 305 | + |
| 306 | + return res; |
| 307 | + } |
| 308 | + |
| 309 | + } |
| 310 | +} |
0 commit comments