diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index fcc59ff0..f4a5fc8b 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -577,7 +577,7 @@ function parseReference(s: ITokenStream): Ast.Identifier { /** * ```abnf - * Object = "{" [ObjectKey ":" Expr *(SEP IDENT ":" Expr) [SEP]] "}" + * Object = "{" [(ObjectKey ":" Expr / IDENT) *(SEP (ObjectKey ":" Expr / IDENT)) [SEP]] "}" * ``` */ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { @@ -592,16 +592,25 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { const map = new Map(); while (!s.is(TokenKind.CloseBrace)) { + const startPos = s.getPos(); + + const isIdentifierKey = s.is(TokenKind.Identifier); const k = parseObjectKey(s); if (map.has(k)) { throw new AiScriptSyntaxError(`Key ${k} is duplicated.`, s.getPos()); } s.next(); - s.expect(TokenKind.Colon); - s.next(); + let v: Ast.Expression; - const v = parseExpr(s, isStatic); + if (!isIdentifierKey || isStatic) s.expect(TokenKind.Colon); + if (s.is(TokenKind.Colon)) { + s.next(); + + v = parseExpr(s, isStatic); + } else { + v = NODE('identifier', { name: k }, startPos, s.getPos()); + } map.set(k, v); diff --git a/test/aison.ts b/test/aison.ts index d6fd38df..89e9c959 100644 --- a/test/aison.ts +++ b/test/aison.ts @@ -62,6 +62,10 @@ greet()`)).toThrow(); expect(() => AiSON.parse('{key: (3 + 5)}')).toThrow(); }); + test.concurrent('not allowed: object shorthand', () => { + expect(() => AiSON.parse('{key}')).toThrow(); + }); + test.concurrent('not allowed: labeled expression', () => { expect(() => AiSON.parse('#label: eval { 1 }')).toThrow(); }); diff --git a/test/literals.ts b/test/literals.ts index 330c82bb..7cacfb71 100644 --- a/test/literals.ts +++ b/test/literals.ts @@ -243,6 +243,26 @@ describe('literal', () => { }); }); + test.concurrent('obj (shorthand)', async () => { + const res = await exe(` + let a = 1 + <: { a } + `); + eq(res, OBJ(new Map([['a', NUM(1)]]))); + }); + + test.concurrent('obj (reserved word as shorthand)', async () => { + await expect(() => exe(` + <: { exists } + `)).rejects.toThrow(AiScriptSyntaxError); + }); + + test.concurrent('obj (string as shorthand)', async () => { + await expect(() => exe(` + <: { "hoge" } + `)).rejects.toThrow(AiScriptSyntaxError); + }); + test.concurrent('obj (escaped reserved word as key)', async () => { await expect(async () => await exe(` <: { diff --git a/test/syntax.ts b/test/syntax.ts index 9aa17481..0a03bbb0 100644 --- a/test/syntax.ts +++ b/test/syntax.ts @@ -635,6 +635,13 @@ describe('Variable declaration', () => { `); eq(res, ARR([NUM(1), NUM(2)])); }); + test.concurrent('destructuring declaration with shorthand', async () => { + const res = await exe(` + let { value } = { value: 1 } + <: value + `); + eq(res, NUM(1)); + }); test.concurrent('empty function', async () => { const res = await exe(` @hoge() { } diff --git a/unreleased/obj-shorthand-sugar-syntax.md b/unreleased/obj-shorthand-sugar-syntax.md new file mode 100644 index 00000000..c7767adb --- /dev/null +++ b/unreleased/obj-shorthand-sugar-syntax.md @@ -0,0 +1,2 @@ +- オブジェクトリテラルとオブジェクトの分割代入構文内で、キーと同名の変数を参照する省略記法を使用できるようになりました。 + - 例えば`{ hoge }`は`{ hoge: hoge }`と等価です。