Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 25 additions & 23 deletions src/parser/syntaxes/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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 };
}
Expand All @@ -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);
Expand Down
26 changes: 24 additions & 2 deletions src/parser/syntaxes/statements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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();

Expand All @@ -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', {
Expand Down
132 changes: 131 additions & 1 deletion test/syntax.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -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(`
Expand All @@ -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
Expand Down Expand Up @@ -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(`
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
Loading