跳转到主要内容

Python的高级宏展开器和语言实验室

项目描述

mcpyrate

Python的高级宏展开器和语言实验室。重点是正确性、宏启用工作的功能完整性,以及简洁性。

我们追求开发友好性。 mcpyrate 为宏启用代码提供正确的覆盖率,尽早报告错误,并使显示任何宏展开的步骤变得容易 - 具有语法高亮、使用站点文件名和源行号

mcpyrate stepping through expansion of letseq from demos 图1。 mcpyrate 正在演示中逐步执行 letseq

mcpyrate 基于 mcpy,采用类似的明确和紧凑的方法,但拥有许多新特性。我们的某些特性深受 macropy 的启发,例如准引号、宏参数和展开追踪。mcpyrate 的原创特性包括通用引导器、集成 REPL 系统(包括 IPython 扩展)以及支持链式整个模块源和 AST 转换器,这些特性源自早期的原型 imacropypydialect;以及多阶段编译(又称 预编译,灵感来自 Racket)和标识符宏。

我们使用 语义版本控制。由于与 mcpy 2.0.0 几乎兼容但不完全兼容,因此初始版本为 3.0.0。宏函数提供的命名参数与 mcpy 有一些不同;详情请参阅 主要用户手册 中的 mcpy 差异

100% Python supported language versions supported implementations CI status codecov
version on PyPI PyPI package format dependency status
license: MIT open issues PRs welcome

本 README 中的某些超文本功能,如指向详细文档的本地链接,在 PyPI 上查看时不受支持;请在 GitHub 上查看以正确运行。

目录

新闻

2024年9月26日

《mcpyrate》项目仍然活跃,但它已经完成了我的需求,并且在过去两年中不需要维护。刚刚,我终于添加了对 Python 3.11 和 3.12 的支持。

如果您想在这个项目中提供帮助,请从 这里 开始。小小的贡献也很重要!

第一个例子

mcpyrate 只用两个源文件即可启用宏功能

# mymacros.py with your macro definitions
def echo(expr, **kw):
    print('Echo')
    return expr

# application.py
from mymacros import macros, echo
echo[6 * 7]

或者甚至只需要一个源文件

# application.py
from mcpyrate.multiphase import macros, phase

with phase[1]:
    def echo(expr, **kw):
        print('Echo')
        return expr

from __self__ import macros, echo
echo[6 * 7]

要运行任何示例,请使用 macropython -m applicationmacropython application.py

更多示例可以在 demo/ 子目录中找到。安装 mcpyrate 后,请转到 mcpyrate 项目目录,并像 macropython demo/anaphoric_if.py 一样运行它们。

在测试中运行额外示例

测试包含更多用法示例,包括高级示例。请参阅 mcpyrate/test/ 子目录。

必须使用源树中的 mcpyrate 运行测试,因为它们期望在模块 mcpyrate.test 中运行,但 test 子目录不是安装的一部分。因此,如果 mcpyrate 的顶级模块名称解析为已安装的副本,则不会存在名为 mcpyrate.test 的模块。

要使用源树中的 mcpyrate 运行,请将 macropython 替换为 python3 -m mcpyrate.repl.macropython。例如,要运行演示,请使用 python3 -m mcpyrate.repl.macropython demo/anaphoric_if.py,或要运行测试,请使用 python3 -m mcpyrate.repl.macropython -m mcpyrate.test.test_compiler。在这里,第一个 -m 传递给 python3,而第二个 -m 传递给 macropython

如果您只想运行所有测试,请使用 python3 runtests.py

特性

  • 敏捷开发工具.

    • 多阶段编译:在定义宏的同一模块中使用宏。
    • 通用引导器:macropython。在主程序中导入和使用宏。
    • 交互式控制台:macropython -i。在控制台会话中导入、定义和使用宏。
      • 类似于 code.InteractiveConsole 的可嵌入。请参阅 mcpyrate.repl.console.MacroConsole
    • IPython 扩展 mcpyrate.repl.iconsole。在 IPython 会话中导入、定义和使用宏。
    • 查看 REPL 系统的完整文档
  • 运行时编译器访问.

    • 即时扩展、编译并运行启用了宏的代码片段。
    • 接受源代码和 AST 输入。(使用伪引号方便地创建 AST。)
    • 动态创建的代码片段支持与从磁盘上的源文件导入代码相同的所有功能。
    • 查看 编译器的完整文档。示例可以在 mcpyrate/test/test_compiler.py 中找到。
  • 测试和调试.

    • 工具如 Coverage.py 正确报告语句覆盖率。
    • 宏扩展错误在宏扩展时报告,并带有使用站点跟踪。
    • 具有逐级展开分解的调试输出 (逐行展开)。查看宏 mcpyrate.debug.step_expansion
      • 具有表达式和块模式。根据需要使用 step_expansion[...]with step_expansion
      • 输出具有 语法高亮,并且基于 AST 的 lineno 字段进行 行号
        • 此外,step_expansion 还突出显示了当前绑定在扩展器中的宏的名称。
        • 行号来自 语句 AST 节点。
      • 显示了不可见的节点 ast.Moduleast.Expr,因为特别是 ast.Expr 是一个常见的陷阱。
      • 要逐步展开运行时 AST 值,请查看宏 mcpyrate.metatools.stepr文档
    • 手动展开一次。查看 expander.visit_once;通过宏的命名参数获取 expander。另请参阅 mcpyrate.metatools 中的 expand1sexpand1r 宏。
  • 闪电速度.

    • 创建并维护字节码缓存(.pyc)。为未更改的模块节省宏扩展成本。使 mcpyrate 在平均情况下快速执行。

      除了 .py 源文件本身之外,我们还会以类似于 make 的方式递归地查看它导入宏的定义文件。

      mtime 是源文件及其宏依赖项中最新者,考虑递归,因此如果源文件宏依赖树中的任何宏定义发生变化,Python 将将该源文件视为“已更改”,从而重新展开和重新编译它(因此,更新相应的 .pyc)。

    • 注意:不支持 PEP 552 - 确定性 pycs;我们仅支持默认的 mtime 无效化模式,至少目前是这样。

  • 伪引号,具有高级功能。

    • 卫生地插入常规值和宏名称。
    • 伪引号代码内的延迟宏扩展。用户可控。
    • 逆伪引号操作符。查看函数 mcpyrate.quotes.unastify
      • 将伪引号 AST 转换回直接 AST,通常用于在重新引用之前进一步处理。
        • 不是反引号;我们也有这些,但反引号的目的将值插入到引用代码中。相反,逆伪引号在应用任何反引号之后取消伪引号操作本身。
    • 查看 伪引号系统的完整文档
  • 宏参数.

    • 可选。通过在您的宏函数上使用 @parametricmacro 装饰器来声明。
    • 使用括号调用,例如 macroname[arg0, ...][expr]。如果没有参数,则省略该部分,例如 macroname[expr]
    • macroname[arg0, ...] 语法在 exprblockdecorator 宏调用中替代裸露的 macroname 使用。
    • 命名参数 args 是宏参数 AST 的原始 list。如果没有发送参数,或者宏函数不是参数化的,则为空。
  • 标识符(也称为名称)宏.

    • 可选。通过在您的宏函数上使用 @namemacro 装饰器来声明。
    • 可用于创建仅出现在特定宏调用中的魔法变量。
  • 方言,即整个模块的源代码和 AST 转换.

    • 类似于 Racket 的 #lang,但适用于 Python。
    • 定义使用 Python 表面语法的语言,但更改语义;或者插入一个按模块分发的转换器,该转换器(在导入时)将其他编程语言的源代码编译成启用了宏的 Python。也可以定义一个方言优化器。方言可以串联。
    • 实际上,没有限制。例如,请参阅 unpythonic 中的 dialects 模块,以获取示例方言。
    • 对于调试,请使用 from mcpyrate.debug import dialects, StepExpansion
    • 如果您正在编写一个将整个模块拼接到模板中的完整模块 AST 转换器,请参阅 mcpyrate.splicing.splice_dialect
    • 请参阅 方言系统的完整文档
  • 便利性.

    • 相对宏导入(对于包中的代码),例如 from .other import macros, kittify
    • 扩展器自动修复 AST 中缺失的 ctx 属性,因此您不需要在宏中关心这些。
    • 在大多数情况下,扩展器还会自动填写正确的内容位置信息(用于覆盖率报告)。如果您正在丢弃输入中的节点,那么您可能必须稍加注意,并适当地使用 ast.copy_location
    • 可以在同一个 with 中调用多个块宏(相当于嵌套它们,最外层最左侧)。
    • AST 访问器和转换器 类似于 macropyWalker,可以轻松管理子树的上下文状态,并在整个遍历过程中收集项目。完整文档
    • 用于在协作宏(和扩展器)之间通信的 AST 标记(伪节点)。
    • 使用 gensym 创建一个全新、未使用的词法标识符。
    • 使用 unparse 将 AST 转换为相应的源代码,可选地带有语法高亮(用于终端输出)。
    • 使用 dump 直接查看 AST 表示,具有(主要)PEP8-兼容的缩进,可选地带有语法高亮(节点类型、字段名称、裸值)。

文档

mcpyrate 的完整文档位于 doc/ 子目录中。一些快速链接

我们的目标是完整的文档。 如果您发现缺少某些内容,请 提交问题。 (如果您已经弄清楚缺少文档的内容,那么文档 PR 也受欢迎!)

与其他 Python 宏展开器的差异

mcpyratemacropymcpy 不兼容。本节总结了它们之间的区别。

以下列出的是最小化的区别列表。此外,我们还提供了许多以前宏展开器中不可用的高级功能,例如方言(完整模块转换)、多阶段编译和运行时编译器访问(包括动态模块创建)。

macropy 的差异

  • mcpyrate 没有宏注册表;每个宏函数都是其自己的分发器。请参阅名为 syntax 的参数。

  • mcpyrate 中,默认情况下宏展开是从外向内(仅从外向内)进行的。

    • 没有 yield。要实现从内向外展开,请在您的宏实现中明确递归到您想要切换到从内向外处理的地方。请参阅主要用户手册
  • 由展开器填充的命名参数(在宏定义中的 **kw)几乎完全不同。

    • 没有 gen_sym 参数;请使用 mcpyrate.gensym 函数。我们使用 UUID,避免了对词法扫描的需求。
  • 宏参数

    • 使用方括号传递。
  • 宏展开错误报告

    • 像往常一样抛出异常,不要使用 assert False
  • 伪代码

    • 伪代码不会自动进行宏展开。
    • 卫生伪代码的工作方式不同。
      • 没有单独的 hq[]。相反,我们提供 h[],它是一个 卫生反引号,可以捕获值或宏。
      • 您可以捕获任意表达式,而不仅仅是标识符。
      • 您也可以捕获宏。
    • ast_literalname 的等效功能增加了,并添加了一个操作符,用于将 AST 列表插值为 ast.Tuple(一个用例是将可变数量的宏参数拼接到伪代码宏调用中)。
  • AST 遍历器

    • ast.NodeTransformerast.NodeVisitor 精神相似,但功能相当于 macropy.core.walkers.Walker。但它的工作方式不同。
    • 特别地,使用与 ast.NodeTransformer 中的 visit 相同的显式递归;没有 stop
    • 有关 ctx 机制,请参阅 withstategeneric_withstate(后者与前者相关,就像 generic_visitvisitast.NodeTransformer 中的关系一样)。

mcpy 的差异

  • 由展开器填充的命名参数
    • 没有 to_source;请使用 mcpyrate.unparse 函数。您可能想要 unparse(tree, debug=True, color=True)
    • 没有 expand_macros;请请求 expander,然后调用 expander.visit

安装与卸载

从 PyPI 安装

pip install mcpyrate

从源代码安装

从 GitHub 克隆存储库。然后,在终端中导航到它,并

pip install .

要卸载

pip uninstall mcpyrate

理解实现方式

我们遵循 mcpy 哲学,即宏展开器不是火箭科学。请参阅 CONTRIBUTING.md

Emacs 语法高亮显示

此 Elisp 片段将特定于 mcpyrate 的关键字语法高亮显示添加到您的 Emacs 配置中

  (defun my/mcpyrate-syntax-highlight-setup ()
    "Set up additional syntax highlighting for `mcpyrate` in Python mode."
    ;; adapted from code in dash.el
    (let ((more-keywords '("macros" "dialects"
                           "q" "u" "n" "a" "s" "t" "h"))
          ;; How to make Emacs recognize your magic variables. Only for the anaphoric if demo.
          ;; A list, like `more-keywords`, even though in the example there is only one item.
          (magic-variables '("it")))
      (font-lock-add-keywords 'python-mode `((,(concat "\\_<" (regexp-opt magic-variables 'paren) "\\_>")
                                              1 font-lock-variable-name-face)) 'append)
      (font-lock-add-keywords 'python-mode `((,(concat "\\_<" (regexp-opt more-keywords 'paren) "\\_>")
                                              1 font-lock-keyword-face)) 'append)
  ))
  (add-hook 'python-mode-hook 'my/mcpyrate-syntax-highlight-setup)

已知问题:由于某种原因,在给定会话期间,这仅在打开第二个 Python 文件时生效。会话期间打开的第一个 Python 文件显示为默认的 Python 语法高亮显示。这可能是与 font-lock 的初始化顺序以及正在使用的 python-mode 有关。

anaconda-mode 进行了测试。

安装(针对 Emacs 定制初学者)

如果您使用 Spacemacs 套件,则可以将此片段插入到 dotspacemacs/user-config 函数中。(如果您使用 Emacs 键绑定,请按 M-m f e d 打开您的配置文件。)以下是我的 spacemacs.d 以供参考;语法高亮代码在 prettify-symbols-config.el 中,并在 init.el 中的 dotspacemacs/user-config 函数中调用。

在基本的Emacs配置中,代码片段将被添加到启动文件~/.emacs中,或者如果你有一个.emacs.d/目录,则添加到~/.emacs.d/init.el中。

为什么需要宏?

尽管它们有着可怕的声誉,但语法宏是解决某些类型问题的一种干净的解决方案。宏的主要用例可以分为几个(不一定完全正交)类别

  1. 语法抽象,提取那些不能作为常规运行时函数提取的模式的工具。常规函数定义是提取某些类型模式的一种工具;宏是另一种这样的工具。这两个工具都旨在消除样板代码,通过允许定义可重用的抽象。

    宏可以替代设计模式,特别是那些围绕语言限制的设计模式。参见Norvig的关于设计模式的经典演讲。具体示例见Seibel

  2. 源代码访问。任何需要获取表达式(或代码块)的源代码副本并运行相同代码的操作都是宏的理想候选。这对于实现例如调试日志记录和测试的工具很有用。

  3. 评估顺序操作。通过编辑代码,宏可以改变代码的评估顺序,以及决定是否运行某个特定的表达式或语句。

    例如,宏允许在严格语言中正确地抽象delay/force。其中force是一个常规函数,但delay需要是一个宏。参见我们的延迟评估演示

  4. 受其他编程语言启发的语言级功能。例如,unpythonic提供了表达式局部变量(let)、自动尾调用优化(TCO)、自动柯里化、懒函数和多轮连续。

    Racket指南所指出的,这对于某些其他语言设计者未批准的语言级功能尤其方便。宏允许用户扩展语言。方言将这个想法更进一步。

  5. 编译时验证。例如,参见Alexis King(2016): Racket中的简单、安全的多方法。我们的姊妹项目unpythonic也使用宏来执行一些简单的检查,以确保某些辅助结构出现在有效的位置,如果不在此处,则在编译时出错。

  6. 嵌入式领域特定语言(eDSLs).

    在这里,“嵌入式”意味着领域特定语言(DSL)无缝集成到周围的编程语言(宿主语言)中。使用嵌入式DSL,无需为DSL实现整个新的解析器,许多操作可以借用宿主语言。中缀算术表示法和正则表达式是常见的嵌入式eDSL示例,这些示例嵌入在许多编程语言中。

    (请注意,通用编程语言并不严格需要提供中缀算术;许多Lisp不这样做。当然,可以通过宏添加中缀算术的形式;这里有一个非常紧凑的Racket解决方案(在页面上搜索“more operators”)。)

    嵌入式方法显著降低了实现DSL所需的工作量,因此使得小型DSL成为一类设计问题的有吸引力的解决方案。语言构建工具包可能比最初听起来更有用。

  7. 移动代码,由macropy开创。在域之间穿梭代码,同时仍然允许它在单个代码库中一起编写。

话虽如此,宏是软件开发中的“核选项”。通常一个好的策略是尽可能多地实现常规函数,然后在上面添加一个小型宏,用于那些其他情况下不可能实现(或过于冗长、过于复杂或只是过于复杂的)部分。《我们的延迟评估演示》是这个策略的一个小例子。

更广泛的例子是宏启用的测试框架 unpythonic.test.fixtures,以及 unpythonic.syntax 中的 let 结构(尽管在这种情况下,宏相当复杂,以与Python的词法作用域集成)。如果您对 “过于复杂的” 的说法感到好奇,比较 unpythonic.ambunpythonic.syntax.forall 的实现——宏版本要干净得多。

关于借用语言特性的例子,请参阅 GrahamPython中的Clojure的withunpythonic.syntax 以及Racket社区这些创造 [1] [2] [3]。但也要注意,宏并不总是必需的:例如,模式匹配、可恢复异常、多重分派 [1] [2]

项目详情


下载文件

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

源代码分发

mcpyrate-3.6.3.tar.gz (125.1 kB 查看哈希值)

上传时间 源代码

构建版本

mcpyrate-3.6.3-py3-none-any.whl (130.2 kB 查看哈希值)

上传时间 Python 3

由以下支持