Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ラベル構文の追加 #885

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
9 changes: 9 additions & 0 deletions etc/aiscript.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ type Attribute = NodeBase & {
// @public (undocumented)
type Block = NodeBase & {
type: 'block';
label?: string;
statements: (Statement | Expression)[];
};

Expand All @@ -230,6 +231,8 @@ type Bool = NodeBase & {
// @public (undocumented)
type Break = NodeBase & {
type: 'break';
label?: string;
expr?: Expression;
};

// @public (undocumented)
Expand All @@ -242,6 +245,7 @@ type Call = NodeBase & {
// @public (undocumented)
type Continue = NodeBase & {
type: 'continue';
label?: string;
};

// @public (undocumented)
Expand All @@ -264,6 +268,7 @@ type Div = NodeBase & {
// @public (undocumented)
type Each = NodeBase & {
type: 'each';
label?: string;
var: Expression;
items: Expression;
for: Statement | Expression;
Expand Down Expand Up @@ -347,6 +352,7 @@ type FnTypeSource = NodeBase & {
// @public (undocumented)
type For = NodeBase & {
type: 'for';
label?: string;
var?: string;
from?: Expression;
to?: Expression;
Expand Down Expand Up @@ -380,6 +386,7 @@ type Identifier = NodeBase & {
// @public (undocumented)
type If = NodeBase & {
type: 'if';
label?: string;
cond: Expression;
then: Statement | Expression;
elseif: {
Expand Down Expand Up @@ -479,6 +486,7 @@ type Loc = {
// @public (undocumented)
type Loop = NodeBase & {
type: 'loop';
label?: string;
statements: (Statement | Expression)[];
};

Expand All @@ -499,6 +507,7 @@ type Lteq = NodeBase & {
// @public (undocumented)
type Match = NodeBase & {
type: 'match';
label?: string;
about: Expression;
qs: {
q: Expression;
Expand Down
23 changes: 19 additions & 4 deletions src/interpreter/control.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AiScriptRuntimeError } from '../error.js';
import { NULL } from './value.js';
import type { Reference } from './reference.js';
import type { Value } from './value.js';

Expand All @@ -9,11 +10,13 @@ export type CReturn = {

export type CBreak = {
type: 'break';
value: null;
label?: string;
value?: Value;
};

export type CContinue = {
type: 'continue';
label?: string;
value: null;
};

Expand All @@ -25,16 +28,28 @@ export const RETURN = (v: CReturn['value']): CReturn => ({
value: v,
});

export const BREAK = (): CBreak => ({
export const BREAK = (label?: string, value?: CBreak['value']): CBreak => ({
type: 'break' as const,
value: null,
label,
value: value,
});

export const CONTINUE = (): CContinue => ({
export const CONTINUE = (label?: string): CContinue => ({
type: 'continue' as const,
label,
value: null,
});

/**
* 値がbreakで、ラベルが一致する場合のみ、その中身を取り出します。
*/
export function unWrapLabeledBreak(v: Value | Control, label: string | undefined): Value | Control {
if (v.type === 'break' && v.label != null && v.label === label) {
return v.value ?? NULL;
}
return v;
}

export function unWrapRet(v: Value | Control): Value {
switch (v.type) {
case 'return':
Expand Down
62 changes: 49 additions & 13 deletions src/interpreter/index.ts
takejohn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import * as Ast from '../node.js';
import { Scope } from './scope.js';
import { std } from './lib/std.js';
import { RETURN, unWrapRet, BREAK, CONTINUE, assertValue, isControl, type Control } from './control.js';
import { RETURN, unWrapRet, BREAK, CONTINUE, assertValue, isControl, type Control, unWrapLabeledBreak } from './control.js';
import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, expectAny, reprValue, isFunction } from './util.js';
import { NULL, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN, ERROR } from './value.js';
import { getPrimProp } from './primitive-props.js';
Expand Down Expand Up @@ -367,54 +367,61 @@
case 'if': {
const cond = await this._eval(node.cond, scope, callStack);
if (isControl(cond)) {
return cond;
return unWrapLabeledBreak(cond, node.label);
}
assertBoolean(cond);
if (cond.value) {
return this._evalClause(node.then, scope, callStack);
return unWrapLabeledBreak(await this._evalClause(node.then, scope, callStack), node.label);
}
for (const elseif of node.elseif) {
const cond = await this._eval(elseif.cond, scope, callStack);
if (isControl(cond)) {
return cond;
return unWrapLabeledBreak(cond, node.label);
}
assertBoolean(cond);
if (cond.value) {
return this._evalClause(elseif.then, scope, callStack);
return unWrapLabeledBreak(await this._evalClause(elseif.then, scope, callStack), node.label);
}
}
if (node.else) {
return this._evalClause(node.else, scope, callStack);
return unWrapLabeledBreak(await this._evalClause(node.else, scope, callStack), node.label);
}
return NULL;
}

case 'match': {
const about = await this._eval(node.about, scope, callStack);
if (isControl(about)) {
return about;
return unWrapLabeledBreak(about, node.label);
}
for (const qa of node.qs) {
const q = await this._eval(qa.q, scope, callStack);
if (isControl(q)) {
return q;
return unWrapLabeledBreak(q, node.label);
}
if (eq(about, q)) {
return await this._evalClause(qa.a, scope, callStack);
return unWrapLabeledBreak(await this._evalClause(qa.a, scope, callStack), node.label);
}
}
if (node.default) {
return await this._evalClause(node.default, scope, callStack);
return unWrapLabeledBreak(await this._evalClause(node.default, scope, callStack), node.label);
}
return NULL;
}

case 'loop': {
// eslint-disable-next-line no-constant-condition
while (true) {

Check warning on line 414 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unnecessary conditional, value is always truthy
const v = await this._run(node.statements, scope.createChildScope(), callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
return v;
}
break;
} else if (v.type === 'continue') {
if (v.label != null && v.label !== node.label) {
return v;
}
} else if (v.type === 'return') {
return v;
}
Expand All @@ -432,7 +439,14 @@
for (let i = 0; i < times.value; i++) {
const v = await this._evalClause(node.for, scope, callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
return v;
}
break;
} else if (v.type === 'continue') {
if (v.label != null && v.label !== node.label) {
return v;
}
} else if (v.type === 'return') {
return v;
}
Expand All @@ -456,7 +470,14 @@
}],
])), callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
return v;
}
break;
} else if (v.type === 'continue') {
if (v.label != null && v.label !== node.label) {
return v;
}
} else if (v.type === 'return') {
return v;
}
Expand All @@ -476,7 +497,14 @@
this.define(eachScope, node.var, item, false);
const v = await this._eval(node.for, eachScope, callStack);
if (v.type === 'break') {
if (v.label != null && v.label !== node.label) {
return v;
}
break;
} else if (v.type === 'continue') {
if (v.label != null && v.label !== node.label) {
return v;
}
} else if (v.type === 'return') {
return v;
}
Expand Down Expand Up @@ -695,7 +723,7 @@
}

case 'block': {
return this._run(node.statements, scope.createChildScope(), callStack);
return unWrapLabeledBreak(await this._run(node.statements, scope.createChildScope(), callStack), node.label);
}

case 'exists': {
Expand Down Expand Up @@ -728,13 +756,21 @@
}

case 'break': {
let val: Value | undefined;
if (node.expr != null) {
const valueOrControl = await this._eval(node.expr, scope, callStack);
if (isControl(valueOrControl)) {
return valueOrControl;
}
val = valueOrControl;
}
this.log('block:break', { scope: scope.name });
return BREAK();
return BREAK(node.label, val);
}

case 'continue': {
this.log('block:continue', { scope: scope.name });
return CONTINUE();
return CONTINUE(node.label);
}

case 'ns': {
Expand Down Expand Up @@ -910,7 +946,7 @@
public pause(): void {
if (this.pausing) return;
let resolve: () => void;
const promise = new Promise<void>(r => { resolve = () => r(); });

Check warning on line 949 in src/interpreter/index.ts

View workflow job for this annotation

GitHub Actions / lint

Missing return type on function
this.pausing = { promise, resolve: resolve! };
for (const handler of this.pauseHandlers) {
handler();
Expand Down
9 changes: 9 additions & 0 deletions src/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,15 @@ export type Return = NodeBase & {

export type Each = NodeBase & {
type: 'each'; // each文
label?: string; // ラベル
var: Expression; // イテレータ宣言
items: Expression; // 配列
for: Statement | Expression; // 本体処理
};

export type For = NodeBase & {
type: 'for'; // for文
label?: string; // ラベル
var?: string; // イテレータ変数名
from?: Expression; // 開始値
to?: Expression; // 終値
Expand All @@ -89,15 +91,19 @@ export type For = NodeBase & {

export type Loop = NodeBase & {
type: 'loop'; // loop文
label?: string; // ラベル
statements: (Statement | Expression)[]; // 処理
};

export type Break = NodeBase & {
type: 'break'; // break文
label?: string; // ラベル
expr?: Expression; // 式
};

export type Continue = NodeBase & {
type: 'continue'; // continue文
label?: string; // ラベル
};

export type AddAssign = NodeBase & {
Expand Down Expand Up @@ -265,6 +271,7 @@ export type Or = NodeBase & {

export type If = NodeBase & {
type: 'if'; // if式
label?: string; // ラベル
cond: Expression; // 条件式
then: Statement | Expression; // then節
elseif: {
Expand All @@ -289,6 +296,7 @@ export type Fn = NodeBase & {

export type Match = NodeBase & {
type: 'match'; // パターンマッチ
label?: string; // ラベル
about: Expression; // 対象
qs: {
q: Expression; // 条件
Expand All @@ -299,6 +307,7 @@ export type Match = NodeBase & {

export type Block = NodeBase & {
type: 'block'; // ブロックまたはeval式
label?: string; // ラベル
statements: (Statement | Expression)[]; // 処理
};

Expand Down
Loading
Loading