HY编程快速入门实践课第三章 HY宏入门

HY 等Lisp语言最有特性的就是宏了,如果其它语言也实现了Lisp里的宏,那么它们就成为了一种Lisp方言,由此可见宏的独特性。本章内容参考:Macros — Hy 0.29.0 manual

先看一下前一章讲的宏的例子:HY编程快速入门实践课第二章 跟着python学HY-CSDN博客

宏是 Lisp 的基本元编程工具。宏是在编译时(即,当 Hy 程序被转换为 Python ast 对象时)调用并返回代码的函数,代码成为最终程序的一部分。下面是一个简单的示例:

(print "Executing")
(defmacro m []
  (print "Now for a slow computation")
  (setv x (% (** 10 10 7) 3))
  (print "Done computing")
  x)
(print "Value:" (m))
(print "Done executing")


(setv x (% (** 10 10 7) 3))的计算量较大,计算机会有明显的计算时间卡顿。在交互模式下执行上面语句每次都需要较长时间,且输出一样,都有“Now for a slow computation”。将内容写入testmacro.hy文件后,执行hy testmacro.hy2次,会发现两次执行的输出不一样,第一次有这句而第二次没有“Now for a slow computation”,且第二次执行的时间大大缩短。

下面我们开始从头学宏

宏有什么用?¶

元编程的要点是它允许您对编程语言本身进行编程(记住这个词)。您可以创建新的控件结构(如 do-while)或其他类型的新语法(例如用于您喜欢的数据结构的简洁文字表示法)。您还可以修改在代码区域中对现有语法的理解方式,例如通过创建从特定模块隐式导入的大写字母开头的标识符。最后,在某些情况下,元编程可以通过有效地内联函数来提高性能,或者在编译时计算一次而不是在运行时计算几次。使用类似 Lisp 的宏系统,您可以以一种更流畅、更不容易出错的方式进行元编程,而不是使用传统的字符串格式或词法分析器级宏(如 C 预处理器提供的宏)将代码生成为文本。

 宏的类型

Hy 提供两种类型的宏:常规宏Regular macros和阅读器宏Reader macros

Regular macros
常规宏,通常用 defmacro 定义,是 Lispers 在谈论“宏”时通常指的那种。常规宏的调用方式类似于函数,其表达式的头部是宏名称:例如, (foo a b) 可以调用名为 foo 的宏。在分析常规宏所在的整个顶级表单之后,在编译时调用常规宏,并接收解析后的模型作为参数。常规宏有三种类型,范围各不相同。

Reader macros,
读取器宏(通常用 defreader 定义)比常规宏级别低。它们用哈希符号 # 调用;例如, #foo 调用名为 foo 的读取器宏。读取器宏在解析时调用。它没有收到常规的参数(arguments)。相反,它使用隐式可用的分析器对象来分析后续源文本。当它返回时,标准 Hy 解析器会从中断的地方继续。

Related constructs 相关构造 ¶


还有另外三种构造可以执行编译时处理,就像宏一样,因此这里值得一提。

  • do-mac is essentially shorthand for defining and then immediately calling a regular macro with no arguments.
    do-mac 本质上是定义然后立即调用没有参数的常规宏的简写。

  • eval-when-compile 
    eval-when-compile 在编译时评估某些代码,但不向最终程序贡献任何代码,例如在不执行任何操作的上下文 None 中返回 None 的宏。

  • eval-and-compile
    eval-and-compile 在编译时计算某些代码,例如 eval-when-compile ,但也保留相同的代码在运行时重新计算。

何时使用


在决定使用什么时,一个好的经验法则是使用功能最弱的选项,该选项足以满足所需的语法、语义和性能。所以首先,看看 Python 的动态特性是否足够。如果不是,请尝试类似宏的构造或常规宏。如果这些还不够,请尝试阅读器宏。使用功能最弱的适用选项将帮助您避免下面描述的宏陷阱,以及其他令人头疼的问题,例如想要在 Python API 需要函数的情况下使用宏。(为了提供更简单的示例,下面的大部分讨论将忽略此建议,并考虑可以很容易地编写为函数的宏。

The basics 基础知识 ¶


可以使用类似于 的 defn 语法来定义 defmacro 常规宏。下面介绍了如何定义和调用一个不接受参数并返回常量的琐碎宏( a trivial macro):

(defmacro seventeen []
  17)

(print (seventeen))

To see that seventeen is expanded at compile-time, run hy2py on this script and notice that it ends with print(17) rather than print(seventeen()). If you insert a print call inside the macro definition, you'll also see that the print happens when the file is compiled, but not when it's rerun (so long as an up-to-date bytecode file exists).
若要查看在编译时扩展的内容 seventeen ,请在此脚本上运行 hy2py 

$ hy2py macro17.hy 
import hy
hy.macros.macro('seventeen')(lambda : 17)
print(17)

注意它以 print(17) 结尾而不是 print(seventeen())结尾 。如果在宏定义中插入 print 调用,您还将看到打印在编译文件时发生,但在重新运行时不会发生(只要存在最新的字节码文件)。

A more useful macro returns code. You can construct a model the long way, like this:
更有用的宏返回代码。可以用很长的方式构造一个模型,如下所示:

(defmacro addition []
  (hy.models.Expression [
    (hy.models.Symbol "+")
    (hy.models.Integer 1)
    (hy.models.Integer 1)]))

or more concisely with quote, like this:
或者更简洁地说 quote ,像这样:

(defmacro addition []
  '(+ 1 1))

You don't need to always return a model because the compiler calls hy.as-model on everything before trying to compile it. Thus, the 17 above works fine in place of (hy.models.Integer 17). But trying to compile something that hy.as-model chokes on, like a function object, is an error.
您不需要总是返回模型,因为编译器在尝试编译模型之前会调用 hy.as-model 所有内容。因此, 上面17可以代替 (hy.models.Integer 17) .但是试图编译 hy.as-model 一些窒息的东西,比如函数对象,是一个错误。

Arguments are always passed in as models. You can use quasiquotation (see quasiquote) to concisely define a model with partly literal and partly evaluated components:
参数始终作为模型传入。您可以使用准引号(请参阅 quasiquote )来简明扼要地定义一个模型,该模型包含部分文字和部分计算的组件:

(defmacro set-to-2 [variable]
 `(setv ~variable 2))
(set-to-2 foobar)
(print foobar)


宏不像函数那样理解关键字参数。相反,关键字对象本身是按字面意思传入的。这使您可以灵活地处理它们。因此, #** kwargs 和*在宏的参数列表中不允许, 尽管 #* args/ 是允许的。查看 hyrule.defmacro-kwargs 是否要像处理函数一样处理关键字参数。

On the inside, macros are functions, and obey the usual Python semantics for functions. For example, setv inside a macro will define or modify a variable local to the current macro call, and return ends macro execution and uses its argument as the expansion.
在内部,宏是函数,并遵循函数的通常 Python 语义。例如, setv 在宏内部将定义或修改当前宏调用的本地变量,并 return 结束宏执行并使用其参数作为扩展。


其他模块中的宏可以使用 require 引入当前范围。

Pitfalls 陷阱 ¶

Macros are powerful, but with great power comes great potential for anguish. There are a few characteristic issues you need to guard against to write macros well, and, to a lesser extent, even to use macros well.
宏是强大的,但强大的力量带来了巨大的痛苦潜力。要编写好宏,甚至只是要很好地使用宏,您需要防范一些特征问题。

Name games 命名游戏 ¶


其中很多问题都是名称主题的变体,而不是引用您的意图,或者换句话说,意外的名称屏蔽。例如,下面的宏旨在定义一个名为 x 的新变量,但它最终修改了一个预先存在的变量。

(defmacro upper-twice [arg]
  `(do
     (setv x (.upper ~arg))
     (+ x x)))
(setv x "Okay guys, ")
(setv salutation (upper-twice "bye"))
(print (+ x salutation))
  ; Intended result: "Okay guys, BYEBYE"
  ; Actual result: "BYEBYEBYE"

我们期待输出"Okay guys, BYEBYE" ,但是真实的输出是 : "BYEBYEBYE" ,也就是在宏里,我们重新定义了x为“BYE”,这导致了最后的输出不符合预期。


如果通过多次使用参数来完全避免赋值,则可能会导致不同的问题:出人意料的多重评估。

(defmacro upper-twice [arg]
  `(+ (.upper ~arg) (.upper ~arg)))
(setv items ["a" "b" "c"])
(print (upper-twice (.pop items)))
  ; Intended result: "CC"
  ; Actual result: "CB"

也就是把items两次pop的参数传给了upper-twice.如果里面有第三个参数,会发现“c” “b” “a”被依次传入。


更好的方法是选择 hy.gensym 变量名称:

(defmacro upper-twice [arg]
  (setv g (hy.gensym))
  `(do
     (setv ~g (.upper ~arg))
     (+ ~g ~g)))

可以发现这回结果终于达到预期了:

=> (setv items ["a" "b" "c"])
=> (print (upper-twice (.pop items)))
CC


Hyrule 提供了一些宏,让使用 gensym 更加方便,例如  defmacro! 和 with-gensyms.。

Macro subroutines 宏子程序 ¶


您可能希望某些内容在宏的扩展范围内,但结果却不是,这种情况是当您想在扩展中调用函数或其他宏时:

(defmacro hypotenuse [a b]
  (import math)
  `(math.sqrt (+ (** ~a 2) (** ~b 2))))
(print (hypotenuse 3 4))
  ; NameError: name 'math' is not defined

The form (import math) here appears in the wrong context, in the macro call itself rather than the expansion. You could use import or require to bind the module name or one of its members to a gensym, but an often more convenient option is to use the one-shot import syntax hy.I or the one-shot require syntax hy.R:
此处的形式 (import math) 出现在错误的上下文中,在宏调用本身而不是扩展中。您可以使用 importrequire 将模块名称或其成员之一绑定到 gensym,但通常更方便的选择是使用 one-shot import 语法 hy.I 或 one-shot require 语法 hy.R:

(defmacro hypotenuse [a b]
  `(hy.I.math.sqrt (+ (** ~a 2) (** ~b 2))))
(print (hypotenuse 3 4))


一个相关但明显的问题是,当您想在宏的代码中使用函数(或其他普通的 Python 对象)时,但它还不够快:

(defn subroutine [x]
  (hy.models.Symbol (.upper x)))
(defmacro uppercase-symbol [x]
  (subroutine x))
(setv (uppercase-symbol foo) 1)
  ; NameError: name 'subroutine' is not defined

在交互模式下不会报错,在文件运行模式下会报错 NameError: name 'subroutine' is not defined

Here, subroutine is only defined at run-time, so uppercase-symbol can't see it when it's expanding (unless you happen to be calling uppercase-symbol from a different module). This is easily worked around by wrapping (defn subroutine …) in eval-and-compile (or eval-when-compile if you want subroutine to be invisible at run-time).
在这里, subroutine 仅在运行时定义,因此 uppercase-symbol 在扩展时看不到它(除非您碰巧从其它的模块调用 uppercase-symbol )。这可以通过在eval-and-compile 或者 eval-when-compile 包装(defn subroutine …) 来轻松解决,如果想 subroutine 在运行时不可见。

By the way, despite the need for eval-and-compile, extracting a lot of complex logic out of a macro into a function is often a good idea. Functions are typically easier to debug and to make use of in other macros.
顺便说一句,尽管需要 eval-and-compile (评估和编译),将大量复杂的逻辑从宏中提取到函数中通常是一个好主意。函数通常更易于调试,也更易于在其他宏中使用。

The important take-home big fat WARNING
重要的警告 要牢记在心

Ultimately it's wisest to use only four kinds of names in macro expansions: gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and hy and its attributes. It's possible to rebind nearly all these names, so surprise shadowing is still theoretically possible. Unfortunately, the only way to prevent these pathological rebindings from coming about is… don't do that. Don't make a new macro named setv or name a function argument type unless you're ready for every macro you call to break, the same way you wouldn't monkey-patch a built-in Python module without thinking carefully. This kind of thing is the responsibility of the macro caller; the macro writer can't do much to defend against it. There is at least a pragma warn-on-core-shadow, enabled by default, that causes defmacro and require to warn you if you give your new macro the same name as a core macro.
归根结底,最明智的做法是在宏扩展中只使用四种名称:gensyms、核心宏、Python 默认放在范围内的对象(如其内置函数)及其 hy 属性(gensyms, core macros, objects that Python puts in scope by default (like its built-in functions), and hy and its attributes.)。几乎可以重新绑定所有这些名称,因此理论上仍然可以进行意外阴影。不幸的是,防止这些病理性重新结合发生的唯一方法是--不去做。不要为新宏命名 setv 或命名函数参数 type ,除非你准备好让你调用的每个宏都中断,就像你不会在不仔细思考的情况下对内置 Python 模块进行monkey-patch一样。这种事情是宏调用者的责任;宏编写者无法做太多事情来防御它。默认情况下,至少有一个 pragma warn-on-core-shadow(默认启用),如果你给新宏起的名字与核心宏相同,它会导致defmacro和require发出警告。

Reader macros 阅读器宏 ¶


读取器宏允许您直接挂接到 Hy 的解析器中,以自定义将文本解析为模型的方式。它们使用 defreader 定义,或者像常规宏一样,通过 require 从其他模块引入。读取器宏不是接收函数参数,而是可以访问名为 &reader 的 hy.HyReader 对象,该对象提供了 Hy 用于解析自身的所有文本解析逻辑(有关可用方法,请参阅 hy.HyReader 及其基类 hy.Reader)。读取器宏使用散列符号 # 调用,与常规宏一样,它应该返回模型或可转换为模型的东西。

The simplest kind of reader macro doesn't read anything:
最简单的阅读器宏不会读取任何内容:

(defreader hi
  `(print "Hello."))

#hi #hi #hi

输出:

=> #hi #hi #hi
Hello.
Hello.
Hello.

A less trivial, and more common, usage of reader macros is to call hy.HyReader.parse_one_form() to get a single form from the following source text. Such a reader macro is like a unary regular macro that's called with # instead of parentheses.
读取器宏的一个不太简单且更常见的用法是从以下源文本中 hy.HyReader.parse_one_form() 调用以获取单个表单。这样的读取器宏就像一个一元正则宏,用 # 括号而不是括号来调用。

(defreader do-twice
  (setv x (.parse-one-form &reader))
  `(do ~x ~x))

#do-twice (print "This line prints twice.")
=> #do-twice (print "This line prints twice.")
This line prints twice.
This line prints twice.

Here's a moderately complex example of a reader macro that couldn't be implemented as a regular macro. It reads in a list of lists in which the inner lists are newline-separated, but newlines are allowed inside elements.
下面是一个中等复杂的读取器宏示例,该宏无法作为常规宏实现。它读取列表列表,其中内部列表以换行符分隔,但允许在元素内部使用换行符。

(defreader matrix
  (.slurp-space &reader)
  (setv start (.getc &reader))
  (assert (= start "["))
  (.slurp-space &reader)
  (setv out [[]])
  (while (not (.peek-and-getc &reader "]"))
    (cond
      (any (gfor  c " \t"  (.peek-and-getc &reader c)))
        None
      (.peek-and-getc &reader "\n")
        (.append out [])
      True
        (.append (get out -1) (.parse-one-form &reader))))
  (lfor  line out  :if line  line))

(print (hy.repr #matrix [
    1 (+ 1 1) 3
    4 ["element" "containing"
          "a" "newline"]        6
    7 8 9]))
  ; => [[1 2 3] [4 ["element" "containing" "a" "newline"] 6] [7 8 9]]

Note that because reader macros are evaluated at parse-time, and top-level forms are completely parsed before any further compile-time execution occurs, you can't use a reader macro in the same top-level form that defines it:
请注意,由于读取器宏是在解析时计算的,并且顶级表单在发生任何进一步的编译时执行之前被完全分析,因此不能在定义它的同一顶级表单中使用读取器宏:

(do
  (defreader up
    (.slurp-space &reader)
    (.upper (.read-one-form &reader)))
  (print #up "hello?"))
    ; LexException: reader macro '#up' is not defined

Of the potential problems discussed above that apply to regular macros, such as surprise shadowing, most also apply to reader macros.
在上面讨论的适用于常规宏的潜在问题(例如意外阴影)中,大多数也适用于阅读器宏。

宏命名空间和宏操作 ¶

Macros don't share namespaces with ordinary Python objects. That's why something like (defmacro m []) (print m) fails with a NameError, and how hy.pyops can provide a function named + without hiding the core macro +.
宏不与普通 Python 对象共享命名空间。这就是为什么像 (defmacro m []) (print m) 之类的东西会失败的原因 NameError ,以及如何 hy.pyops 在不隐藏核心宏的情况下提供命名 + 的函数 +

There are three scoped varieties of regular macro. First are core macros, which are built into Hy; the set of core macros is fixed. They're available by default. You can inspect them in the dictionary bulitins._hy_macros, which is attached to Python's usual builtins module. The keys are strings giving mangled names and the values are the function objects implementing the macros.
常规宏有三种作用域变体。首先是核心宏,它们内置于 Hy 中;核心宏集是固定的。默认情况下,它们可用。您可以在字典中检查它们 bulitins._hy_macros ,该字典附加到 Python 的常用 builtins 模块。键是给出乱七八糟的名称的字符串,值是实现宏的函数对象。

Global macros are associated with modules, like Python global variables. They're defined when you call defmacro or require in a global scope. You can see them in the global variable _hy_macros associated with the same module. You can manipulate _hy_macros to list, add, delete, or get help on macros, but be sure to use eval-and-compile or eval-when-compile when you need the effect to happen at compile-time, which is often. (Modifying bulitins._hy_macros is of course a risky proposition.) Here's an example, which also demonstrates the core macro get-macro. get-macro provides syntactic sugar for getting all sorts of macros as objects.
全局宏与模块相关联,例如 Python 全局变量。它们是在调用 defmacro 时或 require 在全局范围内定义的。您可以在与同一模块 _hy_macros 关联的全局变量中看到它们。您可以操作 _hy_macros 以列出、添加、删除宏或获取有关宏的帮助,但请务必在编译时使用 eval-and-compileeval-when-compile 需要效果时发生,这通常是。(修改 bulitins._hy_macros 当然是一个有风险的提议。下面是一个示例,它还演示了 get-macro 核心宏 。 get-macro 提供语法糖,用于将各种宏作为对象。

(defmacro m []
  "This is a docstring."
  `(print "Hello, world."))
(print (in "m" _hy_macros))   ; => True
(help (get-macro m))
(m)                           ; => "Hello, world."
(eval-and-compile
  (del (get _hy_macros "m")))
(m)                           ; => NameError
(eval-and-compile
  (setv (get _hy_macros (hy.mangle "new-mac")) (fn []
    '(print "Goodbye, world."))))
(new-mac)                     ; => "Goodbye, world."

Local macros are associated with function, class, or comprehension scopes, like Python local variables. They come about when you call defmacro or require in an appropriate scope. You can call local-macros to view local macros, but adding or deleting elements is ineffective.
局部宏与函数、类或推导范围相关联,如 Python 局部变量。它们在您打电话 defmacro 时或 require 在适当的范围内出现。您可以调用 local-macros 以查看本地宏,但添加或删除元素无效。

Finally, _hy_reader_macros is a per-module dictionary like _hy_macros for reader macros, but here, the keys aren't mangled. There are no local reader macros, and there's no official way to introspect on Hy's handful of core reader macros. So, of the three scoped varieties of regular macro, reader macros most resemble global macros.
最后, _hy_reader_macros 是一个像阅读器宏一样 _hy_macros 的每模块字典,但在这里,键没有被破坏。没有本地读者宏,也没有官方的方法来反省 Hy 的少数核心读者宏。因此,在常规宏的三种作用域变体中,读取器宏最类似于全局宏。

宏的python源码例子

macros.py源码

在hy项目的hy/macros.py文件中,我们可以看到一些宏的源码,学习一下:

~/github/hy/hy % more macros.py 
import builtins
import importlib
import inspect
import os
import re
import sys
import traceback
from ast import AST

from funcparserlib.parser import NoParseError

import hy.compiler
from hy.compat import PY3_11
from hy.errors import (
    HyLanguageError,
    HyMacroExpansionError,
    HyRequireError,
    HyTypeError,
)
from hy.model_patterns import whole
from hy.models import Expression, Symbol, as_model, is_unpack, replace_hy_obj
from hy.reader import mangle
from hy.reader.mangling import slashes2dots

EXTRA_MACROS = ["hy.core.result_macros", "hy.core.macros"]


def macro(name):
    """Decorator to define a macro called `name`."""
    return lambda fn: install_macro(name, fn, fn)


def reader_macro(name, fn):
    fn = rename_function(fn, name)
    fn.__globals__.setdefault("_hy_reader_macros", {})[name] = fn


def pattern_macro(names, pattern, shadow=None):
    pattern = whole(pattern)
    py_version_required = None
    if isinstance(names, tuple):
        py_version_required, names = names

    def dec(fn):
        def wrapper_maker(name):
            def wrapper(_hy_compiler, *args):

                if shadow and any(is_unpack("iterable", x) for x in args):
                    # Try a shadow function call with this name instead.
                    return Expression(
                        [Expression(map(Symbol, [".", "hy", "pyops", name])), *args]
                    ).replace(_hy_compiler.this)

                expr = _hy_compiler.this

                if py_version_required and sys.version_info < py_version_required:
                    raise _hy_compiler._syntax_error(
                        expr,
                        "`{}` requires Python {} or later".format(
                            name, ".".join(map(str, py_version_required))
                        ),
                    )

                try:
                    parse_tree = pattern.parse(args)
                except NoParseError as e:
                    raise _hy_compiler._syntax_error(
                        expr[min(e.state.pos + 1, len(expr) - 1)],
                        "parse error for pattern macro '{}': {}".format(
                            name, e.msg.replace("end of input", "end of macro call")
                        ),
                    )
                return fn(_hy_compiler, expr, name, *parse_tree)

            return wrapper

        for name in [names] if isinstance(names, str) else names:
            install_macro(name, wrapper_maker(name), fn)
        return fn

    return dec


hy宏源码

(defmacro defreader [_hy_compiler key #* body]
  "Define a reader macro, at both compile-time and run-time. After the name,
  all arguments are body forms: there is no parameter list as for ``defmacro``,
  since it's up to the reader macro to decide how to parse the source text
  following its call position. See :ref:`reader-macros` for details and
  examples."

  (when (not (isinstance _hy_compiler.scope hy.scoping.ScopeGlobal))
    (raise (_hy_compiler._syntax-error
             _hy_compiler.this
             f"Cannot define reader macro outside of global scope.")))

  (when (not (isinstance key hy.models.Symbol))
    (raise (ValueError f"expected a name, but got {key}")))

  (if (and body (isinstance (get body 0) hy.models.String))
      (setv [docstr #* body] body)
      (setv docstr None))

  (setv dispatch-key (str key))
  `(do (eval-and-compile
         (hy.macros.reader-macro
           ~dispatch-key
           (fn [&reader &key]
             ~@(if docstr [docstr] [])
             ~@body)))
       (eval-when-compile
         (setv (get (. (hy.reader.HyReader.current-reader) reader-macros) ~dispatch-key)
               (get _hy_reader_macros ~dispatch-key)))))

hy宏源码文心的解释

好的,这段 Hy 代码定义了一个名为 defreader 的宏,用于在编译时和运行时定义读取器宏(reader macro)。读取器宏是 Hy 语言中用于在源代码读取阶段处理特定语法结构的特殊宏。

下面我将逐段解释这段代码:

  1. 宏定义

(defmacro defreader [_hy_compiler key #* body]

hy复制代码

  • defmacro 是定义宏的关键字。
  • _hy_compiler 是一个隐式的宏参数,它包含了编译器的当前状态和环境。
  • key 是读取器宏的名称。
  • #* body 是宏的主体部分,它是一个可变数量的参数,表示宏体的其余部分。
  1. 文档字符串

该宏有一个文档字符串,描述了如何使用 defreader 宏来定义读取器宏。

"Define a reader macro, at both compile-time and run-time. After the name,
  all arguments are body forms: there is no parameter list as for ``defmacro``,
  since it's up to the reader macro to decide how to parse the source text
  following its call position. See :ref:`reader-macros` for details and
  examples."
3. 检查全局作用域

(when (not (isinstance _hy_compiler.scope hy.scoping.ScopeGlobal))  
    (raise (_hy_compiler._syntax-error  
             _hy_compiler.this  
             f"Cannot define reader macro outside of global scope.")))
  • 这段代码检查 _hy_compiler.scope 是否是全局作用域。读取器宏只能在全局作用域中定义。
  • 如果不是全局作用域,它将抛出一个语法错误。
  1. 检查 key 是否为 Symbol 类型

(when (not (isinstance key hy.models.Symbol))  
    (raise (ValueError f"expected a name, but got {key}")))
  • 这段代码检查 key 是否为 Hy 的 Symbol 类型。读取器宏的名称必须是一个符号。
  • 如果不是 Symbol 类型,它将抛出一个 ValueError
  1. 处理文档字符串

(if (and body (isinstance (get body 0) hy.models.String))  
    (setv [docstr #* body] body)  
    (setv docstr None))
  • 这段代码检查 body 是否存在,并且其第一个元素是否为一个字符串(这通常表示文档字符串)。
  • 如果是,它将把 docstr 设置为 body(此时 body 的第一个元素是文档字符串,其余元素是宏体)。
  • 如果不是,它将把 docstr 设置为 None
  1. 设置 dispatch key

(setv dispatch-key (str key))
  • 将 key(读取器宏的名称)转换为字符串,并存储在 dispatch-key 变量中。
  1. 定义和注册读取器宏

`(do (eval-and-compile  
       (hy.macros.reader-macro  
         ~dispatch-key  
         (fn [&reader &key]  
           ~@(if docstr [docstr] [])  
           ~@body)))  
     (eval-when-compile  
       (setv (get (. (hy.reader.HyReader.current-reader) reader-macros) ~dispatch-key)  
             (get _hy_reader_macros ~dispatch-key))))
  • 这部分代码使用了 Hy 的反引号(`)来生成并返回一段 Hy 代码。
  • 在 eval-and-compile 块中,它调用了 hy.macros.reader-macro 函数来定义读取器宏。这个宏函数接受 dispatch-key 和一个匿名函数作为参数。匿名函数接受 &reader 和 &key 作为参数(虽然在这段代码中它们没有被直接使用),并执行宏体 body(以及可能的文档字符串)。
  • 在 eval-when-compile 块中,它在编译时注册了读取器宏。它从当前的 Hy 读取器(hy.reader.HyReader.current-reader)中获取 reader-macros 字典,并将新定义的读取器宏添加到该字典中。

总的来说,这个 defreader 宏允许用户在 Hy 语言中定义读取器宏,这些宏可以在源代码读取阶段处理特定的语法结构。

  • 38
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值