Skip to content

Commit 7b83d14

Browse files
committed
added functionality to import JSON file and load it into the context
1 parent 6ec1f12 commit 7b83d14

File tree

6 files changed

+71
-38
lines changed

6 files changed

+71
-38
lines changed

index.html

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,9 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
import '/service.jspy' as obj
33-
from '/service.jspy' import func1, func2, func3
32+
import '/test.json' as obj
3433

35-
return obj.func1(2, 3)
34+
return obj.x
3635
</div>
3736

3837
<button onclick="tokenize()">Tokenize</button>
@@ -95,19 +94,7 @@ <h4>JSPython development console</h4>
9594

9695
interpreter.registerModuleLoader((path => {
9796
return Promise.resolve(`
98-
from 'service' import add
99-
100-
def multiply(x, y):
101-
x * y
102-
103-
def func1(x, y):
104-
print('++>>>', getExecutionContext())
105-
multiply(x, y) + add(x, y)
106-
107-
name = 'test'
108-
someNumber = 55
109-
dateValue = dateTime()
110-
97+
{"x": "test22", "n": 55}
11198
`);
11299
}));
113100

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.1.5",
3+
"version": "2.1.6",
44
"description": "JSPython is a javascript implementation of Python language that runs within web browser or NodeJS environment",
55
"keywords": [
66
"python",

src/common/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ export function parseDatetimeOrNull(value: string | Date): Date | null {
7070
return null;
7171
}
7272

73+
export function getImportType(name: string): 'jspyModule' | 'jsPackage' | 'json' {
74+
75+
if (name.startsWith('/') || name.startsWith('./')) {
76+
return (name.endsWith('.json')) ? 'json' : 'jspyModule';
77+
}
78+
79+
return 'jsPackage';
80+
}
81+
7382
function jspyErrorMessage(error: string, module: string, line: number, column: number, message: string): string {
7483
return `${error}: ${module}(${line},${column}): ${message}`;
7584
}
@@ -99,7 +108,7 @@ export class JspyEvalError extends Error {
99108
}
100109

101110
export class JspyError extends Error {
102-
111+
103112
constructor(public module: string, public line: number, public column: number, public name: string, public message: string) {
104113
super();
105114
this.message = jspyErrorMessage("JspyError", module || 'name.jspy', line, column, message);

src/evaluator/evaluatorAsync.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
getTokenLoc,
77
IfNode, ImportNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, RaiseNode, ReturnNode, SetSingleVarNode, TryExceptNode, WhileNode
88
} from '../common';
9-
import { JspyEvalError, JspyError } from '../common/utils';
9+
import { JspyEvalError, JspyError, getImportType } from '../common/utils';
1010
import { Evaluator } from './evaluator';
1111
import { BlockContext, cloneContext, Scope } from './scope';
1212

@@ -18,13 +18,19 @@ import { BlockContext, cloneContext, Scope } from './scope';
1818
export class EvaluatorAsync {
1919

2020
private moduleParser: (modulePath: string) => Promise<AstBlock> = () => Promise.reject('Module parser is not registered!');
21+
private jsonFileLoader: (jsonFilePath: string) => Promise<string> = () => Promise.reject('{}');
2122
private blockContextFactory?: (modulePath: string, ast: AstBlock) => BlockContext;
2223

2324
registerModuleParser(moduleParser: (modulePath: string) => Promise<AstBlock>): EvaluatorAsync {
2425
this.moduleParser = moduleParser;
2526
return this;
2627
}
2728

29+
registerJsonFileLoader(jsonFileLoader: (modulePath: string) => Promise<string>): EvaluatorAsync {
30+
this.jsonFileLoader = jsonFileLoader;
31+
return this;
32+
}
33+
2834
registerBlockContextFactory(blockContextFactory: (modulePath: string, ast: AstBlock) => BlockContext): EvaluatorAsync {
2935
this.blockContextFactory = blockContextFactory;
3036
return this;
@@ -50,9 +56,15 @@ export class EvaluatorAsync {
5056
if (node.type === 'comment') { continue; }
5157
if (node.type === 'import') {
5258
const importNode = node as ImportNode;
59+
const iType = getImportType(importNode.module.name);
5360

54-
if (!importNode.module.name.startsWith('/') /* || !importNode.module.name.endsWith('.jspy')*/) {
55-
// it is not JSPY imort. It is JS and should be handled externally
61+
if (iType === 'json') {
62+
const jsonValue = JSON.parse(await this.jsonFileLoader(importNode.module.name));
63+
blockContext.blockScope
64+
.set(importNode.module.alias || this.defaultModuleName(importNode.module.name), jsonValue);
65+
continue;
66+
} else if (iType !== 'jspyModule') {
67+
// it is not JSPY import. It is JS and should be handled externally
5668
continue;
5769
}
5870

src/interpreter.spec.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -696,27 +696,47 @@ describe('Interpreter', () => {
696696
}));
697697

698698
const res = await interpreter.evaluate(`
699-
import '/service.jspy' as obj
699+
import './service.jspy' as obj
700700
701701
return obj.func1(2) + obj.multiply(2, 3)
702702
`);
703703

704704
expect(res).toBe(261);
705705
});
706706

707+
it('Import JSON', async () => {
708+
const interpreter = Interpreter.create();
709+
710+
interpreter.registerModuleLoader((path => {
711+
return Promise.resolve(`
712+
{"x": "test1", "n": 22}
713+
`);
714+
}));
715+
716+
const res = await interpreter.evaluate(`
717+
import './some.json' as obj
718+
719+
return obj
720+
`);
721+
722+
expect(res.x).toBe('test1');
723+
expect(res.n).toBe(22);
724+
});
725+
726+
707727
it('Import with package loader', async () => {
708728
const interpreter = Interpreter.create();
709729

710730
interpreter.registerPackagesLoader(path =>
711-
(
712-
path === 'service' ? {
713-
add: (x: number, y: number) => x + y,
714-
remove: (x: number, y: number) => x - y,
715-
times: (x: number, y: number) => x * y,
716-
}
731+
(
732+
path === 'service' ? {
733+
add: (x: number, y: number) => x + y,
734+
remove: (x: number, y: number) => x - y,
735+
times: (x: number, y: number) => x * y,
736+
}
717737
: null
718-
)
719-
);
738+
)
739+
);
720740

721741
interpreter.registerModuleLoader((path => {
722742
return Promise.resolve(`
@@ -784,6 +804,6 @@ describe('Interpreter', () => {
784804
`;
785805
expect(await interpreter.evalAsync(script)).toBe(55);
786806
expect(interpreter.eval(script)).toBe(55);
787-
});
807+
});
788808

789809
});

src/interpreter.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AstBlock, ImportNode, Token } from './common';
2+
import { getImportType } from './common/utils';
23
import { Evaluator } from './evaluator';
34
import { EvaluatorAsync } from './evaluator/evaluatorAsync';
45
import { BlockContext, Scope } from './evaluator/scope';
@@ -94,11 +95,15 @@ export class Interpreter {
9495
this._lastExecutionContext = blockContext.blockScope.getScope();
9596

9697
const result = await evaluator
98+
.registerJsonFileLoader(async (modulePath: string) =>
99+
await (this.moduleLoader ? this.moduleLoader(modulePath)
100+
: Promise.reject('ModuleLoader is not registered')
101+
)
102+
)
97103
.registerModuleParser(async (modulePath) => await this.moduleParser(modulePath))
98104
.registerBlockContextFactory((moduleName, ast: AstBlock) => {
99-
// this line will not be required when we have move package loaders to the evaluator
100-
const newContext = this.assignLegacyImportContext(ast, scope);
101-
105+
// enrich context
106+
const newContext = this.assignImportContext(ast, scope);
102107
const moduleContext = { moduleName, blockScope: new Scope(newContext) }
103108
moduleContext.blockScope.set('printExecutionContext', () => console.log(moduleContext.blockScope.getScope()));
104109
moduleContext.blockScope.set('getExecutionContext', () => moduleContext.blockScope.getScope());
@@ -118,15 +123,15 @@ export class Interpreter {
118123
}
119124

120125
/**
121-
* Compatibility method! Will be deprecated soon
126+
* Compatibility method (with v1). !
122127
*/
123128
async evaluate(script: string, context: object = {}, entryFunctionName: string = ''
124129
, moduleName: string = 'main.jspy'): Promise<any> {
125130
if (!script || !script.length) { return null; }
126131
const ast = this.parse(script, moduleName);
127132

128133
context = (context && typeof context === 'object') ? context : {};
129-
context = this.assignLegacyImportContext(ast, context);
134+
context = this.assignImportContext(ast, context);
130135

131136
const globalScope = {
132137
...this.initialScope,
@@ -169,7 +174,7 @@ export class Interpreter {
169174
return scripts.indexOf(`def ${funcName}`) > -1;
170175
}
171176

172-
private assignLegacyImportContext(ast: AstBlock, context: object): Record<string, unknown> {
177+
private assignImportContext(ast: AstBlock, context: object): Record<string, unknown> {
173178

174179
const nodeToPackage = (im: ImportNode): PackageToImport => {
175180
return {
@@ -182,7 +187,7 @@ export class Interpreter {
182187
const importNodes = ast.body.filter(n => n.type === 'import') as ImportNode[];
183188

184189
const jsImport = importNodes
185-
.filter(im => !im.module.name.startsWith('/'))
190+
.filter(im => getImportType(im.module.name) === 'jsPackage')
186191
.map(im => nodeToPackage(im));
187192

188193
if (jsImport.length && this.packageLoader) {

0 commit comments

Comments
 (0)