diff --git a/docs/syntax.md b/docs/syntax.md index 15b7a419..9962bb74 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -182,6 +182,36 @@ each let v, arr{ // Syntax Error } ``` +### while +条件がtrueの間ループを続けます。 +条件が最初からfalseの場合はループは実行されません。 +```js +var count = 0 +while count < 42 { + count += 1 +} +<: count // 42 +// 条件が最初からfalseの場合 +while false { + <: 'hoge' +} // no output +``` + +### do-while +条件がtrueの間ループを続けます。 +条件が最初からfalseであってもループは一度実行されます。 +```js +var count = 0 +do { + count += 1 +} while count < 42 +<: count // 42 +// 条件が最初からfalseの場合 +do { + <: 'hoge' +} while false // hoge +``` + ### loop `break`されるまで無制限にループを行います。 ```js diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts index a179aba7..97b1a3cf 100644 --- a/src/parser/plugins/validate-keyword.ts +++ b/src/parser/plugins/validate-keyword.ts @@ -19,7 +19,6 @@ const reservedWord = [ 'constructor', // 'def', 'dictionary', - 'do', 'enum', 'export', 'finally', @@ -44,7 +43,6 @@ const reservedWord = [ 'use', 'using', 'when', - 'while', 'yield', 'import', 'is', diff --git a/src/parser/scanner.ts b/src/parser/scanner.ts index 3b3aa6cd..1bba9fa7 100644 --- a/src/parser/scanner.ts +++ b/src/parser/scanner.ts @@ -367,6 +367,12 @@ export class Scanner implements ITokenStream { case 'loop': { return TOKEN(TokenKind.LoopKeyword, loc, { hasLeftSpacing }); } + case 'do': { + return TOKEN(TokenKind.DoKeyword, loc, { hasLeftSpacing }); + } + case 'while': { + return TOKEN(TokenKind.WhileKeyword, loc, { hasLeftSpacing }); + } case 'break': { return TOKEN(TokenKind.BreakKeyword, loc, { hasLeftSpacing }); } diff --git a/src/parser/syntaxes/statements.ts b/src/parser/syntaxes/statements.ts index 717ad579..d60b8d5b 100644 --- a/src/parser/syntaxes/statements.ts +++ b/src/parser/syntaxes/statements.ts @@ -45,6 +45,12 @@ export function parseStatement(s: ITokenStream): Ast.Node { case TokenKind.LoopKeyword: { return parseLoop(s); } + case TokenKind.DoKeyword: { + return parseDoWhile(s); + } + case TokenKind.WhileKeyword: { + return parseWhile(s); + } case TokenKind.BreakKeyword: { s.next(); return NODE('break', {}, loc); @@ -376,6 +382,54 @@ function parseLoop(s: ITokenStream): Ast.Node { return NODE('loop', { statements }, loc); } +/** + * ```abnf + * Loop = "do" BlockOrStatement "while" Expr + * ``` +*/ +function parseDoWhile(s: ITokenStream): Ast.Node { + const doLoc = s.token.loc; + s.nextWith(TokenKind.DoKeyword); + const body = parseBlockOrStatement(s); + const whileLoc = s.token.loc; + s.nextWith(TokenKind.WhileKeyword); + const cond = parseExpr(s, false); + + return NODE('loop', { + statements: [ + body, + NODE('if', { + cond: NODE('not', { expr: cond }, whileLoc), + then: NODE('break', {}, whileLoc), + elseif: [], + }, whileLoc), + ], + }, doLoc); +} + +/** + * ```abnf + * Loop = "while" Expr BlockOrStatement + * ``` +*/ +function parseWhile(s: ITokenStream): Ast.Node { + const loc = s.token.loc; + s.nextWith(TokenKind.WhileKeyword); + const cond = parseExpr(s, false); + const body = parseBlockOrStatement(s); + + return NODE('loop', { + statements: [ + NODE('if', { + cond: NODE('not', { expr: cond }, loc), + then: NODE('break', {}, loc), + elseif: [], + }, loc), + body, + ], + }, loc); +} + /** * ```abnf * Assign = Expr ("=" / "+=" / "-=") Expr diff --git a/src/parser/token.ts b/src/parser/token.ts index 67aca6b6..c07030c7 100644 --- a/src/parser/token.ts +++ b/src/parser/token.ts @@ -19,6 +19,8 @@ export enum TokenKind { EachKeyword, ForKeyword, LoopKeyword, + DoKeyword, + WhileKeyword, BreakKeyword, ContinueKeyword, MatchKeyword, diff --git a/test/keywords.ts b/test/keywords.ts index 79848c5f..88f183a1 100644 --- a/test/keywords.ts +++ b/test/keywords.ts @@ -9,6 +9,8 @@ const reservedWords = [ 'false', 'each', 'for', + 'do', + 'while', 'loop', 'break', 'continue', diff --git a/test/syntax.ts b/test/syntax.ts index eefb8b15..d092591c 100644 --- a/test/syntax.ts +++ b/test/syntax.ts @@ -758,6 +758,50 @@ describe('each', () => { }); }); +describe('while', () => { + test.concurrent('Basic', 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 { + <: 'hoge' + } + `); + eq(res, NULL); + }); +}); + +describe('do-while', () => { + test.concurrent('Basic', 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 { + <: 'hoge' + } while false + `); + eq(res, STR('hoge')); + }); +}); + describe('loop', () => { test.concurrent('Basic', async () => { const res = await exe(` diff --git a/unreleased/while.md b/unreleased/while.md new file mode 100644 index 00000000..c49dc5c1 --- /dev/null +++ b/unreleased/while.md @@ -0,0 +1 @@ +- while文とdo-while文を追加