Skip to content

Commit 363fb97

Browse files
committed
added exception management
1 parent 5c56527 commit 363fb97

File tree

9 files changed

+439
-108
lines changed

9 files changed

+439
-108
lines changed

index.html

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,17 @@
2929
<div class="container">
3030
<h4>JSPython development console</h4>
3131
<div id="editor">
32-
def power(base, exponent):
33-
if exponent == 0:
34-
return 1
35-
else:
36-
return base * power(base, exponent - 1)
37-
38-
"5 ** 10 = " + power(5, 10) + " == " + Math.pow(5, 10)
39-
32+
33+
try:
34+
raise Error('sdsdss')
35+
print("Hello")
36+
except:
37+
print("Something went wrong")
38+
finally:
39+
print("Something went wrong")
40+
else:
41+
print("Nothing went wrong")
42+
4043
</div>
4144
<!--
4245
def f(n):

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.7",
3+
"version": "2.0.8",
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: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@ import { ExpressionOperators, LogicalOperators, OperationTypes, Operators } from
22
import { getTokenLoc, getTokenValue, Token } from "./token-types";
33

44
export type AstNodeType = 'assign' | 'binOp' | 'const'
5-
| 'logicalOp'
5+
| 'logicalOp'
66
| 'getSingleVar' | 'setSingleVar' | 'dotObjectAccess' | 'bracketObjectAccess'
77
| 'funcCall' | 'funcDef' | 'arrowFuncDef'
88
| 'createObject' | 'createArray'
9-
| 'if' | 'for' | 'while'
9+
| 'if' | 'for' | 'while' | 'tryExcept' | 'raise'
1010
| 'import' | 'comment'
1111
| 'return' | 'continue' | 'break';
1212

13-
export abstract class AstNode {
14-
loc: Uint16Array | undefined = undefined;
15-
constructor(public type: AstNodeType) { }
13+
export interface NameAlias {
14+
name: string,
15+
alias: string | undefined
1616
}
1717

18+
export interface ExceptBody {
19+
error: NameAlias;
20+
body: AstNode[];
21+
}
22+
1823
export interface FuncDefNode {
1924
params: string[];
2025
funcAst: AstBlock;
@@ -24,6 +29,16 @@ export interface IsNullCoelsing {
2429
nullCoelsing: boolean | undefined
2530
}
2631

32+
export interface ObjectPropertyInfo {
33+
name: AstNode;
34+
value: AstNode;
35+
}
36+
37+
export abstract class AstNode {
38+
loc: Uint16Array | undefined = undefined;
39+
constructor(public type: AstNodeType) { }
40+
}
41+
2742
export class AssignNode extends AstNode {
2843
constructor(
2944
public target: AstNode,
@@ -58,6 +73,13 @@ export class ReturnNode extends AstNode {
5873
}
5974
}
6075

76+
export class RaiseNode extends AstNode {
77+
constructor(public errorName: string, public errorMessage: string | undefined, public loc: Uint16Array) {
78+
super('raise');
79+
this.loc = loc;
80+
}
81+
}
82+
6183
export class ContinueNode extends AstNode {
6284
constructor() {
6385
super('continue');
@@ -103,12 +125,29 @@ export class ArrowFuncDefNode extends AstNode implements FuncDefNode {
103125
}
104126

105127
export class IfNode extends AstNode {
106-
constructor(public conditionNode: AstNode, public ifBody: AstNode[], public elseBody: AstNode[] | undefined = undefined, public loc: Uint16Array) {
128+
constructor(
129+
public conditionNode: AstNode,
130+
public ifBody: AstNode[],
131+
public elseBody: AstNode[] | undefined = undefined,
132+
public loc: Uint16Array) {
107133
super('if');
108134
this.loc = loc
109135
}
110136
}
111137

138+
export class TryExceptNode extends AstNode {
139+
constructor(
140+
public tryBody: AstNode[],
141+
public exepts: ExceptBody[],
142+
public elseBody: AstNode[] | undefined,
143+
public finallyBody: AstNode[] | undefined,
144+
145+
public loc: Uint16Array) {
146+
super('tryExcept');
147+
this.loc = loc
148+
}
149+
}
150+
112151
export class ForNode extends AstNode {
113152
constructor(public sourceArray: AstNode, public itemVarName: string, public body: AstNode[], public loc: Uint16Array) {
114153
super('for');
@@ -123,11 +162,6 @@ export class WhileNode extends AstNode {
123162
}
124163
}
125164

126-
export interface NameAlias {
127-
name: string,
128-
alias: string | undefined
129-
}
130-
131165
export class ImportNode extends AstNode {
132166
constructor(public module: NameAlias, public body: AstBlock, public parts: NameAlias[] | undefined = undefined, public loc: Uint16Array) {
133167
super('import');
@@ -153,10 +187,6 @@ export class DotObjectAccessNode extends AstNode {
153187
this.loc = loc;
154188
}
155189
}
156-
export interface ObjectPropertyInfo {
157-
name: AstNode;
158-
value: AstNode;
159-
}
160190

161191
export class CreateObjectNode extends AstNode {
162192
constructor(public props: ObjectPropertyInfo[], public loc: Uint16Array) {

src/common/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,15 @@ export class JspyEvalError extends Error {
9797
Object.setPrototypeOf(this, JspyEvalError.prototype);
9898
}
9999
}
100+
101+
export class JspyError extends Error {
102+
public line: number = 0;
103+
public column: number = 0;
104+
public moduleName: string = '';
105+
106+
constructor(public name: string, public message: string) {
107+
super();
108+
this.message = message;
109+
Object.setPrototypeOf(this, JspyError.prototype);
110+
}
111+
}

src/evaluator/evaluator.ts

Lines changed: 90 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ import {
22
ArrowFuncDefNode,
33
AssignNode, AstBlock, AstNode, BinOpNode, BracketObjectAccessNode, ConstNode, CreateArrayNode,
44
CreateObjectNode, DotObjectAccessNode, ForNode, FuncDefNode, FunctionCallNode, FunctionDefNode, GetSingleVarNode,
5-
IfNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, ReturnNode, SetSingleVarNode, WhileNode
5+
IfNode, IsNullCoelsing, LogicalOpNode, OperationFuncs, Primitive, RaiseNode, ReturnNode, SetSingleVarNode, TryExceptNode, WhileNode
66
} from '../common';
7-
import { JspyEvalError } from '../common/utils';
8-
import { BlockContext, Scope } from './scope';
7+
import { JspyError, JspyEvalError } from '../common/utils';
8+
import { BlockContext, cloneContext, Scope } from './scope';
99

1010
export class Evaluator {
1111

@@ -46,11 +46,13 @@ export class Evaluator {
4646
break;
4747
}
4848
} catch (err) {
49-
if (err instanceof JspyEvalError) {
49+
const loc = node.loc ? node.loc : [0, 0]
50+
if (err instanceof JspyError) {
51+
throw err;
52+
} else if (err instanceof JspyEvalError) {
5053
throw err;
5154
} else {
52-
const loc = node.loc ? node.loc : [0, 0];
53-
throw new JspyEvalError(ast.name, loc[0], loc[1], err.message || err)
55+
throw new JspyEvalError(blockContext.moduleName, loc[0], loc[1], err.message || err)
5456
}
5557
}
5658

@@ -64,10 +66,7 @@ export class Evaluator {
6466
const ast = Object.assign({}, funcDef.funcAst);
6567
ast.type = 'func';
6668

67-
const blockContext = {
68-
moduleName: context.moduleName,
69-
blockScope: context.blockScope.clone()
70-
} as BlockContext;
69+
const blockContext = cloneContext(context);
7170

7271
// set parameters into new scope, based incomming arguments
7372
for (let i = 0; i < args?.length || 0; i++) {
@@ -82,40 +81,45 @@ export class Evaluator {
8281
}
8382

8483
private invokeFunction(func: (...args: unknown[]) => unknown, fps: unknown[]): unknown {
85-
if (fps.length === 0) { return func(); }
86-
if (fps.length === 1) { return func(fps[0]); }
87-
if (fps.length === 2) { return func(fps[0], fps[1]); }
88-
if (fps.length === 3) { return func(fps[0], fps[1], fps[2]); }
89-
if (fps.length === 4) {
90-
return func(fps[0], fps[1], fps[2], fps[3]);
91-
}
92-
if (fps.length === 5) {
93-
return func(fps[0], fps[1], fps[2], fps[3], fps[4]);
94-
}
84+
try {
85+
if (fps.length === 0) { return func(); }
86+
if (fps.length === 1) { return func(fps[0]); }
87+
if (fps.length === 2) { return func(fps[0], fps[1]); }
88+
if (fps.length === 3) { return func(fps[0], fps[1], fps[2]); }
89+
if (fps.length === 4) {
90+
return func(fps[0], fps[1], fps[2], fps[3]);
91+
}
92+
if (fps.length === 5) {
93+
return func(fps[0], fps[1], fps[2], fps[3], fps[4]);
94+
}
9595

96-
if (fps.length === 6) {
97-
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5]);
98-
}
96+
if (fps.length === 6) {
97+
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5]);
98+
}
9999

100-
if (fps.length === 7) {
101-
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6]);
102-
}
100+
if (fps.length === 7) {
101+
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6]);
102+
}
103103

104-
if (fps.length === 8) {
105-
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7]);
106-
}
104+
if (fps.length === 8) {
105+
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7]);
106+
}
107107

108-
if (fps.length === 9) {
109-
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8]);
110-
}
108+
if (fps.length === 9) {
109+
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8]);
110+
}
111111

112-
if (fps.length === 10) {
113-
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8], fps[9]);
114-
}
112+
if (fps.length === 10) {
113+
return func(fps[0], fps[1], fps[2], fps[3], fps[4], fps[5], fps[6], fps[7], fps[8], fps[9]);
114+
}
115115

116-
if (fps.length > 10) {
117-
throw Error('Function has too many parameters. Current limitation is 10');
116+
if (fps.length > 10) {
117+
throw Error('Function has too many parameters. Current limitation is 10');
118+
}
119+
} catch (err) {
120+
throw new JspyError('FuncCall', err.message || err);
118121
}
122+
119123
}
120124

121125
private evalNode(node: AstNode, blockContext: BlockContext): unknown {
@@ -139,6 +143,52 @@ export class Evaluator {
139143
return;
140144
}
141145

146+
if (node.type === 'raise') {
147+
const raiseNode = node as RaiseNode;
148+
const err = new JspyError(raiseNode.errorName, raiseNode.errorMessage || "");
149+
err.line = raiseNode.loc[0];
150+
err.column = raiseNode.loc[1];
151+
err.moduleName = blockContext.moduleName;
152+
throw err;
153+
}
154+
155+
if (node.type === 'tryExcept') {
156+
const tryNode = node as TryExceptNode;
157+
try {
158+
this.evalBlock({ name: blockContext.moduleName, type: 'trycatch', body: tryNode.tryBody } as AstBlock, blockContext);
159+
160+
if (tryNode.elseBody?.length || 0 > 0) {
161+
this.evalBlock({ name: blockContext.moduleName, type: 'trycatch', body: tryNode.elseBody } as AstBlock, blockContext);
162+
}
163+
}
164+
catch (err) {
165+
if (err instanceof JspyEvalError) {
166+
// evaluation error should not be handled
167+
throw err;
168+
} else {
169+
const name = (err instanceof JspyError) ? (err as JspyError).name : typeof (err);
170+
const message = (err instanceof JspyError) ? (err as JspyError).message : err ?? err.message;
171+
const moduleName = (err instanceof JspyError) ? (err as JspyError).moduleName : 0;
172+
const line = (err instanceof JspyError) ? (err as JspyError).line : 0;
173+
const column = (err instanceof JspyError) ? (err as JspyError).column : 0;
174+
175+
const firstExept = tryNode.exepts[0];
176+
const catchBody = firstExept.body;
177+
const ctx = blockContext;// cloneContext(blockContext);
178+
ctx.blockScope.set(firstExept.error?.alias || "error", { name, message, line, column, moduleName })
179+
this.evalBlock({ name: blockContext.moduleName, type: 'trycatch', body: catchBody } as AstBlock, ctx);
180+
ctx.blockScope.set(firstExept.error?.alias || "error", null)
181+
}
182+
}
183+
finally {
184+
if (tryNode.finallyBody?.length || 0 > 0) {
185+
this.evalBlock({ name: blockContext.moduleName, type: 'trycatch', body: tryNode.finallyBody } as AstBlock, blockContext);
186+
}
187+
}
188+
189+
return;
190+
}
191+
142192
if (node.type === 'return') {
143193
const returnNode = node as ReturnNode;
144194
blockContext.returnCalled = true;
@@ -196,7 +246,7 @@ export class Evaluator {
196246
const name = (node as GetSingleVarNode).name;
197247

198248
const value = blockContext.blockScope.get((node as GetSingleVarNode).name);
199-
if(value === undefined){
249+
if (value === undefined) {
200250
throw new Error(`Variable ${name} is not defined.`);
201251
}
202252
return value;
@@ -235,7 +285,7 @@ export class Evaluator {
235285
if (node.type === "funcCall") {
236286
const funcCallNode = node as FunctionCallNode;
237287
const func = blockContext.blockScope.get(funcCallNode.name) as (...args: unknown[]) => unknown;
238-
if(typeof func !== 'function') {
288+
if (typeof func !== 'function') {
239289
throw Error(`'${funcCallNode.name}' is not a function or not defined.`)
240290
}
241291

@@ -305,7 +355,7 @@ export class Evaluator {
305355
const funcCallNode = nestedProp as FunctionCallNode;
306356
const func = startObject[funcCallNode.name] as (...args: unknown[]) => unknown;
307357

308-
if(typeof func !== 'function') {
358+
if (typeof func !== 'function') {
309359
throw Error(`'${funcCallNode.name}' is not a function or not defined.`)
310360
}
311361

0 commit comments

Comments
 (0)