From 133c0df6b6bb69cfb4b70f85fe9a16e252ef3a5e Mon Sep 17 00:00:00 2001 From: uzmoi <63585970+uzmoi@users.noreply.github.com> Date: Wed, 14 Jan 2026 21:46:44 +0900 Subject: [PATCH 1/4] =?UTF-8?q?=E3=82=AA=E3=83=96=E3=82=B8=E3=82=A7?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=83=86=E3=83=A9=E3=83=AB=E5=86=85?= =?UTF-8?q?=E3=81=A7=E7=9C=81=E7=95=A5=E8=A8=98=E6=B3=95=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/syntaxes/expressions.ts | 15 +++++++++++---- test/literals.ts | 8 ++++++++ test/syntax.ts | 7 +++++++ unreleased/obj-shorthand-sugar-syntax.md | 2 ++ 4 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 unreleased/obj-shorthand-sugar-syntax.md diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index fcc59ff0..c1f71be9 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] *(SEP ObjectKey [":" Expr]) [SEP]] "}" * ``` */ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { @@ -592,16 +592,23 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { const map = new Map(); while (!s.is(TokenKind.CloseBrace)) { + const startPos = s.getPos(); + 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 (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/literals.ts b/test/literals.ts index 330c82bb..1d68f4f6 100644 --- a/test/literals.ts +++ b/test/literals.ts @@ -243,6 +243,14 @@ 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 (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 }`と等価です。 From 78257135ed9755e83aa9d8e6efba1b89c71591e5 Mon Sep 17 00:00:00 2001 From: uzmoi <63585970+uzmoi@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:11:45 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20isStatic=E3=81=8C=E7=9C=9F=E3=81=AA?= =?UTF-8?q?=E3=82=89=E3=82=B7=E3=83=A7=E3=83=BC=E3=83=88=E3=83=8F=E3=83=B3?= =?UTF-8?q?=E3=83=89=E6=A7=8B=E6=96=87=E3=82=92=E7=84=A1=E5=8A=B9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/syntaxes/expressions.ts | 1 + test/aison.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index c1f71be9..2b5076ba 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -602,6 +602,7 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { let v: Ast.Expression; + if (isStatic) s.expect(TokenKind.Colon); if (s.is(TokenKind.Colon)){ s.next(); 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(); }); From 800855b89cfdf9a370a64217ad716d857361fc27 Mon Sep 17 00:00:00 2001 From: uzmoi <63585970+uzmoi@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:26:32 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=E3=82=AD=E3=83=BC=E3=81=8CIdentifie?= =?UTF-8?q?r=E3=81=AA=E3=81=A8=E3=81=8D=E3=81=AB=E3=81=AE=E3=81=BF?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E5=8F=AF=E8=83=BD=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/parser/syntaxes/expressions.ts | 5 +++-- test/literals.ts | 12 ++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 2b5076ba..62b15fd2 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 ObjectKey [":" Expr]) [SEP]] "}" + * Object = "{" [(ObjectKey ":" Expr / IDENT) *(SEP (ObjectKey ":" Expr / IDENT)) [SEP]] "}" * ``` */ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { @@ -594,6 +594,7 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { 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()); @@ -602,7 +603,7 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { let v: Ast.Expression; - if (isStatic) s.expect(TokenKind.Colon); + if (!isIdentifierKey || isStatic) s.expect(TokenKind.Colon); if (s.is(TokenKind.Colon)){ s.next(); diff --git a/test/literals.ts b/test/literals.ts index 1d68f4f6..7cacfb71 100644 --- a/test/literals.ts +++ b/test/literals.ts @@ -251,6 +251,18 @@ describe('literal', () => { 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(` <: { From 7aea9f7dc7fb2d5dd673595472eebcdcb5da7c1a Mon Sep 17 00:00:00 2001 From: uzmoi <63585970+uzmoi@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:45:57 +0900 Subject: [PATCH 4/4] fix lint --- src/parser/syntaxes/expressions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 62b15fd2..f4a5fc8b 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -604,12 +604,12 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Obj { let v: Ast.Expression; if (!isIdentifierKey || isStatic) s.expect(TokenKind.Colon); - if (s.is(TokenKind.Colon)){ + if (s.is(TokenKind.Colon)) { s.next(); v = parseExpr(s, isStatic); } else { - v = NODE("identifier", { name: k }, startPos, s.getPos()); + v = NODE('identifier', { name: k }, startPos, s.getPos()); } map.set(k, v);