Skip to content

Commit 2c371c1

Browse files
committed
added elif (#3 and #21)
1 parent f373ea2 commit 2c371c1

File tree

7 files changed

+168
-24
lines changed

7 files changed

+168
-24
lines changed

index.html

+6-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
"1,2,3".split(',')[0]
32+
a = 33
33+
b = 33
34+
if b > a:
35+
print("b is greater than a")
36+
elif a == b:
37+
print("a and b are equal")
3338
</div>
3439

3540
<button onclick="tokenize()">Tokenize</button>

package.json

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

src/common/ast-types.ts

+14-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export type AstNodeType =
1616
| 'createObject'
1717
| 'createArray'
1818
| 'if'
19+
| 'elif'
1920
| 'for'
2021
| 'while'
2122
| 'tryExcept'
@@ -142,12 +143,24 @@ export class ArrowFuncDefNode extends AstNode implements FuncDefNode {
142143
}
143144
}
144145

146+
export class ElifNode extends AstNode {
147+
constructor(
148+
public conditionNode: AstNode,
149+
public elifBody: AstNode[],
150+
public loc: Uint16Array
151+
) {
152+
super('elif');
153+
this.loc = loc;
154+
}
155+
}
156+
145157
export class IfNode extends AstNode {
146158
constructor(
147159
public conditionNode: AstNode,
148160
public ifBody: AstNode[],
161+
public elifs: ElifNode[] | undefined = undefined,
149162
public elseBody: AstNode[] | undefined = undefined,
150-
public loc: Uint16Array
163+
public loc: Uint16Array,
151164
) {
152165
super('if');
153166
this.loc = loc;
@@ -243,7 +256,6 @@ export class ChainingObjectAccessNode extends AstNode {
243256
}
244257
}
245258

246-
247259
export interface LogicalNodeItem {
248260
node: AstNode;
249261
op: LogicalOperators | undefined;

src/evaluator/evaluator.ts

+26-17
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
AstBlock,
55
AstNode,
66
BinOpNode,
7-
ChainingCallsNode,
7+
ChainingCallsNode,
88
ChainingObjectAccessNode,
99
ConstNode,
1010
CreateArrayNode,
@@ -139,12 +139,29 @@ export class Evaluator {
139139

140140
if (node.type === 'if') {
141141
const ifNode = node as IfNode;
142+
let doElse = true;
142143
if (this.evalNode(ifNode.conditionNode, blockContext)) {
143144
this.evalBlock(
144145
{ name: blockContext.moduleName, type: 'if', body: ifNode.ifBody } as AstBlock,
145146
blockContext
146147
);
147-
} else if (ifNode.elseBody) {
148+
doElse = false;
149+
} else if (ifNode.elifs?.length) {
150+
for (let i = 0; i < ifNode.elifs.length; i++) {
151+
const elIfNode = ifNode.elifs[i];
152+
153+
if (this.evalNode(elIfNode.conditionNode, blockContext)) {
154+
this.evalBlock(
155+
{ name: blockContext.moduleName, type: 'if', body: elIfNode.elifBody } as AstBlock,
156+
blockContext
157+
);
158+
doElse = false;
159+
break;
160+
}
161+
}
162+
}
163+
164+
if (doElse && ifNode.elseBody) {
148165
this.evalBlock(
149166
{ name: blockContext.moduleName, type: 'if', body: ifNode.elseBody } as AstBlock,
150167
blockContext
@@ -369,10 +386,7 @@ export class Evaluator {
369386

370387
if (assignNode.target.type === 'getSingleVar') {
371388
const node = assignNode.target as SetSingleVarNode;
372-
blockContext.blockScope.set(
373-
node.name,
374-
this.evalNode(assignNode.source, blockContext)
375-
);
389+
blockContext.blockScope.set(node.name, this.evalNode(assignNode.source, blockContext));
376390
} else if (assignNode.target.type === 'chainingCalls') {
377391
const targetNode = assignNode.target as ChainingCallsNode;
378392

@@ -382,7 +396,7 @@ export class Evaluator {
382396
targetNode.innerNodes.slice(0, targetNode.innerNodes.length - 1),
383397
targetNode.loc
384398
);
385-
const targetObject = (this.evalNode(targetObjectNode, blockContext)) as Record<
399+
const targetObject = this.evalNode(targetObjectNode, blockContext) as Record<
386400
string,
387401
unknown
388402
>;
@@ -393,10 +407,10 @@ export class Evaluator {
393407
if (lastInnerNode.type === 'getSingleVar') {
394408
lastPropertyName = (lastInnerNode as GetSingleVarNode).name;
395409
} else if (lastInnerNode.type === 'chainingObjectAccess') {
396-
lastPropertyName = (this.evalNode(
410+
lastPropertyName = this.evalNode(
397411
(lastInnerNode as ChainingObjectAccessNode).indexerBody,
398412
blockContext
399-
)) as string;
413+
) as string;
400414
} else {
401415
throw Error('Not implemented Assign operation with chaining calls');
402416
}
@@ -411,7 +425,6 @@ export class Evaluator {
411425
return this.resolveChainingCallsNode(node as ChainingCallsNode, blockContext);
412426
}
413427

414-
415428
if (node.type === 'createObject') {
416429
const createObjectNode = node as CreateObjectNode;
417430
const obj = {} as Record<string, unknown>;
@@ -435,12 +448,9 @@ export class Evaluator {
435448
}
436449
}
437450

438-
private resolveChainingCallsNode(
439-
chNode: ChainingCallsNode,
440-
blockContext: BlockContext
441-
): unknown {
451+
private resolveChainingCallsNode(chNode: ChainingCallsNode, blockContext: BlockContext): unknown {
442452
// eslint-disable-next-line @typescript-eslint/no-explicit-any
443-
let startObject = (this.evalNode(chNode.innerNodes[0], blockContext)) as any;
453+
let startObject = this.evalNode(chNode.innerNodes[0], blockContext) as any;
444454

445455
for (let i = 1; i < chNode.innerNodes.length; i++) {
446456
const nestedProp = chNode.innerNodes[i];
@@ -455,7 +465,7 @@ export class Evaluator {
455465
const node = nestedProp as ChainingObjectAccessNode;
456466
// startObject = startObject[node.] as unknown;
457467
startObject = startObject[
458-
(this.evalNode(node.indexerBody, blockContext)) as string
468+
this.evalNode(node.indexerBody, blockContext) as string
459469
] as unknown;
460470
} else if (nestedProp.type === 'funcCall') {
461471
const funcCallNode = nestedProp as FunctionCallNode;
@@ -489,5 +499,4 @@ export class Evaluator {
489499

490500
return startObject === undefined ? null : startObject;
491501
}
492-
493502
}

src/evaluator/evaluatorAsync.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,30 @@ export class EvaluatorAsync {
238238

239239
if (node.type === 'if') {
240240
const ifNode = node as IfNode;
241+
let doElse = true;
242+
241243
if (await this.evalNodeAsync(ifNode.conditionNode, blockContext)) {
242244
await this.evalBlockAsync(
243245
{ name: blockContext.moduleName, type: 'if', body: ifNode.ifBody } as AstBlock,
244246
blockContext
245247
);
246-
} else if (ifNode.elseBody) {
248+
doElse = false;
249+
} else if (ifNode.elifs?.length) {
250+
for (let i = 0; i < ifNode.elifs.length; i++) {
251+
const elIfNode = ifNode.elifs[i];
252+
253+
if (await this.evalNodeAsync(elIfNode.conditionNode, blockContext)) {
254+
await this.evalBlockAsync(
255+
{ name: blockContext.moduleName, type: 'if', body: elIfNode.elifBody } as AstBlock,
256+
blockContext
257+
);
258+
doElse = false;
259+
break;
260+
}
261+
}
262+
}
263+
264+
if (doElse && ifNode.elseBody) {
247265
await this.evalBlockAsync(
248266
{ name: blockContext.moduleName, type: 'if', body: ifNode.elseBody } as AstBlock,
249267
blockContext

src/interpreter.spec.ts

+72
Original file line numberDiff line numberDiff line change
@@ -909,5 +909,77 @@ describe('Interpreter', () => {
909909
expect(interpreter.eval('"\\"12\\"34\\""')).toBe('"12"34"');
910910
});
911911

912+
it('- elif 1', async () => {
913+
const interpreter = Interpreter.create();
914+
915+
const script = `
916+
x = 5
917+
if x == 6:
918+
x = 20
919+
elif x == 5:
920+
x = 10
921+
else:
922+
x = 30
923+
924+
return x
925+
`;
926+
expect(await interpreter.evalAsync(script)).toBe(10);
927+
expect(interpreter.eval(script)).toBe(10);
928+
});
929+
930+
it('- elif 2', async () => {
931+
const interpreter = Interpreter.create();
932+
933+
const script = `
934+
x = 6
935+
if x == 6:
936+
x = 20
937+
elif x == 5:
938+
x = 10
939+
else:
940+
x = 30
941+
942+
return x
943+
`;
944+
expect(await interpreter.evalAsync(script)).toBe(20);
945+
expect(interpreter.eval(script)).toBe(20);
946+
});
947+
948+
it('- elif 3', async () => {
949+
const interpreter = Interpreter.create();
950+
951+
const script = `
952+
x = 11
953+
if x == 6:
954+
x = 20
955+
elif x == 5:
956+
x = 10
957+
else:
958+
x = 30
959+
return x
960+
`;
961+
expect(await interpreter.evalAsync(script)).toBe(30);
962+
expect(interpreter.eval(script)).toBe(30);
963+
});
964+
965+
it('- elif 4', async () => {
966+
const interpreter = Interpreter.create();
967+
968+
const script = `
969+
x = 11
970+
if x == 6:
971+
x = 20
972+
elif x == 11:
973+
x = 11
974+
elif x == 5:
975+
x = 10
976+
else:
977+
x = 30
978+
return x
979+
`;
980+
expect(await interpreter.evalAsync(script)).toBe(11);
981+
expect(interpreter.eval(script)).toBe(11);
982+
});
983+
912984
//
913985
});

src/parser/parser.ts

+30-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ import {
4646
findChainingCallTokensIndexes,
4747
splitTokensByIndexes,
4848
ChainingCallsNode,
49-
ChainingObjectAccessNode
49+
ChainingObjectAccessNode,
50+
ElifNode
5051
} from '../common';
5152
import { JspyParserError } from '../common/utils';
5253

@@ -197,6 +198,31 @@ export class Parser {
197198
? this.groupLogicalOperations(logicOpIndexes, conditionTokens)
198199
: this.createExpressionNode(conditionTokens);
199200

201+
// elifs
202+
const elifNodes: ElifNode[] = [];
203+
while (
204+
instructions.length > i + 1 &&
205+
getTokenValue(instructions[i + 1].tokens[0]) === 'elif'
206+
) {
207+
208+
const elifInstruction = instructions[++i];
209+
210+
const endOfElif = findTokenValueIndex(elifInstruction.tokens, v => v === ':');
211+
212+
const conditionTokens = elifInstruction.tokens.slice(1, endDefOfDef);
213+
214+
const elifConditionNode = findIndexes(conditionTokens, OperationTypes.Logical, logicOpIndexes)
215+
? this.groupLogicalOperations(logicOpIndexes, conditionTokens)
216+
: this.createExpressionNode(conditionTokens);
217+
218+
const elifBody = getBody(elifInstruction.tokens, endOfElif+1);
219+
elifNodes.push(
220+
new ElifNode(elifConditionNode, elifBody, getTokenLoc(elifInstruction.tokens[0]))
221+
);
222+
223+
}
224+
225+
// else
200226
let elseBody: AstNode[] | undefined = undefined;
201227
if (
202228
instructions.length > i + 1 &&
@@ -207,7 +233,9 @@ export class Parser {
207233
i++;
208234
}
209235

210-
ast.body.push(new IfNode(conditionNode, ifBody, elseBody, getTokenLoc(firstToken)));
236+
ast.body.push(
237+
new IfNode(conditionNode, ifBody, elifNodes, elseBody, getTokenLoc(firstToken))
238+
);
211239
} else if (getTokenValue(firstToken) === 'try') {
212240
if (getTokenValue(instruction.tokens[1]) !== ':') {
213241
throw `'try' statement should be followed by ':'`;

0 commit comments

Comments
 (0)