- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我目前正在编写解析器。解析器生成一个 AST,然后我使用各种遍历来处理它。 AST 是(简化的):
type LiteralExpr = {
readonly kind: 'literal',
readonly value: number,
};
type UnaryExpr = {
readonly kind: 'unary',
readonly operator: '!' | '-',
readonly operand: Expr,
};
type BinaryExpr = {
readonly kind: 'binary',
readonly left: Expr,
readonly operator: '+' | '-' | '*' | '/',
readonly right: Expr,
};
/** Parenthesized expression */
type GroupingExpr = {
readonly kind: 'grouping',
readonly subExpr: Expr,
};
type Expr = LiteralExpr | UnaryExpr | BinaryExpr | GroupingExpr;
每一遍都会稍微改变 AST,从而产生一个新的 AST。例如,我通过消除 grouping
节点:
class ParensRemover {
doPass(expr: Expr): Expr {
switch (expr.kind) {
case 'literal': return expr;
case 'unary': return { ...expr, operand: this.doPass(expr.operand) };
case 'binary': return { ...expr, left: this.doPass(expr.left), right: this.doPass(expr.right) };
case 'grouping': return this.doPass(expr.subExpr);
}
}
}
但是,这段代码很快就变成了样板文件,尤其是。当我有大量节点时,所以我想使用访问者模式将其重构为基本递归类:
abstract class ASTVisitor {
doPass(expr: Expr): Expr {
switch (expr.kind) {
case 'literal': return this.visitLiteral(expr);
case 'unary': return this.visitUnary(expr);
case 'binary': return this.visitBinary(expr);
case 'grouping': return this.visitGrouping(expr);
}
}
protected visitLiteral(expr: LiteralExpr): Expr {
return expr;
}
protected visitUnary(expr: UnaryExpr): Expr {
return { ...expr, operand: this.doPass(expr.operand) };
}
protected visitBinary(expr: BinaryExpr): Expr {
return { ...expr, left: this.doPass(expr.left), right: this.doPass(expr.right) };
}
protected visitGrouping(expr: GroupingExpr): Expr {
return { ...expr, subExpr: this.doPass(expr.subExpr) };
}
}
class ParensRemover extends ASTVisitor {
protected visitGrouping(expr: GroupingExpr): Expr {
return this.doPass(expr.subExpr);
}
}
到目前为止一切顺利。这段代码的问题在于,ParensRemover
之后的下一个过程将不得不处理节点类型 grouping
,尽管当然不会有这种类型的节点。这可能看起来没什么大不了的,但我有很多种节点和很多遍,几乎每一个都稍微改变了 AST——删除节点或添加另一个节点,或者更改属性的类型。所以我将 AST Expr
类型更改为以下内容:
type LiteralExpr = {
readonly kind: 'literal',
readonly value: number,
};
type UnaryExpr<Addition> = {
readonly kind: 'unary',
readonly operator: '!' | '-',
readonly operand: ExprBase<Addition>,
};
type BinaryExpr<Addition> = {
readonly kind: 'binary',
readonly left: ExprBase<Addition>,
readonly operator: '+' | '-' | '*' | '/',
readonly right: ExprBase<Addition>,
};
/** Parenthesized expression */
type GroupingExpr = {
readonly kind: 'grouping',
readonly subExpr: BeforeRemoveParensExpr,
};
type ExprBase<Addition> = LiteralExpr | UnaryExpr | BinaryExpr | Addition;
type BeforeRemoveParensExpr = ExprBase<GroupingExpr>;
type AfterRemoveParensExpr = ExprBase<never>;
但是现在 ASTVisitor
如何知道正确的类型呢?我尝试了以下方法:
type AllExprs = BeforeRemoveParensExpr | AfterRemoveParensExpr;
type PickExpr<E extends AllExprs, K extends E['kind']> = /* details not important, this type pulls a specific kind out of Expr */;
abstract class ASTVisitor<InputExpr extends AllExprs, OutputExpr extends AllExprs> {
doPass(expr: InputExpr): OutputExpr {
switch (expr.kind) {
case 'literal': return this.visitLiteral(expr as any);
case 'unary': return this.visitUnary(expr as any);
case 'binary': return this.visitBinary(expr as any);
case 'grouping': return this.visitGrouping(expr as any);
}
}
protected visitLiteral(expr: PickExpr<InputExpr, 'literal'>) {
return expr as unknown OutputExpr;
}
protected visitUnary(expr: PickExpr<InputExpr, 'unary'>) {
return { ...expr, operand: this.doPass(expr.operand) } as unknown as OutputExpr;
}
protected visitBinary(expr: PickExpr<InputExpr, 'binary'>) {
return { ...expr, left: this.doPass(expr.left), right: this.doPass(expr.right) } as unknown as OutputExpr;
}
protected visitGrouping(expr: PickExpr<InputExpr, 'grouping'>) {
return { ...expr, subExpr: this.doPass(expr.subExpr) } as unknown as OutputExpr;
}
}
class ParensRemover extends ASTVisitor<BeforeRemoveParensExpr, AfterRemoveParensExpr> {
protected visitGrouping(expr: GroupingExpr): AfterRemoveParensExpr {
return this.doPass(expr.subExpr);
}
}
但我对这个解决方案并不满意。除了在 ASTVisitor
中对 any
的多次强制转换外,它失去了类型安全性。如果我忘记为 X
覆盖一个 visitX()
,它应该在两次之间改变,我不会得到编译器错误,而是程序会以一种奇怪的方式失败。
我可以做我想做的事而不失去 TypeScript 提供的安全性吗?如果需要,我可以将 AST 的表示更改为其他内容。
抱歉这篇文章太长了。提前致谢。
最佳答案
听起来您正在寻找 Exclude<Type, ExcludedUnion>
utility type .
该类型的核心非常简单:
type Foo = A | B | C;
type Bar = Exclude<Foo, A>; // Equal to B | C
虽然您可能需要重组代码以合理地接受不同的输出和输入,但您可以这样输入您的函数:
function visitGrouping(expr: Expr): Exclude<Expr, GroupingExpr> { ... }
function doPass(expr: Expr) {
switch (expr.kind) {
case 'grouping': return visitGrouping(expr);
// ...
}
}
在这种情况下,Typescript 可以自己找出空白。
关于typescript - TypeScript 中的递归 AST 访问者,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64960391/
我正在尝试检查 Go 源代码以制作一个工具。为此,我使用 ast.Inspect 函数。 我需要知道函数/方法内部如何使用 channel 。 我将此作为要检查的示例代码: package main
我正在为我自己的语言制作一个解释器作为一个业余爱好项目。目前我的解释器只是执行它看到的代码。我听说你应该让解析器从源代码生成 AST。所以我想知道,正如解析器所见,AST 实际上如何使事情比仅仅线性执
我正在为 JavaScript 实现一个突变测试工具。修改 AST 并针对修改后的代码执行测试用例。运行测试用例后,我想将修改后的 AST 恢复为原始 AST,以便我可以重复变异过程。但是,我不知道如
AST 文档:https://www.dartdocs.org/documentation/analyzer_experimental/0.8.0/analyzer/parseCompilationU
更新2:再次感谢@deepak-azad,我设法解决了我的问题:这里是主代码的链接:https://gist.github.com/1714641 更新:感谢@deepak-azad,我补充了代码,但
我正在编写一些 Go AST 代码,而编译器在这一行上令人窒息: var call ast.Expr = ast.CallExpr{Fun: ast.NewIdent("foo"), Args: []
我正在对 c 程序进行静态分析。我搜索了 antlr 网站,似乎没有合适的语法文件为 c 程序生成 ast。这是否意味着我必须从一开始就自己做。或者是有一个更快的方法。我还需要一个可以遍历解析器创建的
是否可以像这样采用带引号的 Elixir 表达式(AST 树): quote do: 1 + 1 => {:+, [context: Elixir, import: Kernel], [1, 1]}
我遇到了这个异常: unexpected AST node: query 我的查询是: SELECT u.user_id, u.username,u.email,u.phone,u.status,r
我是 Java 编程语言的初学者。我想从 java 源代码中提取 AST 并将 AST 打印到文件或标准输出。 我按照本教程学习了如何使用 AST。 http://www.programcreek.c
NodeVisitor 以深度优先的方式遍历 AST,并且在进入时仅访问每个节点一次。因此,用它做一些严肃的事情是有问题的。是否可以更改其默认行为? 最佳答案 也许有人会对一些草拟的例子感兴趣,如何做
目前,我正在努力用 Java 表示我用 SML 编写的 AST 树,这样我就可以随时用 Java 遍历它。 我想知道是否应该在 Java 中创建一个 Node 类,其中包含我想要表示的数据,以及一个数
我正在尝试修改/重构输入的 C 源代码。我试图在输入代码的每一行之后添加一个 printf 语句。 例如如果我的输入是 - void foo(){ // Sample input code
我目前正在使用 eclipse AST 来生成源代码。除了在大多数示例中,我是在独立应用程序中从头开始生成源代码,而不是在 eclipse 插件中。 当从 ASTParser 读取时,您可以通过调用
我有一个 HQL 查询: query = select item.itemNumber from items item where item.stock>0 and item.price it = q
根据 Om Next's documentation : query->ast (om.next/query->ast '[(:foo {:bar 1})]) Given a query expres
如果能学到一些有用的东西,我会非常感激,至于现在,我一直在盲目地行动。所以问题出在python的ast.NodeTransformer上。我想知道是否可以使用这种方式向现有类添加一个函数,而不是生气。
我们希望为 Elixir 开发一个静态代码分析器,以检测并发问题(主要是死锁和竞争条件)。我们对分析器的结构有了一些基本的了解,但我们的问题是哪种 AST 更适合这项任务。正如我们所了解的,Elixi
我在以下代码段中遇到错误 using (var session = Database.OpenSession()) { var q = from x in session.Query()
我正在使用以下 C# 代码: public IList GetAllByExpression(Expression> expression, int startIndex, int count, Fu
我是一名优秀的程序员,十分优秀!