diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts index ca79dc59..7ca8358d 100644 --- a/src/parser/plugins/validate-keyword.ts +++ b/src/parser/plugins/validate-keyword.ts @@ -128,8 +128,7 @@ function validateNode(node: Ast.Node): Ast.Node { } case 'ns': case 'attr': - case 'identifier': - case 'prop': { + case 'identifier': { validateName(node.name, node.loc.start); break; } diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 1b638781..48b76b36 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -103,8 +103,7 @@ function parseInfix(s: ITokenStream, left: Ast.Expression, minBp: number): Ast.E } if (op === TokenKind.Dot) { - s.expect(TokenKind.Identifier); - const name = s.getTokenValue(); + const name = parseObjectKey(s); s.next(); return NODE('prop', { diff --git a/test/identifiers.ts b/test/identifiers.ts index 9b676b8e..1097558d 100644 --- a/test/identifiers.ts +++ b/test/identifiers.ts @@ -204,12 +204,6 @@ const sampleCodes = Object.entries<[(definedName: string, referredName: string) <: ${referredName}:f() `, NUM(1)], - prop: [(definedName, referredName) => - ` - let x = { ${definedName}: 1 } - x.${referredName} - `, NUM(1)], - meta: [(definedName) => ` ### ${definedName} 1 @@ -303,6 +297,67 @@ describe.each( }); }); +describe('identifier validation on obj key', () => { + const codes: [string, (definedName: string, referredName: string) => string][] = [ + ['literal', (definedName: string, referredName: string) => ` + let x = { ${definedName}: 1 } + <: x["${referredName}"] + `], + + ['prop', (definedName: string, referredName: string) => ` + let x = {} + x.${definedName} = 1 + <: x.${referredName} + `], + ] + + describe.each(codes)('%s', (_, code) => { + test.concurrent.each( + reservedWords + )('reserved word %s must be allowed', async (word) => { + const res = await exe(code(word, word)); + eq(res, NUM(1)); + }); + + test.concurrent.each( + identifierCases + )('%s is allowed: %s', async (word, allowed) => { + expect.hasAssertions(); + if (allowed) { + const res = await exe(code(word, word)); + eq(res, NUM(1)); + } else { + expect(() => parser.parse(code(word, word))).toThrow(AiScriptSyntaxError); + await Promise.resolve(); // https://github.com/vitest-dev/vitest/issues/4750 + } + }); + }); +}); + +describe('reserved word validation on string obj key', () => { + const codes: [string, (definedName: string, referredName: string) => string][] = [ + ['literal', (definedName: string, referredName: string) => ` + let x = { "${definedName}": 1 } + <: x["${referredName}"] + `], + + ['prop', (definedName: string, referredName: string) => ` + let x = {} + x."${definedName}" = 1 + <: x."${referredName}" + `], + ] + + describe.each(codes)('%s', (_, code) => { + test.concurrent.each( + reservedWords + )('reserved word %s must be allowed', async (word) => { + const res = await exe(code(word, word)); + eq(res, NUM(1)); + }); + }); +}); + test.concurrent('Keyword cannot contain escape characters', async () => { await expect(async () => await exe(` \\u0069\\u0066 true { diff --git a/test/index.ts b/test/index.ts index fa158707..b94fd9ac 100644 --- a/test/index.ts +++ b/test/index.ts @@ -197,9 +197,7 @@ describe('Object', () => { ]))); }); - /* 未実装 - * see also: test/literals.ts > literal > obj (string key) - * issue: https://github.com/aiscript-dev/aiscript/issues/62 + // see also: test/literals.ts > literal > obj (string key) test.concurrent('string key', async () => { const res = await exe(` let obj = { @@ -222,7 +220,10 @@ describe('Object', () => { eq(res, NUM(42)); }); - test.concurrent('expression key', async () => { + // 未実装 + // issues: https://github.com/aiscript-dev/aiscript/issues/62 + // https://github.com/aiscript-dev/aiscript/issues/225 + test.concurrent.skip('expression key', async () => { const res = await exe(` let key = "藍" @@ -234,7 +235,6 @@ describe('Object', () => { `); eq(res, NUM(42)); }); - */ }); describe('Array', () => { @@ -1092,7 +1092,7 @@ describe('Security', () => { `); assert.fail(); } catch (e) { - assert.ok(e instanceof AiScriptSyntaxError); + assert.ok(e instanceof AiScriptRuntimeError); } try { diff --git a/unreleased/reserved-word-keys.md b/unreleased/reserved-word-keys.md new file mode 100644 index 00000000..1cf6144d --- /dev/null +++ b/unreleased/reserved-word-keys.md @@ -0,0 +1,2 @@ +- プロパティアクセスのドット記法に予約語を記述できるようになりました。 +- プロパティアクセスのドット記法に文字列リテラルを記述できるようになりました。