Typescript編譯原理(一)

王三麻子發表於2018-12-22

首先,tsgithub 地址:github.com/Microsoft/T… 。各位可先行下載。其編譯部分位於 src/compiler 目錄下。

其中分為以下幾個關鍵部分,

  • Scanner 掃描器scanner.ts
  • Parser 解析器parser.ts
  • Binder 繫結器binder.ts
  • Checker 檢查器checker.ts
  • Emitter 發射器emitter.ts

每個部分在原始檔中均有獨立檔案,稍後會解釋這些部分在編譯過程中所起到的左右。

概覽

Typescript編譯原理(一)

上圖簡單說明 TypeScript 編譯器如何將上述幾個關鍵部分組合在一起:

  1. 原始碼 ~ scanner(掃描器) ~ token資料流 ~ parser(解析器) -> AST(抽象語法樹)
  2. AST(抽象語法樹) ~ binder(繫結器) -> symbols(符號)
  3. AST(抽象語法樹) + symbols ~ checker(檢查器) -> 型別檢查功能
  4. AST(抽象語法樹) + checker(檢查器) ~ emitter(發射器) -> js程式碼

流程 1: 原始碼 => AST

原始碼 ~ scanner(掃描器) ~ token資料流 ~ parser(解析器) -> AST(抽象語法樹)

typescript的掃描器位於scanner.ts,解析器位於parser.ts,在內部,由 parser解析器控制scanner掃描器原始碼轉化為抽象語法樹(AST)。流程如下:

Typescript編譯原理(一)

若以常見的AST生成過程類比,可簡單類比上述的 掃描器階段 可對應為 詞法分析過程解析器階段可對應為語法分析過程

Typescript編譯原理(一)

有關AST抽象語法樹 可參考 AST抽象語法樹

Parser 解析器對 Scanner 掃描器的使用

通過 parseSourceFile 設定初始狀態並將工作交給 parseSourceFileWorker 函式。

parseSourceFile

        export function parseSourceFile(fileName: string, sourceText: string, languageVersion: ScriptTarget, syntaxCursor: IncrementalParser.SyntaxCursor | undefined, setParentNodes = false, scriptKind?: ScriptKind): SourceFile {
            scriptKind = ensureScriptKind(fileName, scriptKind);

            //初始化狀態
            if (scriptKind === ScriptKind.JSON) {
                const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes);
                convertToObjectWorker(result, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined);
                result.referencedFiles = emptyArray;
                result.typeReferenceDirectives = emptyArray;
                result.libReferenceDirectives = emptyArray;
                result.amdDependencies = emptyArray;
                result.hasNoDefaultLib = false;
                result.pragmas = emptyMap;
                return result;
            }

            //專備好掃描器狀態
            initializeState(sourceText, languageVersion, syntaxCursor, scriptKind);
            
            //將工作交給 parseSourceFileWorker
            const result = parseSourceFileWorker(fileName, languageVersion, setParentNodes, scriptKind);

            clearState();

            return result;
        }
複製程式碼

parseSourceFileWorker

該函式先建立一個 SourceFile AST 節點,然後從 parseStatement 函式開始解析原始碼。一旦返回結果,就用額外資訊(例如 nodeCount, identifierCount等) 完善 SourceFile 節點。

        function parseSourceFileWorker(fileName: string, languageVersion: ScriptTarget, setParentNodes: boolean, scriptKind: ScriptKind): SourceFile {
            const isDeclarationFile = isDeclarationFileName(fileName);
            if (isDeclarationFile) {
                contextFlags |= NodeFlags.Ambient;
            }

            // 先創造一個  SourceFile AST 節點
            sourceFile = createSourceFile(fileName, languageVersion, scriptKind, isDeclarationFile);
            sourceFile.flags = contextFlags;

            // Prime the scanner.
            nextToken();
            // A member of ReadonlyArray<T> isn't assignable to a member of T[] (and prevents a direct cast) - but this is where we set up those members so they can be readonly in the future
            processCommentPragmas(sourceFile as {} as PragmaContext, sourceText);
            processPragmasIntoFields(sourceFile as {} as PragmaContext, reportPragmaDiagnostic);

            // 呼叫 parseStatement 函式解析原始碼
            sourceFile.statements = parseList(ParsingContext.SourceElements, parseStatement);
            Debug.assert(token() === SyntaxKind.EndOfFileToken);

            // 至871行 均為完善 sourcefile AST 節點
            sourceFile.endOfFileToken = addJSDocComment(parseTokenNode());

            setExternalModuleIndicator(sourceFile);

            sourceFile.nodeCount = nodeCount;
            sourceFile.identifierCount = identifierCount;
            sourceFile.identifiers = identifiers;
            sourceFile.parseDiagnostics = parseDiagnostics;

            if (setParentNodes) {
                fixupParentReferences(sourceFile);
            }

            return sourceFile;

            function reportPragmaDiagnostic(pos: number, end: number, diagnostic: DiagnosticMessage) {
                parseDiagnostics.push(createFileDiagnostic(sourceFile, pos, end, diagnostic));
            }
        }
複製程式碼

節點建立:parseStatement/parseXXXX等

其中parseStatement函式,它根據掃描器返回的當前 token 來切換(呼叫相應的 parseXXX 函式),生成AST節點。

function parseStatement(): Statement {

    // 此處 token 為 scanner掃描器 返回的 當前token流,  SyntaxKind為AST的常量列舉型別,根據不同的型別建立不同的節點
    switch (token()) {
        // 型別為 SemicolonToken,呼叫parseEmptyStatement
        case SyntaxKind.SemicolonToken:
            return parseEmptyStatement();
        case SyntaxKind.OpenBraceToken:
            return parseBlock(/*ignoreMissingOpenBrace*/ false);
        case SyntaxKind.VarKeyword:
            return parseVariableStatement(<VariableStatement>createNodeWithJSDoc(SyntaxKind.VariableDeclaration));
        case SyntaxKind.LetKeyword:
            if (isLetDeclaration()) {
                return parseVariableStatement(<VariableStatement>createNodeWithJSDoc(SyntaxKind.VariableDeclaration));
            }
            break;
        case SyntaxKind.FunctionKeyword:
            return parseFunctionDeclaration(<FunctionDeclaration>createNodeWithJSDoc(SyntaxKind.FunctionDeclaration));
        case SyntaxKind.ClassKeyword:
            return parseClassDeclaration(<ClassDeclaration>createNodeWithJSDoc(SyntaxKind.ClassDeclaration));
        case SyntaxKind.IfKeyword:
            return parseIfStatement();
        case SyntaxKind.DoKeyword:
            return parseDoStatement();
        case SyntaxKind.WhileKeyword:
            return parseWhileStatement();
        case SyntaxKind.ForKeyword:
            return parseForOrForInOrForOfStatement();
        case SyntaxKind.ContinueKeyword:
            return parseBreakOrContinueStatement(SyntaxKind.ContinueStatement);
        case SyntaxKind.BreakKeyword:
            return parseBreakOrContinueStatement(SyntaxKind.BreakStatement);
        case SyntaxKind.ReturnKeyword:
            return parseReturnStatement();
        case SyntaxKind.WithKeyword:
            return parseWithStatement();
        case SyntaxKind.SwitchKeyword:
            return parseSwitchStatement();
        case SyntaxKind.ThrowKeyword:
            return parseThrowStatement();
        case SyntaxKind.TryKeyword:
        // Include 'catch' and 'finally' for error recovery.
        case SyntaxKind.CatchKeyword:
        case SyntaxKind.FinallyKeyword:
            return parseTryStatement();
        case SyntaxKind.DebuggerKeyword:
            return parseDebuggerStatement();
        case SyntaxKind.AtToken:
            return parseDeclaration();
        case SyntaxKind.AsyncKeyword:
        case SyntaxKind.InterfaceKeyword:
        case SyntaxKind.TypeKeyword:
        case SyntaxKind.ModuleKeyword:
        case SyntaxKind.NamespaceKeyword:
        case SyntaxKind.DeclareKeyword:
        case SyntaxKind.ConstKeyword:
        case SyntaxKind.EnumKeyword:
        case SyntaxKind.ExportKeyword:
        case SyntaxKind.ImportKeyword:
        case SyntaxKind.PrivateKeyword:
        case SyntaxKind.ProtectedKeyword:
        case SyntaxKind.PublicKeyword:
        case SyntaxKind.AbstractKeyword:
        case SyntaxKind.StaticKeyword:
        case SyntaxKind.ReadonlyKeyword:
        case SyntaxKind.GlobalKeyword:
            if (isStartOfDeclaration()) {
                return parseDeclaration();
            }
            break;
    }
    return parseExpressionOrLabeledStatement();
}
複製程式碼

例如:如果當前 token 是一個 SemicolonToken(分號標記),就會呼叫 paserEmptyStatement 為空語句建立一個 AST 節點。

paserEmptyStatement/parseIfStatement等等

function parseEmptyStatement(): Statement {
    const node = <Statement>createNode(SyntaxKind.EmptyStatement);
    parseExpected(SyntaxKind.SemicolonToken);
    return finishNode(node);
}
    
function parseIfStatement(): IfStatement {
    const node = <IfStatement>createNode(SyntaxKind.IfStatement);
    parseExpected(SyntaxKind.IfKeyword);
    parseExpected(SyntaxKind.OpenParenToken);
    node.expression = allowInAnd(parseExpression);
    parseExpected(SyntaxKind.CloseParenToken);
    node.thenStatement = parseStatement();
    node.elseStatement = parseOptional(SyntaxKind.ElseKeyword) ? parseStatement() : undefined;
    return finishNode(node);
}
複製程式碼

觀察上述parseXXXX等,會發現其中存在三個關鍵函式createNodeparseExpectedfinishNode

createNode

function createNode(kind: SyntaxKind, pos?: number): Node {
    nodeCount++;
    // 獲取初始位置(可呼叫掃描器scanner的startPos,'Start position of whitespace before current token')
    const p = pos! >= 0 ? pos! : scanner.getStartPos();
    // 返回節點型別
    return isNodeKind(kind) || kind === SyntaxKind.Unknown ? new NodeConstructor(kind, p, p) :
        kind === SyntaxKind.Identifier ? new IdentifierConstructor(kind, p, p) :
        new TokenConstructor(kind, p, p);
}
複製程式碼

parseExpected

    function parseExpected(kind: SyntaxKind, diagnosticMessage?: DiagnosticMessage, shouldAdvance = true): boolean {
        // 檢查當前token是否與當前傳入的kind是否一致
        if (token() === kind) {
            if (shouldAdvance) {
                nextToken();
            }
            return true;
        }

        // 如token與kind不一致,則根據是否傳入diagnosticMessage(診斷資訊),回傳錯誤
        if (diagnosticMessage) {
            parseErrorAtCurrentToken(diagnosticMessage);
        }
        else {
            parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(kind));
        }
        return false;
    }
複製程式碼

finishNode

function finishNode<T extends Node>(node: T, end?: number): T {
    // 獲取結束位置
    node.end = end === undefined ? scanner.getStartPos() : end;
    
    // 新增標記
    if (contextFlags) {
        node.flags |= contextFlags;
    }

    //判斷是否出現錯誤,若出現錯誤就不會標記任何後續節點。
    if (parseErrorBeforeNextFinishedNode) {
        parseErrorBeforeNextFinishedNode = false;
        node.flags |= NodeFlags.ThisNodeHasError;
    }

    return node;
}
複製程式碼

至此, AST構建完成。

未完待續。。。

相關文章