用于特定目的的javascript解析器

时间:2015-01-19 21:43:18

标签: javascript abstract-syntax-tree

我正在尝试创建一个工具来查找.html文件中缺少的翻译。我们的一些翻译是在JS代码中在运行时完成的。我想把这些放在一起。下面是一个例子。

<select id="dropDown"></select>

// js
bindings: { 
 "dropDown": function() {
              translate(someValue);
              // translate then set option
           }
 }

以上你可以看到我有一个下拉值,其中创建了值&amp;在运行时翻译。我当时认为AST是实现这一目标的正确方法。基本上我需要通过.html文件查找缺少内联翻译的标签(使用{{t value}}完成)并搜索相应的.js文件以进行运行时翻译。有没有更好的方法来实现这一目标?有关创建AST的工具的任何建议吗?

1 个答案:

答案 0 :(得分:4)

我认为你想要寻找代码中的模式。特别是,我认为您希望为每个HTML select构造确定是否存在具有正确嵌入ID名称的相应的,形状正确的JavaScript片段。

你可以用AST做到这一点,对吧。在您的情况下,您需要一个用于HTML文件的AST(节点本质上是HTML标记),其中包含用于包含已解析JavaScript的脚本块()标记的子AST。

为此你需要两个解析器:一个解析HTML(一个讨厌只是因为HTML是乱七八糟),它产生一个包含脚本节点只有文本的树。然后,您需要一个JavaScript解析器,您可以将其应用于Script标签下的文本blob,以生成JavaScript AST;理想情况下,将它们拼接到HTML树中以替换文本blob节点。现在你有一个混合树,一些节点是html,有一些是JavaScript的子树。理想情况下,HMTL节点标记为HTML,JavaScript节点标记为Javascript。

现在,您可以在树中搜索选择节点,获取ID,并搜索预期结构的所有javascript子树。

你可以在程序上对匹配进行编码,但它会很混乱:

  for all node
     if node is HTML and nodetype is Select
        then
            functionname=node.getchild("ID").text
            for all node
               if node is JavaScript and node.parent is HTML and nodetype is pair
                  then if node.getchild(left).ext is "bindings"
                       then if node.getchild(right)=structure
                          then...   (lots more....)

这里有很多头发。技术上它只是汗水。您必须知道(并编码)树的精确细节,正确地爬上和缩小其链接并逐个检查节点类型。如果语法稍有变化,这段代码也会中断;它对语法知之甚多。

您可以通过从头开始编写自己的解析器来完成此操作。更多的汗水。

有一些工具可以使这更容易;见Program Transformation Systems。这些工具允许您定义语言语法,并为这些语法生成解析器和AST构建器。 (作为一般规则,他们非常擅长定义工作语法,因为它们旨在应用于多种语言)。这至少会为这个过程带来很多结构,它们提供了很多机制来完成这项工作。但好处是你可以表达模式,通常是源语言表面语法,这可以使表达更容易。

其中一个工具是我们的DMS软件再造工具包(我是架构师)。

DMS已经拥有脏HTML和完整的JavaScript解析器,因此不需要构建它们。 您必须为DMS编写一些代码来调用HTML解析器,找到脚本节点的子树,然后应用JavaScript解析器。 DMS通过允许您将一个文本块解析为语法中的任意非终结符来实现这一点。在这种情况下,您希望将这些blob解析为非终结表达式。

完成所有这些后,您现在可以编写支持检查的模式:

 pattern select_node(property: string): HTML~dirty.HTMLform =
       " <select ID=\property></select> ";

 pattern script(code: string): HTML~dirty.HTMLform =
       " <script>\code</script> ";

 pattern js_bindings(s: string, e:expression):JavaScript.expression =
       " bindings : { \s : function () 
                            { translate(\e);
                            }
                    } ";

虽然这些模式看起来像文本,但它们被DMS解析为具有参数列表元素的占位符节点的AST,由&#34; \ nnnn&#34;表示。内 (meta)引用&#34; ...&#34;围绕着感兴趣的节目文本。这种AST模式可以与AST模式匹配;如果模式树匹配,它们匹配,然后模式变量叶被捕获为绑定。 (请参阅下面的注册表:PatternMatch,以及匹配的插槽(布尔值)和绑定(匹配产生的绑定子树数组)得到的匹配参数。工具生成器的一大胜利:他不需要知道多少关于语法的细节,因为他编写了模式,并且该工具隐含地为他生成了所有树节点。

使用这些模式,您可以编写程序PARLANSE(DMS的Lisp样式编程语言)代码来实现检查(为缩短演示文稿而采取的自由):

(;; `Parse HTML file':
      (= HTML_tree (HMTL:ParseFile .... ))
    `Find script nodes and replace by ASTs for same':
       (AST:FindAllMatchingSubtrees HTML_tree
         (lambda (function boolean [html_node AST:Node])
           (let (= [match Registry:Match]
                   (Registry:PatternMatch html_node "script"))
              (ifthenelse match:boolean
                (value (;; (AST:ReplaceNode node
                               (JavaScript:ParseStream
                                  "expression" ; desired nonterminal
                                  (make Streams:Stream
                                       (AST:GetString match:bindings:1))))
                       );;
                  ~f ; false: don't visit subtree
                )value
                ~t ; true: continue scanning into subtree
              )ifthenelse
           )let
         )lambda )
     `Now find select nodes, and check sanity':
       (AST:FindAllMatchingSubtrees HTML_tree
         (lambda (function boolean [html_node AST:node])
           (let (;; (= [select_match Registry:Match] ; capture match data
                       (Registry:PatternMatch "select" html_node)) ; hunt for this pattern
                    [select_function_name string]
                );;
              (ifthenelse select_match:boolean
                (value (;; `Found <select> node.
                            Get name of function...':
                           (= select_function_name
                             (AST:GetString select_match:bindings:1))

                           `... and search for matching script fragment':
                            (ifthen
                              (~ (AST:FindFirstMatchingSubtree HTML_tree
                                     (lambda (function boolean [js_node AST:Node])
                                       (let (;; (= [match Registry:Match] ; capture match data 
                                                (Registry:PatternMatch js_node "js_bindings")) ; hunt for this pattern
                                          (&& match:boolean
                                             (== select_match:bindings:1
                                                 select_function_name)
                                         )&& ; is true if we found matching function
                                     )let
                                   )lambda ) )~
                              (;; `Complain if we cant find matching script fragment'
                                  (Format:SNN `Select #S with missing translation at line #D column #D'
                                     select_function_name
                                     (AST:GetLineNumber select_match:source_position)
                                     (AST:GetColumnNumber select_match:source_position)
                                  )
                              );;
                           )ifthen

                     );;
                  ~f ; don't visit subtree
                )value
                ~t ; continue scanning into subtree
              )ifthenelse
           )let
         )lambda )
);;

此过程代码首先解析生成HTML树的HTML源文件。所有这些节点都标记为来自&#34; HTML~dirty&#34;的langauge。 然后它扫描该树以找到SCRIPT节点,并用从所遇到的脚本节点的文本内容的JavaScript表达式解析获得的AST替换它们。最后,它找到所有SELECT节点,选出ID子句中提到的函数的名称,并检查所有JavaScript AST是否匹配&#34;绑定&#34; OP指定的表达式。所有这些都依赖于模式匹配机制,而后者又依赖于低级AST库,它提供了各种方法来检查/导航/更改树节点。

我遗漏了一些细节(尤其是错误处理代码),显然没有对此进行测试。但是这给出了使用DMS的方法。

其他程序转换系统中也提供了类似的模式和匹配过程。