Skip to content

Commit c3d71b5

Browse files
takejohnFineArchs
andauthored
feat: ラベル構文の追加 (#885)
* 繰り返し処理文にラベルをつけられるように * break,continueにラベルをつけられるように * 静的な整合性解析 * インデントをタブに変換 * if,match,evalからbreakできるように * if,match,evalのbreakに値を指定できるように * if,match,eval以外のbreakに値が指定されているとエラーに * テストの追加と修正 * ラベルに空白を使用できないように * 違法なcontinue文のテスト * APIレポート * CHANGELOG * breakのラベルを省略してラベル付き式を指定したときのエラーを定義 * Update unreleased/jump-statements.md Co-authored-by: FineArchs <[email protected]> * バグ修正の文言を追加 * ifの条件やmatchのターゲットとパターンでbreakできないように * 不正な位置でのcontinueを意味解析で弾く * elseifの条件とmatchのパターンにおいてbreakをunwrapしないように --------- Co-authored-by: FineArchs <[email protected]>
1 parent d53cb31 commit c3d71b5

File tree

15 files changed

+1861
-587
lines changed

15 files changed

+1861
-587
lines changed

etc/aiscript.api.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ type Attribute = NodeBase & {
215215
// @public (undocumented)
216216
type Block = NodeBase & {
217217
type: 'block';
218+
label?: string;
218219
statements: (Statement | Expression)[];
219220
};
220221

@@ -230,6 +231,8 @@ type Bool = NodeBase & {
230231
// @public (undocumented)
231232
type Break = NodeBase & {
232233
type: 'break';
234+
label?: string;
235+
expr?: Expression;
233236
};
234237

235238
// @public (undocumented)
@@ -242,6 +245,7 @@ type Call = NodeBase & {
242245
// @public (undocumented)
243246
type Continue = NodeBase & {
244247
type: 'continue';
248+
label?: string;
245249
};
246250

247251
// @public (undocumented)
@@ -264,6 +268,7 @@ type Div = NodeBase & {
264268
// @public (undocumented)
265269
type Each = NodeBase & {
266270
type: 'each';
271+
label?: string;
267272
var: Expression;
268273
items: Expression;
269274
for: Statement | Expression;
@@ -347,6 +352,7 @@ type FnTypeSource = NodeBase & {
347352
// @public (undocumented)
348353
type For = NodeBase & {
349354
type: 'for';
355+
label?: string;
350356
var?: string;
351357
from?: Expression;
352358
to?: Expression;
@@ -380,6 +386,7 @@ type Identifier = NodeBase & {
380386
// @public (undocumented)
381387
type If = NodeBase & {
382388
type: 'if';
389+
label?: string;
383390
cond: Expression;
384391
then: Statement | Expression;
385392
elseif: {
@@ -479,6 +486,7 @@ type Loc = {
479486
// @public (undocumented)
480487
type Loop = NodeBase & {
481488
type: 'loop';
489+
label?: string;
482490
statements: (Statement | Expression)[];
483491
};
484492

@@ -499,6 +507,7 @@ type Lteq = NodeBase & {
499507
// @public (undocumented)
500508
type Match = NodeBase & {
501509
type: 'match';
510+
label?: string;
502511
about: Expression;
503512
qs: {
504513
q: Expression;

src/interpreter/control.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AiScriptRuntimeError } from '../error.js';
2+
import { NULL } from './value.js';
23
import type { Reference } from './reference.js';
34
import type { Value } from './value.js';
45

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

1011
export type CBreak = {
1112
type: 'break';
12-
value: null;
13+
label?: string;
14+
value?: Value;
1315
};
1416

1517
export type CContinue = {
1618
type: 'continue';
19+
label?: string;
1720
value: null;
1821
};
1922

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

28-
export const BREAK = (): CBreak => ({
31+
export const BREAK = (label?: string, value?: CBreak['value']): CBreak => ({
2932
type: 'break' as const,
30-
value: null,
33+
label,
34+
value: value,
3135
});
3236

33-
export const CONTINUE = (): CContinue => ({
37+
export const CONTINUE = (label?: string): CContinue => ({
3438
type: 'continue' as const,
39+
label,
3540
value: null,
3641
});
3742

43+
/**
44+
* 値がbreakで、ラベルが一致する場合のみ、その中身を取り出します。
45+
*/
46+
export function unWrapLabeledBreak(v: Value | Control, label: string | undefined): Value | Control {
47+
if (v.type === 'break' && v.label != null && v.label === label) {
48+
return v.value ?? NULL;
49+
}
50+
return v;
51+
}
52+
3853
export function unWrapRet(v: Value | Control): Value {
3954
switch (v.type) {
4055
case 'return':

src/interpreter/index.ts

Lines changed: 45 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { AiScriptError, NonAiScriptError, AiScriptNamespaceError, AiScriptIndexO
77
import * as Ast from '../node.js';
88
import { Scope } from './scope.js';
99
import { std } from './lib/std.js';
10-
import { RETURN, unWrapRet, BREAK, CONTINUE, assertValue, isControl, type Control } from './control.js';
10+
import { RETURN, unWrapRet, BREAK, CONTINUE, assertValue, isControl, type Control, unWrapLabeledBreak } from './control.js';
1111
import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, expectAny, reprValue, isFunction } from './util.js';
1212
import { NULL, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN, ERROR } from './value.js';
1313
import { getPrimProp } from './primitive-props.js';
@@ -371,7 +371,7 @@ export class Interpreter {
371371
}
372372
assertBoolean(cond);
373373
if (cond.value) {
374-
return this._evalClause(node.then, scope, callStack);
374+
return unWrapLabeledBreak(await this._evalClause(node.then, scope, callStack), node.label);
375375
}
376376
for (const elseif of node.elseif) {
377377
const cond = await this._eval(elseif.cond, scope, callStack);
@@ -380,11 +380,11 @@ export class Interpreter {
380380
}
381381
assertBoolean(cond);
382382
if (cond.value) {
383-
return this._evalClause(elseif.then, scope, callStack);
383+
return unWrapLabeledBreak(await this._evalClause(elseif.then, scope, callStack), node.label);
384384
}
385385
}
386386
if (node.else) {
387-
return this._evalClause(node.else, scope, callStack);
387+
return unWrapLabeledBreak(await this._evalClause(node.else, scope, callStack), node.label);
388388
}
389389
return NULL;
390390
}
@@ -400,11 +400,11 @@ export class Interpreter {
400400
return q;
401401
}
402402
if (eq(about, q)) {
403-
return await this._evalClause(qa.a, scope, callStack);
403+
return unWrapLabeledBreak(await this._evalClause(qa.a, scope, callStack), node.label);
404404
}
405405
}
406406
if (node.default) {
407-
return await this._evalClause(node.default, scope, callStack);
407+
return unWrapLabeledBreak(await this._evalClause(node.default, scope, callStack), node.label);
408408
}
409409
return NULL;
410410
}
@@ -414,7 +414,14 @@ export class Interpreter {
414414
while (true) {
415415
const v = await this._run(node.statements, scope.createChildScope(), callStack);
416416
if (v.type === 'break') {
417+
if (v.label != null && v.label !== node.label) {
418+
return v;
419+
}
417420
break;
421+
} else if (v.type === 'continue') {
422+
if (v.label != null && v.label !== node.label) {
423+
return v;
424+
}
418425
} else if (v.type === 'return') {
419426
return v;
420427
}
@@ -432,7 +439,14 @@ export class Interpreter {
432439
for (let i = 0; i < times.value; i++) {
433440
const v = await this._evalClause(node.for, scope, callStack);
434441
if (v.type === 'break') {
442+
if (v.label != null && v.label !== node.label) {
443+
return v;
444+
}
435445
break;
446+
} else if (v.type === 'continue') {
447+
if (v.label != null && v.label !== node.label) {
448+
return v;
449+
}
436450
} else if (v.type === 'return') {
437451
return v;
438452
}
@@ -456,7 +470,14 @@ export class Interpreter {
456470
}],
457471
])), callStack);
458472
if (v.type === 'break') {
473+
if (v.label != null && v.label !== node.label) {
474+
return v;
475+
}
459476
break;
477+
} else if (v.type === 'continue') {
478+
if (v.label != null && v.label !== node.label) {
479+
return v;
480+
}
460481
} else if (v.type === 'return') {
461482
return v;
462483
}
@@ -476,7 +497,14 @@ export class Interpreter {
476497
this.define(eachScope, node.var, item, false);
477498
const v = await this._eval(node.for, eachScope, callStack);
478499
if (v.type === 'break') {
500+
if (v.label != null && v.label !== node.label) {
501+
return v;
502+
}
479503
break;
504+
} else if (v.type === 'continue') {
505+
if (v.label != null && v.label !== node.label) {
506+
return v;
507+
}
480508
} else if (v.type === 'return') {
481509
return v;
482510
}
@@ -695,7 +723,7 @@ export class Interpreter {
695723
}
696724

697725
case 'block': {
698-
return this._run(node.statements, scope.createChildScope(), callStack);
726+
return unWrapLabeledBreak(await this._run(node.statements, scope.createChildScope(), callStack), node.label);
699727
}
700728

701729
case 'exists': {
@@ -728,13 +756,21 @@ export class Interpreter {
728756
}
729757

730758
case 'break': {
759+
let val: Value | undefined;
760+
if (node.expr != null) {
761+
const valueOrControl = await this._eval(node.expr, scope, callStack);
762+
if (isControl(valueOrControl)) {
763+
return valueOrControl;
764+
}
765+
val = valueOrControl;
766+
}
731767
this.log('block:break', { scope: scope.name });
732-
return BREAK();
768+
return BREAK(node.label, val);
733769
}
734770

735771
case 'continue': {
736772
this.log('block:continue', { scope: scope.name });
737-
return CONTINUE();
773+
return CONTINUE(node.label);
738774
}
739775

740776
case 'ns': {

src/node.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,15 @@ export type Return = NodeBase & {
7373

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

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

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

9598
export type Break = NodeBase & {
9699
type: 'break'; // break文
100+
label?: string; // ラベル
101+
expr?: Expression; // 式
97102
};
98103

99104
export type Continue = NodeBase & {
100105
type: 'continue'; // continue文
106+
label?: string; // ラベル
101107
};
102108

103109
export type AddAssign = NodeBase & {
@@ -265,6 +271,7 @@ export type Or = NodeBase & {
265271

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

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

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

0 commit comments

Comments
 (0)