diff --git a/src/parser/syntaxes/common.ts b/src/parser/syntaxes/common.ts index 6c200ad5..b4bd9dc0 100644 --- a/src/parser/syntaxes/common.ts +++ b/src/parser/syntaxes/common.ts @@ -8,7 +8,7 @@ import type * as Ast from '../../node.js'; /** * ```abnf - * Params = "(" [IDENT *(("," / SPACE) IDENT)] ")" + * Params = "(" [IDENT [":" Type] *(SEP IDENT [":" Type])] ")" * ``` */ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node }[] { @@ -16,16 +16,11 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node s.nextWith(TokenKind.OpenParen); - while (s.kind !== TokenKind.CloseParen) { - // separator - if (items.length > 0) { - if (s.kind === TokenKind.Comma) { - s.next(); - } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected', s.token.loc); - } - } + if (s.kind === TokenKind.NewLine) { + s.next(); + } + while (s.kind !== TokenKind.CloseParen) { s.expect(TokenKind.Identifier); const name = s.token.value!; s.next(); @@ -37,6 +32,27 @@ export function parseParams(s: ITokenStream): { name: string, argType?: Ast.Node } items.push({ name, argType: type }); + + // separator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { + s.next(); + break; + } + case TokenKind.Comma: { + s.next(); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.CloseParen: { + break; + } + default: { + throw new AiScriptSyntaxError('separator expected', s.token.loc); + } + } } s.nextWith(TokenKind.CloseParen); @@ -60,11 +76,21 @@ export function parseBlock(s: ITokenStream): Ast.Node[] { while (s.kind !== TokenKind.CloseBrace) { steps.push(parseStatement(s)); - if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.CloseBrace) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); - } - while ((s.kind as TokenKind) === TokenKind.NewLine) { - s.next(); + // terminator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: + case TokenKind.SemiColon: { + while ([TokenKind.NewLine, TokenKind.SemiColon].includes(s.kind)) { + s.next(); + } + break; + } + case TokenKind.CloseBrace: { + break; + } + default: { + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); + } } } @@ -86,7 +112,7 @@ export function parseType(s: ITokenStream): Ast.Node { /** * ```abnf * FnType = "@" "(" ParamTypes ")" "=>" Type - * ParamTypes = [Type *(("," / SPACE) Type)] + * ParamTypes = [Type *(SEP Type)] * ``` */ function parseFnType(s: ITokenStream): Ast.Node { @@ -98,10 +124,14 @@ function parseFnType(s: ITokenStream): Ast.Node { const params: Ast.Node[] = []; while (s.kind !== TokenKind.CloseParen) { if (params.length > 0) { - if (s.kind === TokenKind.Comma) { - s.next(); - } else if (!s.token.hasLeftSpacing) { - throw new AiScriptSyntaxError('separator expected', s.token.loc); + switch (s.kind as TokenKind) { + case TokenKind.Comma: { + s.next(); + break; + } + default: { + throw new AiScriptSyntaxError('separator expected', s.token.loc); + } } } const type = parseType(s); diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index eecbc66f..3adda1bd 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -287,7 +287,7 @@ function parseAtom(s: ITokenStream, isStatic: boolean): Ast.Node { } /** - * Call = "(" [Expr *(("," / SPACE) Expr)] ")" + * Call = "(" [Expr *(SEP Expr) [SEP]] ")" */ function parseCall(s: ITokenStream, target: Ast.Node): Ast.Node { const loc = s.token.loc; @@ -295,17 +295,33 @@ function parseCall(s: ITokenStream, target: Ast.Node): Ast.Node { s.nextWith(TokenKind.OpenParen); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + while (s.kind !== TokenKind.CloseParen) { + items.push(parseExpr(s, false)); + // separator - if (items.length > 0) { - if (s.kind === TokenKind.Comma) { + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { s.next(); - } else if (!s.token.hasLeftSpacing) { + break; + } + case TokenKind.Comma: { + s.next(); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.CloseParen: { + break; + } + default: { throw new AiScriptSyntaxError('separator expected', s.token.loc); } } - - items.push(parseExpr(s, false)); } s.nextWith(TokenKind.CloseParen); @@ -377,7 +393,8 @@ function parseFnExpr(s: ITokenStream): Ast.Node { /** * ```abnf - * Match = "match" Expr "{" *("case" Expr "=>" BlockOrStatement) ["default" "=>" BlockOrStatement] "}" + * Match = "match" Expr "{" [MatchCases] ["default" "=>" BlockOrStatement [SEP]] "}" + * MatchCases = "case" Expr "=>" BlockOrStatement *(SEP "case" Expr "=>" BlockOrStatement) [SEP] * ``` */ function parseMatch(s: ITokenStream): Ast.Node { @@ -387,7 +404,10 @@ function parseMatch(s: ITokenStream): Ast.Node { const about = parseExpr(s, false); s.nextWith(TokenKind.OpenBrace); - s.nextWith(TokenKind.NewLine); + + if (s.kind === TokenKind.NewLine) { + s.next(); + } const qs: { q: Ast.Node, a: Ast.Node }[] = []; while (s.kind !== TokenKind.DefaultKeyword && s.kind !== TokenKind.CloseBrace) { @@ -395,8 +415,29 @@ function parseMatch(s: ITokenStream): Ast.Node { const q = parseExpr(s, false); s.nextWith(TokenKind.Arrow); const a = parseBlockOrStatement(s); - s.nextWith(TokenKind.NewLine); qs.push({ q, a }); + + // separator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { + s.next(); + break; + } + case TokenKind.Comma: { + s.next(); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.DefaultKeyword: + case TokenKind.CloseBrace: { + break; + } + default: { + throw new AiScriptSyntaxError('separator expected', s.token.loc); + } + } } let x; @@ -404,7 +445,27 @@ function parseMatch(s: ITokenStream): Ast.Node { s.next(); s.nextWith(TokenKind.Arrow); x = parseBlockOrStatement(s); - s.nextWith(TokenKind.NewLine); + + // separator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { + s.next(); + break; + } + case TokenKind.Comma: { + s.next(); + if ((s.kind as TokenKind) === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.CloseBrace: { + break; + } + default: { + throw new AiScriptSyntaxError('separator expected', s.token.loc); + } + } } s.nextWith(TokenKind.CloseBrace); @@ -470,7 +531,7 @@ function parseReference(s: ITokenStream): Ast.Node { /** * ```abnf - * Object = "{" [IDENT ":" Expr *(("," / ";" / SPACE) IDENT ":" Expr) ["," / ";"]] "}" + * Object = "{" [IDENT ":" Expr *(SEP IDENT ":" Expr) [SEP]] "}" * ``` */ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Node { @@ -495,23 +556,25 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Node { map.set(k, v); // separator - if ((s.kind as TokenKind) === TokenKind.CloseBrace) { - break; - } else if (s.kind === TokenKind.Comma) { - s.next(); - } else if (s.kind === TokenKind.SemiColon) { - s.next(); - } else if (s.kind === TokenKind.NewLine) { - // noop - } else { - if (!s.token.hasLeftSpacing) { + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { + s.next(); + break; + } + case TokenKind.Comma: { + s.next(); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.CloseBrace: { + break; + } + default: { throw new AiScriptSyntaxError('separator expected', s.token.loc); } } - - if (s.kind === TokenKind.NewLine) { - s.next(); - } } s.nextWith(TokenKind.CloseBrace); @@ -521,7 +584,7 @@ function parseObject(s: ITokenStream, isStatic: boolean): Ast.Node { /** * ```abnf - * Array = "[" [Expr *(("," / SPACE) Expr) [","]] "]" + * Array = "[" [Expr *(SEP Expr) [SEP]] "]" * ``` */ function parseArray(s: ITokenStream, isStatic: boolean): Ast.Node { @@ -538,21 +601,25 @@ function parseArray(s: ITokenStream, isStatic: boolean): Ast.Node { value.push(parseExpr(s, isStatic)); // separator - if ((s.kind as TokenKind) === TokenKind.CloseBracket) { - break; - } else if (s.kind === TokenKind.Comma) { - s.next(); - } else if (s.kind === TokenKind.NewLine) { - // noop - } else { - if (!s.token.hasLeftSpacing) { + switch (s.kind as TokenKind) { + case TokenKind.NewLine: { + s.next(); + break; + } + case TokenKind.Comma: { + s.next(); + if (s.kind === TokenKind.NewLine) { + s.next(); + } + break; + } + case TokenKind.CloseBracket: { + break; + } + default: { throw new AiScriptSyntaxError('separator expected', s.token.loc); } } - - if (s.kind === TokenKind.NewLine) { - s.next(); - } } s.nextWith(TokenKind.CloseBracket); diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index c5655175..4c816a5b 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -212,7 +212,7 @@ function parseEach(s: ITokenStream): Ast.Node { if (s.kind === TokenKind.Comma) { s.next(); - } else if (!s.token.hasLeftSpacing) { + } else { throw new AiScriptSyntaxError('separator expected', s.token.loc); } @@ -262,7 +262,7 @@ function parseFor(s: ITokenStream): Ast.Node { if ((s.kind as TokenKind) === TokenKind.Comma) { s.next(); - } else if (!s.token.hasLeftSpacing) { + } else { throw new AiScriptSyntaxError('separator expected', s.token.loc); } diff --git a/src/parser/syntaxes/toplevel.ts b/src/parser/syntaxes/toplevel.ts index a84e60de..c0969e65 100644 --- a/src/parser/syntaxes/toplevel.ts +++ b/src/parser/syntaxes/toplevel.ts @@ -35,11 +35,21 @@ export function parseTopLevel(s: ITokenStream): Ast.Node[] { } } - if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.EOF) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); - } - while ((s.kind as TokenKind) === TokenKind.NewLine) { - s.next(); + // terminator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: + case TokenKind.SemiColon: { + while ([TokenKind.NewLine, TokenKind.SemiColon].includes(s.kind)) { + s.next(); + } + break; + } + case TokenKind.EOF: { + break; + } + default: { + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); + } } } @@ -81,11 +91,21 @@ export function parseNamespace(s: ITokenStream): Ast.Node { } } - if ((s.kind as TokenKind) !== TokenKind.NewLine && (s.kind as TokenKind) !== TokenKind.CloseBrace) { - throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); - } - while ((s.kind as TokenKind) === TokenKind.NewLine) { - s.next(); + // terminator + switch (s.kind as TokenKind) { + case TokenKind.NewLine: + case TokenKind.SemiColon: { + while ([TokenKind.NewLine, TokenKind.SemiColon].includes(s.kind)) { + s.next(); + } + break; + } + case TokenKind.CloseBrace: { + break; + } + default: { + throw new AiScriptSyntaxError('Multiple statements cannot be placed on a single line.', s.token.loc); + } } } s.nextWith(TokenKind.CloseBrace); diff --git a/test/index.ts b/test/index.ts index 48cb8a6b..69923757 100644 --- a/test/index.ts +++ b/test/index.ts @@ -494,6 +494,368 @@ describe('Cannot put multiple statements in a line', () => { }); }); +describe('terminator', () => { + describe('top-level', () => { + test.concurrent('newline', async () => { + const res = await exe(` + :: A { + let x = 1 + } + :: B { + let x = 2 + } + <: A:x + `); + eq(res, NUM(1)); + }); + + test.concurrent('semi colon', async () => { + const res = await exe(` + ::A{let x = 1};::B{let x = 2} + <: A:x + `); + eq(res, NUM(1)); + }); + + test.concurrent('semi colon of the tail', async () => { + const res = await exe(` + ::A{let x = 1}; + <: A:x + `); + eq(res, NUM(1)); + }); + }); + + describe('block', () => { + test.concurrent('newline', async () => { + const res = await exe(` + eval { + let x = 1 + let y = 2 + <: x + y + } + `); + eq(res, NUM(3)); + }); + + test.concurrent('semi colon', async () => { + const res = await exe(` + eval{let x=1;let y=2;<:x+y} + `); + eq(res, NUM(3)); + }); + + test.concurrent('semi colon of the tail', async () => { + const res = await exe(` + eval{let x=1;<:x;} + `); + eq(res, NUM(1)); + }); + }); + + describe('namespace', () => { + test.concurrent('newline', async () => { + const res = await exe(` + :: A { + let x = 1 + let y = 2 + } + <: A:x + A:y + `); + eq(res, NUM(3)); + }); + + test.concurrent('semi colon', async () => { + const res = await exe(` + ::A{let x=1;let y=2} + <: A:x + A:y + `); + eq(res, NUM(3)); + }); + + test.concurrent('semi colon of the tail', async () => { + const res = await exe(` + ::A{let x=1;} + <: A:x + `); + eq(res, NUM(1)); + }); + }); +}); + +describe('separator', () => { + describe('match', () => { + test.concurrent('multi line', async () => { + const res = await exe(` + let x = 1 + <: match x { + case 1 => "a" + case 2 => "b" + } + `); + eq(res, STR('a')); + }); + + test.concurrent('multi line with semi colon', async () => { + const res = await exe(` + let x = 1 + <: match x { + case 1 => "a", + case 2 => "b" + } + `); + eq(res, STR('a')); + }); + + test.concurrent('single line', async () => { + const res = await exe(` + let x = 1 + <:match x{case 1=>"a",case 2=>"b"} + `); + eq(res, STR('a')); + }); + + test.concurrent('single line with tail semi colon', async () => { + const res = await exe(` + let x = 1 + <: match x{case 1=>"a",case 2=>"b",} + `); + eq(res, STR('a')); + }); + + test.concurrent('multi line (default)', async () => { + const res = await exe(` + let x = 3 + <: match x { + case 1 => "a" + case 2 => "b" + default => "c" + } + `); + eq(res, STR('c')); + }); + + test.concurrent('multi line with semi colon (default)', async () => { + const res = await exe(` + let x = 3 + <: match x { + case 1 => "a", + case 2 => "b", + default => "c" + } + `); + eq(res, STR('c')); + }); + + test.concurrent('single line (default)', async () => { + const res = await exe(` + let x = 3 + <:match x{case 1=>"a",case 2=>"b",default=>"c"} + `); + eq(res, STR('c')); + }); + + test.concurrent('single line with tail semi colon (default)', async () => { + const res = await exe(` + let x = 3 + <:match x{case 1=>"a",case 2=>"b",default=>"c",} + `); + eq(res, STR('c')); + }); + }); + + describe('call', () => { + test.concurrent('multi line', async () => { + const res = await exe(` + @f(a, b, c) { + a * b + c + } + <: f( + 2 + 3 + 1 + ) + `); + eq(res, NUM(7)); + }); + + test.concurrent('multi line with comma', async () => { + const res = await exe(` + @f(a, b, c) { + a * b + c + } + <: f( + 2, + 3, + 1 + ) + `); + eq(res, NUM(7)); + }); + + test.concurrent('single line', async () => { + const res = await exe(` + @f(a, b, c) { + a * b + c + } + <:f(2,3,1) + `); + eq(res, NUM(7)); + }); + + test.concurrent('single line with tail comma', async () => { + const res = await exe(` + @f(a, b, c) { + a * b + c + } + <:f(2,3,1,) + `); + eq(res, NUM(7)); + }); + }); + + describe('obj', () => { + test.concurrent('multi line', async () => { + const res = await exe(` + let x = { + a: 1 + b: 2 + } + <: x.b + `); + eq(res, NUM(2)); + }); + + test.concurrent('multi line with comma', async () => { + const res = await exe(` + let x = { + a: 1, + b: 2 + } + <: x.b + `); + eq(res, NUM(2)); + }); + + test.concurrent('single line', async () => { + const res = await exe(` + let x={a:1,b:2} + <: x.b + `); + eq(res, NUM(2)); + }); + + test.concurrent('single line with tail comma', async () => { + const res = await exe(` + let x={a:1,b:2,} + <: x.b + `); + eq(res, NUM(2)); + }); + }); + + describe('arr', () => { + test.concurrent('multi line', async () => { + const res = await exe(` + let x = [ + 1 + 2 + ] + <: x[1] + `); + eq(res, NUM(2)); + }); + + test.concurrent('multi line with comma', async () => { + const res = await exe(` + let x = [ + 1, + 2 + ] + <: x[1] + `); + eq(res, NUM(2)); + }); + + test.concurrent('single line', async () => { + const res = await exe(` + let x=[1,2] + <: x[1] + `); + eq(res, NUM(2)); + }); + + test.concurrent('single line with tail comma', async () => { + const res = await exe(` + let x=[1,2,] + <: x[1] + `); + eq(res, NUM(2)); + }); + }); + + describe('function params', () => { + test.concurrent('single line', async () => { + const res = await exe(` + @f(a, b) { + a + b + } + <: f(1, 2) + `); + eq(res, NUM(3)); + }); + + test.concurrent('single line with tail comma', async () => { + const res = await exe(` + @f(a, b, ) { + a + b + } + <: f(1, 2) + `); + eq(res, NUM(3)); + }); + + test.concurrent('multi line', async () => { + const res = await exe(` + @f( + a + b + ) { + a + b + } + <: f(1, 2) + `); + eq(res, NUM(3)); + }); + + test.concurrent('multi line with comma', async () => { + const res = await exe(` + @f( + a, + b + ) { + a + b + } + <: f(1, 2) + `); + eq(res, NUM(3)); + }); + + test.concurrent('multi line with tail comma', async () => { + const res = await exe(` + @f( + a, + b, + ) { + a + b + } + <: f(1, 2) + `); + eq(res, NUM(3)); + }); + }); +}); + test.concurrent('empty function', async () => { const res = await exe(` @hoge() { } @@ -537,8 +899,8 @@ test.concurrent('Closure (counter)', async () => { @create_counter() { var count = 0 { - get_count: @() { count }; - count: @() { count = (count + 1) }; + get_count: @() { count }, + count: @() { count = (count + 1) }, } } @@ -807,9 +1169,9 @@ describe('Object', () => { let obj = { a: { b: { - c: 42; - }; - }; + c: 42, + }, + }, } <: obj.a.b.c @@ -824,9 +1186,9 @@ describe('Object', () => { let obj = { a: { b: { - c: f; - }; - }; + c: f, + }, + }, } <: obj.a.b.c() @@ -866,7 +1228,7 @@ describe('Object', () => { test.concurrent('string key', async () => { const res = await exe(` let obj = { - "藍": 42; + "藍": 42, } <: obj."藍" @@ -877,7 +1239,7 @@ describe('Object', () => { test.concurrent('string key including colon and period', async () => { const res = await exe(` let obj = { - ":.:": 42; + ":.:": 42, } <: obj.":.:" @@ -890,7 +1252,7 @@ describe('Object', () => { let key = "藍" let obj = { - : 42; + : 42, } <: obj @@ -965,8 +1327,8 @@ describe('chain', () => { const res = await exe(` let obj = { a: { - b: [@(name) { name }, @(str) { "chan" }, @() { "kawaii" }]; - }; + b: [@(name) { name }, @(str) { "chan" }, @() { "kawaii" }], + }, } <: obj.a.b[0]("ai") @@ -978,8 +1340,8 @@ describe('chain', () => { const res = await exe(` let obj = { a: { - b: ["ai", "chan", "kawaii"]; - }; + b: ["ai", "chan", "kawaii"], + }, } obj.a.b[1] = "taso" @@ -997,8 +1359,8 @@ describe('chain', () => { const res = await exe(` let obj = { a: { - b: ["ai", "chan", "kawaii"]; - }; + b: ["ai", "chan", "kawaii"], + }, } var x = null @@ -1013,8 +1375,8 @@ describe('chain', () => { const res = await exe(` let arr = [ { - a: 1; - b: 2; + a: 1, + b: 2, } ] @@ -1035,8 +1397,8 @@ describe('chain', () => { const res = await exe(` let obj = { a: { - b: [1, 2, 3]; - }; + b: [1, 2, 3], + }, } obj.a.b[1] += 1 @@ -1194,16 +1556,6 @@ describe('Function call', () => { eq(res, NUM(2)); }); - test.concurrent('with args (separated by space)', async () => { - const res = await exe(` - @f(x y) { - (x + y) - } - <: f(1 1) - `); - eq(res, NUM(2)); - }); - test.concurrent('std: throw AiScript error when required arg missing', async () => { try { await exe(` @@ -1392,7 +1744,7 @@ describe('exists', () => { test.concurrent('Basic', async () => { const res = await exe(` let foo = null - <: [(exists foo) (exists bar)] + <: [(exists foo), (exists bar)] `); eq(res, ARR([BOOL(true), BOOL(false)])); }); @@ -1589,7 +1941,7 @@ describe('loop', () => { test.concurrent('with continue', async () => { const res = await exe(` - var a = ["ai" "chan" "kawaii" "yo" "!"] + var a = ["ai", "chan", "kawaii", "yo", "!"] var b = [] loop { var x = a.shift() @@ -1711,7 +2063,7 @@ describe('for of', () => { test.concurrent('Break', async () => { const res = await exe(` let msgs = [] - each let item, ["ai", "chan", "kawaii" "yo"] { + each let item, ["ai", "chan", "kawaii", "yo"] { if (item == "kawaii") break msgs.push([item, "!"].join()) } @@ -1906,20 +2258,6 @@ describe('literal', () => { eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); }); - test.concurrent('obj (separated by semicolon)', async () => { - const res = await exe(` - <: { a: 1; b: 2; c: 3 } - `); - eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); - }); - - test.concurrent('obj (separated by semicolon) (with trailing semicolon)', async () => { - const res = await exe(` - <: { a: 1; b: 2; c: 3; } - `); - eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); - }); - test.concurrent('obj (separated by line break)', async () => { const res = await exe(` <: { @@ -1931,28 +2269,6 @@ describe('literal', () => { eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); }); - test.concurrent('obj (separated by line break and semicolon)', async () => { - const res = await exe(` - <: { - a: 1; - b: 2; - c: 3 - } - `); - eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); - }); - - test.concurrent('obj (separated by line break and semicolon) (with trailing semicolon)', async () => { - const res = await exe(` - <: { - a: 1; - b: 2; - c: 3; - } - `); - eq(res, OBJ(new Map([['a', NUM(1)], ['b', NUM(2)], ['c', NUM(3)]]))); - }); - test.concurrent('obj and arr (separated by line break)', async () => { const res = await exe(` <: { @@ -1978,7 +2294,7 @@ describe('type declaration', () => { const res = await exe(` let abc: num = 1 var xyz: str = "abc" - <: [abc xyz] + <: [abc, xyz] `); eq(res, ARR([NUM(1), STR('abc')])); }); @@ -2002,7 +2318,7 @@ describe('type declaration', () => { describe('meta', () => { test.concurrent('default meta', async () => { const res = getMeta(` - ### { a: 1; b: 2; c: 3; } + ### { a: 1, b: 2, c: 3, } `); eq(res, new Map([ [null, { @@ -2065,7 +2381,7 @@ describe('meta', () => { describe('Array', () => { test.concurrent('valid', async () => { const res = getMeta(` - ### x [1 2 3] + ### x [1, 2, 3] `); eq(res, new Map([ ['x', [1, 2, 3]] @@ -2075,7 +2391,7 @@ describe('meta', () => { test.concurrent('invalid', async () => { try { getMeta(` - ### x [1 (2 + 2) 3] + ### x [1, (2 + 2), 3] `); } catch (e) { assert.ok(true); @@ -2088,7 +2404,7 @@ describe('meta', () => { describe('Object', () => { test.concurrent('valid', async () => { const res = getMeta(` - ### x { a: 1; b: 2; c: 3; } + ### x { a: 1, b: 2, c: 3, } `); eq(res, new Map([ ['x', { @@ -2102,7 +2418,7 @@ describe('meta', () => { test.concurrent('invalid', async () => { try { getMeta(` - ### x { a: 1; b: (2 + 2); c: 3; } + ### x { a: 1, b: (2 + 2), c: 3, } `); } catch (e) { assert.ok(true); @@ -2213,7 +2529,7 @@ describe('Attribute', () => { let attr: Ast.Attribute; const parser = new Parser(); const nodes = parser.parse(` - #[Endpoint { path: "/notes/create"; }] + #[Endpoint { path: "/notes/create" }] #[Desc "Create a note."] #[Cat true] @createNote(text) { @@ -2525,7 +2841,7 @@ describe('primitive props', () => { test.concurrent('reduce with index', async () => { const res = await exe(` let arr = [1, 2, 3, 4] - <: arr.reduce(@(accumulator, currentValue, index) { (accumulator + (currentValue * index)) } 0) + <: arr.reduce(@(accumulator, currentValue, index) { (accumulator + (currentValue * index)) }, 0) `); eq(res, NUM(20)); }); @@ -2733,13 +3049,13 @@ describe('std', () => { const res = await exe(` @test(seed) { let random = Math:gen_rng(seed) - return random(0 100) + return random(0, 100) } let seed1 = \`{Util:uuid()}\` let seed2 = \`{Date:year()}\` let test1 = if (test(seed1) == test(seed1)) {true} else {false} let test2 = if (test(seed1) == test(seed2)) {true} else {false} - <: [test1 test2] + <: [test1, test2] `) eq(res, ARR([BOOL(true), BOOL(false)])); }); @@ -2748,7 +3064,7 @@ describe('std', () => { describe('Obj', () => { test.concurrent('keys', async () => { const res = await exe(` - let o = { a: 1; b: 2; c: 3; } + let o = { a: 1, b: 2, c: 3, } <: Obj:keys(o) `); @@ -2757,7 +3073,7 @@ describe('std', () => { test.concurrent('vals', async () => { const res = await exe(` - let o = { _nul: null; _num: 24; _str: 'hoge'; _arr: []; _obj: {}; } + let o = { _nul: null, _num: 24, _str: 'hoge', _arr: [], _obj: {}, } <: Obj:vals(o) `); @@ -2766,7 +3082,7 @@ describe('std', () => { test.concurrent('kvs', async () => { const res = await exe(` - let o = { a: 1; b: 2; c: 3; } + let o = { a: 1, b: 2, c: 3, } <: Obj:kvs(o) `);