Skip to content

Commit a240583

Browse files
committed
added JSON parser for object/array creation
1 parent 7f7e4ee commit a240583

File tree

5 files changed

+111
-22
lines changed

5 files changed

+111
-22
lines changed

index.html

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,8 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
def add(x, y):
33-
z = 5
34-
x + z + y
35-
36-
add(3, 5)
37-
32+
x = { obj1: 2 + 4 }
33+
x
3834
</div>
3935
<button onclick="tokenize()">Tokenize</button>
4036
<button onclick="parse()">Parse</button>

src/common/ast-types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export abstract class AstNode {
88
'assign' | 'binOp' | 'const'
99
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
1010
| 'funcCall' | 'funcDef'
11+
| 'createObject' | 'createArray'
1112
| 'if' | 'while' | 'tryCatch'
1213
) { }
1314
}
@@ -66,6 +67,25 @@ export class DotObjectAccessNode extends AstNode {
6667
super('dotObjectAccess');
6768
}
6869
}
70+
export interface ObjectPropertyInfo {
71+
name: AstNode;
72+
value: AstNode;
73+
}
74+
75+
export class CreateObjectNode extends AstNode {
76+
constructor(public props: ObjectPropertyInfo[]) {
77+
super('createObject');
78+
}
79+
}
80+
81+
export class CreateArrayNode extends AstNode {
82+
constructor(
83+
public items: AstNode[]
84+
) {
85+
super('createArray');
86+
}
87+
}
88+
6989

7090
export class BracketObjectAccessNode extends AstNode {
7191
constructor(

src/evaluator/evaluator.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { AssignNode, Ast, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, DotObjectAccessNode, FunctionCallNode, GetSingleVarNode, Operators, SetSingleVarNode } from '../common';
1+
import {
2+
AssignNode, Ast, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
3+
CreateObjectNode, DotObjectAccessNode, FunctionCallNode, GetSingleVarNode, SetSingleVarNode
4+
} from '../common';
25
import { Scope } from './scope';
36

47
type Primitive = string | number | boolean | null;
@@ -70,7 +73,6 @@ export class Evaluator {
7073
}
7174
}
7275

73-
7476
private evalNode(node: AstNode, scope: Scope): unknown {
7577
if (node.type === "const") {
7678
return (node as ConstNode).value;
@@ -141,13 +143,13 @@ export class Evaluator {
141143
const node = dotObject.nestedProps[i] as BracketObjectAccessNode;
142144
startObject = startObject[node.propertyName] as unknown;
143145
startObject = startObject[this.evalNode(node.bracketBody, scope) as string] as unknown;
144-
} else if(dotObject.nestedProps[i].type === 'funcCall'){
146+
} else if (dotObject.nestedProps[i].type === 'funcCall') {
145147
const funcCallNode = dotObject.nestedProps[i] as FunctionCallNode;
146148
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
147149
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, scope)) || []
148150

149151
startObject = this.invokeFunction(func, pms);
150-
152+
151153
} else {
152154
throw Error("Can't resolve dotObjectAccess node")
153155
}
@@ -156,6 +158,28 @@ export class Evaluator {
156158
return startObject;
157159
}
158160

161+
if (node.type === 'createObject') {
162+
const createObjectNode = node as CreateObjectNode;
163+
const obj = {} as Record<string, unknown>;
164+
165+
for (const p of createObjectNode.props) {
166+
obj[this.evalNode(p.name, scope) as string] = this.evalNode(p.value, scope);
167+
}
168+
169+
return obj;
170+
}
171+
172+
if (node.type === 'createArray') {
173+
const arrayNode = node as CreateArrayNode;
174+
const res = [] as unknown[];
175+
176+
for (const item of arrayNode.items) {
177+
res.push(this.evalNode(item, scope));
178+
}
179+
180+
return res;
181+
}
182+
159183
}
160184

161185

src/interpreter.spec.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,14 @@ describe('Interpreter', () => {
8181

8282
it('func call', async () => {
8383
const obj = { add: (x: number, y: number) => x + y };
84-
84+
8585
expect(await e.evaluate("add(2, 3)", obj)).toBe(5)
8686
expect(await e.evaluate("add(2+10, 3)", obj)).toBe(15)
8787
});
8888

8989
it('Object call', async () => {
90-
const obj = { o: { add: (x: number, y: number) => x + y }};
91-
90+
const obj = { o: { add: (x: number, y: number) => x + y } };
91+
9292
expect(await e.evaluate("o.add(2, 3)", obj)).toBe(5)
9393
expect(await e.evaluate("o.add(2 * 10, 3)", obj)).toBe(23)
9494
expect(await e.evaluate(`
@@ -100,14 +100,30 @@ describe('Interpreter', () => {
100100
});
101101

102102
it('Object call2', async () => {
103-
const obj = { o: {
104-
add: (x: number, y: number) => x + y,
105-
getObject: p => { return {p}}
106-
}};
107-
103+
const obj = {
104+
o: {
105+
add: (x: number, y: number) => x + y,
106+
getObject: (p: string) => { return { p } }
107+
}
108+
};
109+
108110
expect(await e.evaluate("o.getObject(5).p", obj)).toBe(5)
109111
expect(await e.evaluate("x = o.getObject(5)\nx.p * x.p", obj)).toBe(25)
110112
});
111113

114+
it('json obj', async () => {
115+
expect(await e.evaluate("x = {m1: 1+2*3, m2: 'ee'}\nx.m1")).toBe(7);
116+
expect(await e.evaluate("x = {'m1': 1+2*3}\nx.m1")).toBe(7);
117+
expect(await e.evaluate("x = {'m'+1: 1+2*3}\nx.m1")).toBe(7);
118+
});
119+
120+
it('json array', async () => {
121+
expect(await e.evaluate("x = [{m1: 1+2*3, m2: 'ee'}]\nx.length")).toBe(1);
122+
expect(await e.evaluate("x = [1,2,3]\nx.length")).toBe(3);
123+
expect(await e.evaluate("x = [1,2,3]\nx[1]")).toBe(2);
124+
expect(await e.evaluate("x = [{f1:1, f2:12}, {f1:2, f2:22}, {f1:3, f2:32}]\nx[1].f2")).toBe(22);
125+
expect(await e.evaluate("x = [{f1:1, f2:12}, {f1:2, f2:22}, {f1:3, f2:32}]\nx[1].f2 = 55\nx[1].f2")).toBe(55);
126+
});
127+
112128

113129
});

src/parser/parser.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import {
2-
BinOpNode, ConstNode, Ast, Token, ParserOptions, AstNode, OperatorsMap, OperationTypes,
3-
Operators, AssignNode, TokenTypes, SetSingleVarNode, GetSingleVarNode, FunctionCallNode,
4-
getTokenType, getTokenValue, isTokenTypeLiteral, getStartLine, getStartColumn, getEndColumn,
5-
getEndLine, findOperators, splitTokens, DotObjectAccessNode, BracketObjectAccessNode, findTokenValueIndexes, findTokenValueIndex, FunctionDefNode
2+
BinOpNode, ConstNode, Ast, 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
66
} from '../common';
77

88
export class InstructionLine {
@@ -246,6 +246,39 @@ export class Parser {
246246
return new BracketObjectAccessNode(name, paramsNodes);
247247
}
248248

249+
// create Object Node
250+
if (getTokenValue(tokens[0]) === '{' && getTokenValue(tokens[tokens.length - 1]) === '}') {
251+
const keyValueTokens = splitTokens(tokens.splice(1, tokens.length - 2), ',');
252+
const props = [] as ObjectPropertyInfo[];
253+
for (let i = 0; i < keyValueTokens.length; i++) {
254+
const keyValue = splitTokens(keyValueTokens[i], ':');
255+
if (keyValue.length !== 2) {
256+
throw Error('Incorrect JSON')
257+
}
258+
259+
// unquoted string becomes a variable, so, we don't need it, that is why we are creating const node explicitlely
260+
const name = (keyValue[0].length === 1 && !`'"`.includes((getTokenValue(keyValue[0][0]) as string)[0]))
261+
? new ConstNode(keyValue[0][0])
262+
: this.createExpressionNode(keyValue[0]);
263+
const pInfo = {
264+
name,
265+
value: this.createExpressionNode(keyValue[1])
266+
} as ObjectPropertyInfo;
267+
268+
props.push(pInfo);
269+
}
270+
271+
return new CreateObjectNode(props)
272+
}
273+
274+
// create Array Node
275+
if (getTokenValue(tokens[0]) === '[' && getTokenValue(tokens[tokens.length - 1]) === ']') {
276+
const items = splitTokens(tokens.splice(1, tokens.length - 2), ',')
277+
.map(tkns => this.createExpressionNode(tkns));
278+
279+
return new CreateArrayNode(items);
280+
}
281+
249282
throw new Error('Undefined node error.');
250283
}
251284
}

0 commit comments

Comments
 (0)