Typescript編譯原理(一)

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

其中分爲如下幾個關鍵部分,git

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

每一個部分在源文件中均有獨立文件,稍後會解釋這些部分在編譯過程當中所起到的左右。github

概覽

上圖簡單說明 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(抽象語法樹)express

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

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

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

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

經過 parseSourceFile 設置初始狀態並將工做交給 parseSourceFileWorker 函數。post

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 節點。ui

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構建完成。

未完待續。。。

相關文章
相關標籤/搜索