Skip to content

Commit 1e46f27

Browse files
committed
added v1 compatibility and lots of broken test
1 parent 3969c8e commit 1e46f27

File tree

10 files changed

+2111
-151
lines changed

10 files changed

+2111
-151
lines changed

index.html

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,20 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
from datapipe-js-array import sort, first as f, fullJoin
33-
</div>
32+
print(
33+
""
34+
)
35+
</div>
36+
<!--
37+
def f(n):
38+
n * n
39+
x = [2, 3, 4]
40+
x.map(f)
41+
42+
# arr = [1,2,3]
43+
# arr.map(mapFunc)
44+
45+
-->
3446
<button onclick="tokenize()">Tokenize</button>
3547
<button onclick="parse()">Parse</button>
3648
<button onclick="runInterpreter()">Run</button>
@@ -82,7 +94,7 @@ <h4>JSPython development console</h4>
8294
}
8395
}
8496

85-
function runInterpreter() {
97+
async function runInterpreter() {
8698

8799
const scripts = editor.getValue();
88100
try {
@@ -102,8 +114,14 @@ <h4>JSPython development console</h4>
102114
}
103115
}
104116
};
105-
const result = jsPython()
106-
.evaluate(scripts, scope);
117+
// const result = await jsPython()
118+
// .evaluate(scripts, scope);
119+
120+
// const result = jsPython()
121+
// .eval(scripts, scope);
122+
123+
const result = await jsPython()
124+
.evalAsync(scripts, scope);
107125

108126
const data = typeof result === 'object' ? JSON.stringify(result, null, '\t') : String(result);
109127
resultEditor.getSession().setValue(data)

src/common/ast-types.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,17 @@
11
import { ExpressionOperators, OperationTypes, Operators } from "./operators";
22
import { Token } from "./token-types";
33

4+
export type AstNodeType = 'assign' | 'binOp' | 'const'
5+
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
6+
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
7+
| 'createObject' | 'createArray'
8+
| 'if' | 'for' | 'while'
9+
| 'import'
10+
| 'return' | 'continue' | 'break';
11+
412
export abstract class AstNode {
513
loc: Uint16Array | undefined = undefined;
6-
constructor(
7-
public type:
8-
'assign' | 'binOp' | 'const'
9-
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
10-
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
11-
| 'createObject' | 'createArray'
12-
| 'if' | 'for' | 'while'
13-
| 'import'
14-
| 'return' | 'continue' | 'break'
15-
) { }
14+
constructor(public type: AstNodeType) { }
1615
}
1716

1817
export class AssignNode extends AstNode {
@@ -65,13 +64,18 @@ export class FunctionCallNode extends AstNode {
6564
}
6665
}
6766

68-
export class FunctionDefNode extends AstNode {
67+
export interface FuncDefNode {
68+
params: string[];
69+
body: AstNode[];
70+
}
71+
72+
export class FunctionDefNode extends AstNode implements FuncDefNode {
6973
constructor(public name: string, public params: string[], public body: AstNode[]) {
70-
super('funcDef');
74+
super('funcDef',);
7175
}
7276
}
7377

74-
export class ArrowFuncDefNode extends AstNode {
78+
export class ArrowFuncDefNode extends AstNode implements FuncDefNode {
7579
constructor(public params: string[], public body: AstNode[]) {
7680
super('arrowFuncDef');
7781
}

src/common/utils.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
export function parseDatetimeOrNull(value: string | Date): Date | null {
2+
if (!value) { return null; }
3+
if (value instanceof Date && !isNaN(value.valueOf())) { return value; }
4+
// only string values can be converted to Date
5+
if (typeof value !== 'string') { return null; }
6+
7+
const strValue = String(value);
8+
if (!strValue.length) { return null; }
9+
10+
const parseMonth = (mm: string): number => {
11+
if (!mm || !mm.length) {
12+
return NaN;
13+
}
14+
15+
const m = parseInt(mm, 10);
16+
if (!isNaN(m)) {
17+
return m - 1;
18+
}
19+
20+
// make sure english months are coming through
21+
if (mm.startsWith('jan')) { return 0; }
22+
if (mm.startsWith('feb')) { return 1; }
23+
if (mm.startsWith('mar')) { return 2; }
24+
if (mm.startsWith('apr')) { return 3; }
25+
if (mm.startsWith('may')) { return 4; }
26+
if (mm.startsWith('jun')) { return 5; }
27+
if (mm.startsWith('jul')) { return 6; }
28+
if (mm.startsWith('aug')) { return 7; }
29+
if (mm.startsWith('sep')) { return 8; }
30+
if (mm.startsWith('oct')) { return 9; }
31+
if (mm.startsWith('nov')) { return 10; }
32+
if (mm.startsWith('dec')) { return 11; }
33+
34+
return NaN;
35+
};
36+
37+
const correctYear = (yy: number) => {
38+
if (yy < 100) {
39+
return yy < 68 ? yy + 2000 : yy + 1900;
40+
} else {
41+
return yy;
42+
}
43+
};
44+
45+
const validDateOrNull =
46+
(yyyy: number, month: number, day: number, hours: number, mins: number, ss: number): Date | null => {
47+
if (month > 11 || day > 31 || hours >= 60 || mins >= 60 || ss >= 60) { return null; }
48+
49+
const dd = new Date(yyyy, month, day, hours, mins, ss, 0);
50+
return !isNaN(dd.valueOf()) ? dd : null;
51+
};
52+
53+
const strTokens = strValue.replace('T', ' ').toLowerCase().split(/[: /-]/);
54+
const dt = strTokens.map(parseFloat);
55+
56+
// try ISO first
57+
let d = validDateOrNull(dt[0], dt[1] - 1, dt[2], dt[3] || 0, dt[4] || 0, dt[5] || 0);
58+
if (d) { return d; }
59+
60+
// then UK
61+
d = validDateOrNull(correctYear(dt[2]), parseMonth(strTokens[1]), dt[0], dt[3] || 0, dt[4] || 0, dt[5] || 0);
62+
if (d) { return d; }
63+
64+
// then US
65+
d = validDateOrNull(correctYear(dt[2]), parseMonth(strTokens[0]), correctYear(dt[1]), dt[3] || 0, dt[4] || 0, dt[5] || 0);
66+
if (d) { return d; }
67+
68+
return null;
69+
}

src/evaluator/evaluator.ts

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

88
export class Evaluator {
99

10-
1110
evalBlock(ast: AstBlock, scope: Scope): unknown {
1211
let lastResult = null;
1312

1413
for (let node of ast?.funcs || []) {
1514
const funcDef = node as FunctionDefNode;
1615

17-
const ast = {
18-
name: '',
19-
type: 'func',
20-
funcs: [],
21-
body: funcDef.body
22-
} as AstBlock;
23-
2416
// a child scope needs to be created here
2517
const newScope = scope;
2618

27-
scope.set(funcDef.name, (...args: unknown[]): unknown => {
28-
29-
// set parameters into new scope, based incomming arguments
30-
for (let i = 0; i < args?.length || 0; i++) {
31-
if (i >= funcDef.params.length) {
32-
break;
33-
// throw new Error('Too much parameters provided');
34-
}
35-
newScope.set(funcDef.params[i], args[i]);
36-
}
37-
return this.evalBlock(ast, newScope);
38-
}
19+
scope.set(funcDef.name,
20+
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, scope, ...args)
3921
);
22+
}
23+
24+
for (const node of ast.body) {
25+
lastResult = this.evalNode(node, scope);
26+
}
27+
28+
return lastResult;
29+
}
30+
31+
async evalBlockAsync(ast: AstBlock, scope: Scope): Promise<unknown> {
32+
let lastResult = null;
33+
34+
for (let node of ast?.funcs || []) {
35+
const funcDef = node as FunctionDefNode;
4036

37+
// a child scope needs to be created here
38+
const newScope = scope;
39+
40+
scope.set(funcDef.name,
41+
(...args: unknown[]): unknown => this.jspyFuncInvoker(funcDef, scope, ...args)
42+
);
4143
}
4244

4345
for (const node of ast.body) {
@@ -47,6 +49,21 @@ export class Evaluator {
4749
return lastResult;
4850
}
4951

52+
private jspyFuncInvoker(funcDef: FuncDefNode, newScope: Scope, ...args: unknown[]): unknown {
53+
54+
const ast = { name: '', type: 'func', funcs: [], body: funcDef.body } as AstBlock;
55+
56+
// set parameters into new scope, based incomming arguments
57+
for (let i = 0; i < args?.length || 0; i++) {
58+
if (i >= funcDef.params.length) {
59+
break;
60+
// throw new Error('Too much parameters provided');
61+
}
62+
newScope.set(funcDef.params[i], args[i]);
63+
}
64+
return this.evalBlock(ast, newScope);
65+
}
66+
5067
private invokeFunction(func: (...args: unknown[]) => unknown, fps: unknown[]): unknown {
5168
if (fps.length === 0) { return func(); }
5269
if (fps.length === 1) { return func(fps[0]); }
@@ -143,28 +160,9 @@ export class Evaluator {
143160

144161
if (node.type === "arrowFuncDef") {
145162
const arrowFuncDef = node as ArrowFuncDefNode;
146-
147163
const newScope = scope;
148-
const ast = {
149-
name: '',
150-
type: 'func',
151-
funcs: [],
152-
body: arrowFuncDef.body
153-
} as AstBlock;
154-
155-
const arrowFuncHandler = (...args: unknown[]): unknown => {
156-
// set parameters into new scope, based incomming arguments
157-
for (let i = 0; i < args?.length || 0; i++) {
158-
if (i >= arrowFuncDef.params.length) {
159-
break;
160-
// throw new Error('Too much parameters provided');
161-
}
162-
newScope.set(arrowFuncDef.params[i], args[i]);
163-
}
164-
return this.evalBlock(ast, newScope);
165-
}
166164

167-
return arrowFuncHandler;
165+
return (...args:unknown[]): unknown => this.jspyFuncInvoker(arrowFuncDef, newScope, ...args);
168166
}
169167

170168
if (node.type === "funcCall") {
@@ -194,6 +192,12 @@ export class Evaluator {
194192
const lastPropertyName = (targetNode.nestedProps[targetNode.nestedProps.length - 1] as GetSingleVarNode).name
195193

196194
targetObject[lastPropertyName] = this.evalNode(assignNode.source, scope);
195+
} else if (assignNode.target.type === 'bracketObjectAccess') {
196+
const targetNode = assignNode.target as BracketObjectAccessNode;
197+
const keyValue = this.evalNode(targetNode.bracketBody, scope) as string | number;
198+
const targetObject = scope.get(targetNode.propertyName as string) as Record<string, unknown>;
199+
200+
targetObject[keyValue] = this.evalNode(assignNode.source, scope);
197201
} else {
198202
throw Error('Not implemented Assign operation');
199203
// get chaining calls
@@ -206,7 +210,7 @@ export class Evaluator {
206210
const sbNode = node as BracketObjectAccessNode;
207211
const key = this.evalNode(sbNode.bracketBody, scope) as string;
208212
const obj = scope.get(sbNode.propertyName as string) as Record<string, unknown>;
209-
return obj[key];
213+
return (obj[key] === undefined)? null : obj[key];
210214
}
211215

212216
if (node.type === "dotObjectAccess") {
@@ -265,6 +269,4 @@ export class Evaluator {
265269
}
266270

267271
}
268-
269-
270-
}
272+
}

src/initialScope.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { parseDatetimeOrNull } from "./common/utils";
2+
3+
export const INITIAL_SCOPE = {
4+
jsPython(): string {
5+
return [`JSPython v2.0.1`, "(c) FalconSoft Ltd"].join('\n')
6+
},
7+
dateTime: (str: number | string | any = null) => (str && str.length)
8+
? parseDatetimeOrNull(str) || new Date() : new Date(),
9+
range: range,
10+
print: (...args: any[]) => { console.log(...args); return args.length > 0 ? args[0] : null; },
11+
isNull: (v: any, defValue: any = null): boolean | any => defValue === null ? v === null : v || defValue,
12+
deleteProperty: (obj: any, propName: string): boolean => delete obj[propName],
13+
Math: Math,
14+
Object: Object,
15+
Array: Array,
16+
JSON: JSON,
17+
printExecutionContext: () => {}, // will be overriden at runtime
18+
getExecutionContext: () => {} // will be overriden at runtime
19+
};
20+
21+
/**
22+
* This interface needs to be replaced
23+
*/
24+
export interface PackageToImport {
25+
name: string;
26+
properties?: { name: string, as?: string }[];
27+
as?: string;
28+
}
29+
30+
export type PackageLoader = (packageName: string) => any;
31+
export type FileLoader = (filePath: string) => Promise<any>;
32+
33+
function range(start: number, stop: number = NaN, step: number = 1): number[] {
34+
const arr: number[] = [];
35+
const isStopNaN = isNaN(stop);
36+
stop = isStopNaN ? start : stop;
37+
start = isStopNaN ? 0 : start;
38+
let i = start;
39+
while (i < stop) {
40+
arr.push(i);
41+
i += step;
42+
}
43+
return arr;
44+
}

0 commit comments

Comments
 (0)