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

Merged
merged 20 commits into from
Jan 12, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b596595
繰り返し処理文にラベルをつけられるように
takejohn Nov 18, 2024
4bdded1
break,continueにラベルをつけられるように
takejohn Nov 18, 2024
31c272f
静的な整合性解析
takejohn Nov 18, 2024
30b305b
インデントをタブに変換
takejohn Dec 14, 2024
1e29f27
if,match,evalからbreakできるように
takejohn Dec 14, 2024
c166339
if,match,evalのbreakに値を指定できるように
takejohn Dec 14, 2024
44e17c6
if,match,eval以外のbreakに値が指定されているとエラーに
takejohn Dec 14, 2024
f9b2e4c
テストの追加と修正
takejohn Dec 14, 2024
b4404c3
ラベルに空白を使用できないように
takejohn Dec 14, 2024
a04fd8f
Merge branch 'master' into feat/loop-label_2
takejohn Dec 24, 2024
31885c0
違法なcontinue文のテスト
takejohn Dec 24, 2024
e32c1f9
APIレポート
takejohn Dec 24, 2024
541a143
CHANGELOG
takejohn Dec 24, 2024
fd8d4e0
breakのラベルを省略してラベル付き式を指定したときのエラーを定義
takejohn Dec 24, 2024
d9b4ea6
Update unreleased/jump-statements.md
takejohn Dec 31, 2024
c1695fd
バグ修正の文言を追加
takejohn Dec 31, 2024
c67f3d9
ifの条件やmatchのターゲットとパターンでbreakできないように
takejohn Jan 5, 2025
2e22cfc
不正な位置でのcontinueを意味解析で弾く
takejohn Jan 6, 2025
74b1a65
elseifの条件とmatchのパターンにおいてbreakをunwrapしないように
takejohn Jan 7, 2025
d551057
Merge branch 'master' into feat/loop-label_2
takejohn Jan 12, 2025
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
54 changes: 45 additions & 9 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 @@ -371,7 +371,7 @@
}
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);
Expand All @@ -380,11 +380,11 @@
}
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;
}
Expand All @@ -400,21 +400,28 @@
return q;
}
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