Skip to content

Commit 92496f5

Browse files
committed
improved a logical operators
1 parent 705d80e commit 92496f5

File tree

6 files changed

+95
-63
lines changed

6 files changed

+95
-63
lines changed

index.html

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

33-
async def f2():
34-
"""
35-
long comment
36-
"""
37-
778
38-
39-
f2()
40-
33+
x?.p1?.p2?.p3 or "N/A"
34+
4135
</div>
4236
<!--
4337
def f(n):

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jspython-interpreter",
3-
"version": "2.0.4",
3+
"version": "2.0.5",
44
"description": "JSPython is a javascript implementation of Python language that runs within web browser or NodeJS environment",
55
"keywords": [
66
"python",

src/evaluator/evaluator.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {
22
ArrowFuncDefNode,
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
44
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
5-
IfNode, IsNullCoelsing, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
5+
IfNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
66
} from '../common';
77
import { JspyEvalError } from '../common/utils';
88
import { BlockContext, Scope } from './scope';
@@ -193,7 +193,8 @@ export class Evaluator {
193193
}
194194

195195
if (node.type === "getSingleVar") {
196-
return blockContext.blockScope.get((node as GetSingleVarNode).name);
196+
const value = blockContext.blockScope.get((node as GetSingleVarNode).name);
197+
return value === undefined ? null : value;
197198
}
198199

199200
if (node.type === "binOp") {
@@ -203,6 +204,23 @@ export class Evaluator {
203204
return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive);
204205
}
205206

207+
if (node.type === "logicalOp") {
208+
const logicalGroups = (node as LogicalOpNode);
209+
let ind = 0;
210+
let gResult: any = true;
211+
212+
while (ind < logicalGroups.items.length) {
213+
const eg = logicalGroups.items[ind++];
214+
215+
gResult = this.evalNode(eg.node, blockContext)
216+
217+
if (eg.op === 'and' && !gResult) { return false; }
218+
if (eg.op === 'or' && gResult) { return gResult; }
219+
}
220+
221+
return gResult;
222+
}
223+
206224
if (node.type === "arrowFuncDef") {
207225
const arrowFuncDef = node as ArrowFuncDefNode;
208226

@@ -278,9 +296,9 @@ export class Evaluator {
278296
const funcCallNode = nestedProp as FunctionCallNode;
279297
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
280298

281-
if(func === undefined
282-
&& (dotObject.nestedProps[i - 1] as unknown as IsNullCoelsing).nullCoelsing){
283-
continue;
299+
if (func === undefined
300+
&& (dotObject.nestedProps[i - 1] as unknown as IsNullCoelsing).nullCoelsing) {
301+
continue;
284302
}
285303

286304
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, blockContext)) || []

src/evaluator/evaluatorAsync.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
44
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
55
getTokenLoc,
6-
IfNode, IsNullCoelsing, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
6+
IfNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
77
} from '../common';
88
import { JspyEvalError } from '../common/utils';
99
import { Evaluator } from './evaluator';
@@ -57,7 +57,7 @@ export class EvaluatorAsync {
5757
if (err instanceof JspyEvalError) {
5858
throw err;
5959
} else {
60-
const loc = node.loc? node.loc : [0, 0]
60+
const loc = node.loc ? node.loc : [0, 0]
6161
throw new JspyEvalError(blockContext.moduleName, loc[0], loc[1], err.message || err)
6262
}
6363
}
@@ -200,7 +200,8 @@ export class EvaluatorAsync {
200200
}
201201

202202
if (node.type === "getSingleVar") {
203-
return blockContext.blockScope.get((node as GetSingleVarNode).name);
203+
const value = blockContext.blockScope.get((node as GetSingleVarNode).name);
204+
return value === undefined? null : value;
204205
}
205206

206207
if (node.type === "binOp") {
@@ -210,6 +211,23 @@ export class EvaluatorAsync {
210211
return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive);
211212
}
212213

214+
if (node.type === "logicalOp") {
215+
const logicalGroups = (node as LogicalOpNode);
216+
let ind = 0;
217+
let gResult: any = true;
218+
219+
while (ind < logicalGroups.items.length) {
220+
const eg = logicalGroups.items[ind++];
221+
222+
gResult = await this.evalNodeAsync(eg.node, blockContext)
223+
224+
if (eg.op === 'and' && !gResult) { return false; }
225+
if (eg.op === 'or' && gResult) { return gResult; }
226+
}
227+
228+
return gResult;
229+
}
230+
213231
if (node.type === "arrowFuncDef") {
214232
const arrowFuncDef = node as ArrowFuncDefNode;
215233

@@ -288,9 +306,9 @@ export class EvaluatorAsync {
288306
const funcCallNode = nestedProp as FunctionCallNode;
289307
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
290308

291-
if(func === undefined
292-
&& (dotObject.nestedProps[i - 1] as unknown as IsNullCoelsing).nullCoelsing){
293-
continue;
309+
if (func === undefined
310+
&& (dotObject.nestedProps[i - 1] as unknown as IsNullCoelsing).nullCoelsing) {
311+
continue;
294312
}
295313

296314
const pms = []

src/interpreter.spec.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ describe('Interpreter', () => {
271271
272272
f()?.prop or 5
273273
`
274-
;
274+
;
275275
expect(e.eval(script)).toBe(5);
276276
});
277277

@@ -285,7 +285,7 @@ describe('Interpreter', () => {
285285
286286
f2()
287287
`
288-
;
288+
;
289289
expect(e.eval(script)).toBe(5);
290290
});
291291

@@ -307,20 +307,31 @@ describe('Interpreter', () => {
307307
// ** migration issue for now
308308
it('simple and operator', async () => {
309309
expect(await e.evaluate('2 == 2 and 3 == 3')).toBe(true)
310-
// expect(await e.evaluate('(2 == 2) and (3 == 3) and (5 == 5)')).toBe(true)
311-
// expect(await e.evaluate('(2 == 2) and (3 != 3) and 5 == 5)')).toBe(false)
312-
// expect(await e.evaluate('(2 != 2) and (3 != 3) and (5 == 5)')).toBe(false)
313-
// expect(await e.evaluate('(2 != 2) and (3 == 3) and (5 == 5)')).toBe(false)
314-
// expect(await e.evaluate('(2 == 2) and (3 == 3) and 5 != 5)')).toBe(false)
315-
})
310+
expect(await e.evaluate('(2 == 2) and (3 == 3) and (5 == 5)')).toBe(true)
311+
expect(await e.evaluate('(2 == 2) and (3 != 3) and (5 == 5)')).toBe(false)
312+
expect(await e.evaluate('(2 != 2) and (3 != 3) and (5 == 5)')).toBe(false)
313+
expect(await e.evaluate('(2 != 2) and (3 == 3) and (5 == 5)')).toBe(false)
314+
expect(await e.evaluate('(2 == 2) and (3 == 3) and (5 != 5)')).toBe(false)
315+
});
316316

317317
it('simple or operator', async () => {
318318
expect(await e.evaluate('2 == 2 or 3 == 3')).toBe(true)
319-
// expect(await e.evaluate('2 == 2 or 3 == 3 or 5 == 5')).toBe(true)
320-
// expect(await e.evaluate('2 != 2 or 3 != 3 or 5 != 5')).toBe(false)
321-
// expect(await e.evaluate('2 == 2 or 3 != 3 or 5 != 5')).toBe(true)
322-
// expect(await e.evaluate('2 == 2 or 3 == 3 and 5 != 5')).toBe(true)
323-
})
319+
expect(await e.evaluate('2 == 2 or 3 == 3 or 5 == 5')).toBe(true)
320+
expect(await e.evaluate('2 != 2 or 3 != 3 or 5 != 5')).toBe(false)
321+
expect(await e.evaluate('2 == 2 or 3 != 3 or 5 != 5')).toBe(true)
322+
expect(await e.evaluate('2 == 2 or 3 == 3 and 5 != 5')).toBe(true)
323+
});
324324

325+
it('conditionals', async () => {
326+
expect(await e.evaluate('x == null')).toBe(true)
327+
expect(await e.evaluate('x?.p1?.p == null')).toBe(true)
328+
expect(await e.evaluate('x != null and x.length >0')).toBe(false)
329+
expect(await e.evaluate('x?.p1?.p != null and x.length >0')).toBe(false)
330+
});
331+
332+
it('arithmetic + comparison', async () => {
333+
expect(await e.evaluate('1+2*3 == 5 or 1 > 3')).toBe(false)
334+
expect(await e.evaluate('1+2*3 == 5 or 1 < 3')).toBe(true)
335+
});
325336

326337
});

src/parser/parser.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export class Parser {
9191
const firstToken = instruction.tokens[0];
9292
const secondToken = instruction.tokens.length > 1 ? instruction.tokens[1] : null;
9393
this._currentToken = firstToken;
94-
94+
9595
const logicOpIndexes: number[] = [];
9696
const comparisonOpIndexs: number[] = [];
9797
const assignTokenIndexes: number[] = [];
@@ -220,7 +220,7 @@ export class Parser {
220220
const source = this.createExpressionNode(assignTokens[1]);
221221
ast.body.push(new AssignNode(target, source, getTokenLoc(assignTokens[0][0])));
222222
} else if (findIndexes(instruction.tokens, OperationTypes.Logical, logicOpIndexes)) {
223-
ast.body.push(this.groupComparisonOperations(logicOpIndexes, instruction));
223+
ast.body.push(this.groupLogicalOperations(logicOpIndexes, instruction));
224224
} else if (findIndexes(instruction.tokens, OperationTypes.Comparison, comparisonOpIndexs)) {
225225
ast.body.push(this.groupComparisonOperations(comparisonOpIndexs, instruction));
226226
} else {
@@ -230,25 +230,26 @@ export class Parser {
230230
}
231231
}
232232

233+
private sliceWithBrackets(a: Token[], begin: number, end: number): Token[] {
234+
// if expression is in brackets, then we need clean brackets
235+
if (getTokenValue(a[begin]) === '(') {
236+
begin++;
237+
end--;
238+
}
239+
240+
return a.slice(begin, end);
241+
}
242+
233243
private groupComparisonOperations(indexes: number[], instruction: InstructionLine): AstNode {
234244
let start = 0;
235-
const slice = (a: Token[], begin: number, end: number): Token[] => {
236-
// if expression is in brackets, then we need clean brackets
237-
if (getTokenValue(a[begin]) === '(') {
238-
begin++;
239-
end--;
240-
}
241-
242-
return a.slice(begin, end);
243-
}
244245

245246
let leftNode: AstNode | null = null;
246247
for (let i = 0; i < indexes.length; i++) {
247248
const opToken = getTokenValue(instruction.tokens[indexes[i]]) as ComparisonOperators;
248-
leftNode = (leftNode) ? leftNode : this.createExpressionNode(slice(instruction.tokens, start, indexes[i]))
249+
leftNode = (leftNode) ? leftNode : this.createExpressionNode(this.sliceWithBrackets(instruction.tokens, start, indexes[i]))
249250

250251
const endInd = (i + 1 < indexes.length) ? indexes[i + 1] : instruction.tokens.length;
251-
const rightNode = this.createExpressionNode(slice(instruction.tokens, indexes[i] + 1, endInd))
252+
const rightNode = this.createExpressionNode(this.sliceWithBrackets(instruction.tokens, indexes[i] + 1, endInd))
252253

253254
leftNode = new BinOpNode(leftNode, opToken, rightNode, getTokenLoc(instruction.tokens[0]));
254255
}
@@ -261,7 +262,7 @@ export class Parser {
261262
const logicItems: LogicalNodeItem[] = [];
262263
for (let i = 0; i < logicOp.length; i++) {
263264
const opToken = instruction.tokens[logicOp[i]];
264-
const logicalSlice = instruction.tokens.slice(start, logicOp[i]);
265+
const logicalSlice = this.sliceWithBrackets(instruction.tokens, start, logicOp[i]);
265266
logicItems.push({
266267
node: this.createExpressionNode(logicalSlice),
267268
op: getTokenValue(opToken) as LogicalOperators
@@ -271,7 +272,7 @@ export class Parser {
271272
}
272273

273274
logicItems.push({
274-
node: this.createExpressionNode(instruction.tokens.slice(start))
275+
node: this.createExpressionNode(this.sliceWithBrackets(instruction.tokens, start, instruction.tokens.length))
275276
} as LogicalNodeItem);
276277

277278
const lop = new LogicalOpNode(logicItems, getTokenLoc(instruction.tokens[0]));
@@ -370,16 +371,6 @@ export class Parser {
370371
// create arithmetic expression
371372
const ops = findOperators(tokens);
372373
if (ops.length) {
373-
// create binary node here
374-
const slice = (a: Token[], begin: number, end: number): Token[] => {
375-
// if expression is in brackets, then we need clean brackets
376-
if (getTokenValue(a[begin]) === '(') {
377-
begin++;
378-
end--;
379-
}
380-
381-
return a.slice(begin, end);
382-
}
383374

384375
var prevNode: AstNode | null;
385376
for (let i = 0; i < ops.length; i++) {
@@ -394,8 +385,8 @@ export class Parser {
394385
do {
395386
const nextOpIndex2 = i + 2 < ops.length ? ops[i + 2] : null;
396387

397-
const leftSlice2 = slice(tokens, opIndex + 1, nextOpIndex);
398-
const rightSlice2 = slice(tokens, nextOpIndex + 1, nextOpIndex2 || tokens.length);
388+
const leftSlice2 = this.sliceWithBrackets(tokens, opIndex + 1, nextOpIndex);
389+
const rightSlice2 = this.sliceWithBrackets(tokens, nextOpIndex + 1, nextOpIndex2 || tokens.length);
399390

400391
const left2 = this.createExpressionNode(leftSlice2);
401392
const right2 = this.createExpressionNode(rightSlice2);
@@ -409,14 +400,14 @@ export class Parser {
409400

410401
// add up result
411402
if (prevNode === null) {
412-
const leftSlice = slice(tokens, 0, opIndex);
403+
const leftSlice = this.sliceWithBrackets(tokens, 0, opIndex);
413404
prevNode = this.createExpressionNode(leftSlice);
414405
}
415406
prevNode = new BinOpNode(prevNode, op as ExpressionOperators, rightNode, getTokenLoc(tokens[0]))
416407

417408
} else {
418-
const leftSlice = prevNode ? [] : slice(tokens, 0, opIndex);
419-
const rightSlice = slice(tokens, opIndex + 1, nextOpIndex || tokens.length);
409+
const leftSlice = prevNode ? [] : this.sliceWithBrackets(tokens, 0, opIndex);
410+
const rightSlice = this.sliceWithBrackets(tokens, opIndex + 1, nextOpIndex || tokens.length);
420411
const left = prevNode || this.createExpressionNode(leftSlice, prevNode);
421412
const right = this.createExpressionNode(rightSlice);
422413
prevNode = new BinOpNode(left, op as ExpressionOperators, right, getTokenLoc(tokens[0]));

0 commit comments

Comments
 (0)