Skip to content

Commit 266c85a

Browse files
committed
null coelsing and simple for loop
1 parent 2c42e75 commit 266c85a

File tree

5 files changed

+97
-20
lines changed

5 files changed

+97
-20
lines changed

index.html

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
x = 1
33-
34-
if x == 3:
35-
x = 5
36-
else:
37-
x = 10
32+
sum = 0
33+
34+
for item in [1,2,3]:
35+
sum = sum + item
36+
37+
sum
3838

3939
</div>
4040
<button onclick="tokenize()">Tokenize</button>
@@ -94,10 +94,10 @@ <h4>JSPython development console</h4>
9494
try {
9595
const scope = {
9696
Math,
97-
print: (p1, p2) => {return p1;},
97+
print: (p1, p2) => { return p1; },
9898
x: 10,
9999
o: {
100-
f1: (x,y) => {return {x, y};},
100+
f1: (x, y) => { return { x, y }; },
101101
v1: 55,
102102
sub1: {
103103
subValue: 88

src/common/ast-types.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ export abstract class AstNode {
99
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
1010
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
1111
| 'createObject' | 'createArray'
12-
| 'if' | 'while' | 'tryCatch'
12+
| 'if' | 'for' | 'tryCatch'
13+
| 'return' | 'continue' | 'break'
1314
) { }
1415
}
1516

@@ -31,6 +32,24 @@ export class ConstNode extends AstNode {
3132
}
3233
}
3334

35+
export class ReturnNode extends AstNode {
36+
constructor(public returnValue: AstNode) {
37+
super('return');
38+
}
39+
}
40+
41+
export class ContinueNode extends AstNode {
42+
constructor() {
43+
super('continue');
44+
}
45+
}
46+
47+
export class BreakNode extends AstNode {
48+
constructor() {
49+
super('break');
50+
}
51+
}
52+
3453
export class SetSingleVarNode extends AstNode {
3554
public name: string;
3655
constructor(token: Token) {
@@ -63,6 +82,12 @@ export class IfNode extends AstNode {
6382
}
6483
}
6584

85+
export class ForNode extends AstNode {
86+
constructor(public sourceArray: AstNode, public itemVarName: string, public body: AstNode[]) {
87+
super('for');
88+
}
89+
}
90+
6691
export class GetSingleVarNode extends AstNode {
6792
name: string;
6893
nullCoelsing: boolean | undefined = undefined;
@@ -98,7 +123,6 @@ export class CreateArrayNode extends AstNode {
98123
}
99124
}
100125

101-
102126
export class BracketObjectAccessNode extends AstNode {
103127
constructor(
104128
public propertyName: string,

src/evaluator/evaluator.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
22
ArrowFuncDefNode,
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
4-
CreateObjectNode, DotObjectAccessNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, SetSingleVarNode
4+
CreateObjectNode, DotObjectAccessNode, ForNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, SetSingleVarNode
55
} from '../common';
66
import { Scope } from './scope';
77

@@ -97,6 +97,20 @@ export class Evaluator {
9797
return;
9898
}
9999

100+
if (node.type === 'for') {
101+
const forNode = node as ForNode;
102+
const newScope = scope;
103+
104+
const array = this.evalNode(forNode.sourceArray, newScope) as unknown[] | string;
105+
106+
for(let item of array){
107+
newScope.set(forNode.itemVarName, item);
108+
this.evalBlock({ body: forNode.body } as AstBlock, newScope);
109+
}
110+
return;
111+
}
112+
113+
100114
if (node.type === "const") {
101115
return (node as ConstNode).value;
102116
}
@@ -185,15 +199,20 @@ export class Evaluator {
185199

186200
let startObject = this.evalNode(dotObject.nestedProps[0], scope) as any;
187201
for (let i = 1; i < dotObject.nestedProps.length; i++) {
202+
const nestedProp = dotObject.nestedProps[i];
203+
204+
if((dotObject.nestedProps[i - 1] as any).nullCoelsing && !startObject) {
205+
startObject = {};
206+
}
188207

189-
if (dotObject.nestedProps[i].type === 'getSingleVar') {
190-
startObject = startObject[(dotObject.nestedProps[i] as SetSingleVarNode).name] as unknown;
191-
} else if (dotObject.nestedProps[i].type === 'bracketObjectAccess') {
192-
const node = dotObject.nestedProps[i] as BracketObjectAccessNode;
208+
if (nestedProp.type === 'getSingleVar') {
209+
startObject = startObject[(nestedProp as SetSingleVarNode).name] as unknown;
210+
} else if (nestedProp.type === 'bracketObjectAccess') {
211+
const node = nestedProp as BracketObjectAccessNode;
193212
startObject = startObject[node.propertyName] as unknown;
194213
startObject = startObject[this.evalNode(node.bracketBody, scope) as string] as unknown;
195-
} else if (dotObject.nestedProps[i].type === 'funcCall') {
196-
const funcCallNode = dotObject.nestedProps[i] as FunctionCallNode;
214+
} else if (nestedProp.type === 'funcCall') {
215+
const funcCallNode = nestedProp as FunctionCallNode;
197216
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
198217
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, scope)) || []
199218

@@ -204,7 +223,8 @@ export class Evaluator {
204223
}
205224
}
206225

207-
return startObject;
226+
// no undefined values, make it rather null
227+
return (startObject === undefined)? null : startObject;
208228
}
209229

210230
if (node.type === 'createObject') {

src/interpreter.spec.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ describe('Interpreter', () => {
209209

210210

211211
it('if condition', () => {
212-
const script = (p) => `
212+
const script = (p: ) => `
213213
x = 1
214214
if x == ${p}:
215215
x = 5
@@ -221,5 +221,24 @@ describe('Interpreter', () => {
221221
expect(e.evaluate(script(2))).toBe(10);
222222
});
223223

224+
it('if condition', () => {
225+
const script = `
226+
x = {o1: {ov: 55}}
227+
x.o1.ov1?.someProp or 32
228+
`;
229+
expect(e.evaluate(script)).toBe(32);
230+
expect(e.evaluate("x={}\nx?.p1?.ff")).toBe(null);
231+
});
232+
233+
it('simple for', () => {
234+
const script = `
235+
sum = 0
236+
for item in [1,2,3]:
237+
sum = sum + item
238+
sum
239+
`;
240+
expect(e.evaluate(script)).toBe(6);
241+
});
242+
224243

225244
});

src/parser/parser.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
BinOpNode, ConstNode, AstBlock, Token, ParserOptions, AstNode, Operators, AssignNode, TokenTypes,
33
GetSingleVarNode, FunctionCallNode, getTokenType, getTokenValue, isTokenTypeLiteral, getStartLine,
44
getStartColumn, getEndColumn, getEndLine, findOperators, splitTokens, DotObjectAccessNode, BracketObjectAccessNode,
5-
findTokenValueIndex, FunctionDefNode, CreateObjectNode, ObjectPropertyInfo, CreateArrayNode, ArrowFuncDefNode, ExpressionOperators, IfNode
5+
findTokenValueIndex, FunctionDefNode, CreateObjectNode, ObjectPropertyInfo, CreateArrayNode, ArrowFuncDefNode, ExpressionOperators, IfNode, ForNode
66
} from '../common';
77

88
export class InstructionLine {
@@ -111,6 +111,20 @@ export class Parser {
111111

112112
ast.body.push(new IfNode(conditionNode, ifBody, elseBody))
113113

114+
} else if (getTokenValue(instruction.tokens[0]) === 'for') {
115+
116+
const endDefOfDef = findTokenValueIndex(instruction.tokens, v => v === ':');
117+
118+
if (endDefOfDef === -1) {
119+
throw (`Can't find : for if`)
120+
}
121+
122+
const itemVarName = getTokenValue(instruction.tokens[1]) as string;
123+
const sourceArray = this.createExpressionNode(instruction.tokens.slice(3, endDefOfDef))
124+
const forBody = getBody(instruction.tokens, endDefOfDef + 1);
125+
126+
ast.body.push(new ForNode(sourceArray, itemVarName, forBody))
127+
114128
} else if (assignTokens.length > 1) {
115129
const target = this.createExpressionNode(assignTokens[0]);
116130
const source = this.createExpressionNode(assignTokens[1]);

0 commit comments

Comments
 (0)