diff --git a/src/parser/syntaxes/expressions.ts b/src/parser/syntaxes/expressions.ts index 1b638781..5dd4e3fa 100644 --- a/src/parser/syntaxes/expressions.ts +++ b/src/parser/syntaxes/expressions.ts @@ -448,38 +448,36 @@ function parseMatch(s: ITokenStream): Ast.Match { s.expect(TokenKind.MatchKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); + const about = parseExpr(s, false); + if (s.is(TokenKind.NewLine)) s.next(); s.expect(TokenKind.OpenBrace); s.next(); - - if (s.is(TokenKind.NewLine)) { - s.next(); - } + if (s.is(TokenKind.NewLine)) s.next(); const qs: Ast.Match['qs'] = []; - let x: Ast.Match['default']; - if (s.is(TokenKind.CaseKeyword)) { - qs.push(parseMatchCase(s)); - let sep = parseOptionalSeparator(s); - while (s.is(TokenKind.CaseKeyword)) { - if (!sep) { - throw new AiScriptSyntaxError('separator expected', s.getPos()); + let x: Ast.Match['default'] | undefined; + do { + switch (s.getTokenKind()) { + case TokenKind.CaseKeyword: { + qs.push(parseMatchCase(s)); + break; } - qs.push(parseMatchCase(s)); - sep = parseOptionalSeparator(s); - } - if (s.is(TokenKind.DefaultKeyword)) { - if (!sep) { - throw new AiScriptSyntaxError('separator expected', s.getPos()); + case TokenKind.DefaultKeyword: { + if (x != null) throw new AiScriptSyntaxError('multiple defaults not allowed in match expression.', s.getPos()); + x = parseDefaultCase(s); + break; + } + case TokenKind.CloseBrace: { + break; + } + default: { + throw unexpectedTokenError(s.getTokenKind(), s.getPos()); } - x = parseDefaultCase(s); - parseOptionalSeparator(s); } - } else if (s.is(TokenKind.DefaultKeyword)) { - x = parseDefaultCase(s); - parseOptionalSeparator(s); - } + } while (parseOptionalSeparator(s) && !s.is(TokenKind.CloseBrace)); s.expect(TokenKind.CloseBrace); s.next(); @@ -495,9 +493,12 @@ function parseMatch(s: ITokenStream): Ast.Match { function parseMatchCase(s: ITokenStream): Ast.Match['qs'][number] { s.expect(TokenKind.CaseKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const q = parseExpr(s, false); + if (s.is(TokenKind.NewLine)) s.next(); s.expect(TokenKind.Arrow); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const a = parseBlockOrStatement(s); return { q, a }; } @@ -510,6 +511,7 @@ function parseMatchCase(s: ITokenStream): Ast.Match['qs'][number] { function parseDefaultCase(s: ITokenStream): Ast.Match['default'] { s.expect(TokenKind.DefaultKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); s.expect(TokenKind.Arrow); s.next(); return parseBlockOrStatement(s); diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index fef67df7..ec3dd18b 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -247,28 +247,35 @@ function parseEach(s: ITokenStream): Ast.Each { s.expect(TokenKind.EachKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); if (s.is(TokenKind.OpenParen)) { hasParen = true; s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } s.expect(TokenKind.LetKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const dest = parseDest(s); + if (s.is(TokenKind.NewLine)) s.next(); if (s.is(TokenKind.Comma)) { s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } else { throw new AiScriptSyntaxError('separator expected', s.getPos()); } const items = parseExpr(s, false); + if (s.is(TokenKind.NewLine)) s.next(); if (hasParen) { s.expect(TokenKind.CloseParen); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } const body = parseBlockOrStatement(s); @@ -295,41 +302,49 @@ function parseFor(s: ITokenStream): Ast.For { s.expect(TokenKind.ForKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); if (s.is(TokenKind.OpenParen)) { hasParen = true; s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } if (s.is(TokenKind.LetKeyword)) { // range syntax s.next(); - - const identPos = s.getPos(); + if (s.is(TokenKind.NewLine)) s.next(); s.expect(TokenKind.Identifier); + const identPos = s.getPos(); const name = s.getTokenValue(); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); let _from: Ast.Expression; if (s.is(TokenKind.Eq)) { s.next(); + if (s.is(TokenKind.NewLine)) s.next(); _from = parseExpr(s, false); } else { _from = NODE('num', { value: 0 }, identPos, identPos); } + if (s.is(TokenKind.NewLine)) s.next(); if (s.is(TokenKind.Comma)) { s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } else { throw new AiScriptSyntaxError('separator expected', s.getPos()); } const to = parseExpr(s, false); + if (s.is(TokenKind.NewLine)) s.next(); if (hasParen) { s.expect(TokenKind.CloseParen); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } const body = parseBlockOrStatement(s); @@ -344,10 +359,12 @@ function parseFor(s: ITokenStream): Ast.For { // times syntax const times = parseExpr(s, false); + if (s.is(TokenKind.NewLine)) s.next(); if (hasParen) { s.expect(TokenKind.CloseParen); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); } const body = parseBlockOrStatement(s); @@ -454,10 +471,13 @@ function parseDoWhile(s: ITokenStream): Ast.Loop { const doStartPos = s.getPos(); s.expect(TokenKind.DoKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const body = parseBlockOrStatement(s); const whilePos = s.getPos(); + if (s.is(TokenKind.NewLine)) s.next(); s.expect(TokenKind.WhileKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const cond = parseExpr(s, false); const endPos = s.getPos(); @@ -482,8 +502,10 @@ function parseWhile(s: ITokenStream): Ast.Loop { const startPos = s.getPos(); s.expect(TokenKind.WhileKeyword); s.next(); + if (s.is(TokenKind.NewLine)) s.next(); const cond = parseExpr(s, false); const condEndPos = s.getPos(); + if (s.is(TokenKind.NewLine)) s.next(); const body = parseBlockOrStatement(s); return NODE('loop', { diff --git a/test/syntax.ts b/test/syntax.ts index 628dd6cf..e27f4979 100644 --- a/test/syntax.ts +++ b/test/syntax.ts @@ -110,6 +110,25 @@ describe('separator', () => { eq(res, STR('a')); }); + test.concurrent('maximum 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 @@ -696,6 +715,24 @@ describe('for', () => { eq(res, NUM(55)); }); + test.concurrent('Basic (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + for + ( + let + i + , + 10 + ) + { + count += i + 1 + } + <: count + `); + eq(res, NUM(55)); + }); + test.concurrent('initial value', async () => { const res = await exe(` var count = 0 @@ -707,7 +744,27 @@ describe('for', () => { eq(res, NUM(65)); }); - test.concurrent('wuthout iterator', async () => { + test.concurrent('initial value (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + for + ( + let + i + = + 2 + , + 10 + ) + { + count += i + } + <: count + `); + eq(res, NUM(65)); + }); + + test.concurrent('without iterator', async () => { const res = await exe(` var count = 0 for (10) { @@ -717,6 +774,21 @@ describe('for', () => { `); eq(res, NUM(10)); }); + test.concurrent('without iterator (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + for + ( + 10 + ) + { + count = (count + 1) + } + <: count + `); + eq(res, NUM(10)); + }); + test.concurrent('without brackets', async () => { const res = await exe(` @@ -729,6 +801,22 @@ describe('for', () => { eq(res, NUM(45)); }); + test.concurrent('without brackets (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + for + let + i + , + 10 + { + count = (count + i) + } + <: count + `); + eq(res, NUM(45)); + }); + test.concurrent('Break', async () => { const res = await exe(` var count = 0 @@ -808,6 +896,21 @@ describe('each', () => { `); eq(res, ARR([STR('ai!'), STR('chan!'), STR('kawaii!')])); }); + test.concurrent('standard (maximum multiline)', async () => { + const res = await exe(` + let msgs = [] + each + let + item + , + ["ai", "chan", "kawaii"] + { + msgs.push([item, "!"].join()) + } + <: msgs + `); + eq(res, ARR([STR('ai!'), STR('chan!'), STR('kawaii!')])); + }); test.concurrent('destructuring declaration', async () => { const res = await exe(` @@ -877,6 +980,19 @@ describe('while', () => { eq(res, NUM(42)); }); + test.concurrent('Basic (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + while + count < 42 + { + count += 1 + } + <: count + `); + eq(res, NUM(42)); + }); + test.concurrent('start false', async () => { const res = await exe(` while false { @@ -910,6 +1026,20 @@ describe('do-while', () => { eq(res, NUM(42)); }); + test.concurrent('Basic (maximum multiline)', async () => { + const res = await exe(` + var count = 0 + do + { + count += 1 + } + while + count < 42 + <: count + `); + eq(res, NUM(42)); + }); + test.concurrent('start false', async () => { const res = await exe(` do {