Skip to content

Commit 2c42e75

Browse files
committed
Added if condition support
1 parent c67f84a commit 2c42e75

File tree

5 files changed

+78
-20
lines changed

5 files changed

+78
-20
lines changed

index.html

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
arr = [1,2,3]
33-
arr.map(n =>
34-
n = n * 2
35-
{
36-
t1: n * 2,
37-
t2: n * 3
38-
}
39-
)
32+
x = 1
33+
34+
if x == 3:
35+
x = 5
36+
else:
37+
x = 10
4038

4139
</div>
4240
<button onclick="tokenize()">Tokenize</button>

src/common/ast-types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ export class ArrowFuncDefNode extends AstNode {
5757
}
5858
}
5959

60+
export class IfNode extends AstNode {
61+
constructor(public conditionNode: AstNode, public ifBody: AstNode[], public elseBody: AstNode[] | undefined = undefined) {
62+
super('if');
63+
}
64+
}
65+
6066
export class GetSingleVarNode extends AstNode {
6167
name: string;
6268
nullCoelsing: boolean | undefined = undefined;

src/evaluator/evaluator.ts

Lines changed: 15 additions & 3 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, OperationFuncs, Primitive, SetSingleVarNode
4+
CreateObjectNode, DotObjectAccessNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, IfNode, OperationFuncs, Primitive, SetSingleVarNode
55
} from '../common';
66
import { Scope } from './scope';
77

@@ -85,6 +85,18 @@ export class Evaluator {
8585
}
8686

8787
private evalNode(node: AstNode, scope: Scope): unknown {
88+
if (node.type === 'if') {
89+
const ifNode = node as IfNode;
90+
const newScope = scope;
91+
if (this.evalNode(ifNode.conditionNode, scope)) {
92+
this.evalBlock({ body: ifNode.ifBody } as AstBlock, newScope);
93+
} else if (ifNode.elseBody) {
94+
this.evalBlock({ body: ifNode.elseBody } as AstBlock, newScope);
95+
}
96+
97+
return;
98+
}
99+
88100
if (node.type === "const") {
89101
return (node as ConstNode).value;
90102
}
@@ -100,7 +112,7 @@ export class Evaluator {
100112
return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive);
101113
}
102114

103-
if(node.type === "arrowFuncDef") {
115+
if (node.type === "arrowFuncDef") {
104116
const arrowFuncDef = node as ArrowFuncDefNode;
105117

106118
const newScope = scope;
@@ -120,7 +132,7 @@ export class Evaluator {
120132
}
121133
newScope.set(arrowFuncDef.params[i], args[i]);
122134
}
123-
return this.evalBlock(ast, newScope);
135+
return this.evalBlock(ast, newScope);
124136
}
125137

126138
return arrowFuncHandler;

src/interpreter.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,4 +207,19 @@ describe('Interpreter', () => {
207207
expect(e.evaluate(script)).toBe("96,216");
208208
});
209209

210+
211+
it('if condition', () => {
212+
const script = (p) => `
213+
x = 1
214+
if x == ${p}:
215+
x = 5
216+
else:
217+
x = 10
218+
x
219+
`;
220+
expect(e.evaluate(script(1))).toBe(5);
221+
expect(e.evaluate(script(2))).toBe(10);
222+
});
223+
224+
210225
});

src/parser/parser.ts

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

88
export class InstructionLine {
@@ -52,12 +52,19 @@ export class Parser {
5252

5353
private instructionsToNodes(instructions: InstructionLine[], ast: AstBlock): void {
5454

55-
for (let instruction of instructions) {
55+
const getBody = (tokens: Token[], startTokenIndex: number): AstNode[] => {
56+
const instructionLines = this.getBlock(tokens, getStartLine(tokens[startTokenIndex]));
57+
const bodyAst = { body: [] as AstNode[], funcs: [] as AstNode[] } as AstBlock;
58+
this.instructionsToNodes(instructionLines, bodyAst);
59+
return bodyAst.body;
60+
}
61+
62+
for (let i = 0; i < instructions.length; i++) {
63+
const instruction = instructions[i]
5664

5765
if (!instruction.tokens.length) {
5866
continue;
5967
}
60-
6168
const assignTokens = splitTokens(instruction.tokens, '=');
6269

6370
if (getTokenValue(instruction.tokens[0]) === 'def') {
@@ -84,6 +91,26 @@ export class Parser {
8491

8592
ast.funcs.push(new FunctionDefNode(funcName, params, funcAst.body))
8693

94+
} else if (getTokenValue(instruction.tokens[0]) === 'if') {
95+
96+
const endDefOfDef = findTokenValueIndex(instruction.tokens, v => v === ':');
97+
98+
if (endDefOfDef === -1) {
99+
throw (`Can't find : for if`)
100+
}
101+
102+
const ifBody = getBody(instruction.tokens, endDefOfDef + 1);
103+
const conditionNode = this.createExpressionNode(instruction.tokens.slice(1, endDefOfDef))
104+
105+
let elseBody: AstNode[] | undefined = undefined;
106+
if (getTokenValue(instructions[i + 1].tokens[0]) === 'else'
107+
&& getTokenValue(instructions[i + 1].tokens[1]) === ':') {
108+
elseBody = getBody(instructions[i + 1].tokens, 2);
109+
i++;
110+
}
111+
112+
ast.body.push(new IfNode(conditionNode, ifBody, elseBody))
113+
87114
} else if (assignTokens.length > 1) {
88115
const target = this.createExpressionNode(assignTokens[0]);
89116
const source = this.createExpressionNode(assignTokens[1]);
@@ -112,7 +139,7 @@ export class Parser {
112139
currentLine = sLine;
113140
}
114141

115-
if (column === sColumn && !")}]".includes(value as string) ) {
142+
if (column === sColumn && !")}]".includes(value as string)) {
116143
currentLine = sLine;
117144
lines.push(line);
118145
line = new InstructionLine();
@@ -164,7 +191,7 @@ export class Parser {
164191

165192
// arrow function
166193
const arrowFuncParts = splitTokens(tokens, '=>');
167-
if(arrowFuncParts.length > 1) {
194+
if (arrowFuncParts.length > 1) {
168195
const params = splitTokens(arrowFuncParts[0], ',').map(t => getTokenValue(t[0]) as string);
169196

170197
const instructionLines = this.getBlock(arrowFuncParts[1], 0);
@@ -275,7 +302,7 @@ export class Parser {
275302
}
276303

277304
// unquoted string becomes a variable, so, we don't need it, that is why we are creating const node explicitlely
278-
const name = (keyValue[0].length === 1 && !`'"`.includes((getTokenValue(keyValue[0][0]) as string)[0]))
305+
const name = (keyValue[0].length === 1 && !`'"`.includes((getTokenValue(keyValue[0][0]) as string)[0]))
279306
? new ConstNode(keyValue[0][0])
280307
: this.createExpressionNode(keyValue[0]);
281308
const pInfo = {

0 commit comments

Comments
 (0)