Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f7c906d

Browse files
PavloPavlo
Pavlo
authored and
Pavlo
committed
added native module support and default parameters
1 parent 3d61009 commit f7c906d

File tree

7 files changed

+179
-104
lines changed

7 files changed

+179
-104
lines changed

‎index.html

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,10 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
x = 5
33-
y = 10
32+
import '/service.jspy' as obj
33+
from '/service.jspy' import func1, func2, func3
3434

35-
if x == 5 or y == 10:
36-
return 100
37-
38-
return 1
35+
return obj.func1(2, 3)
3936
</div>
4037

4138
<button onclick="tokenize()">Tokenize</button>
@@ -55,13 +52,15 @@ <h4>JSPython development console</h4>
5552
resultEditor.setTheme("ace/theme/monokai");
5653
resultEditor.session.setMode("ace/mode/json");
5754

58-
const jsPython = jspython.jsPython;
55+
const interpreter = jspython.jsPython();
56+
console.log({interpreter});
57+
5958
function tokenize() {
60-
tokenizer = (s) => console.log(`tokens => ${s}`, jsPython().tokenize(s))
59+
tokenizer = (s) => console.log(`tokens => ${s}`, interpreter().tokenize(s))
6160

6261
const scripts = editor.getValue();
6362
try {
64-
const result = jsPython()
63+
const result = interpreter
6564
.tokenize(scripts)
6665
.map((t, i) => `${t[1]} : '${t[0]}'`)
6766
.join('\n');
@@ -78,7 +77,7 @@ <h4>JSPython development console</h4>
7877

7978
const scripts = editor.getValue();
8079
try {
81-
const result = jsPython()
80+
const result = interpreter
8281
.parse(scripts);
8382

8483
const data = typeof result === 'object' ? JSON.stringify(result, null, '\t') : result;
@@ -93,6 +92,25 @@ <h4>JSPython development console</h4>
9392

9493
const scripts = editor.getValue();
9594
try {
95+
96+
interpreter.registerModuleLoader((path => {
97+
return Promise.resolve(`
98+
def multiply(x, y):
99+
x * y
100+
101+
def func1(x, y):
102+
if y == null:
103+
y = 77
104+
105+
multiply(x, y) + someNumber
106+
107+
name = 'test'
108+
someNumber = 55
109+
dateValue = dateTime()
110+
111+
`);
112+
}));
113+
96114
const scope = {
97115
Math,
98116
errorFunc: p => {
@@ -115,7 +133,7 @@ <h4>JSPython development console</h4>
115133
}
116134
}
117135
};
118-
const result = await jsPython()
136+
const result = await interpreter
119137
.evaluate(scripts, scope, undefined, 'index1.jspy');
120138

121139
// const result = jsPython()
@@ -125,6 +143,7 @@ <h4>JSPython development console</h4>
125143
// .evalAsync(scripts, scope);
126144

127145
const data = typeof result === 'object' ? JSON.stringify(result, null, '\t') : String(result);
146+
128147
resultEditor.getSession().setValue(data)
129148
} catch (err) {
130149
console.log('error', err);

‎package.json

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

‎src/evaluator/evaluator.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export class Evaluator {
2525

2626
for (const node of ast.body) {
2727
if (node.type === 'comment') { continue; }
28+
if (node.type === 'import') {
29+
// we can't use it here, because loader has to be promise
30+
throw new Error(`Import is not support with 'eval'. Use method 'evalAsync' instead`);
31+
}
2832
try {
2933
lastResult = this.evalNode(node, blockContext);
3034

@@ -67,21 +71,12 @@ export class Evaluator {
6771

6872
const blockContext = cloneContext(context);
6973

70-
71-
for (let i = 0; i < args?.length || 0; i++) {
72-
if (i >= funcDef.params.length) {
73-
break;
74-
// throw new Error('Too many parameters provided');
75-
}
76-
blockContext.blockScope.set(funcDef.params[i], args[i]);
74+
// set parameters into new scope, based incomming arguments
75+
for (let i = 0; i < funcDef.params?.length || 0; i++) {
76+
const argValue = args?.length > i ? args[i] : null;
77+
blockContext.blockScope.set(funcDef.params[i], argValue);
7778
}
7879

79-
// // set parameters into new scope, based incomming arguments
80-
// for (let i = 0; i < funcDef.params?.length || 0; i++) {
81-
// const argValue = args?.length > i ? args[i] : null;
82-
// blockContext.blockScope.set(funcDef.params[i], argValue);
83-
// }
84-
8580
return this.evalBlock(ast, blockContext);
8681
}
8782

‎src/evaluator/evaluatorAsync.ts

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
55
getStartLine,
66
getTokenLoc,
7-
IfNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, RaiseNode, ReturnNode, SetSingleVarNode, TryExceptNode, WhileNode
7+
IfNode, ImportNode,IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, RaiseNode, ReturnNode, SetSingleVarNode, TryExceptNode, WhileNode
88
} from '../common';
99
import { JspyEvalError, JspyError } from '../common/utils';
1010
import { Evaluator } from './evaluator';
@@ -17,6 +17,19 @@ import { BlockContext, cloneContext, Scope } from './scope';
1717
*/
1818
export class EvaluatorAsync {
1919

20+
private moduleParser: (modulePath: string) => Promise<AstBlock> = () => Promise.reject('Module parser is not registered!');
21+
private blockContextFactory?: (modulePath: string) => BlockContext;
22+
23+
registerModuleParser(moduleParser: (modulePath: string) => Promise<AstBlock>): EvaluatorAsync {
24+
this.moduleParser = moduleParser;
25+
return this;
26+
}
27+
28+
registerBlockContextFactory(blockContextFactory: (modulePath: string) => BlockContext): EvaluatorAsync {
29+
this.blockContextFactory = blockContextFactory;
30+
return this;
31+
}
32+
2033
async evalBlockAsync(ast: AstBlock, blockContext: BlockContext): Promise<unknown> {
2134
let lastResult = null;
2235

@@ -35,6 +48,21 @@ export class EvaluatorAsync {
3548

3649
for (const node of ast.body) {
3750
if (node.type === 'comment') { continue; }
51+
if (node.type === 'import') {
52+
const importNode = node as ImportNode;
53+
54+
if (typeof this.blockContextFactory !== 'function') {
55+
throw new Error('blockContextFactory is not initialized');
56+
}
57+
58+
const moduleAst = await this.moduleParser(importNode.module.name)
59+
const moduleBlockContext = this.blockContextFactory(importNode.module.name);
60+
await this.evalBlockAsync(moduleAst, moduleBlockContext)
61+
62+
blockContext.blockScope.set(importNode.module.alias || this.defaultModuleName(importNode.module.name), moduleBlockContext.blockScope.getScope())
63+
64+
continue;
65+
}
3866

3967
try {
4068
lastResult = await this.evalNodeAsync(node, blockContext);
@@ -69,28 +97,23 @@ export class EvaluatorAsync {
6997
return lastResult;
7098
}
7199

100+
private defaultModuleName(name: string): string {
101+
return name.substring(name.lastIndexOf('/') + 1, name.lastIndexOf('.'))
102+
}
103+
72104
private async jspyFuncInvokerAsync(funcDef: FuncDefNode, context: BlockContext, ...args: unknown[]): Promise<unknown> {
73105

74106
const ast = Object.assign({}, funcDef.funcAst);
75107
ast.type = 'func';
76108

77109
const blockContext = cloneContext(context);
78110

79-
for (let i = 0; i < args?.length || 0; i++) {
80-
if (i >= funcDef.params.length) {
81-
break;
82-
// throw new Error('Too many parameters provided');
83-
}
84-
blockContext.blockScope.set(funcDef.params[i], args[i]);
111+
// set parameters into new scope, based incomming arguments
112+
for (let i = 0; i < funcDef.params?.length || 0; i++) {
113+
const argValue = args?.length > i ? args[i] : null;
114+
blockContext.blockScope.set(funcDef.params[i], argValue);
85115
}
86116

87-
88-
// // set parameters into new scope, based incomming arguments
89-
// for (let i = 0; i < funcDef.params?.length || 0; i++) {
90-
// const argValue = args?.length > i ? args[i] : null;
91-
// blockContext.blockScope.set(funcDef.params[i], argValue);
92-
// }
93-
94117
return await this.evalBlockAsync(ast, blockContext);
95118
}
96119

@@ -135,8 +158,7 @@ export class EvaluatorAsync {
135158

136159
private async evalNodeAsync(node: AstNode, blockContext: BlockContext): Promise<unknown> {
137160
if (node.type === 'import') {
138-
// skip this for now. As modules are implemented externally
139-
return null;
161+
throw new Error('Import should be defined at the start');
140162
}
141163

142164
if (node.type === 'comment') {

‎src/evaluator/scope.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11

22
export interface BlockContext {
33
moduleName: string;
4-
returnCalled: boolean;
5-
breakCalled: boolean;
6-
continueCalled: boolean;
7-
returnObject: any;
8-
blockScope: Scope
4+
blockScope: Scope;
5+
returnCalled?: boolean;
6+
breakCalled?: boolean;
7+
continueCalled?: boolean;
8+
returnObject?: any;
99
}
1010

1111
export function cloneContext(context: BlockContext): BlockContext {

‎src/interpreter.spec.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,4 +598,83 @@ describe('Interpreter', () => {
598598
);
599599

600600

601+
it('unknown property is null', async () => {
602+
const script = `
603+
x = {}
604+
605+
if x.someValue == null:
606+
return true
607+
else:
608+
return false
609+
`
610+
expect(await e.evaluate(script)).toBe(true);
611+
expect(e.eval(script)).toBe(true);
612+
}
613+
);
614+
615+
it('boolean value', async () => {
616+
const script = `
617+
x = 2 == 2
618+
619+
if x:
620+
return true
621+
else:
622+
return false
623+
`
624+
expect(await e.evaluate(script)).toBe(true);
625+
expect(e.eval(script)).toBe(true);
626+
}
627+
);
628+
629+
it('Import', async () => {
630+
const interpreter = Interpreter.create();
631+
632+
interpreter.registerModuleLoader((path => {
633+
return Promise.resolve(`
634+
def multiply(x, y):
635+
x * y
636+
637+
def func1(x, y):
638+
multiply(x, y) + someNumber
639+
640+
someNumber = 55
641+
`);
642+
}));
643+
644+
const res = await interpreter.evaluate(`
645+
import '/service.jspy' as obj
646+
647+
return obj.func1(2, 3) + obj.multiply(2, 3) + obj.someNumber
648+
`);
649+
650+
expect(res).toBe(122);
651+
});
652+
653+
654+
it('Import and calling function with default value', async () => {
655+
const interpreter = Interpreter.create();
656+
657+
interpreter.registerModuleLoader((path => {
658+
return Promise.resolve(`
659+
def multiply(x, y):
660+
x * y
661+
662+
def func1(x, y):
663+
# if y is null then 100 will be passed
664+
multiply(x, y or 100) + someNumber
665+
666+
someNumber = 55
667+
`);
668+
}));
669+
670+
const res = await interpreter.evaluate(`
671+
import '/service.jspy' as obj
672+
673+
return obj.func1(2) + obj.multiply(2, 3) + obj.someNumber
674+
`);
675+
676+
expect(res).toBe(316);
677+
});
678+
679+
601680
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /