角待ちは対空

おもむろガウェイン

Typescriptのコードを読む #2

Scanner

TypeScript/scanner.ts at 3cd9f3d2d4afc1c817ea53b3e40d9598197e9aaa · Microsoft/TypeScript · GitHub

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 ) を取得する。

TypeScript/types.ts at 3cd9f3d2d4afc1c817ea53b3e40d9598197e9aaa · Microsoft/TypeScript · GitHub

tokenが識別子(keyword)かどうかは token > SyntaxKind.Identifer で判定できるようになっている。最近入ったJSDocの解釈に対応するつため JSDoc*Type みたいな SyntaxKind もある。以前だったら単なるコメントなので *CommentTrivia 扱いされていたはず。はず。

scan() 自体は普通に pos を進めていきながら token を判定していく感じ。

TypeScript/scanner.ts at 3cd9f3d2d4afc1c817ea53b3e40d9598197e9aaa · Microsoft/TypeScript · GitHub

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 みたいのもあって親切。

Typescriptのコードを読む #1

突然だけどTypescriptのコードを読んでいくことにする。特に目的はない、のでどこから読んでいいかわからないけど、scannerからいく。そして、scanner自体もそこそこあるし、疲れるので、大体関数1個ずつくらいのペースで行くのではないか。飽きたらやめる。

lookupInUnicodeMap

TypeScript/scanner.ts at 3cd9f3d2d4afc1c817ea53b3e40d9598197e9aaa · Microsoft/TypeScript · GitHub

ES3以前とES5移行で識別子に使えるコードポイントが違うので使える文字であるのかチェックするのがlookupInUnicodeMapである。

識別子に使える文字も最初に使えるコードポイントとそれ移行に使えるコードポイントは違うため

  • unicodeES5IdentifierStart
  • unicodeES5IdentifierPart

のように2つの定数が定義されている。(もちろんES3版もある)

この配列の見方は、たとえば [2, 2, 4, 6] のようになっていれば2〜2と4〜6、つまり2、4、5、6が含まれるということである。

Goで正規表現のバイトコードを見たときもこういう感じの表現の仕方をしていたのでこれがセオリーっぽい。

function lookupInUnicodeMap(code: number, map: number[]): boolean {
    ...
}

第二引数に unicodeES5IdentifierStartunicodeES5IdentifierPart を与え、第一引数に与えたコードポイントが含まれているかをチェックする。

含まれているかのチェック自体は素朴な二分探索法という感じ。2要素で1ペアとしその中に code が含まれるか調べていく。

isUnicodeIdentifierStartisUnicodeIdentifierPartlookupInUnicodeMap のwrapperである。さらにそれらのwrapperである isIdentifierStartisIdentifierPart が存在する。 isIdentifier* ではascii文字であるならば直ちに ture を返すことになっている(ES5かES3か区別する必要ないからね)。

isIdentifier* は今後いたるところで使われる。

感想

scannerはscannerだし読まなくて良い気がしてきた。

今日はここまで。