Skip to content

Commit f0673fd

Browse files
committed
added functions defs/calls as well as arrow funcs
1 parent a240583 commit f0673fd

File tree

7 files changed

+248
-101
lines changed

7 files changed

+248
-101
lines changed

index.html

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,15 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
x = { obj1: 2 + 4 }
33-
x
32+
arr = [1,2,3]
33+
arr.map(n =>
34+
n = n * 2
35+
{
36+
t1: n * 2,
37+
t2: n * 3
38+
}
39+
)
40+
3441
</div>
3542
<button onclick="tokenize()">Tokenize</button>
3643
<button onclick="parse()">Parse</button>
@@ -68,11 +75,11 @@ <h4>JSPython development console</h4>
6875
}
6976
}
7077

71-
async function parse() {
78+
function parse() {
7279

7380
const scripts = editor.getValue();
7481
try {
75-
const result = await jsPython()
82+
const result = jsPython()
7683
.parse(scripts);
7784

7885
const data = typeof result === 'object' ? JSON.stringify(result, null, '\t') : result;
@@ -83,7 +90,7 @@ <h4>JSPython development console</h4>
8390
}
8491
}
8592

86-
async function runInterpreter() {
93+
function runInterpreter() {
8794

8895
const scripts = editor.getValue();
8996
try {
@@ -99,7 +106,7 @@ <h4>JSPython development console</h4>
99106
}
100107
}
101108
};
102-
const result = await jsPython()
109+
const result = jsPython()
103110
.evaluate(scripts, scope);
104111

105112
const data = typeof result === 'object' ? JSON.stringify(result, null, '\t') : String(result);

src/common/ast-types.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export abstract class AstNode {
77
public type:
88
'assign' | 'binOp' | 'const'
99
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
10-
| 'funcCall' | 'funcDef'
10+
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
1111
| 'createObject' | 'createArray'
1212
| 'if' | 'while' | 'tryCatch'
1313
) { }
@@ -51,6 +51,12 @@ export class FunctionDefNode extends AstNode {
5151
}
5252
}
5353

54+
export class ArrowFuncDefNode extends AstNode {
55+
constructor(public params: string[], public body: AstNode[] ) {
56+
super('arrowFuncDef');
57+
}
58+
}
59+
5460
export class GetSingleVarNode extends AstNode {
5561
name: string;
5662
nullCoelsing: boolean | undefined = undefined;
@@ -105,8 +111,9 @@ export class BinOpNode extends AstNode {
105111
}
106112
}
107113

108-
export interface Ast {
114+
export interface AstBlock {
109115
name: string;
116+
type: 'module' | 'func' | 'if' | 'for' | 'trycatch'
110117
funcs: FunctionDefNode[];
111118
body: AstNode[];
112119
}

src/common/token-types.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,13 +101,18 @@ export function findTokenValueIndexes(tokens: Token[], predicate: (value: TokenV
101101
const opIndexes: number[] = [];
102102

103103
for (let i = 0; i < tokens.length; i++) {
104-
if (getTokenValue(tokens[i]) === '(') {
104+
const tValue = getTokenValue(tokens[i]);
105+
const tType = getTokenType(tokens[i]);
106+
107+
if (tType === TokenTypes.LiteralString) { continue; }
108+
109+
if (tValue === '(') {
105110
i = skipInnerBrackets(tokens, i, '(', ')');
106-
} else if (getTokenValue(tokens[i]) === '[') {
111+
} else if (tValue === '[') {
107112
i = skipInnerBrackets(tokens, i, '[', ']');
108-
} else if (getTokenValue(tokens[i]) === '{') {
113+
} else if (tValue === '{') {
109114
i = skipInnerBrackets(tokens, i, '{', '}');
110-
} else if (predicate(getTokenValue(tokens[i]))) {
115+
} else if (predicate(tValue)) {
111116
opIndexes.push(i);
112117
}
113118
}

src/evaluator/evaluator.ts

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import {
2-
AssignNode, Ast, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
3-
CreateObjectNode, DotObjectAccessNode, FunctionCallNode, GetSingleVarNode, SetSingleVarNode
1+
import {
2+
ArrowFuncDefNode,
3+
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
4+
CreateObjectNode, DotObjectAccessNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode, SetSingleVarNode
45
} from '../common';
56
import { Scope } from './scope';
67

@@ -14,26 +15,44 @@ const OperationFuncs: Record<string, (l: Primitive, r: Primitive) => Primitive>
1415

1516
export class Evaluator {
1617

17-
registerFunction(funcName: string, fn: () => unknown): Evaluator {
18-
throw new Error('Not implemented yet!')
19-
}
2018

21-
registerInstance(instanceName: string, instance: Record<string, unknown>): Evaluator {
22-
throw new Error('Not implemented yet!')
23-
}
19+
evalBlock(ast: AstBlock, scope: Scope): unknown {
20+
let lastResult = null;
2421

25-
assignObject(obj: Record<string, unknown>): Evaluator {
26-
throw new Error('Not implemented yet!')
27-
}
22+
for (let node of ast?.funcs || []) {
23+
const funcDef = node as FunctionDefNode;
2824

29-
eval(ast: Ast, scope: Scope): Promise<unknown> {
30-
let lastResult = null;
25+
const ast = {
26+
name: '',
27+
type: 'func',
28+
funcs: [],
29+
body: funcDef.body
30+
} as AstBlock;
31+
32+
// a child scope needs to be created here
33+
const newScope = scope;
34+
35+
scope.set(funcDef.name, (...args: unknown[]): unknown => {
36+
37+
// set parameters into new scope, based incomming arguments
38+
for (let i = 0; i < args?.length || 0; i++) {
39+
if (i >= funcDef.params.length) {
40+
break;
41+
// throw new Error('Too much parameters provided');
42+
}
43+
newScope.set(funcDef.params[i], args[i]);
44+
}
45+
return this.evalBlock(ast, newScope);
46+
}
47+
);
48+
49+
}
3150

3251
for (const node of ast.body) {
3352
lastResult = this.evalNode(node, scope);
3453
}
3554

36-
return Promise.resolve(lastResult);
55+
return lastResult;
3756
}
3857

3958
private invokeFunction(func: (...args: unknown[]) => unknown, fps: unknown[]): unknown {
@@ -89,6 +108,32 @@ export class Evaluator {
89108
return OperationFuncs[binOpNode.op](left as Primitive, right as Primitive);
90109
}
91110

111+
if(node.type === "arrowFuncDef") {
112+
const arrowFuncDef = node as ArrowFuncDefNode;
113+
114+
const newScope = scope;
115+
const ast = {
116+
name: '',
117+
type: 'func',
118+
funcs: [],
119+
body: arrowFuncDef.body
120+
} as AstBlock;
121+
122+
const arrowFuncHandler = (...args: unknown[]): unknown => {
123+
// set parameters into new scope, based incomming arguments
124+
for (let i = 0; i < args?.length || 0; i++) {
125+
if (i >= arrowFuncDef.params.length) {
126+
break;
127+
// throw new Error('Too much parameters provided');
128+
}
129+
newScope.set(arrowFuncDef.params[i], args[i]);
130+
}
131+
return this.evalBlock(ast, newScope);
132+
}
133+
134+
return arrowFuncHandler;
135+
}
136+
92137
if (node.type === "funcCall") {
93138
const funcCallNode = node as FunctionCallNode;
94139
const func = scope.get(funcCallNode.name) as (...args: unknown[]) => unknown;
@@ -148,7 +193,7 @@ export class Evaluator {
148193
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
149194
const pms = funcCallNode.paramNodes?.map(n => this.evalNode(n, scope)) || []
150195

151-
startObject = this.invokeFunction(func, pms);
196+
startObject = this.invokeFunction(func.bind(startObject), pms);
152197

153198
} else {
154199
throw Error("Can't resolve dotObjectAccess node")

0 commit comments

Comments
 (0)