Skip to content

Commit a56e4bb

Browse files
committed
added continue / break and further test fixes
1 parent 1e46f27 commit a56e4bb

File tree

8 files changed

+353
-236
lines changed

8 files changed

+353
-236
lines changed

index.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,13 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
print(
33-
""
34-
)
35-
</div>
32+
33+
def func1():
34+
# test
35+
15 + 25
36+
func1()
37+
38+
</div>
3639
<!--
3740
def f(n):
3841
n * n

src/common/ast-types.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export type AstNodeType = 'assign' | 'binOp' | 'const'
66
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
77
| 'createObject' | 'createArray'
88
| 'if' | 'for' | 'while'
9-
| 'import'
9+
| 'import' | 'comment'
1010
| 'return' | 'continue' | 'break';
1111

1212
export abstract class AstNode {
@@ -32,8 +32,14 @@ export class ConstNode extends AstNode {
3232
}
3333
}
3434

35+
export class CommentNode extends AstNode {
36+
constructor(public comment: string) {
37+
super('comment');
38+
}
39+
}
40+
3541
export class ReturnNode extends AstNode {
36-
constructor(public returnValue: AstNode) {
42+
constructor(public returnValue: AstNode | undefined = undefined) {
3743
super('return');
3844
}
3945
}
@@ -66,17 +72,17 @@ export class FunctionCallNode extends AstNode {
6672

6773
export interface FuncDefNode {
6874
params: string[];
69-
body: AstNode[];
75+
funcAst: AstBlock;
7076
}
7177

7278
export class FunctionDefNode extends AstNode implements FuncDefNode {
73-
constructor(public name: string, public params: string[], public body: AstNode[]) {
79+
constructor(public funcAst: AstBlock, public params: string[]) {
7480
super('funcDef',);
7581
}
7682
}
7783

7884
export class ArrowFuncDefNode extends AstNode implements FuncDefNode {
79-
constructor(public params: string[], public body: AstNode[]) {
85+
constructor(public funcAst: AstBlock, public params: string[]) {
8086
super('arrowFuncDef');
8187
}
8288
}

src/evaluator/evaluator.ts

Lines changed: 112 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,103 @@
11
import {
22
ArrowFuncDefNode,
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
4-
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, SetSingleVarNode, WhileNode
4+
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
55
} from '../common';
6-
import { Scope } from './scope';
6+
import { BlockContext, Scope } from './scope';
77

88
export class Evaluator {
99

10-
evalBlock(ast: AstBlock, scope: Scope): unknown {
10+
evalBlock(ast: AstBlock, blockContext: BlockContext): unknown {
1111
let lastResult = null;
1212

1313
for (let node of ast?.funcs || []) {
1414
const funcDef = node as FunctionDefNode;
1515

1616
// a child scope needs to be created here
17-
const newScope = scope;
17+
const newScope = blockContext.blockScope;
1818

19-
scope.set(funcDef.name,
20-
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, scope, ...args)
19+
newScope.set(funcDef.funcAst.name,
20+
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, blockContext, ...args)
2121
);
2222
}
2323

2424
for (const node of ast.body) {
25-
lastResult = this.evalNode(node, scope);
25+
if (node.type === 'comment') { continue; }
26+
lastResult = this.evalNode(node, blockContext);
27+
28+
if (blockContext.returnCalled) {
29+
const res = blockContext.returnObject;
30+
blockContext.returnCalled = false;
31+
blockContext.returnObject = null;
32+
return res;
33+
}
34+
35+
if (blockContext.continueCalled) {
36+
break;
37+
}
38+
if (blockContext.breakCalled) {
39+
break;
40+
}
2641
}
2742

2843
return lastResult;
2944
}
3045

31-
async evalBlockAsync(ast: AstBlock, scope: Scope): Promise<unknown> {
46+
async evalBlockAsync(ast: AstBlock, blockContext: BlockContext): Promise<unknown> {
3247
let lastResult = null;
3348

3449
for (let node of ast?.funcs || []) {
3550
const funcDef = node as FunctionDefNode;
3651

3752
// a child scope needs to be created here
38-
const newScope = scope;
53+
const newScope = blockContext.blockScope;
3954

40-
scope.set(funcDef.name,
41-
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, scope, ...args)
55+
newScope.set(funcDef.funcAst.name,
56+
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, blockContext, ...args)
4257
);
4358
}
4459

4560
for (const node of ast.body) {
46-
lastResult = this.evalNode(node, scope);
61+
if (node.type === 'comment') { continue; }
62+
63+
lastResult = this.evalNode(node, blockContext);
64+
if (blockContext.returnCalled) {
65+
const res = blockContext.returnObject;
66+
blockContext.returnCalled = false;
67+
blockContext.returnObject = null;
68+
return res;
69+
}
70+
71+
if (blockContext.continueCalled) {
72+
break;
73+
}
74+
if (blockContext.breakCalled) {
75+
break;
76+
}
4777
}
4878

4979
return lastResult;
5080
}
5181

52-
private jspyFuncInvoker(funcDef: FuncDefNode, newScope: Scope, ...args: unknown[]): unknown {
82+
private jspyFuncInvoker(funcDef: FuncDefNode, context: BlockContext, ...args: unknown[]): unknown {
83+
84+
const ast = funcDef.funcAst;
5385

54-
const ast = { name: '', type: 'func', funcs: [], body: funcDef.body } as AstBlock;
86+
const blockContext = {
87+
namelessFuncsCount: 0,
88+
blockScope: context.blockScope.clone()
89+
} as BlockContext;
5590

5691
// set parameters into new scope, based incomming arguments
5792
for (let i = 0; i < args?.length || 0; i++) {
5893
if (i >= funcDef.params.length) {
5994
break;
6095
// throw new Error('Too much parameters provided');
6196
}
62-
newScope.set(funcDef.params[i], args[i]);
97+
blockContext.blockScope.set(funcDef.params[i], args[i]);
6398
}
64-
return this.evalBlock(ast, newScope);
99+
100+
return this.evalBlock(ast, blockContext);
65101
}
66102

67103
private invokeFunction(func: (...args: unknown[]) => unknown, fps: unknown[]): unknown {
@@ -101,44 +137,72 @@ export class Evaluator {
101137
}
102138
}
103139

104-
private evalNode(node: AstNode, scope: Scope): unknown {
140+
private evalNode(node: AstNode, blockContext: BlockContext): unknown {
105141
if (node.type === 'import') {
106142
// skip this for now. As modules are implemented externally
107143
return null;
108144
}
109145

146+
if (node.type === 'comment') {
147+
return null;
148+
}
149+
110150
if (node.type === 'if') {
111151
const ifNode = node as IfNode;
112-
const newScope = scope;
113-
if (this.evalNode(ifNode.conditionNode, scope)) {
114-
this.evalBlock({ body: ifNode.ifBody } as AstBlock, newScope);
152+
if (this.evalNode(ifNode.conditionNode, blockContext)) {
153+
this.evalBlock({ body: ifNode.ifBody } as AstBlock, blockContext);
115154
} else if (ifNode.elseBody) {
116-
this.evalBlock({ body: ifNode.elseBody } as AstBlock, newScope);
155+
this.evalBlock({ body: ifNode.elseBody } as AstBlock, blockContext);
117156
}
118157

119158
return;
120159
}
121160

161+
if (node.type === 'return') {
162+
const returnNode = node as ReturnNode;
163+
blockContext.returnCalled = true;
164+
blockContext.returnObject = returnNode.returnValue ?
165+
this.evalNode(returnNode.returnValue, blockContext)
166+
: null;
167+
168+
return blockContext.returnObject;
169+
}
170+
171+
if (node.type === 'continue') {
172+
blockContext.continueCalled = true;
173+
return;
174+
}
175+
176+
if (node.type === 'break') {
177+
blockContext.breakCalled = true;
178+
return;
179+
}
180+
122181
if (node.type === 'for') {
123182
const forNode = node as ForNode;
124-
const newScope = scope;
125183

126-
const array = this.evalNode(forNode.sourceArray, newScope) as unknown[] | string;
184+
const array = this.evalNode(forNode.sourceArray, blockContext) as unknown[] | string;
127185

128186
for (let item of array) {
129-
newScope.set(forNode.itemVarName, item);
130-
this.evalBlock({ body: forNode.body } as AstBlock, newScope);
187+
blockContext.blockScope.set(forNode.itemVarName, item);
188+
this.evalBlock({ body: forNode.body } as AstBlock, blockContext);
189+
if (blockContext.continueCalled) { blockContext.continueCalled = false; }
190+
if (blockContext.breakCalled) { break; }
131191
}
192+
if (blockContext.breakCalled) { blockContext.breakCalled = false; }
132193
return;
133194
}
134195

135196
if (node.type === 'while') {
136197
const forNode = node as WhileNode;
137-
const newScope = scope;
138198

139-
while (this.evalNode(forNode.condition, newScope)) {
140-
this.evalBlock({ body: forNode.body } as AstBlock, newScope);
199+
while (this.evalNode(forNode.condition, blockContext)) {
200+
this.evalBlock({ body: forNode.body } as AstBlock, blockContext);
201+
202+
if (blockContext.continueCalled) { blockContext.continueCalled = false; }
203+
if (blockContext.breakCalled) { break; }
141204
}
205+
if (blockContext.breakCalled) { blockContext.breakCalled = false; }
142206

143207
return;
144208
}
@@ -148,27 +212,26 @@ export class Evaluator {
148212
}
149213

150214
if (node.type === "getSingleVar") {
151-
return scope.get((node as GetSingleVarNode).name);
215+
return blockContext.blockScope.get((node as GetSingleVarNode).name);
152216
}
153217

154218
if (node.type === "binOp") {
155219
const binOpNode = (node as BinOpNode);
156-
var left = this.evalNode(binOpNode.left, scope);
157-
var right = this.evalNode(binOpNode.right, scope);
220+
var left = this.evalNode(binOpNode.left, blockContext);
221+
var right = this.evalNode(binOpNode.right, blockContext);
158222
return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive);
159223
}
160224

161225
if (node.type === "arrowFuncDef") {
162226
const arrowFuncDef = node as ArrowFuncDefNode;
163-
const newScope = scope;
164227

165-
return (...args:unknown[]): unknown => this.jspyFuncInvoker(arrowFuncDef, newScope, ...args);
228+
return (...args: unknown[]): unknown => this.jspyFuncInvoker(arrowFuncDef, blockContext, ...args);
166229
}
167230

168231
if (node.type === "funcCall") {
169232
const funcCallNode = node as FunctionCallNode;
170-
const func = scope.get(funcCallNode.name) as (...args: unknown[]) => unknown;
171-
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, scope)) || []
233+
const func = blockContext.blockScope.get(funcCallNode.name) as (...args: unknown[]) => unknown;
234+
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, blockContext)) || []
172235

173236
return this.invokeFunction(func, pms);
174237
}
@@ -178,26 +241,26 @@ export class Evaluator {
178241

179242
if (assignNode.target.type === 'getSingleVar') {
180243
const node = assignNode.target as SetSingleVarNode;
181-
scope.set(node.name, this.evalNode(assignNode.source, scope));
244+
blockContext.blockScope.set(node.name, this.evalNode(assignNode.source, blockContext));
182245
} else if (assignNode.target.type === 'dotObjectAccess') {
183246
const targetNode = assignNode.target as DotObjectAccessNode;
184247

185248
// create a node for all but last property token
186249
// potentially it can go to parser
187250
const targetObjectNode = new DotObjectAccessNode(targetNode.nestedProps.slice(0, targetNode.nestedProps.length - 1));
188-
const targetObject = this.evalNode(targetObjectNode, scope) as Record<string, unknown>;
251+
const targetObject = this.evalNode(targetObjectNode, blockContext) as Record<string, unknown>;
189252

190253
// not sure nested properties should be GetSingleVarNode
191254
// can be factored in the parser
192255
const lastPropertyName = (targetNode.nestedProps[targetNode.nestedProps.length - 1] as GetSingleVarNode).name
193256

194-
targetObject[lastPropertyName] = this.evalNode(assignNode.source, scope);
257+
targetObject[lastPropertyName] = this.evalNode(assignNode.source, blockContext);
195258
} else if (assignNode.target.type === 'bracketObjectAccess') {
196259
const targetNode = assignNode.target as BracketObjectAccessNode;
197-
const keyValue = this.evalNode(targetNode.bracketBody, scope) as string | number;
198-
const targetObject = scope.get(targetNode.propertyName as string) as Record<string, unknown>;
260+
const keyValue = this.evalNode(targetNode.bracketBody, blockContext) as string | number;
261+
const targetObject = blockContext.blockScope.get(targetNode.propertyName as string) as Record<string, unknown>;
199262

200-
targetObject[keyValue] = this.evalNode(assignNode.source, scope);
263+
targetObject[keyValue] = this.evalNode(assignNode.source, blockContext);
201264
} else {
202265
throw Error('Not implemented Assign operation');
203266
// get chaining calls
@@ -208,15 +271,15 @@ export class Evaluator {
208271

209272
if (node.type === 'bracketObjectAccess') {
210273
const sbNode = node as BracketObjectAccessNode;
211-
const key = this.evalNode(sbNode.bracketBody, scope) as string;
212-
const obj = scope.get(sbNode.propertyName as string) as Record<string, unknown>;
213-
return (obj[key] === undefined)? null : obj[key];
274+
const key = this.evalNode(sbNode.bracketBody, blockContext) as string;
275+
const obj = blockContext.blockScope.get(sbNode.propertyName as string) as Record<string, unknown>;
276+
return (obj[key] === undefined) ? null : obj[key];
214277
}
215278

216279
if (node.type === "dotObjectAccess") {
217280
const dotObject = node as DotObjectAccessNode;
218281

219-
let startObject = this.evalNode(dotObject.nestedProps[0], scope) as any;
282+
let startObject = this.evalNode(dotObject.nestedProps[0], blockContext) as any;
220283
for (let i = 1; i < dotObject.nestedProps.length; i++) {
221284
const nestedProp = dotObject.nestedProps[i];
222285

@@ -229,11 +292,11 @@ export class Evaluator {
229292
} else if (nestedProp.type === 'bracketObjectAccess') {
230293
const node = nestedProp as BracketObjectAccessNode;
231294
startObject = startObject[node.propertyName] as unknown;
232-
startObject = startObject[this.evalNode(node.bracketBody, scope) as string] as unknown;
295+
startObject = startObject[this.evalNode(node.bracketBody, blockContext) as string] as unknown;
233296
} else if (nestedProp.type === 'funcCall') {
234297
const funcCallNode = nestedProp as FunctionCallNode;
235298
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
236-
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, scope)) || []
299+
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, blockContext)) || []
237300

238301
startObject = this.invokeFunction(func.bind(startObject), pms);
239302

@@ -251,7 +314,7 @@ export class Evaluator {
251314
const obj = {} as Record<string, unknown>;
252315

253316
for (const p of createObjectNode.props) {
254-
obj[this.evalNode(p.name, scope) as string] = this.evalNode(p.value, scope);
317+
obj[this.evalNode(p.name, blockContext) as string] = this.evalNode(p.value, blockContext);
255318
}
256319

257320
return obj;
@@ -262,7 +325,7 @@ export class Evaluator {
262325
const res = [] as unknown[];
263326

264327
for (const item of arrayNode.items) {
265-
res.push(this.evalNode(item, scope));
328+
res.push(this.evalNode(item, blockContext));
266329
}
267330

268331
return res;

0 commit comments

Comments
 (0)