跳转到主要内容

ECMAScript标准的各种解析器。

项目描述

calmjs.parse

理解ECMAScript的一系列解析器和辅助库;是slimit的一个功能接近完整的分支。该包的命令行界面(CLI)前端作为crimp单独发布。

https://github.com/calmjs/calmjs.parse/actions/workflows/build.yml/badge.svg?branch=1.3.1 https://ci.appveyor.com/api/projects/status/5dj8dnu9gmj02msu/branch/1.3.1?svg=true https://coveralls.io/repos/github/calmjs/calmjs.parse/badge.svg?branch=1.3.1

介绍

对于任何与模块系统结合使用的JavaScript代码构建系统,理解给定源集需要或提供哪些模块的能力是至关重要的。由于Calmjs项目提供了一个产生和消费这些模块定义的框架,因此理解给定JavaScript源的能力是必然的。这个目标最初是通过使用slimit实现的,这是一个JavaScript最小化库,它还提供了一个综合的解析器类,该类是用Python Lex-Yacc(即ply)构建的。

然而,截至2017年中期,值得注意的是< span class="docutils literal">slimit 已处于最低维护状态超过四年(其最近一次发布版本0.8.1是在2013年3月26日),同时一些严重的问题在这一时间段内未得到关注和解决。由于Calmjs框架的开发需要尽快解决这些问题,因此决定从< span class="docutils literal">slimit 的解析器部分进行分支。这是为了满足当时Calmjs项目当前的利益。

分支最初是从< span class="docutils literal">slimit 的另一个分支(具体为lelit/slimit)中切割出来的,因为它引入并汇总了来自各个来源的许多错误修复。为了确保更好的质量控制,删除了该分支引入的一些有问题的更改。此外,创建了新的测试以实现全面的覆盖率,并记录了在< span class="docutils literal">slimit 跟踪器上报告的问题,并将适用的问题正式化为测试用例。最后,更新了语法规则,以确保更好地符合ECMA-262(ES5)规范。

< span class="docutils literal">calmjs.parse 的目标是提供一个与< span class="docutils literal">slimit 相似的API,但以更具扩展性的方式实现,并增加了更多的正确性检查。然而,这导致了一些操作可能比< span class="docutils literal">slimit 实现的时间更长,例如输出的格式化。

通过< span class="docutils literal">crimp 提供了一个使用此包的CLI前端。

安装

以下命令可以从PyPI获取< span class="docutils literal">calmjs.parse 的最新稳定版本轮文件,并将其安装到当前Python环境中。

$ pip install calmjs.parse

由于此包使用< span class="docutils literal">ply,它需要为其词法分析器生成优化模块。由于< span class="docutils literal">calmjs.parse 的轮文件包含< span class="docutils literal">ply 3.11(当时发布的最新版本)的预生成模块,因此不需要此额外步骤,但如果安装的< span class="docutils literal">ply 版本超出支持版本,以下注意事项将适用。

如果< span class="docutils literal">ply 的最新版本可用,并且环境升级到该版本,则预生成的模块可能不兼容,这可能导致性能下降和/或错误。如果无法获得< span class="docutils literal">calmjs.parse 的新版本,可以通过手动优化 步骤来纠正,或者如果可能,将< span class="docutils literal">ply 回退到版本3.11。

或者,安装具有最完整预生成模块集的较新版本的< span class="docutils literal">calmjs.parse 轮文件。

一旦安装了包,就可以测试直接使用

手动安装和打包要求

本节面向开发人员和高级用户;包含有关包维护者(例如,Linux)的重要信息,可防止下游用户获得不理想的使用体验。

关于< span class="docutils literal">calmjs.parse 的开发仍在进行中,有关最新功能和错误修复,可以通过以下方式通过git安装开发版本:

$ pip install ply setuptools  # this MUST be done first; see below for reason
$ pip install -e git+https://github.com/calmjs/calmjs.parse.git#egg=calmjs.parse

请注意,所有依赖项都必须预先安装,以便运行< span class="docutils literal">setup.py build 步骤,否则创建预生成模块所需的构建步骤将导致失败。

如果尚未安装< span class="docutils literal">ply

$ python -m pip install -e .
...
running egg_info
...
WARNING: cannot find distribution for 'ply'; using default value,
assuming 'ply==3.11' for pre-generated modules
ERROR: cannot find pre-generated modules for the assumed 'ply'
version from above and/or cannot `import ply` to build generated
modules, aborting build; please either ensure that the source
archive containing the pre-generate modules is being used, or that
the python package 'ply' is installed and available for import
before attempting to use the setup.py to build this package; please
refer to the top level README for further details

如果 setuptools 未安装

$ python -m pip install -e .
...
running egg_info
...
Traceback (most recent call last):
  ...
ModuleNotFoundError: No module named 'pkg_resources'

当然,可以直接克隆 git 仓库,并在源目录的根目录下执行 python setup.py develop;同样,plysetuptools 必须已经可用以便导入。

由于 git 仓库不包含任何预生成的模块或代码,上面的信息可能会在第一次尝试与此软件交互的开发人员或发行版维护人员那里看到。然而,从版本 1.3.0 开始,PyPI 上发布的 zip 存档确实包含这些完全预生成的模块,因此它们可以作为标准安装步骤的一部分使用,即在使用 setup.py 之前不需要导入 ply。虽然可能会显示关于 ply 缺失的相同警告信息,但预生成的模块将允许构建步骤按正常进行。

手动优化

由于 lex 和 yacc 需要生成符号表,优化性能的一种方法是对结果进行缓存。对于 ply,这是通过一个自动生成的模块来完成的。然而,生成的文件带有版本号,因为结果可能特定于安装的 ply 版本。在 calmjs.parse 中,这是通过为 ply 的版本和主要 Python 版本提供特定名称来处理的,因为两者结合会导致自动生成模块的输出和预期存在细微差异。

通常,这种优化的过程是自动的,并且会生成正确的符号表,但是也有一些情况下这会失败,因此出于这个原因,calmjs.parse 提供了一个可选调用的辅助模块和可执行文件,以确保使用正确的编码来生成该文件。其他可能需要这样做的情况是,允许系统管理员为他们的高级用户这样做,因为他们可能在该级别没有写入权限。

要从 shell 中执行优化器,可以使用提供的辅助脚本,如下所示

$ python -m calmjs.parse.parsers.optimize

如果出现警告,指出已定义但未使用的标记,可以安全地忽略。

此步骤通常是可选的,对于通过 PyPI 使用 Python 轮安装此包的用户来说,只要解决安装部分中概述的注意事项即可。

测试安装

为了确保 calmjs.parse 安装正确,可以通过以下方式执行内置测试套件

$ python -m unittest calmjs.parse.tests.make_suite

如果有失败,请在 问题跟踪器 上提交一个包含完整跟踪回溯和/或安装方法的工单。请还包括有关环境的信息,例如本软件的版本、Python 版本、操作系统环境、安装的 ply 版本,以及其他与问题相关的事项。

用法

作为解析库,不提供可执行的 shell 命令。然而,在顶层提供了一个辅助可调用对象,可以立即访问解析功能。可以这样使用

>>> from calmjs.parse import es5
>>> program_source = '''
... // simple program
... var main = function(greet) {
...     var hello = "hello " + greet;
...     return hello;
... };
... console.log(main('world'));
... '''
>>> program = es5(program_source)
>>> # for a simple repr-like nested view of the ast
>>> program  # equivalent to repr(program)
<ES5Program @3:1 ?children=[
  <VarStatement @3:1 ?children=[
    <VarDecl @3:5 identifier=<Identifier ...>, initializer=<FuncExpr ...>>
  ]>,
  <ExprStatement @7:1 expr=<FunctionCall @7:1 args=<Arguments ...>,
    identifier=<DotAccessor ...>>>
]>
>>> # automatic reconstruction of ast into source, without having to
>>> # call something like `.to_ecma()`
>>> print(program)  # equivalent to str(program)
var main = function(greet) {
  var hello = "hello " + greet;
  return hello;
};
console.log(main('world'));

>>>

请注意缩进的变化,因为默认打印机有自己的缩进方案。如果需要注释,可以使用 with_comments=True 来调用解析器。

>>> program_wc = es5(program_source, with_comments=True)
>>> print(program_wc)
// simple program
var main = function(greet) {
  var hello = "hello " + greet;
  return hello;
};
console.log(main('world'));

>>>

此外,请注意注释捕获的限制,如 限制 部分中所述。

解析器类组织在 calmjs.parse.parsers 模块下,每种语言都有自己的模块。在 calmjs.parse.lexers 模块下还提供了同名的对应词法分析器类。目前,仅实现了对 ES5 的支持。

漂亮的/压缩的打印

还提供了一组漂亮的打印辅助函数,可以将抽象语法树(AST)转换回字符串。这些函数或类构造函数可通过在 calmjs.parse.unparsers 和相关模块中组合各种低级类来生成。

还有一个默认的简写辅助函数,可以将之前生成的AST转换回字符串,可以手动调用,并传递某些参数,例如缩进使用的字符:(注意,之前通过 print 显示的 __str__ 调用是通过这种方式实现的)。

>>> from calmjs.parse.unparsers.es5 import pretty_print
>>> print(pretty_print(program, indent_str='    '))
var main = function(greet) {
    var hello = "hello " + greet;
    return hello;
};
console.log(main('world'));

>>>

还有一个用于打印不带任何不必要的空白字符的辅助函数,它充当源压缩器。

>>> from calmjs.parse.unparsers.es5 import minify_print
>>> print(minify_print(program))
var main=function(greet){var hello="hello "+greet;return hello;};...
>>> print(minify_print(program, obfuscate=True, obfuscate_globals=True))
var a=function(b){var a="hello "+b;return a;};console.log(a('world'));

请注意,在第二个例子中,obfuscate_globals 选项仅启用以演示全局作用域的源混淆,这通常不是在生产库代码上启用的选项,该代码旨在由其他包(其他引用原始未混淆名称的源)重用(引用原始未混淆名称的其他源将无法这样做)。

或者,可以使用与最初导入的基对象同名的属性在原始字符串上直接调用。相关关键字参数将被重定向到适当的底层函数,例如

>>> # pretty print without comments being parsed
>>> print(es5.pretty_print(program_source))
var main = function(greet) {
  var hello = "hello " + greet;
  return hello;
};
console.log(main('world'));

>>> # pretty print with comments parsed
>>> print(es5.pretty_print(program_source, with_comments=True))
// simple program
var main = function(greet) {
  var hello = "hello " + greet;
  return hello;
};
console.log(main('world'));

>>> # minify print
>>> print(es5.minify_print(program_source, obfuscate=True))
var main=function(b){var a="hello "+b;return a;};console.log(main('world'));

生成源映射

对于生成源映射,可以通过打印工厂函数之一构建一个低级反解析器实例。传入一个AST节点将产生一个生成器,该生成器生成包含生成的文本片段和其他信息的元组,这些信息将有助于生成源映射。可以使用来自 calmjs.parse.sourcemap 模块的辅助函数以这种方式写入重生的源代码到某个流中,并将结果处理成源映射文件。示例

>>> import json
>>> from io import StringIO
>>> from calmjs.parse.unparsers.es5 import pretty_printer
>>> from calmjs.parse.sourcemap import encode_sourcemap, write
>>> stream_p = StringIO()
>>> print_p = pretty_printer()
>>> rawmap_p, _, names_p = write(print_p(program), stream_p)
>>> sourcemap_p = encode_sourcemap(
...     'demo.min.js', rawmap_p, ['custom_name.js'], names_p)
>>> print(json.dumps(sourcemap_p, indent=2, sort_keys=True))
{
  "file": "demo.min.js",
  "mappings": "AAEA;IACI;IACA;AACJ;AACA;",
  "names": [],
  "sources": [
    "custom_name.js"
  ],
  "version": 3
}
>>> print(stream_p.getvalue())
var main = function(greet) {
...

同样,这同样适用于压缩打印器,它提供了创建带有不必要的空白字符去除和以最短可能的值混淆标识符的压缩输出的能力。

请注意,在先前的例子中,写入方法中的第二个返回值未被使用,并且传递了一个自定义值。这仅仅是因为程序是从字符串生成的,因此 sourcepath 属性没有分配一个可用于填充结果源映射中 "sources" 列表的可用的值。对于下面的例子,直接在程序上分配一个值给该属性。

>>> from calmjs.parse.unparsers.es5 import minify_printer
>>> program.sourcepath = 'demo.js'  # say this was opened there
>>> stream_m = StringIO()
>>> print_m = minify_printer(obfuscate=True, obfuscate_globals=True)
>>> sourcemap_m = encode_sourcemap(
...     'demo.min.js', *write(print_m(program), stream_m))
>>> print(json.dumps(sourcemap_m, indent=2, sort_keys=True))
{
  "file": "demo.min.js",
  "mappings": "AAEA,IAAIA,CAAK,CAAE,SAASC,CAAK,CAAE,CACvB,...,YAAYF,CAAI",
  "names": [
    "main",
    "greet",
    "hello"
  ],
  "sources": [
    "demo.js"
  ],
  "version": 3
}
>>> print(stream_m.getvalue())
var a=function(b){var a="hello "+b;return a;};console.log(a('world'));

io 模块的 readwrite 函数提供了一个用于处理命名流(即已打开的文件或具有名称属性的流对象,如 io.StringIO)的高级API。下面的示例展示了如何使用该函数从流中读取并将相关项目写回只写流。

>>> from calmjs.parse import io
>>> h4_program_src = open('/tmp/html4.js')
>>> h4_program_min = open('/tmp/html4.min.js', 'w+')
>>> h4_program_map = open('/tmp/html4.min.js.map', 'w+')
>>> h4_program = io.read(es5, h4_program_src)
>>> print(h4_program)
var bold = function(s) {
  return '<b>' + s + '</b>';
};
var italics = function(s) {
  return '<i>' + s + '</i>';
};
>>> io.write(print_m, h4_program, h4_program_min, h4_program_map)
>>> pos = h4_program_map.seek(0)
>>> print(h4_program_map.read())
{"file": "html4.min.js", "mappings": ..., "version": 3}
>>> pos = h4_program_min.seek(0)
>>> print(h4_program_min.read())
var b=function(a){return'<b>'+a+'</b>';};var a=function(a){...};
//# sourceMappingURL=html4.min.js.map

对于将多个源简单连接到单个文件,并带有内联源映射(即,sourceMappingURL 是 JSON 字符串的 base64 编码的 data: URL),可以这样做

>>> files = [open('/tmp/html4.js'), open('/tmp/legacy.js')]
>>> combined = open('/tmp/combined.js', 'w+')
>>> io.write(print_p, (io.read(es5, f) for f in files), combined, combined)
>>> pos = combined.seek(0)
>>> print(combined.read())
var bold = function(s) {
    return '<b>' + s + '</b>';
};
var italics = function(s) {
    return '<i>' + s + '</i>';
};
var marquee = function(s) {
    return '<marquee>' + s + '</marquee>';
};
var blink = function(s) {
    return '<blink>' + s + '</blink>';
};
//# sourceMappingURL=data:application/json;base64;...

在这个例子中,io.write 函数提供了漂亮的反解析器,一个生成器表达式,将产生两个来自两个源文件的AST,然后目标和源映射参数相同,这强制源映射生成器生成base64编码。

请注意,如果向混淆全局变量的minifying打印机提供多个AST,则生成的脚本将具有较早混淆的全局名称被后来的混淆名称破坏,因为unparse操作是由io.write函数分别进行的。

将AST提取到dict

为了帮助从ast中提取值到dict,calmjs.parse.unparsers.extractor模块提供了一个名为ast_to_dict的辅助函数来帮助完成这项工作。此函数将接受任何有效的ast作为参数,该ast被解析为:

>>> from calmjs.parse.unparsers.extractor import ast_to_dict
>>> configuration = es5('''
... var config = module.exports = {};
...
... var name = "Morgan"
... msg = "Hello, " + name + "! " + "Welcome to the host.";
...
... config.server = {
...   host: '0.0.0.0',
...   port: process.env.PORT || 8000,
...   threads: 4 + 4,
...   columns: ['id', 'name', 'description'],
...   memory: 1 << 15,
...   msg: msg
... };
...
... // default proxy stub
... config.proxy = {
...   host: 'localhost',
...   port: 8080,
...   options: {
...     "https": !1,
...     "threshold": -100
...   }
... };
... ''')
>>> baseconf = ast_to_dict(configuration)

访问值只需要像映射一样进行

>>> print(baseconf['name'])
Morgan

赋值绑定到整个表达式,即不会解释到单独的现有赋值。

>>> baseconf['config']
{}
>>> baseconf['config.server']['columns']
['id', 'name', 'description']
>>> baseconf['config.server']['msg']
'msg'
>>> baseconf['config.proxy']['options']['threshold']
-100

请注意,-100值涉及使用-运算符折叠一元表达式,并且默认情况下,其他此类表达式将简单地按原样写回。

>>> baseconf['config.proxy']['options']['https']
'!1'
>>> baseconf['msg']
'"Hello, " + name + "! " + "Welcome to the host."'
>>> baseconf['config.server']['threads']
'4 + 4'

为了帮助更广泛地使用,ast_to_dict提供了一个额外的fold_ops参数。当设置为True时,将启用对支持类型的运算符折叠;例如,将尝试将常量折叠为单个值,如下所述ECMAScript规范中运算符的处理。这通常非常有用,可以确保合并连接的字符串,并通过!0!1来规范化布尔值的简写定义,以及其他常见表达式。

>>> foldedconf = ast_to_dict(configuration, fold_ops=True)
>>> foldedconf['config.server']['threads']
8
>>> foldedconf['config.server']['memory']
32768
>>> foldedconf['config.server']['port']
8000
>>> foldedconf['config.proxy']['options']['https']
False
>>> # variables will remain as is
>>> foldedconf['config.server']['msg']
'msg'
>>> # however, in the context of a concatenated string, it will form
>>> # a format string instead.
>>> foldedconf['msg']
'Hello, {name}! Welcome to the host.'

如前所述,任何有效的AST都可以作为输入参数,任何悬垂表达式(即那些没有分配或绑定到名称的表达式)将简单地追加到其最外层asttype的键下。

>>> from calmjs.parse.asttypes import (
...     Identifier, FuncExpr, UnaryExpr)
>>> dict_of_ast = ast_to_dict(es5(u"""
... var i;
... i;
... !'ok';
... function foo(bar) {
...     baz = true;
... }
... (function(y) {
...     x = 1;
... });
... """), fold_ops=True)
>>> dict_of_ast['i']
>>> dict_of_ast[Identifier]
['i']
>>> dict_of_ast[UnaryExpr]  # not simply string or boolean
[False]
>>> dict_of_ast['foo']  # named function resolved
[['bar'], {'baz': True}]
>>> dict_of_ast[FuncExpr]
[[['y'], {'x': 1}]]

高级使用

更低级别的unparse API

当然,前面演示的打印机是使用底层的Unparser类构建的,该类反过来又结合了walker模块中找到的walk函数和Dispatcher类。walk函数使用Dispatcher类的实例遍历AST节点,该实例为特定类型的AST节点提供所有节点类型的描述以及相关处理程序。可以通过现有的规则提供函数来设置这些处理程序。例如,可以构建一个用于混淆标识符名称的同时保持ES5 AST输出缩进的打印机,如下所示:

>>> from calmjs.parse.unparsers.es5 import Unparser
>>> from calmjs.parse.rules import indent
>>> from calmjs.parse.rules import obfuscate
>>> pretty_obfuscate = Unparser(rules=(
...     # note that indent must come after, so that the whitespace
...     # handling rules by indent will shadow over the minimum set
...     # provided by obfuscate.
...     obfuscate(obfuscate_globals=False),
...     indent(indent_str='    '),
... ))
>>> math_module = es5('''
... (function(root) {
...   var fibonacci = function(count) {
...     if (count < 2)
...       return count;
...     else
...       return fibonacci(count - 1) + fibonacci(count - 2);
...   };
...
...   var factorial = function(n) {
...     if (n < 1)
...       throw new Error('factorial where n < 1 not supported');
...     else if (n == 1)
...       return 1;
...     else
...       return n * factorial(n - 1);
...   }
...
...   root.fibonacci = fibonacci;
...   root.factorial = factorial;
... })(window);
...
... var value = window.factorial(5) / window.fibonacci(5);
... console.log('the value is ' + value);
... ''')
>>> print(''.join(c.text for c in pretty_obfuscate(math_module)))
(function(b) {
    var a = function(b) {
        if (b < 2) return b;
        else return a(b - 1) + a(b - 2);
    };
    var c = function(a) {
        if (a < 1) throw new Error('factorial where n < 1 not supported');
        else if (a == 1) return 1;
        else return a * c(a - 1);
    };
    b.fibonacci = a;
    b.factorial = c;
})(window);
var value = window.factorial(5) / window.fibonacci(5);
console.log('the value is ' + value);

每个规则(函数)都有特定选项,这些选项使用特定关键字参数设置,详细信息请参阅各自的docstrings。

在更低级别,ruletypes子模块包含构成当前可用的每个Dispatcher实现的基础定义的原始操作。关于如何将其扩展到仅返回文本的unparse之外的示例,请参阅extractor unparser模块的源代码。

树遍历

在适当的命名模块calmjs.parse.walkers下定义了AST(抽象语法树)通用遍历类。提供了两个默认的遍历类。其中一个是之前演示的ReprWalker类。另一个是Walker类,它提供了一组通用的树遍历方法,用于AST节点树。以下是如何从给定的脚本文件中提取所有对象赋值的示例:

>>> from calmjs.parse import es5
>>> from calmjs.parse.asttypes import Object, VarDecl, FunctionCall
>>> from calmjs.parse.walkers import Walker
>>> walker = Walker()
>>> declarations = es5('''
... var i = 1;
... var s = {
...     a: "test",
...     o: {
...         v: "value"
...     }
... };
... foo({foo: "bar"});
... function bar() {
...     var t = {
...         foo: "bar",
...     };
...     return t;
... }
... foo.bar = bar;
... foo.bar();
... ''')
>>> # print out the object nodes that were part of some assignments
>>> for node in walker.filter(declarations, lambda node: (
...         isinstance(node, VarDecl) and
...         isinstance(node.initializer, Object))):
...     print(node.initializer)
...
{
  a: "test",
  o: {
    v: "value"
  }
}
{
  foo: "bar"
}
>>> # print out all function calls
>>> for node in walker.filter(declarations, lambda node: (
...         isinstance(node, FunctionCall))):
...     print(node.identifier)
...
foo
foo.bar

有关更多详细信息和使用示例,请参阅模块中找到的各种docstrings。

限制

当前注释可能不完整

由于词法分析器/解析器的实现以及ast节点类型的实现方式,如果启用,对注释的暴露位置存在限制。目前,这种限制存在于由一次消耗多个词法标记的生产规则创建的节点 - 只有位于第一个标记之前的注释将被捕获,所有其余的注释将被丢弃。

例如,这种限制意味着在else标记之前的所有注释将被省略(因为注释将由if标记提供),因为If节点的生产规则消耗这两个标记,并且按照当前实现,该节点只为注释提供了一个槽位。同样,三行语句中:标记之前的任何注释也将被丢弃,因为那是生成Conditional节点时消耗的第二个标记。

故障排除

解析器类实例化失败,出现UnicodeEncodeError

对于未将utf8配置为默认编码的平台或系统,在构建解析器实例时,自动表生成可能会失败。例如

>>> from calmjs.parse.parsers import es5
>>> parser = es5.Parser()
Traceback (most recent call last):
  ...
  File "c:\python35\....\ply\lex.py", line 1043, in lex
    lexobj.writetab(lextab, outputdir)
  File "c:\python35\....\ply\lex.py", line 195, in writetab
    tf.write('_lexstatere   = %s\n' % repr(tabre))
  File "c:\python35\lib\encodings\cp1252.py", line 19, in encode
    return codecs.charmap_encode(input,self.errors,encoding_table)[0]
UnicodeEncodeError: 'charmap' codec can't encode character '\u02c1' ...

提供了一个工作区辅助脚本,可以像这样执行

$ python -m calmjs.parse.parsers.optimize

关于此主题的更多详细信息,请参阅本文件的手动优化部分。

警告:导入时存在未使用的标记

这表明导入此包的方法或源没有优化。一种快速解决方法是按照本文件的手动优化部分的说明操作,以确保不再生成这些消息(如果此警告每次导入模块时都会出现,这意味着符号表每次都会被重新生成,并且应该通过生成优化模块来纠正这种额外的计算开销)。

优化模块包含在PyPI上的wheel发行版和源发行版中,但它们不是源存储库的一部分,因为生成的代码从未被提交。如果第三方制作的二进制发行版在导入时导致此警告,则应将其修复以包含优化模块。

此外,还实施了安全措施,以防止从1.3.1版本开始的发行版生成此警告,通过在构建时更严格地执行此优化步骤。但持续的(或粗心的)行为者可能在构建过程中规避此限制,但通过PyPI发布的官方版本应包含对所有支持的ply版本(包括版本3.6至3.11)所需的所有优化。

或者,此问题也可能通过使用pyinstaller发生,如果在使用版本在calmjs.parse-1.3.1之前的版本时未复制ply的包元数据,并且如果未声明这些优化模块的隐藏导入,则始终会发生。以下钩子可以确保在编译的二进制文件中calmjs.parse正确运行

from PyInstaller.utils.hooks import collect_data_files, copy_metadata
from calmjs.parse.utils import generate_tab_names

datas = []
datas.extend(collect_data_files("ply"))
datas.extend(copy_metadata("ply"))
datas.extend(collect_data_files("calmjs.parse"))
datas.extend(copy_metadata("calmjs.parse"))

hiddenimports = []
hiddenimports.extend(generate_tab_names('calmjs.parse.parsers.es5'))

# if running under Python 3 with ply-3.11, above is equivalent to
# hiddenimports = [
#     "calmjs.parse.parsers.lextab_es5_py3_ply3_11",
#     "calmjs.parse.parsers.yacctab_es5_py3_ply3_11",
# ]

性能慢

由于此程序基本上完全分解成非常小的函数,因此与其他实现相比,由于函数调用是Python中最昂贵的操作之一,这导致巨大的性能损失。可能可以通过在分发器描述中将每个asttype Node类型的解析器函数组合在一起进一步优化定义,但这可能需要标记和布局函数没有命名冲突的参数,并且新函数将一次性接收所有这些参数。

尝试安装时的导入错误信息

如错误信息所示,在缺少预生成的模块的情况下尝试安装构建包之前,必须安装plysetuptools包。这种情况可能是由于直接使用源代码库提供的源代码构建,或者没有与已安装的ply版本匹配的预生成的模块。请确保在从源安装之前,ply已安装并可用,如果遇到此错误信息。

贡献

变更日志

1.3.1 - 2023-10-28

  • 将现有的setup.py钩子从安装钩子更改为构建钩子,以确保生成的模块文件存在。如果这些模块中的任何一个缺失,并且所需的依赖项(即plysetuptools)不存在,构建将导致非零退出状态,并且记录的错误信息应反映哪些所需的依赖项缺失。

1.3.0 - 2021-10-08

  • 引入提取器反解析器——一个将有效的AST反解析为字典的反解析器。[ #35 #38 ]

  • 修正了JoinAttr规则类型,以便在定义为空定义值的情况下将其传递给walk,以避免意外的无限递归。[ #36 ]

1.2.5 - 2020-07-03

  • 现在将从Python 3.3+的位置导入Iterable,因为先前位置在Python 3.9中被标记为删除。导入将仍然有回退到先前位置,以保持对Python 2.7的支持。[ #31 ]

  • 提供测试用例辅助工具,以确保提供通用的Program repr签名,以协助测试用例的可移植性。[ #33 ]

  • calmjs.parse.vlq模块中,实现了与encode_vlq辅助工具的完整性和对称性decode_vlq辅助工具。[ #33 ]

1.2.4 - 2020-03-17

  • 正确使用__doc__以支持第2级优化模式。[ #29 #30 ]

  • 纠正了一些小的字符串定义语法,其中应使用原始字符串前缀,但之前并没有这样做。

1.2.2 - 2020-01-18

  • 正确包含sdist中的LICENSE文件。[ #27 #28 ]

  • 包括一些先前添加的测试用例的正确测试数据通用形式,以更好地适应已计划的功能。

1.2.1 - 2019-11-21

  • 修复了由于词法分析器处于无法立即区分REGEX或DIV标记类型的状态而导致正则表达式语句失败的错误问题。因为像RBRACE、PLUSPLUS或MINUSMINUS这样的标记必须被解析器消费才能进行区分,但由于yacc的预览特性,DIV标记将被过早地产生,而实现这一点的唯一方法是在错误处理阶段。[ #25 #26 ]

  • 之前的修复部分还移除了将换行符或注释标记报告为解析错误信息的一部分。

1.2.0 - 2019-08-15

  • 对注释解析的部分支持。目前并非所有注释在解析过程中都会被捕获,因为希望可以通过它提供的通用comments属性简化对它们的访问。asttypes.Node实例。[ #24 ]

    • 通过将with_comments=True传递给解析器来启用。

    • 局限性在于,如果一个节点有多个标记槽(例如if...else),则位于第一个之前的注释将被捕获,而位于后续的注释将被忽略。修复将涉及提供完整的语法树节点类型,并且解析规则需要以更灵活的方式实现,以便能够生成此类类型。

    • 使用comments属性可以访问节点之前立即的所有注释。

    • 这些注释节点不会通过children()方法返回。

    • 各种功能和方法的更新已考虑注释。值得注意的是,源映射生成将能够处理包含换行符的源片段,前提是同时提供了colno和lineno。

  • 在报告特定字符位置的同时,正确处理错误的十六进制/Unicode转义序列;同时报告未终止字符串字面量的起始位置。[ #23 ]

1.1.3 - 2018-11-08

  • 修复了某些非可选空格在minify打印示例中被省略的问题,这导致了输出格式不正确。[ #22 ]

1.1.2 - 2018-08-20

  • 对于合成的节点或没有分配列或行号的节点,默认的repr不再会出错。[ #20 ]

  • 1.1.0中引入的用于字符串行续的相同行终止正则表达式现在应用于词法分析器的行终止模式,以便更正Windows特定的<CR><LF>序列的行号。[ #21 ]

1.1.1 - 2018-08-11

  • 确保在布局处理器指定了用于组合处理的布局规则元组的情况下正确地执行布局规则块的计算。[ #19 ]

    • 这种错误在多个布局规则标记以重复模式产生并且有布局处理器规则的情况下表现得特别严重,而这种模式在标准打印机的正常代码中并不常见(因为布局块很多,它们通常不会产生重复的模式)。然而,在省略分号的压缩输出中,这表现得尤为严重,因为这基本上保证了任何适合模式的闭合块将被简单地删除。

1.1.0 - 2018-08-07

  • 修复字符串中行续的实现。这也意味着对最小化反解析器的更改,以便继续删除行续序列。[ #16 ]

  • 通过引入专用令牌类型来修复 ASI(自动分号插入)的实现,这样空语句的产生就不再发生,并且将其与应应用 ASI 的语句的生产区分开来,从而不再导致由于此问题而解析成功的情况。[ #18 rspivak/slimit#29 rspivak/slimit#101 ]

1.0.1 - 2018-04-19

  • 确保 es5 反解析器在构造函数中传递 prewalk_hooks 参数。

  • 一些包装修复;还包括 ply-3.11 的优化模块。

1.0.0 - 2017-09-26

完全支持源映射;以下是一些使这成为可能的更改

  • 由新的 io 模块提供的高级读写功能。

  • 现在有一个用于标记需要额外处理的特定令牌的 Deferrable 规则类型。对这个的支持已经改变了处理此设置的各个 API。

  • 为了支持源映射生成,增加了一些新的规则类型。

  • 源映射写入函数的参数顺序已修改,以更好地支持输入节点源路径跟踪功能。其返回值现在也符合 encode_sourcemap 函数的顺序。

  • 规则类型中的块类型已重命名,并引入了一个新类型称为 StreamFragment,以便多个源输出到单个流可以被源映射过程正确跟踪。

  • rspivak/slimit#66 现在应完全支持。

最小化打印器现在具有缩短/混淆标识符的能力

  • 提供一个用于缩短标识符的名称混淆函数,以进一步实现最小化输出。请注意,这尚未完全达到 slimit 的最小化级别;未来版本可能将此功能作为各种 AST 转换实现。

  • 还提供了删除不需要的分号的能力。

其他重要更改

  • 为 1.0.0 版本进行了各种内部类和函数名称的更改。以下是与之前主要版本相比相对于此包名的根模块的更改的非详尽列表

    asttypes
    • 删除了所有 slimit 兼容功能。

    • Switch(错误的版本)已删除。

    • SwitchStatement -> Switch

    • SetPropAssign 构造函数: parameters -> parameter

    • UnaryOp -> UnaryExpr

    • 还删除了其他一些一般弃用的功能。

    factory
    • Factory -> SRFactory

    visitors
    • 已删除(详情见下文)。

    walkers
    • visitors.generic.ReprVisitor -> walkers.ReprWalker

    layouts
    • 模块已被拆分和重新组织;简单的基本模块可以在 handlers.core 中找到,与缩进相关的功能现在在 handlers.indentation 中。

    unparsers.base
    • .default_layout_handlers -> handlers.core.default_rules

    • .minimum_layout_handlers -> handlers.core.minimum_rules

    unparsers.prettyprint
    • 重命名为 unparsers.walker

    • 实际上实现的是标准树遍历,从未有正确实现的访客函数/类。

    vlq
    • .create_sourcemap -> sourcemap.create_sourcemap

  • 将访客类拆分,因为它们并不真正符合描述的访客。新的实现(calmjs.parse-0.9.0)实际上是行者,所以将它们移动到那个名称,并保持原样。方法也被重命名,以更好地反映它们的实现和目的。

  • 移除了许多slimit兼容性模块、类和错误实现的函数。

  • 现在强制使用Python 3的str类型(Python 2中的unicode),以避免发生类型不匹配的各种失败情况。

  • 基本Node asttype具有sourcepath属性,用于跟踪节点的原始源;如果已分配,则应将其所有未定义sourcepath的子节点视为来自该源。

  • 还通过calmjs.parse.io模块提供了一个更高级别的函数,用于通过流使用。

  • 添加分号和大括号作为渲染的结构。

错误修复

  • 以非单词字符开始的函数现在将始终在其前面渲染一个空格,以避免语法错误。

  • 纠正walk函数中的不正确的迭代器使用。

  • 确保列表分隔符不使用后续省略节点(Elision node)的rowcol位置。

  • 在错误时,词法分析器只报告真实的词法分析标记(现在丢弃由ASI生成的标记,因为它们在原始源中不存在,这导致行/列报告混乱)。

  • rspivak/slimit#57,事实上,'\0'不被视为八进制,而是一个<NUL>字符,而解析规则实际上没有包含在此版本之前拉入的词法分析器补丁中。

  • rspivak/slimit#75,现在默认禁用命名闭包的名称阴影选项(混淆的命名闭包不会被混淆的子名称所阴影)。

  • 表达式不能再包含无命名的函数。

0.10.1 - 2017-08-26

  • 修正了词法分析器的行号报告,并正确传播到解析器和Node子类。修复了moses-palmer/slimit@8f9a39c7769中添加的不正确实现(当存在注释时,行号会错误地标记,以及moses-palmer/slimit@6aa92d68e0中添加的yacc跟踪,其中自定义词法分析器类不提供ply所需的属性)。

  • 实现了列号的记账。

  • 对AST进行了其他各种更改,但由于兼容性原因(为了避免强制进行主要semver升级),它们仅通过ES5解析器的标志来启用。

  • 更正了switch/case语句处理的方式,这可能会破坏兼容性;修复仅在标记后启用。rspivak/slimit#94

  • Node的repr形式现在默认显示行/列号信息;ReprVisitor类的visit方法没有改变,只是通过可调用形式调用它,因为那是__repr__的调用目标。这是提到的一个好时机,即命名方法提供了最大的控制,如已记录的文档所述。

  • 解析器现在在其构造过程中接受asttypes模块。

  • 提供源映射生成类的支持。

  • 引入了一个灵活的访问者函数/状态类,该类接受生成与源映射生成兼容的块元组的规则的定义。可以使用此模块实现新的美化打印和压缩方式。

0.9.0 - 2017-06-09

项目详情


下载文件

下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于 安装包 的信息。

源分布

calmjs.parse-1.3.1.zip (469.7 kB 查看散列

上传时间

构建分布

calmjs.parse-1.3.1-py3-none-any.whl (297.0 kB 查看散列

上传时间 Python 3

calmjs.parse-1.3.1-py2-none-any.whl (305.4 kB 查看散列

上传时间 Python 2

支持