- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
呃....4年前开了一个坑,准备写一套完整介绍TS 原理的文章。坑很大,要慢慢填,今天就来填一个把.
本节主要介绍语法增量解析.
增量解析的意思是,如果我们直接从源码解析成语法树,叫做全量解析.
语法树是由很多个节点对象组成的,比较吃内存.
当用户修改源码后(无论修改哪里,包括插入一个空格),我们都需要重新解析文件,生成新的语法树.
如果每次都全量解析,那意味着释放之前的所有节点,然后重新创建所有节点.
但实际上,用户每次只会修改一部分内容,而整个语法树的大部分节点都不会发生变化.
如果解析时,发现节点没有变化,就可以复用之前的节点对象,只重新创建变化的节点,这个过程叫增量解析.
增量解析是一种性能优化,它不是必须的.
假如我们修改了函数中某行代码的内容,从原则上说,这个函数之前的节点都是不变的.
函数之后的节点大概率不变,但也有小概率会变,比如我们插入了“}”,导致函数的范围发生变化,或者插入“`”,导致后面的内容都变成字符串的一部分了.
看上去好像很复杂,但 TS 采用了一个折中的做法,大幅降低了复杂度.
TS 是以语句为单位进行复用的,即每条语句或者完全复用,或者完全不复用,即使单条语句里面存在可复用的部分子节点。(这种说法其实并不准确,但为了方便理解,可以先这么认为) 。
核心逻辑为:
1. 当在 pos 位置解析一条语句前,TS 先检测该位置是否存在可复用的旧节点,如果不存在当然就无法增量解析,就转成常规解析.
2. 如果存在旧节点,则检查该旧节点所在区域是否发生变化,如果发生变化,则放弃复用,转为常规解析.
3. 如果没有发生变化,那这条语句就直接解析完毕,然后从这行语句的 end 位置继续解析下一条语句,重复前面的步骤.
。
代码位于 parser.ts 。
export interface SyntaxCursor {
currentNode(position: number): Node;
}
SyntaxCursor 用于从原始的语法树中查找指定位置对应的可复用的旧节点。
export function createSyntaxCursor(sourceFile: SourceFile): SyntaxCursor { let currentArray: NodeArray<Node> = sourceFile.statements; let currentArrayIndex = 0; Debug.assert(currentArrayIndex < currentArray.length); let current = currentArray[currentArrayIndex]; let lastQueriedPosition = InvalidPosition.Value; return { currentNode(position: number) { // 函数内基于一个事实做了一个性能优化
// 就是解析时,position 会逐步变大,因此查找的时候,不需要每次都重头查找,而是记住上一次查找的位置
// 下次查找就从上次的位置继续查找,这样找起来更快
if (position !== lastQueriedPosition) { if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) { currentArrayIndex++; current = currentArray[currentArrayIndex]; } // 如果上次的位置和要查找的位置不匹配,就重头查找。 if (!current || current.pos !== position) { findHighestListElementThatStartsAtPosition(position); } } // 记住本次查找的位置,加速下次查找 lastQueriedPosition = position; Debug.assert(!current || current.pos === position); return current; }, }; // 标准的深度优先搜索算法,找到就近的节点 function findHighestListElementThatStartsAtPosition(position: number) { currentArray = undefined!; currentArrayIndex = InvalidPosition.Value; current = undefined!; forEachChild(sourceFile, visitNode, visitArray); return; function visitNode(node: Node) { if (position >= node.pos && position < node.end) { forEachChild(node, visitNode, visitArray); return true; } // position wasn't in this node, have to keep searching. return false; } function visitArray(array: NodeArray<Node>) { if (position >= array.pos && position < array.end) { for (let i = 0; i < array.length; i++) { const child = array[i]; if (child) { if (child.pos === position) { currentArray = array; currentArrayIndex = i; current = child; return true; } else { if (child.pos < position && position < child.end) { forEachChild(child, visitNode, visitArray); return true; } } } } } return false; } } }
每个列表(包括语句块的语句列表)都是使用 parseList 解析的。每个元素都是通过 parseListElement 解析.
function parseList<T extends Node>(kind: ParsingContext, parseElement: () => T): NodeArray<T> { const saveParsingContext = parsingContext; parsingContext |= 1 << kind; const list = []; const listPos = getNodePos(); while (!isListTerminator(kind)) { if (isListElement(kind, /*inErrorRecovery*/ false)) { list.push(parseListElement(kind, parseElement)); continue; } if (abortParsingListOrMoveToNextToken(kind)) { break; } } parsingContext = saveParsingContext; return createNodeArray(list, listPos); }
parseListElement 中会先检测可复用的节点,如果存在,就复用并解析下一个元素,否则正常解析当前元素.
function parseListElement<T extends Node | undefined>(parsingContext: ParsingContext, parseElement: () => T): T { const node = currentNode(parsingContext); if (node) { return consumeNode(node) as T; } return parseElement(); }
function currentNode(parsingContext: ParsingContext, pos?: number): Node | undefined { if (!syntaxCursor || !isReusableParsingContext(parsingContext) || parseErrorBeforeNextFinishedNode) { return undefined; } const node = syntaxCursor.currentNode(pos ?? scanner.getTokenFullStart()); // 存在语法错误的节点不能复用,因为我们需要重新解析,重新报错。 if (nodeIsMissing(node) || intersectsIncrementalChange(node) || containsParseError(node)) { return undefined; } const nodeContextFlags = node.flags & NodeFlags.ContextFlags; if (nodeContextFlags !== contextFlags) { return undefined; } // 有些节点不能复用,因为存在一定场景导致复用出错 if (!canReuseNode(node, parsingContext)) { return undefined; } if (canHaveJSDoc(node) && node.jsDoc?.jsDocCache) { node.jsDoc.jsDocCache = undefined; } return node; }
当节点被复用后,使用 consumeNode 设置下次扫描的位置.
function consumeNode(node: Node) { scanner.resetTokenState(node.end); nextToken(); return node; }
有些场景复用是有问题的,(很多场景都是社区通过 Issue 给 TS 报的 BUG,然后修复的).
比如泛型:
var a = b < c, d, e
从复用角度,这是一个列表,列表项分别为
a = b < c
理论在 e 后面插入任何字符,都不影响前面的节点,但存在一个特例,就是">" 。
var a = b<c,d,e>
当 <> 成对,它变成了泛型。这会导致需要重新解析整个语句.
TS 的做法并不是检测是否插入了“>”,而是因为存在整个特例,就完全不复用变量列表的任何节点,即使多数情况复用的安全的.
毕竟增量解析只是一种性能优化,没有也不是不能用.
完整的检测特殊情况的逻辑在 canReuseNode,因为比较琐碎,且逻辑都比较简单,这里就不贴了.
经过增量解析后,部分节点会被重新使用.
从算法中可以得出,如果子节点被修改了,那父节点一定也会被修改。而源文件本身在每次增量解析时,都会被重新创建.
。
最后此篇关于TS原理详细解读(6)--语法增量解析的文章就讲到这里了,如果你想了解更多关于TS原理详细解读(6)--语法增量解析的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这个问题已经有答案了: What is x after "x = x++"? (18 个回答) 已关闭 6 年前。 public static void main(String[] args)
我目前正在使用 jquery 循环插件。我有 3 个不同的幻灯片,它们彼此相邻并同时循环播放。我想做的是先关闭第一张幻灯片,然后是第二张幻灯片,然后是第三张幻灯片。无论如何,我可以通过增量或超时来做到
这个问题在这里已经有了答案: 关闭 11 年前。 Possible Duplicate: ++someVariable Vs. someVariable++ in Javascript 我知道您可以
我一直在查看 SVN 手册,但无法找到“svn log”和“svn st”的“--incremental”选项的简单用法示例或解释。 我正在编写一个开源 SVN GUI 前端,因此我需要一些有关此标志
我有这种矩阵。 非常抱歉,我没有可重现的示例。 表 1: [,1][,2][,3][,4][,5][,6][,7][,8][,9][,10] [1,] 3 NA NA NA
我在hdfs中有一个 Parquet 文件作为我的数据的初始加载。接下来的所有拼花地板只是这些数据集每天都会更改为初始负载(按时间顺序)。这是我的三角洲。 我想读取全部或部分 Parquet 文件,以
我目前有这样的功能,可以将任何输入数字四舍五入到最接近的模糊整数值: $(function(){ $('#my_value').blur(function() { $(this).va
已关闭。此问题不符合Stack Overflow guidelines 。目前不接受答案。 要求提供代码的问题必须表现出对所解决问题的最低限度的了解。包括尝试的解决方案、为什么它们不起作用以及预期结果
我对 SQL 还很陌生,我想知道我是否可以使用它来自动解决我数据库中的一个复杂问题。 也就是说,我每天都在跟踪条目。因此,我们关注的列是: YYYY MM DD XXX YYYY 是年,MM 是月,D
我正在开发一个非常简单的数据库,但我不知道数据透视表是否是一个很好的解决方案。如果我使用数据透视表,我需要添加无用的表只是为了增量。 让我们从头开始。 在用户注册期间,会创建一个新表 GROUP。在G
在 MySQL 中你可以做这样的事情 SELECT @n := @n + 1 n, first_name, last_name FROM table1, (SELECT
如果我正在使用一个类,我知道如何重载运算符 += class temp { public: int i; temp(){ i = 10; } int operator+=(in
我有两个文件:file1、file2。我想从 file2 中获取 file1 中不存在的行。 我读过 post这告诉我使用 grep 的 -v 标志来执行此操作(我阅读了 grep 的手册页,但仍然不
我看了很多类似的题,功能很简单,用于API的嵌套for循环,每分钟可以调用5次。所以我将一年数据的范围设置为 75。你们能帮我解决这个问题吗?提前致谢! 第一部分正在运行,输入列表中的邮政编码。 fo
所以我想计算每日返回/增量的一些时间序列数据,其中每日增量 = value_at_time(T)/value_at_time(T-1) import pandas as pd df=pd.DataFr
请帮我解决这个问题。该表达式之后的步骤是: //Expression offSpring1[m1++] = temp1; //Steps: 1.- increment m1 2.- assign te
我正在开发一个解决方案,在该解决方案中,我通过 webapp 不同类型的实体(例如中央数据库上的用户、组、部门信息)和 ldap 进行身份验证。但是最终用户将与来自远程位置(他的办公室、节点)的数据交
我有以下 Python 数据结构: data1 = [{'name': u'String 1'}, {'name': u'String 2'}] data2 = [{'name': u'String
如果 AtomicInteger 会发生什么?达到 Integer.MAX_VALUE 并递增? 值会回到零吗? 最佳答案 由于integer overflow,它会环绕, 到 Integer.MIN
我是 C 的初学者,我正在尝试在 While 循环中进行 0.00001 增量。例如,double t = 0.00001 并且我希望循环每次以 0.00001 的增量运行,第二次是 0.00002
我是一名优秀的程序员,十分优秀!