Skip to content

Commit 4efaabe

Browse files
committed
added error reporting infrastructure
1 parent d0a876d commit 4efaabe

File tree

10 files changed

+327
-220
lines changed

10 files changed

+327
-220
lines changed

index.html

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
33-
def f(n):
34-
n * n
35-
x = [2, 3, 4]
36-
x.map(f).join("|")
32+
33+
def f():
34+
null
35+
36+
f()?.prop or 1
3737

3838
</div>
3939
<!--
@@ -78,7 +78,7 @@ <h4>JSPython development console</h4>
7878
resultEditor.getSession().setValue(data)
7979
} catch (err) {
8080
console.error(err);
81-
resultEditor.getSession().setValue('Error:\n' + String(err))
81+
resultEditor.getSession().setValue(String(err.message))
8282
}
8383
}
8484

@@ -93,7 +93,7 @@ <h4>JSPython development console</h4>
9393
resultEditor.getSession().setValue(data)
9494
} catch (err) {
9595
console.error(err);
96-
resultEditor.getSession().setValue('Error:\n' + String(err))
96+
resultEditor.getSession().setValue(String(err.message))
9797
}
9898
}
9999

@@ -118,11 +118,11 @@ <h4>JSPython development console</h4>
118118
}
119119
}
120120
};
121-
// const result = await jsPython()
122-
// .evaluate(scripts, scope);
121+
const result = await jsPython()
122+
.evaluate(scripts, scope);
123123

124-
const result = jsPython()
125-
.eval(scripts, scope);
124+
// const result = jsPython()
125+
// .eval(scripts, scope);
126126

127127
// const result = await jsPython()
128128
// .evalAsync(scripts, scope);
@@ -131,7 +131,7 @@ <h4>JSPython development console</h4>
131131
resultEditor.getSession().setValue(data)
132132
} catch (err) {
133133
console.error(err);
134-
resultEditor.getSession().setValue('Error:\n' + String(err))
134+
resultEditor.getSession().setValue(String(err.message))
135135
}
136136
}
137137

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

src/common/ast-types.ts

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ExpressionOperators, OperationTypes, Operators } from "./operators";
2-
import { Token } from "./token-types";
2+
import { getTokenLoc, getTokenValue, Token } from "./token-types";
33

44
export type AstNodeType = 'assign' | 'binOp' | 'const'
55
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
@@ -14,11 +14,22 @@ export abstract class AstNode {
1414
constructor(public type: AstNodeType) { }
1515
}
1616

17+
export interface FuncDefNode {
18+
params: string[];
19+
funcAst: AstBlock;
20+
}
21+
22+
export interface IsNullCoelsing {
23+
nullCoelsing: boolean | undefined
24+
}
25+
1726
export class AssignNode extends AstNode {
1827
constructor(
1928
public target: AstNode,
20-
public source: AstNode) {
29+
public source: AstNode,
30+
public loc: Uint16Array) {
2131
super('assign');
32+
this.loc = loc;
2233
}
2334
}
2435

@@ -27,20 +38,22 @@ export class ConstNode extends AstNode {
2738

2839
constructor(token: Token) {
2940
super('const');
30-
this.value = token[0];
31-
//this.loc = token[1].subarray(1);
41+
this.value = getTokenValue(token);
42+
this.loc = getTokenLoc(token);
3243
}
3344
}
3445

3546
export class CommentNode extends AstNode {
36-
constructor(public comment: string) {
47+
constructor(public comment: string, public loc: Uint16Array) {
3748
super('comment');
49+
this.loc = loc;
3850
}
3951
}
4052

4153
export class ReturnNode extends AstNode {
42-
constructor(public returnValue: AstNode | undefined = undefined) {
54+
constructor(public returnValue: AstNode | undefined = undefined, public loc: Uint16Array) {
4355
super('return');
56+
this.loc = loc;
4457
}
4558
}
4659

@@ -61,47 +74,51 @@ export class SetSingleVarNode extends AstNode {
6174
constructor(token: Token) {
6275
super('setSingleVar');
6376
this.name = token[0] as string
77+
this.loc = getTokenLoc(token);
6478
}
6579
}
6680

67-
export class FunctionCallNode extends AstNode {
68-
constructor(public name: string, public paramNodes: AstNode[] | null) {
81+
export class FunctionCallNode extends AstNode implements IsNullCoelsing {
82+
public nullCoelsing: boolean | undefined = undefined;
83+
84+
constructor(public name: string, public paramNodes: AstNode[] | null, public loc: Uint16Array) {
6985
super('funcCall');
86+
this.loc = loc;
7087
}
7188
}
7289

73-
export interface FuncDefNode {
74-
params: string[];
75-
funcAst: AstBlock;
76-
}
77-
7890
export class FunctionDefNode extends AstNode implements FuncDefNode {
79-
constructor(public funcAst: AstBlock, public params: string[], public isAsync: boolean) {
80-
super('funcDef',);
91+
constructor(public funcAst: AstBlock, public params: string[], public isAsync: boolean, public loc: Uint16Array) {
92+
super('funcDef');
93+
this.loc = loc;
8194
}
8295
}
8396

8497
export class ArrowFuncDefNode extends AstNode implements FuncDefNode {
85-
constructor(public funcAst: AstBlock, public params: string[]) {
98+
constructor(public funcAst: AstBlock, public params: string[], public loc: Uint16Array) {
8699
super('arrowFuncDef');
100+
this.loc = loc;
87101
}
88102
}
89103

90104
export class IfNode extends AstNode {
91-
constructor(public conditionNode: AstNode, public ifBody: AstNode[], public elseBody: AstNode[] | undefined = undefined) {
105+
constructor(public conditionNode: AstNode, public ifBody: AstNode[], public elseBody: AstNode[] | undefined = undefined, public loc: Uint16Array) {
92106
super('if');
107+
this.loc = loc
93108
}
94109
}
95110

96111
export class ForNode extends AstNode {
97-
constructor(public sourceArray: AstNode, public itemVarName: string, public body: AstNode[]) {
112+
constructor(public sourceArray: AstNode, public itemVarName: string, public body: AstNode[], public loc: Uint16Array) {
98113
super('for');
114+
this.loc = loc;
99115
}
100116
}
101117

102118
export class WhileNode extends AstNode {
103-
constructor(public condition: AstNode, public body: AstNode[]) {
119+
constructor(public condition: AstNode, public body: AstNode[], public loc: Uint16Array) {
104120
super('while');
121+
this.loc = loc;
105122
}
106123
}
107124

@@ -111,25 +128,28 @@ export interface NameAlias {
111128
}
112129

113130
export class ImportNode extends AstNode {
114-
constructor(public module: NameAlias, public body: AstBlock, public parts: NameAlias[] | undefined = undefined) {
131+
constructor(public module: NameAlias, public body: AstBlock, public parts: NameAlias[] | undefined = undefined, public loc: Uint16Array) {
115132
super('import');
133+
this.loc = loc;
116134
}
117135
}
118136

119-
export class GetSingleVarNode extends AstNode {
137+
export class GetSingleVarNode extends AstNode implements IsNullCoelsing {
120138
name: string;
121139
nullCoelsing: boolean | undefined = undefined;
122140

123141
constructor(token: Token, nullCoelsing: boolean | undefined = undefined) {
124142
super('getSingleVar');
125143
this.name = token[0] as string;
126144
this.nullCoelsing = nullCoelsing;
145+
this.loc = getTokenLoc(token);
127146
}
128147
}
129148

130149
export class DotObjectAccessNode extends AstNode {
131-
constructor(public nestedProps: AstNode[]) {
150+
constructor(public nestedProps: AstNode[], public loc: Uint16Array) {
132151
super('dotObjectAccess');
152+
this.loc = loc;
133153
}
134154
}
135155
export interface ObjectPropertyInfo {
@@ -138,34 +158,38 @@ export interface ObjectPropertyInfo {
138158
}
139159

140160
export class CreateObjectNode extends AstNode {
141-
constructor(public props: ObjectPropertyInfo[]) {
161+
constructor(public props: ObjectPropertyInfo[], public loc: Uint16Array) {
142162
super('createObject');
163+
this.loc = loc;
143164
}
144165
}
145166

146167
export class CreateArrayNode extends AstNode {
147-
constructor(
148-
public items: AstNode[]
149-
) {
168+
constructor(public items: AstNode[], public loc: Uint16Array) {
150169
super('createArray');
170+
this.loc = loc;
151171
}
152172
}
153173

154174
export class BracketObjectAccessNode extends AstNode {
155175
constructor(
156176
public propertyName: string,
157177
public bracketBody: AstNode,
158-
public nullCoalescing: boolean | undefined = undefined) {
178+
public nullCoalescing: boolean | undefined = undefined,
179+
public loc: Uint16Array) {
159180
super('bracketObjectAccess');
181+
this.loc = loc;
160182
}
161183
}
162184

163185
export class BinOpNode extends AstNode {
164186
constructor(
165187
public left: AstNode,
166188
public op: ExpressionOperators,
167-
public right: AstNode) {
189+
public right: AstNode,
190+
public loc: Uint16Array) {
168191
super('binOp');
192+
this.loc = loc;
169193
}
170194
}
171195

@@ -175,3 +199,5 @@ export interface AstBlock {
175199
funcs: FunctionDefNode[];
176200
body: AstNode[];
177201
}
202+
203+

src/common/utils.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,31 @@ export function parseDatetimeOrNull(value: string | Date): Date | null {
6767

6868
return null;
6969
}
70+
71+
function jspyErrorMessage(error: string, module: string, line: number, column: number, message: string): string {
72+
return `${error}: ${module}(${line},${column}): ${message}`;
73+
}
74+
75+
export class JspyTokenizerError extends Error {
76+
constructor(public module: string, public line: number, public column: number, public message: string) {
77+
super();
78+
this.message = jspyErrorMessage("JspyTokenizerError", module, line, column, message);
79+
Object.setPrototypeOf(this, JspyTokenizerError.prototype);
80+
}
81+
}
82+
83+
export class JspyParserError extends Error {
84+
constructor(public module: string, public line: number, public column: number, public message: string) {
85+
super();
86+
this.message = jspyErrorMessage("JspyParserError", module, line, column, message);
87+
Object.setPrototypeOf(this, JspyParserError.prototype);
88+
}
89+
}
90+
91+
export class JspyEvalError extends Error {
92+
constructor(public module: string, public line: number, public column: number, public message: string) {
93+
super();
94+
this.message = jspyErrorMessage("JspyEvalError", module, line, column, message);
95+
Object.setPrototypeOf(this, JspyParserError.prototype);
96+
}
97+
}

src/evaluator/evaluator.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import {
22
ArrowFuncDefNode,
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
4-
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
4+
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
55
IfNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
66
} from '../common';
7+
import { JspyEvalError } from '../common/utils';
78
import { BlockContext, Scope } from './scope';
89

910
export class Evaluator {
@@ -24,25 +25,35 @@ export class Evaluator {
2425

2526
for (const node of ast.body) {
2627
if (node.type === 'comment') { continue; }
27-
lastResult = Evaluator.evalNode(node, blockContext);
28-
29-
if (blockContext.returnCalled) {
30-
const res = blockContext.returnObject;
28+
try {
29+
lastResult = Evaluator.evalNode(node, blockContext);
30+
31+
if (blockContext.returnCalled) {
32+
const res = blockContext.returnObject;
33+
34+
// stop processing return
35+
if (ast.type == 'func' || ast.type == 'module') {
36+
blockContext.returnCalled = false;
37+
blockContext.returnObject = null;
38+
}
39+
return res;
40+
}
3141

32-
// stop processing return
33-
if(ast.type == 'func' || ast.type == 'module'){
34-
blockContext.returnCalled = false;
35-
blockContext.returnObject = null;
42+
if (blockContext.continueCalled) {
43+
break;
44+
}
45+
if (blockContext.breakCalled) {
46+
break;
47+
}
48+
} catch (err) {
49+
if (err instanceof JspyEvalError) {
50+
throw err;
51+
} else {
52+
const loc = node.loc ? node.loc : [0, 0]
53+
throw new JspyEvalError('mainModule', loc[0], loc[1], err.message)
3654
}
37-
return res;
3855
}
3956

40-
if (blockContext.continueCalled) {
41-
break;
42-
}
43-
if (blockContext.breakCalled) {
44-
break;
45-
}
4657
}
4758

4859
return lastResult;
@@ -155,7 +166,7 @@ export class Evaluator {
155166

156167
for (let item of array) {
157168
blockContext.blockScope.set(forNode.itemVarName, item);
158-
Evaluator.evalBlock({ type:'for', body: forNode.body } as AstBlock, blockContext);
169+
Evaluator.evalBlock({ type: 'for', body: forNode.body } as AstBlock, blockContext);
159170
if (blockContext.continueCalled) { blockContext.continueCalled = false; }
160171
if (blockContext.breakCalled) { break; }
161172
}
@@ -167,7 +178,7 @@ export class Evaluator {
167178
const whileNode = node as WhileNode;
168179

169180
while (Evaluator.evalNode(whileNode.condition, blockContext)) {
170-
Evaluator.evalBlock({ type:'while', body: whileNode.body } as AstBlock, blockContext);
181+
Evaluator.evalBlock({ type: 'while', body: whileNode.body } as AstBlock, blockContext);
171182

172183
if (blockContext.continueCalled) { blockContext.continueCalled = false; }
173184
if (blockContext.breakCalled) { break; }
@@ -217,7 +228,7 @@ export class Evaluator {
217228

218229
// create a node for all but last property token
219230
// potentially it can go to parser
220-
const targetObjectNode = new DotObjectAccessNode(targetNode.nestedProps.slice(0, targetNode.nestedProps.length - 1));
231+
const targetObjectNode = new DotObjectAccessNode(targetNode.nestedProps.slice(0, targetNode.nestedProps.length - 1), targetNode.loc);
221232
const targetObject = Evaluator.evalNode(targetObjectNode, blockContext) as Record<string, unknown>;
222233

223234
// not sure nested properties should be GetSingleVarNode

0 commit comments

Comments
 (0)