Typescriptのコードを読む #2

Scanner

export interface Scanner {
    getStartPos(): number;
    getToken(): SyntaxKind;
    getTextPos(): number;
    getTokenPos(): number;
    getTokenText(): string;
    getTokenValue(): string;
    hasExtendedUnicodeEscape(): boolean;
    hasPrecedingLineBreak(): boolean;
    isIdentifier(): boolean;
    isReservedWord(): boolean;
    isUnterminated(): boolean;
    /* @internal */
    getNumericLiteralFlags(): NumericLiteralFlags;
    reScanGreaterToken(): SyntaxKind;
    reScanSlashToken(): SyntaxKind;
    reScanTemplateToken(): SyntaxKind;
    scanJsxIdentifier(): SyntaxKind;
    scanJsxAttributeValue(): SyntaxKind;
    reScanJsxToken(): SyntaxKind;
    scanJsxToken(): SyntaxKind;
    scanJSDocToken(): SyntaxKind;
    scan(): SyntaxKind;
    getText(): string;
    // Sets the text for the scanner to scan.  An optional subrange starting point and length
    // can be provided to have the scanner only scan a portion of the text.
    setText(text: string, start?: number, length?: number): void;
    setOnError(onError: ErrorCallback): void;
    setScriptTarget(scriptTarget: ScriptTarget): void;
    setLanguageVariant(variant: LanguageVariant): void;
    setTextPos(textPos: number): void;
    // Invokes the provided callback then unconditionally restores the scanner to the state it
    // was in immediately prior to invoking the callback.  The result of invoking the callback
    // is returned from this function.
    lookAhead<T>(callback: () => T): T;

    // Invokes the callback with the scanner set to scan the specified range. When the callback
    // returns, the scanner is restored to the state it was in before scanRange was called.
    scanRange<T>(start: number, length: number, callback: () => T): T;

    // Invokes the provided callback.  If the callback returns something falsy, then it restores
    // the scanner to the state it was in immediately prior to invoking the callback.  If the
    // callback returns something truthy, then the scanner state is not rolled back.  The result
    // of invoking the callback is returned from this function.
    tryScan<T>(callback: () => T): T;
}
Interfaceとしてはこんな感じ。与えられた文字列に対して scan() で1文字ずつ舐めていって token( SyntaxKind ) を取得する。
tokenが識別子(keyword)かどうかは token > SyntaxKind.Identifer で判定できるようになっている。最近入ったJSDocの解釈に対応するつため JSDoc*Type みたいな SyntaxKind もある。以前だったら単なるコメントなので *CommentTrivia 扱いされていたはず。はず。
scan() 自体は普通に pos を進めていきながら token を判定していく感じ。
TypeScriptはあんまり独自のキーワードいれない(実JSに影響及ぼすものに関しては)方針だと思われるけどその中でも enum だけは存在しているのは多分
switch (ch) {
    case CharacterCodes.lineFeed:
みたいなのを書きやすくするためなんじゃないかなぁと思ってる。セルフホスティングだからね。
jsxだったりテンプレートリテラルだったりする場合は特別対応してるんだけど、正直なにやってるかよくわからない。
import * as ts from 'typescript';

let text = `let a = 'm';`;

let scanner =  ts.createScanner(ts.ScriptTarget.Latest, false,ts.LanguageVariant.Standard, text);

console.log(scanner.scan());
console.log(scanner.getTokenValue());
みたいなの書いていけば理解できそうだけどparser読んだほうが良さそうなので今は置いとく。 reScan 系とか使ってる側見たいとイメージ沸かない。
ちなみに ConflictMarkerTrivia みたいのもあって親切。