| 583 | } |
| 584 | |
| 585 | private readTemplate(hasLeftSpacing: boolean): Token { |
| 586 | const elements: Token[] = []; |
| 587 | let buf = ''; |
| 588 | let tokenBuf: Token[] = []; |
| 589 | let state: 'string' | 'escape' | 'expr' | 'finish' = 'string'; |
| 590 | let exprBracketDepth = 0; |
| 591 | |
| 592 | const pos = this.stream.getPos(); |
| 593 | let elementPos = pos; |
| 594 | this.stream.next(); |
| 595 | |
| 596 | while (state !== 'finish') { |
| 597 | switch (state) { |
| 598 | case 'string': { |
| 599 | // テンプレートの終了が無いままEOFに達した |
| 600 | if (this.stream.eof) { |
| 601 | throw new AiScriptUnexpectedEOFError(pos); |
| 602 | } |
| 603 | // エスケープ |
| 604 | if (this.stream.char === '\\') { |
| 605 | this.stream.next(); |
| 606 | state = 'escape'; |
| 607 | break; |
| 608 | } |
| 609 | // テンプレートの終了 |
| 610 | if (this.stream.char === '`') { |
| 611 | this.stream.next(); |
| 612 | if (buf.length > 0) { |
| 613 | elements.push(TOKEN(TokenKind.TemplateStringElement, elementPos, { hasLeftSpacing, value: buf })); |
| 614 | } |
| 615 | state = 'finish'; |
| 616 | break; |
| 617 | } |
| 618 | // 埋め込み式の開始 |
| 619 | if (this.stream.char === '{') { |
| 620 | this.stream.next(); |
| 621 | if (buf.length > 0) { |
| 622 | elements.push(TOKEN(TokenKind.TemplateStringElement, elementPos, { hasLeftSpacing, value: buf })); |
| 623 | buf = ''; |
| 624 | } |
| 625 | // ここから式エレメントになるので位置を更新 |
| 626 | elementPos = this.stream.getPos(); |
| 627 | state = 'expr'; |
| 628 | break; |
| 629 | } |
| 630 | buf += this.stream.char; |
| 631 | this.stream.next(); |
| 632 | break; |
| 633 | } |
| 634 | case 'escape': { |
| 635 | // エスケープ対象の文字が無いままEOFに達した |
| 636 | if (this.stream.eof) { |
| 637 | throw new AiScriptUnexpectedEOFError(pos); |
| 638 | } |
| 639 | // 普通の文字として取り込み |
| 640 | buf += this.stream.char; |
| 641 | this.stream.next(); |
| 642 | // 通常の文字列に戻る |