* ```abnf * For = ForRange / ForTimes * ForRange = "for" "(" "let" IDENT ["=" Expr] "," Expr ")" BlockOrStatement * / "for" "let" IDENT ["=" Expr] "," Expr BlockOrStatement * ForTimes = "for" "(" Expr ")" BlockOrStatement * / "for" Expr BlockOrStatement * ```
(s: ITokenStream)
| 290 | * ``` |
| 291 | */ |
| 292 | function parseFor(s: ITokenStream): Ast.For { |
| 293 | const startPos = s.getPos(); |
| 294 | let hasParen = false; |
| 295 | |
| 296 | s.expect(TokenKind.ForKeyword); |
| 297 | s.next(); |
| 298 | |
| 299 | if (s.is(TokenKind.OpenParen)) { |
| 300 | hasParen = true; |
| 301 | s.next(); |
| 302 | } |
| 303 | |
| 304 | if (s.is(TokenKind.LetKeyword)) { |
| 305 | // range syntax |
| 306 | s.next(); |
| 307 | |
| 308 | const identPos = s.getPos(); |
| 309 | |
| 310 | s.expect(TokenKind.Identifier); |
| 311 | const name = s.getTokenValue(); |
| 312 | s.next(); |
| 313 | |
| 314 | let _from: Ast.Expression; |
| 315 | if (s.is(TokenKind.Eq)) { |
| 316 | s.next(); |
| 317 | _from = parseExpr(s, false); |
| 318 | } else { |
| 319 | _from = NODE('num', { value: 0 }, identPos, identPos); |
| 320 | } |
| 321 | |
| 322 | if (s.is(TokenKind.Comma)) { |
| 323 | s.next(); |
| 324 | } else { |
| 325 | throw new AiScriptSyntaxError('separator expected', s.getPos()); |
| 326 | } |
| 327 | |
| 328 | const to = parseExpr(s, false); |
| 329 | |
| 330 | if (hasParen) { |
| 331 | s.expect(TokenKind.CloseParen); |
| 332 | s.next(); |
| 333 | } |
| 334 | |
| 335 | const body = parseBlockOrStatement(s); |
| 336 | |
| 337 | return NODE('for', { |
| 338 | var: name, |
| 339 | from: _from, |
| 340 | to, |
| 341 | for: body, |
| 342 | }, startPos, s.getPos()); |
| 343 | } else { |
| 344 | // times syntax |
| 345 | |
| 346 | const times = parseExpr(s, false); |
| 347 | |
| 348 | if (hasParen) { |
| 349 | s.expect(TokenKind.CloseParen); |
no test coverage detected