首先,ts
的 github
地址:github.com/Microsoft/T…
。各位可先行下載。其編譯部分位於 src/compiler
目錄下。
其中分為以下幾個關鍵部分,
- Scanner 掃描器(
scanner.ts
) - Parser 解析器(
parser.ts
) - Binder 繫結器(
binder.ts
) - Checker 檢查器(
checker.ts
) - Emitter 發射器(
emitter.ts
)
每個部分在原始檔中均有獨立檔案,稍後會解釋這些部分在編譯過程中所起到的左右。
概覽
上圖簡單說明 TypeScript 編譯器如何將上述幾個關鍵部分組合在一起:
- 原始碼 ~ scanner(掃描器) ~ token資料流 ~ parser(解析器) -> AST(抽象語法樹)
- AST(抽象語法樹) ~ binder(繫結器) -> symbols(符號)
- AST(抽象語法樹) + symbols ~ checker(檢查器) -> 型別檢查功能
- AST(抽象語法樹) + checker(檢查器) ~ emitter(發射器) -> js程式碼
流程 1: 原始碼 => AST
原始碼 ~ scanner(掃描器) ~ token資料流 ~ parser(解析器) -> AST(抽象語法樹)
typescript的掃描器位於scanner.ts
,解析器位於parser.ts
,在內部,由
parser解析器
控制scanner掃描器
將原始碼
轉化為抽象語法樹(AST)
。流程如下:
若以常見的AST生成過程類比,可簡單類比上述的
掃描器階段
可對應為
詞法分析過程
,解析器階段
可對應為語法分析過程
。
有關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等
,會發現其中存在三個關鍵函式createNode
,parseExpected
,finishNode
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構建完成。
未完待續。。。