Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 21 additions & 40 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,6 @@ type Bool_2 = NodeBase_2 & ChainProp & {
value: boolean;
};

// @public (undocumented)
const BREAK: () => Value;

// @public (undocumented)
type Break = NodeBase & {
type: 'break';
Expand All @@ -184,6 +181,13 @@ type Break_2 = NodeBase_2 & {
type: 'break';
};

// @public (undocumented)
class BreakError extends AiScriptError {
constructor(nodeType: 'break' | 'continue', message: string, info?: any);
// (undocumented)
nodeType: 'break' | 'continue';
}

// @public (undocumented)
function CALL(target: Call_2['target'], args: Call_2['args'], loc?: {
start: number;
Expand Down Expand Up @@ -213,9 +217,6 @@ type CallChain = NodeBase_2 & {
// @public (undocumented)
type ChainMember = CallChain | IndexChain | PropChain;

// @public (undocumented)
const CONTINUE: () => Value;

// @public (undocumented)
type Continue = NodeBase & {
type: 'continue';
Expand Down Expand Up @@ -324,7 +325,9 @@ declare namespace errors {
SyntaxError_2 as SyntaxError,
TypeError_2 as TypeError,
RuntimeError,
IndexOutOfRangeError
IndexOutOfRangeError,
ReturnError,
BreakError
}
}
export { errors }
Expand Down Expand Up @@ -733,9 +736,6 @@ type PropChain = NodeBase_2 & {
name: string;
};

// @public (undocumented)
const RETURN: (v: VReturn['value']) => Value;

// @public (undocumented)
type Return = NodeBase & {
type: 'return';
Expand All @@ -748,6 +748,15 @@ type Return_2 = NodeBase_2 & {
expr: Expression_2;
};

// @public (undocumented)
class ReturnError extends AiScriptError {
constructor(val: Value, message: string, info?: any);
// (undocumented)
nodeType: string;
// (undocumented)
val: Value;
}

// @public (undocumented)
class RuntimeError extends AiScriptError {
constructor(message: string, info?: any);
Expand Down Expand Up @@ -841,9 +850,6 @@ type TypeSource = NamedTypeSource | FnTypeSource;
// @public (undocumented)
type TypeSource_2 = NamedTypeSource_2 | FnTypeSource_2;

// @public (undocumented)
const unWrapRet: (v: Value) => Value;

declare namespace utils {
export {
expectAny,
Expand Down Expand Up @@ -875,7 +881,7 @@ function valToJs(val: Value): any;
function valToString(val: Value, simple?: boolean): string;

// @public (undocumented)
type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn | VReturn | VBreak | VContinue) & Attr_2;
type Value = (VNull | VBool | VNum | VStr | VArr | VObj | VFn) & Attr_2;

declare namespace values {
export {
Expand All @@ -886,9 +892,6 @@ declare namespace values {
VArr,
VObj,
VFn,
VReturn,
VBreak,
VContinue,
Attr_2 as Attr,
Value,
NULL,
Expand All @@ -900,11 +903,7 @@ declare namespace values {
OBJ,
ARR,
FN,
FN_NATIVE,
RETURN,
BREAK,
CONTINUE,
unWrapRet
FN_NATIVE
}
}
export { values }
Expand All @@ -921,18 +920,6 @@ type VBool = {
value: boolean;
};

// @public (undocumented)
type VBreak = {
type: 'break';
value: null;
};

// @public (undocumented)
type VContinue = {
type: 'continue';
value: null;
};

// @public (undocumented)
type VFn = {
type: 'fn';
Expand Down Expand Up @@ -963,12 +950,6 @@ type VObj = {
value: Map<string, Value>;
};

// @public (undocumented)
type VReturn = {
type: 'return';
value: Value;
};

// @public (undocumented)
type VStr = {
type: 'str';
Expand Down
26 changes: 26 additions & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Value } from './interpreter/value';

export abstract class AiScriptError extends Error {
public info?: any;

Expand Down Expand Up @@ -36,3 +38,27 @@ export class IndexOutOfRangeError extends RuntimeError {
super(message, info);
}
}

// return文でスコープ抜け出しを行う用
// 関数定義ブロックがcatchしなかったらそのままエラーとして扱う
export class ReturnError extends AiScriptError {
public nodeType = 'return';
constructor(
public val: Value,
message: string,
info?: any,
) {
super(message, info);
}
}
// break/continue文でスコープ抜け出しを行う用
// loop/for/eachがcatchしなかったらそのままエラーとして扱う
export class BreakError extends AiScriptError {
constructor(
public nodeType: 'break'|'continue',
message: string,
info?: any,
) {
super(message, info);
}
}
108 changes: 68 additions & 40 deletions src/interpreter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
*/

import autobind from 'autobind-decorator';
import { IndexOutOfRangeError, RuntimeError } from '../error';
import { IndexOutOfRangeError, RuntimeError, ReturnError, BreakError } from '../error';
import { Scope } from './scope';
import { std } from './lib/std';
import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, isString, expectAny, isNumber } from './util';
import { NULL, RETURN, unWrapRet, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN, BREAK, CONTINUE } from './value';
import { NULL, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN } from './value';
import { PRIMITIVE_PROPS } from './primitive-props';
import type { Value, VFn } from './value';
import type * as Ast from '../node';
Expand Down Expand Up @@ -175,7 +175,17 @@ export class Interpreter {
_args.set(fn.args![i]!, args[i]!);
}
const fnScope = fn.scope!.createChildScope(_args);
return unWrapRet(await this._run(fn.statements!, fnScope));
try {
return await this._run(fn.statements!, fnScope);
} catch (e) {
if (e.nodeType === 'return') {
expectAny(e.val);
this.log('block:return caught by function', { scope: fnScope.name, val: e.val });
return e.val;
} else {
throw e;
}
}
}
}

Expand Down Expand Up @@ -237,11 +247,18 @@ export class Interpreter {
case 'loop': {
// eslint-disable-next-line no-constant-condition
while (true) {
const v = await this._run(node.statements, scope.createChildScope());
if (v.type === 'break') {
break;
} else if (v.type === 'return') {
return v;
try {
await this._run(node.statements, scope.createChildScope());
} catch (e) {
if (e.nodeType === 'break') {
this.log('block:break caught by loop', { scope: scope.name });
break;
} else if (e.nodeType === 'continue') {
this.log('block:continue caught by loop', { scope: scope.name });
continue;
} else {
throw e;
}
}
}
return NULL;
Expand All @@ -252,11 +269,18 @@ export class Interpreter {
const times = await this._eval(node.times, scope);
assertNumber(times);
for (let i = 0; i < times.value; i++) {
const v = await this._eval(node.for, scope);
if (v.type === 'break') {
break;
} else if (v.type === 'return') {
return v;
try {
await this._eval(node.for, scope);
} catch (e) {
if (e.nodeType === 'break') {
this.log('block:break caught by for', { scope: scope.name });
break;
} else if (e.nodeType === 'continue') {
this.log('block:continue caught by for', { scope: scope.name });
continue;
} else {
throw e;
}
}
}
} else {
Expand All @@ -265,13 +289,20 @@ export class Interpreter {
assertNumber(from);
assertNumber(to);
for (let i = from.value; i < from.value + to.value; i++) {
const v = await this._eval(node.for, scope.createChildScope(new Map([
[node.var!, NUM(i)],
])));
if (v.type === 'break') {
break;
} else if (v.type === 'return') {
return v;
try {
await this._eval(node.for, scope.createChildScope(new Map([
[node.var!, NUM(i)],
])));
} catch (e) {
if (e.nodeType === 'break') {
this.log('block:break caught by for', { scope: scope.name });
break;
} else if (e.nodeType === 'continue') {
this.log('block:continue caught by for', { scope: scope.name });
continue;
} else {
throw e;
}
}
}
}
Expand All @@ -282,13 +313,20 @@ export class Interpreter {
const items = await this._eval(node.items, scope);
assertArray(items);
for (const item of items.value) {
const v = await this._eval(node.for, scope.createChildScope(new Map([
[node.var, item],
])));
if (v.type === 'break') {
break;
} else if (v.type === 'return') {
return v;
try {
await this._eval(node.for, scope.createChildScope(new Map([
[node.var, item],
])));
} catch (e) {
if (e.nodeType === 'break') {
this.log('block:break caught by each', { scope: scope.name });
break;
} else if (e.nodeType === 'continue') {
this.log('block:continue caught by each', { scope: scope.name });
continue;
} else {
throw e;
}
}
}
return NULL;
Expand Down Expand Up @@ -449,17 +487,17 @@ export class Interpreter {
case 'return': {
const val = await this._eval(node.expr, scope);
this.log('block:return', { scope: scope.name, val: val });
return RETURN(val);
throw new ReturnError(val,'"return" statement outside function');
}

case 'break': {
this.log('block:break', { scope: scope.name });
return BREAK();
throw new BreakError('break','"break" statement outside loop');
}

case 'continue': {
this.log('block:continue', { scope: scope.name });
return CONTINUE();
throw new BreakError('continue','"continue" statement outside loop');
}

case 'ns': {
Expand All @@ -486,16 +524,6 @@ export class Interpreter {
const node = program[i]!;

v = await this._eval(node, scope);
if (v.type === 'return') {
this.log('block:return', { scope: scope.name, val: v.value });
return v;
} else if (v.type === 'break') {
this.log('block:break', { scope: scope.name });
return v;
} else if (v.type === 'continue') {
this.log('block:continue', { scope: scope.name });
return v;
}
}

this.log('block:leave', { scope: scope.name, val: v });
Expand Down
2 changes: 1 addition & 1 deletion src/interpreter/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export function valToJs(val: Value): any {
return obj;
}
case 'str': return val.value;
default: throw new Error(`Unrecognized value type: ${val.type}`);
//default: throw new Error(`Unrecognized value type: ${val.type}`);
}
}

Expand Down
Loading