From 6d0e268a36a86317a5b846453a1c84c462803247 Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Fri, 19 Jan 2024 04:08:37 +0900 Subject: [PATCH 01/12] optional argument --- etc/aiscript.api.md | 14 ++++++++++++++ src/interpreter/index.ts | 19 +++++++++++++++---- src/interpreter/util.ts | 7 ++++++- src/interpreter/value.ts | 8 +++++++- src/node.ts | 1 + src/parser/scanner.ts | 5 +++++ src/parser/syntaxes/common.ts | 9 +++++++-- src/parser/token.ts | 2 ++ test/index.ts | 23 +++++++++++++++++++++++ 9 files changed, 80 insertions(+), 8 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index d89418c6..8277f128 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -267,6 +267,7 @@ type Fn = NodeBase & { type: 'fn'; args: { name: string; + optional?: boolean; argType?: TypeSource; }[]; retType?: TypeSource; @@ -608,6 +609,7 @@ declare namespace values { VObj, VFn, VUserFn, + VFnArg, VNativeFn, VReturn, VBreak, @@ -668,6 +670,13 @@ type VError = { // @public (undocumented) type VFn = VUserFn | VNativeFn; +// @public (undocumented) +type VFnArg = { + name: string; + optional?: boolean; + type?: Type; +}; + // @public type VNativeFn = VFnBase & { native: (args: (Value | undefined)[], opts: { @@ -712,10 +721,15 @@ type VStr = { // @public (undocumented) type VUserFn = VFnBase & { native?: undefined; + args: VFnArg[]; statements: Node_2[]; scope: Scope; }; +// Warnings were encountered during analysis: +// +// src/interpreter/value.ts:47:2 - (ae-forgotten-export) The symbol "Type" needs to be exported by the entry point index.d.ts + // (No @packageDocumentation comment for this package) ``` diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 3b5a5538..242c1cde 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -230,10 +230,11 @@ export class Interpreter { return result ?? NULL; } else { const _args = new Map(); - for (let i = 0; i < (fn.args ?? []).length; i++) { - _args.set(fn.args![i]!, { + for (const i in fn.args) { + if (!fn.args[i]!.optional) expectAny(args[i]); + _args.set(fn.args[i]!.name, { isMutable: true, - value: args[i]!, + value: args[i] ?? NULL, }); } const fnScope = fn.scope!.createChildScope(_args); @@ -486,7 +487,17 @@ export class Interpreter { } case 'fn': { - return FN(node.args.map(arg => arg.name), node.children, scope); + return FN( + node.args.map(arg => { + return { + name: arg.name, + optional: arg.optional, + // type: (TODO) + }; + }), + node.children, + scope, + ); } case 'block': { diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts index 1d60c0f3..6c5d55f7 100644 --- a/src/interpreter/util.ts +++ b/src/interpreter/util.ts @@ -188,7 +188,12 @@ export function reprValue(value: Value, literalLike = false, processedObjects = if (value.type === 'bool') return value.value.toString(); if (value.type === 'null') return 'null'; if (value.type === 'fn') { - return `@( ${(value.args ?? []).join(', ')} ) { ... }`; + if (value.native) { + // そのうちネイティブ関数の引数も表示できるようにしたいが、ホスト向けの破壊的変更を伴うと思われる + return '@( ?? ) { native code }'; + } else { + return `@( ${(value.args.map(v => v.name)).join(', ')} ) { ... }`; + } } return '?'; diff --git a/src/interpreter/value.ts b/src/interpreter/value.ts index 90c531b0..f0616554 100644 --- a/src/interpreter/value.ts +++ b/src/interpreter/value.ts @@ -1,4 +1,5 @@ import type { Node } from '../node.js'; +import type { Type } from '../type.js'; import type { Scope } from './scope.js'; export type VNull = { @@ -33,13 +34,18 @@ export type VObj = { export type VFn = VUserFn | VNativeFn; type VFnBase = { type: 'fn'; - args?: string[]; }; export type VUserFn = VFnBase & { native?: undefined; // if (vfn.native) で型アサーション出来るように + args: VFnArg[]; statements: Node[]; scope: Scope; }; +export type VFnArg = { + name: string; + optional?: boolean; + type?: Type; +} /** * When your AiScript NATIVE function passes VFn.call to other caller(s) whose error thrown outside the scope, use VFn.topCall instead to keep it under AiScript error control system. */ diff --git a/src/node.ts b/src/node.ts index caa33196..09f80e71 100644 --- a/src/node.ts +++ b/src/node.ts @@ -175,6 +175,7 @@ export type Fn = NodeBase & { type: 'fn'; // 関数 args: { name: string; // 引数名 + optional?: boolean; // 引数に?がついているか argType?: TypeSource; // 引数の型 }[]; retType?: TypeSource; // 戻り値の型 diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index 21d42f28..89412772 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -268,6 +268,11 @@ export class Scanner implements ITokenStream { } break; } + case '?': { + this.stream.next(); + token = TOKEN(TokenKind.Question, loc, { hasLeftSpacing }); + break; + } case '@': { this.stream.next(); token = TOKEN(TokenKind.At, loc, { hasLeftSpacing }); diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index b4bd9dc0..c50707ec 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -12,7 +12,7 @@ import type * as Ast from '../../node.js'; * ``` */ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node }[] { - const items: { name: string, argType?: Ast.Node }[] = []; + const items: { name: string, optional?: boolean, argType?: Ast.Node }[] = []; s.nextWith(TokenKind.OpenParen); @@ -25,13 +25,18 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node const name = s.token.value!; s.next(); + let optional; + if ((s.kind as TokenKind) === TokenKind.Question) { + s.next(); + optional = true; + } let type; if ((s.kind as TokenKind) === TokenKind.Colon) { s.next(); type = parseType(s); } - items.push({ name, argType: type }); + items.push({ name, optional, argType: type }); // separator switch (s.kind as TokenKind) { diff --git a/src/parser/token.ts b/src/parser/token.ts index 67aca6b6..455ce450 100644 --- a/src/parser/token.ts +++ b/src/parser/token.ts @@ -87,6 +87,8 @@ export enum TokenKind { Gt, /** ">=" */ GtEq, + /** "?" */ + Question, /** "@" */ At, /** "[" */ diff --git a/test/index.ts b/test/index.ts index 7eb82f9b..05380c63 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1568,6 +1568,29 @@ describe('Function call', () => { eq(res, NUM(2)); }); + test.concurrent('optional args', async () => { + const res = await exe(` + @f(x, y?, z?) { + [x, y, z] + } + <: f(true) + `); + eq(res, ARR([TRUE, NULL, NULL])); + }); + + test.concurrent('missing arg', async () => { + try { + await exe(` + @func(a){} + func() + `); + } catch (e) { + assert.ok(e instanceof AiScriptRuntimeError); + return; + } + assert.fail(); + }); + test.concurrent('std: throw AiScript error when required arg missing', async () => { try { await exe(` From 46f0da92aefcb076a6b71d41c2112109fa6b67aa Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Sun, 19 Nov 2023 00:52:31 +0900 Subject: [PATCH 02/12] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 063a94c2..dfff1246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - 文字列リテラルやテンプレートで、`\`とそれに続く1文字は全てエスケープシーケンスとして扱われるように - 文法エラーやラインタイムエラーの発生位置が表示されるように - **Breaking Change** 多くの予約語を追加。これまで変数名等に使えていた名前に影響が出る可能性があります。 +- 省略可能引数を追加。引数名に`?`を後置することでその引数は省略可能となります。(逆にそうでない引数が省略されると即時エラーとなります) # 0.17.0 - `package.json`を修正 From 5bfc73e64121ebd6aa58dc89817fe78612e07f20 Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Sun, 19 Nov 2023 02:38:08 +0900 Subject: [PATCH 03/12] args with default value --- etc/aiscript.api.md | 6 +++--- src/interpreter/index.ts | 13 +++++++------ src/interpreter/value.ts | 2 +- src/node.ts | 2 +- src/parser/syntaxes/common.ts | 13 +++++++++---- src/parser/visit.ts | 5 +++++ test/index.ts | 10 ++++++++++ 7 files changed, 36 insertions(+), 15 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index aa3df4d2..347b7dee 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -270,7 +270,7 @@ type Fn = NodeBase & { type: 'fn'; args: { name: string; - optional?: boolean; + default?: Expression; argType?: TypeSource; }[]; retType?: TypeSource; @@ -676,8 +676,8 @@ type VFn = VUserFn | VNativeFn; // @public (undocumented) type VFnArg = { name: string; - optional?: boolean; type?: Type; + default?: Value; }; // @public @@ -731,7 +731,7 @@ type VUserFn = VFnBase & { // Warnings were encountered during analysis: // -// src/interpreter/value.ts:47:2 - (ae-forgotten-export) The symbol "Type" needs to be exported by the entry point index.d.ts +// src/interpreter/value.ts:46:2 - (ae-forgotten-export) The symbol "Type" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 88994e5b..3a3a95de 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -231,10 +231,11 @@ export class Interpreter { } else { const _args = new Map(); for (const i in fn.args) { - if (!fn.args[i]!.optional) expectAny(args[i]); - _args.set(fn.args[i]!.name, { + const argdef = fn.args[i]!; + if (!argdef.default) expectAny(args[i]); + _args.set(argdef.name, { isMutable: true, - value: args[i] ?? NULL, + value: args[i] ?? argdef.default!, }); } const fnScope = fn.scope!.createChildScope(_args); @@ -488,13 +489,13 @@ export class Interpreter { case 'fn': { return FN( - node.args.map(arg => { + await Promise.all(node.args.map(async (arg) => { return { name: arg.name, - optional: arg.optional, + default: arg.default ? await this._eval(arg.default, scope) : undefined, // type: (TODO) }; - }), + })), node.children, scope, ); diff --git a/src/interpreter/value.ts b/src/interpreter/value.ts index f0616554..3250afd7 100644 --- a/src/interpreter/value.ts +++ b/src/interpreter/value.ts @@ -43,8 +43,8 @@ export type VUserFn = VFnBase & { }; export type VFnArg = { name: string; - optional?: boolean; type?: Type; + default?: Value; } /** * When your AiScript NATIVE function passes VFn.call to other caller(s) whose error thrown outside the scope, use VFn.topCall instead to keep it under AiScript error control system. diff --git a/src/node.ts b/src/node.ts index 09f80e71..1994b25a 100644 --- a/src/node.ts +++ b/src/node.ts @@ -175,7 +175,7 @@ export type Fn = NodeBase & { type: 'fn'; // 関数 args: { name: string; // 引数名 - optional?: boolean; // 引数に?がついているか + default?: Expression; // 引数の初期値 argType?: TypeSource; // 引数の型 }[]; retType?: TypeSource; // 戻り値の型 diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index c50707ec..a0519ceb 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -2,6 +2,7 @@ import { TokenKind } from '../token.js'; import { AiScriptSyntaxError } from '../../error.js'; import { NODE } from '../utils.js'; import { parseStatement } from './statements.js'; +import { parseExpr } from './expressions.js'; import type { ITokenStream } from '../streams/token-stream.js'; import type * as Ast from '../../node.js'; @@ -12,7 +13,7 @@ import type * as Ast from '../../node.js'; * ``` */ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node }[] { - const items: { name: string, optional?: boolean, argType?: Ast.Node }[] = []; + const items: { name: string, default?: Ast.Node, argType?: Ast.Node }[] = []; s.nextWith(TokenKind.OpenParen); @@ -25,18 +26,22 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node const name = s.token.value!; s.next(); - let optional; + let defaultExpr; if ((s.kind as TokenKind) === TokenKind.Question) { s.next(); - optional = true; + defaultExpr = { type: 'null' } as Ast.Null; } let type; if ((s.kind as TokenKind) === TokenKind.Colon) { s.next(); type = parseType(s); } + if ((s.kind as TokenKind) === TokenKind.Eq) { + s.next(); + defaultExpr = parseExpr(s, false); + } - items.push({ name, optional, argType: type }); + items.push({ name, default: defaultExpr, argType: type }); // separator switch (s.kind as TokenKind) { diff --git a/src/parser/visit.ts b/src/parser/visit.ts index 29b5cbdf..861e8caf 100644 --- a/src/parser/visit.ts +++ b/src/parser/visit.ts @@ -61,6 +61,11 @@ export function visitNode(node: Ast.Node, fn: (node: Ast.Node) => Ast.Node): Ast break; } case 'fn': { + for (const i in result.args) { + if (result.args[i]!.default) { + result.args[i]!.default = visitNode(result.args[i]!.default!, fn) as Ast.Fn['args'][number]['default']; + } + } for (let i = 0; i < result.children.length; i++) { result.children[i] = visitNode(result.children[i]!, fn) as Ast.Fn['children'][number]; } diff --git a/test/index.ts b/test/index.ts index 1c4cff5e..cc7c49f1 100644 --- a/test/index.ts +++ b/test/index.ts @@ -1613,6 +1613,16 @@ describe('Function call', () => { eq(res, ARR([TRUE, NULL, NULL])); }); + test.concurrent('args with default value', async () => { + const res = await exe(` + @f(x, y=1, z=2) { + [x, y, z] + } + <: f(5, 3) + `); + eq(res, ARR([NUM(5), NUM(3), NUM(2)])); + }); + test.concurrent('missing arg', async () => { try { await exe(` From edcf51cf4cae079c380b6e10548b158243139b88 Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Sun, 19 Nov 2023 02:44:40 +0900 Subject: [PATCH 04/12] update CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfff1246..eecb12a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ - 文字列リテラルやテンプレートで、`\`とそれに続く1文字は全てエスケープシーケンスとして扱われるように - 文法エラーやラインタイムエラーの発生位置が表示されるように - **Breaking Change** 多くの予約語を追加。これまで変数名等に使えていた名前に影響が出る可能性があります。 -- 省略可能引数を追加。引数名に`?`を後置することでその引数は省略可能となります。(逆にそうでない引数が省略されると即時エラーとなります) +- 省略可能引数と初期値付き引数を追加。引数名に`?`を後置することでその引数は省略可能となります。引数に`=<式>`を後置すると引数に初期値を設定できます。省略可能引数は初期値`null`の引数と同等です。いずれでもない引数が省略されると即時エラーとなります。 # 0.17.0 - `package.json`を修正 From 6335dbfd05fe3e17380eeb291409b7d4ad879247 Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Sun, 19 Nov 2023 21:01:02 +0900 Subject: [PATCH 05/12] Update syntax.md --- docs/syntax.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/syntax.md b/docs/syntax.md index 1cc63213..bb4562f4 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -71,7 +71,29 @@ let add2 = @(x, y) { ) { x + y } -@add5(x,y){x+y} // ワンライナー +// 省略可能引数 +@func1(a, b?) { + <: a + <: b // 省略されるとnullになる +} +func1('hoge') // 'hoge' null +// 初期値を設定された引数(省略可能引数と組み合わせて使用可能) +@func2(a, b?, c = 'piyo', d?) { + <: a + <: b + <: c + <: d +} +func2('hoge', 'fuga') // 'hoge' 'fuga' 'piyo' null +// 初期値には変数を使用可能(値は宣言時点で固定) +var v = 'hoge' +@func3(a = v) { + <: a +} +v = 'fuga' +func3() // 'hoge' +// ワンライナー +@func4(a,b?,c=1){<:a;<:b;<:c} ``` ```js // match等の予約語は関数名として使用できない From 3bb168561a0caa08edc8f116383e309899181e3e Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Sun, 19 Nov 2023 22:38:25 +0900 Subject: [PATCH 06/12] move optionality process --- etc/aiscript.api.md | 1 + src/interpreter/index.ts | 2 +- src/node.ts | 1 + src/parser/syntaxes/common.ts | 17 +++++++++-------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 347b7dee..64a7eda2 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -270,6 +270,7 @@ type Fn = NodeBase & { type: 'fn'; args: { name: string; + optional?: boolean; default?: Expression; argType?: TypeSource; }[]; diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 3a3a95de..2513be3b 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -492,7 +492,7 @@ export class Interpreter { await Promise.all(node.args.map(async (arg) => { return { name: arg.name, - default: arg.default ? await this._eval(arg.default, scope) : undefined, + default: arg.default ? await this._eval(arg.default, scope) : arg.optional ? NULL : undefined, // type: (TODO) }; })), diff --git a/src/node.ts b/src/node.ts index 1994b25a..537928c4 100644 --- a/src/node.ts +++ b/src/node.ts @@ -175,6 +175,7 @@ export type Fn = NodeBase & { type: 'fn'; // 関数 args: { name: string; // 引数名 + optional?: boolean; default?: Expression; // 引数の初期値 argType?: TypeSource; // 引数の型 }[]; diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index a0519ceb..209b101a 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -13,7 +13,7 @@ import type * as Ast from '../../node.js'; * ``` */ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node }[] { - const items: { name: string, default?: Ast.Node, argType?: Ast.Node }[] = []; + const items: { name: string, optional?: boolean, default?: Ast.Node, argType?: Ast.Node }[] = []; s.nextWith(TokenKind.OpenParen); @@ -26,22 +26,23 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node const name = s.token.value!; s.next(); - let defaultExpr; + let optional; if ((s.kind as TokenKind) === TokenKind.Question) { s.next(); - defaultExpr = { type: 'null' } as Ast.Null; + optional = true; + } + let defaultExpr; + if ((s.kind as TokenKind) === TokenKind.Eq) { + s.next(); + defaultExpr = parseExpr(s, false); } let type; if ((s.kind as TokenKind) === TokenKind.Colon) { s.next(); type = parseType(s); } - if ((s.kind as TokenKind) === TokenKind.Eq) { - s.next(); - defaultExpr = parseExpr(s, false); - } - items.push({ name, default: defaultExpr, argType: type }); + items.push({ name, optional, default: defaultExpr, argType: type }); // separator switch (s.kind as TokenKind) { From c03fdaed28850b072104e988568913508210290d Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Mon, 20 Nov 2023 08:48:36 +0900 Subject: [PATCH 07/12] change type --- etc/aiscript.api.md | 2 +- src/node.ts | 2 +- src/parser/syntaxes/common.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md index 64a7eda2..2a2ffca8 100644 --- a/etc/aiscript.api.md +++ b/etc/aiscript.api.md @@ -270,7 +270,7 @@ type Fn = NodeBase & { type: 'fn'; args: { name: string; - optional?: boolean; + optional: boolean; default?: Expression; argType?: TypeSource; }[]; diff --git a/src/node.ts b/src/node.ts index 537928c4..27df81ff 100644 --- a/src/node.ts +++ b/src/node.ts @@ -175,7 +175,7 @@ export type Fn = NodeBase & { type: 'fn'; // 関数 args: { name: string; // 引数名 - optional?: boolean; + optional: boolean; default?: Expression; // 引数の初期値 argType?: TypeSource; // 引数の型 }[]; diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index 209b101a..262ab151 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -26,7 +26,7 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node const name = s.token.value!; s.next(); - let optional; + let optional = false; if ((s.kind as TokenKind) === TokenKind.Question) { s.next(); optional = true; From 76343b37d86356bf2765379c44feea775e93234d Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:52:31 +0900 Subject: [PATCH 08/12] use for...of .keys() --- src/parser/visit.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parser/visit.ts b/src/parser/visit.ts index 861e8caf..17d11f0f 100644 --- a/src/parser/visit.ts +++ b/src/parser/visit.ts @@ -61,7 +61,7 @@ export function visitNode(node: Ast.Node, fn: (node: Ast.Node) => Ast.Node): Ast break; } case 'fn': { - for (const i in result.args) { + for (const i of result.args.keys()) { if (result.args[i]!.default) { result.args[i]!.default = visitNode(result.args[i]!.default!, fn) as Ast.Fn['args'][number]['default']; } From c8ad88fa2524c9f98725db7b4469a871048f8d7b Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Mon, 1 Apr 2024 16:55:02 +0900 Subject: [PATCH 09/12] use for...of .keys() --- src/interpreter/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts index 2513be3b..a8748c12 100644 --- a/src/interpreter/index.ts +++ b/src/interpreter/index.ts @@ -230,7 +230,7 @@ export class Interpreter { return result ?? NULL; } else { const _args = new Map(); - for (const i in fn.args) { + for (const i of fn.args.keys()) { const argdef = fn.args[i]!; if (!argdef.default) expectAny(args[i]); _args.set(argdef.name, { From 1391781523b8e68ab4611634ff4905bc6d603bad Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Mon, 1 Apr 2024 17:17:20 +0900 Subject: [PATCH 10/12] Update common.ts --- src/parser/syntaxes/common.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index b851194d..fe8ddf39 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -27,12 +27,12 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node s.next(); let optional = false; - if ((s.kind as TokenKind) === TokenKind.Question) { + if ((s.getKind() as TokenKind) === TokenKind.Question) { s.next(); optional = true; } let defaultExpr; - if ((s.kind as TokenKind) === TokenKind.Eq) { + if ((s.getKind() as TokenKind) === TokenKind.Eq) { s.next(); defaultExpr = parseExpr(s, false); } From 81bad3d0876ce60f44744a4c3ae9384a026ebf7d Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Tue, 4 Jun 2024 16:29:40 +0900 Subject: [PATCH 11/12] test conflict --- test/index.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/index.ts b/test/index.ts index 5160b336..6b441ffd 100644 --- a/test/index.ts +++ b/test/index.ts @@ -635,16 +635,6 @@ describe('Function call', () => { } assert.fail(); }); - - test.concurrent('omitted args', async () => { - const res = await exe(` - @f(x, y) { - [x, y] - } - <: f(1) - `); - eq(res, ARR([NUM(1), NULL])); - }); }); describe('Return', () => { From 2144420043ebca405349dc4a215651bfe2e5174b Mon Sep 17 00:00:00 2001 From: Fine Archs Date: Thu, 6 Jun 2024 06:30:45 +0900 Subject: [PATCH 12/12] forbid ?= --- docs/syntax.md | 2 ++ src/parser/syntaxes/common.ts | 5 ++--- test/index.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index 4ec904f5..4d491f1d 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -109,6 +109,8 @@ var func = null @func() { // Runtime Error 'hoge' } +// 省略可能引数構文と初期値構文は併用できない +@func(a? = 1) {} // Syntax Error ``` ### 代入 diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index fe8ddf39..f185a158 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -27,12 +27,11 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node s.next(); let optional = false; + let defaultExpr; if ((s.getKind() as TokenKind) === TokenKind.Question) { s.next(); optional = true; - } - let defaultExpr; - if ((s.getKind() as TokenKind) === TokenKind.Eq) { + } else if ((s.getKind() as TokenKind) === TokenKind.Eq) { s.next(); defaultExpr = parseExpr(s, false); } diff --git a/test/index.ts b/test/index.ts index 6b441ffd..9cb52495 100644 --- a/test/index.ts +++ b/test/index.ts @@ -611,6 +611,18 @@ describe('Function call', () => { eq(res, ARR([NUM(5), NUM(3), NUM(2)])); }); + test.concurrent('args must not be both optional and default-valued', async () => { + try { + Parser.parse(` + @func(a? = 1){} + `); + } catch (e) { + assert.ok(e instanceof AiScriptSyntaxError); + return; + } + assert.fail(); + }); + test.concurrent('missing arg', async () => { try { await exe(`