Skip to content

Commit 07389b4

Browse files
committed
added async function to the parser
1 parent 3103841 commit 07389b4

File tree

8 files changed

+327
-53
lines changed

8 files changed

+327
-53
lines changed

index.html

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,8 @@
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
3232

33-
x = 5
34-
if 2 == 2:
35-
return x
36-
37-
x + 10
33+
sqrPromise(5) * 2
34+
3835

3936
</div>
4037
<!--
@@ -105,6 +102,7 @@ <h4>JSPython development console</h4>
105102
const scope = {
106103
Math,
107104
dateTime: () => new Date(),
105+
sqrPromise: (fn) => new Promise((s, f) => setTimeout(() => s(fn * fn), 5)),
108106
print: (p1, p2) => {
109107
console.log(p1);
110108
return p1;

src/common/ast-types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export interface FuncDefNode {
7676
}
7777

7878
export class FunctionDefNode extends AstNode implements FuncDefNode {
79-
constructor(public funcAst: AstBlock, public params: string[]) {
79+
constructor(public funcAst: AstBlock, public params: string[], public isAsync: boolean) {
8080
super('funcDef',);
8181
}
8282
}

src/common/token-types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ export function getTokenType(token: Token): TokenTypes {
3939
return token[1][0] as TokenTypes;
4040
}
4141

42-
export function getTokenValue(token: Token): TokenValue {
43-
return token[0];
42+
export function getTokenValue(token: Token | null): TokenValue {
43+
return token? token[0] : null;
4444
}
4545

4646
export function getTokenLoc(token: Token): Uint16Array {

src/evaluator/evaluator.ts

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -47,45 +47,6 @@ export class Evaluator {
4747
return lastResult;
4848
}
4949

50-
async evalBlockAsync(ast: AstBlock, blockContext: BlockContext): Promise<unknown> {
51-
let lastResult = null;
52-
53-
for (let node of ast?.funcs || []) {
54-
const funcDef = node as FunctionDefNode;
55-
56-
// a child scope needs to be created here
57-
const newScope = blockContext.blockScope;
58-
59-
newScope.set(funcDef.funcAst.name,
60-
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, blockContext, ...args)
61-
);
62-
}
63-
64-
for (const node of ast.body) {
65-
if (node.type === 'comment') { continue; }
66-
67-
lastResult = this.evalNode(node, blockContext);
68-
if (blockContext.returnCalled) {
69-
const res = blockContext.returnObject;
70-
// stop processing return
71-
if(ast.type == 'func' || ast.type == 'module'){
72-
blockContext.returnCalled = false;
73-
blockContext.returnObject = null;
74-
}
75-
return res;
76-
}
77-
78-
if (blockContext.continueCalled) {
79-
break;
80-
}
81-
if (blockContext.breakCalled) {
82-
break;
83-
}
84-
}
85-
86-
return lastResult;
87-
}
88-
8950
private jspyFuncInvoker(funcDef: FuncDefNode, context: BlockContext, ...args: unknown[]): unknown {
9051

9152
const ast = Object.assign({}, funcDef.funcAst);

src/evaluator/evaluatorAsync.ts

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
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

Comments
 (0)