Fix macros.quote custom op symbol interpolation. (#17256)

Provides a workaround/fix for #7589.
https://github.com/nim-lang/Nim/issues/7589

Updated docs and tutorial to reflect change.

Updated runnableExamples to include an example.

Co-authored-by: name <name@example.com>
This commit is contained in:
quantimnot 2021-03-05 08:41:33 -05:00 • committed by GitHub
commit b2b23d723a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 51 deletions

View file

@ -4,6 +4,8 @@
## Standard library additions and changes ## Standard library additions and changes
- Make custom op in macros.quote work for all statements.
- On Windows the SSL library now checks for valid certificates. - On Windows the SSL library now checks for valid certificates.
It uses the `cacert.pem` file for this purpose which was extracted It uses the `cacert.pem` file for this purpose which was extracted
from `https://curl.se/ca/cacert.pem`. Besides from `https://curl.se/ca/cacert.pem`. Besides

View file

@ -2021,18 +2021,28 @@ proc processQuotations(c: PContext; n: var PNode, op: string,
n = newIdentNode(getIdent(c.cache, $quotes.len), n.info) n = newIdentNode(getIdent(c.cache, $quotes.len), n.info)
ids.add n ids.add n
return return
template handlePrefixOp(prefixed) =
if prefixed[0].kind == nkIdent:
let examinedOp = prefixed[0].ident.s
if examinedOp == op:
returnQuote prefixed[1]
elif examinedOp.startsWith(op):
prefixed[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), prefixed.info)
if n.kind == nkPrefix: if n.kind == nkPrefix:
checkSonsLen(n, 2, c.config) checkSonsLen(n, 2, c.config)
if n[0].kind == nkIdent: handlePrefixOp(n)
var examinedOp = n[0].ident.s elif n.kind == nkAccQuoted:
if examinedOp == op: if op == "``":
returnQuote n[1] returnQuote n[0]
elif examinedOp.startsWith(op): else: # [bug #7589](https://github.com/nim-lang/Nim/issues/7589)
n[0] = newIdentNode(getIdent(c.cache, examinedOp.substr(op.len)), n.info) if n.len == 2 and n[0].ident.s == op:
elif n.kind == nkAccQuoted and op == "``": var tempNode = nkPrefix.newTree()
returnQuote n[0] tempNode.newSons(2)
tempNode[0] = n[0]
tempNode[1] = n[1]
handlePrefixOp(tempNode)
elif n.kind == nkIdent: elif n.kind == nkIdent:
if n.ident.s == "result": if n.ident.s == "result":
n = ids[0] n = ids[0]

View file

@ -20,15 +20,15 @@ a Nim syntax tree into a different tree.
Examples of things that can be implemented in macros: Examples of things that can be implemented in macros:
* An assert macro that prints both sides of a comparison operator, if * An assert macro that prints both sides of a comparison operator, if
the assertion fails. ``myAssert(a == b)`` is converted to the assertion fails. `myAssert(a == b)` is converted to
``if a != b: quit($a " != " $b)`` `if a != b: quit($a " != " $b)`
* A debug macro that prints the value and the name of the symbol. * A debug macro that prints the value and the name of the symbol.
``myDebugEcho(a)`` is converted to ``echo "a: ", a`` `myDebugEcho(a)` is converted to `echo "a: ", a`
* Symbolic differentiation of an expression. * Symbolic differentiation of an expression.
``diff(a*pow(x,3) + b*pow(x,2) + c*x + d, x)`` is converted to `diff(a*pow(x,3) + b*pow(x,2) + c*x + d, x)` is converted to
``3*a*pow(x,2) + 2*b*x + c`` `3*a*pow(x,2) + 2*b*x + c`
Macro Arguments Macro Arguments
@ -36,14 +36,14 @@ Macro Arguments
The types of macro arguments have two faces. One face is used for The types of macro arguments have two faces. One face is used for
the overload resolution and the other face is used within the macro the overload resolution and the other face is used within the macro
body. For example, if ``macro foo(arg: int)`` is called in an body. For example, if `macro foo(arg: int)` is called in an
expression ``foo(x)``, ``x`` has to be of a type compatible to int, but expression `foo(x)`, `x` has to be of a type compatible to int, but
*within* the macro's body ``arg`` has the type ``NimNode``, not ``int``! *within* the macro's body `arg` has the type `NimNode`, not `int`!
Why it is done this way will become obvious later, when we have seen Why it is done this way will become obvious later, when we have seen
concrete examples. concrete examples.
There are two ways to pass arguments to a macro, an argument can be There are two ways to pass arguments to a macro, an argument can be
either ``typed`` or ``untyped``. either `typed` or `untyped`.
Untyped Arguments Untyped Arguments
@ -58,11 +58,11 @@ result somehow. The result of a macro expansion is always checked
by the compiler, so apart from weird error messages, nothing bad by the compiler, so apart from weird error messages, nothing bad
can happen. can happen.
The downside for an ``untyped`` argument is that these do not play The downside for an `untyped` argument is that these do not play
well with Nim's overloading resolution. well with Nim's overloading resolution.
The upside for untyped arguments is that the syntax tree is The upside for untyped arguments is that the syntax tree is
quite predictable and less complex compared to its ``typed`` quite predictable and less complex compared to its `typed`
counterpart. counterpart.
@ -74,8 +74,8 @@ does transformations on it, before it is passed to the macro. Here
identifier nodes are resolved as symbols, implicit type identifier nodes are resolved as symbols, implicit type
conversions are visible in the tree as calls, templates are conversions are visible in the tree as calls, templates are
expanded, and probably most importantly, nodes have type information. expanded, and probably most importantly, nodes have type information.
Typed arguments can have the type ``typed`` in the arguments list. Typed arguments can have the type `typed` in the arguments list.
But all other types, such as ``int``, ``float`` or ``MyObjectType`` But all other types, such as `int`, `float` or `MyObjectType`
are typed arguments as well, and they are passed to the macro as a are typed arguments as well, and they are passed to the macro as a
syntax tree. syntax tree.
@ -84,17 +84,17 @@ Static Arguments
---------------- ----------------
Static arguments are a way to pass values as values and not as syntax Static arguments are a way to pass values as values and not as syntax
tree nodes to a macro. For example for ``macro foo(arg: static[int])`` tree nodes to a macro. For example for `macro foo(arg: static[int])`
in the expression ``foo(x)``, ``x`` needs to be an integer constant, in the expression `foo(x)`, `x` needs to be an integer constant,
but in the macro body ``arg`` is just like a normal parameter of type but in the macro body `arg` is just like a normal parameter of type
``int``. `int`.
.. code-block:: nim .. code-block:: nim
import std/macros import std/macros
macro myMacro(arg: static[int]): untyped = macro myMacro(arg: static[int]): untyped =
echo arg # just an int (7), not ``NimNode`` echo arg # just an int (7), not `NimNode`
myMacro(1 + 2 * 3) myMacro(1 + 2 * 3)
@ -104,7 +104,7 @@ Code Blocks as Arguments
It is possible to pass the last argument of a call expression in a It is possible to pass the last argument of a call expression in a
separate code block with indentation. For example, the following code separate code block with indentation. For example, the following code
example is a valid (but not a recommended) way to call ``echo``: example is a valid (but not a recommended) way to call `echo`:
.. code-block:: nim .. code-block:: nim
@ -125,10 +125,10 @@ code is represented as a syntax tree, and how such a tree needs to
look like so that the Nim compiler will understand it. The nodes of the look like so that the Nim compiler will understand it. The nodes of the
Nim syntax tree are documented in the `macros <macros.html>`_ module. Nim syntax tree are documented in the `macros <macros.html>`_ module.
But a more interactive way to explore the Nim But a more interactive way to explore the Nim
syntax tree is with ``macros.treeRepr``, it converts a syntax tree syntax tree is with `macros.treeRepr`, it converts a syntax tree
into a multi-line string for printing on the console. It can be used into a multi-line string for printing on the console. It can be used
to explore how the argument expressions are represented in tree form to explore how the argument expressions are represented in tree form
and for debug printing of generated syntax tree. ``dumpTree`` is a and for debug printing of generated syntax tree. `dumpTree` is a
predefined macro that just prints its argument in a tree representation, predefined macro that just prints its argument in a tree representation,
but does nothing else. Here is an example of such a tree representation: but does nothing else. Here is an example of such a tree representation:
@ -160,9 +160,9 @@ The first thing that a macro should do with its arguments is to check
if the argument is in the correct form. Not every type of wrong input if the argument is in the correct form. Not every type of wrong input
needs to be caught here, but anything that could cause a crash during needs to be caught here, but anything that could cause a crash during
macro evaluation should be caught and create a nice error message. macro evaluation should be caught and create a nice error message.
``macros.expectKind`` and ``macros.expectLen`` are a good start. If `macros.expectKind` and `macros.expectLen` are a good start. If
the checks need to be more complex, arbitrary error messages can the checks need to be more complex, arbitrary error messages can
be created with the ``macros.error`` proc. be created with the `macros.error` proc.
.. code-block:: nim .. code-block:: nim
@ -174,19 +174,37 @@ Generating Code
--------------- ---------------
There are two ways to generate the code. Either by creating the syntax There are two ways to generate the code. Either by creating the syntax
tree with expressions that contain a lot of calls to ``newTree`` and tree with expressions that contain a lot of calls to `newTree` and
``newLit``, or with ``quote do:`` expressions. The first option offers `newLit`, or with `quote do:` expressions. The first option offers
the best low-level control for the syntax tree generation, but the the best low-level control for the syntax tree generation, but the
second option is much less verbose. If you choose to create the syntax second option is much less verbose. If you choose to create the syntax
tree with calls to ``newTree`` and ``newLit`` the macro tree with calls to `newTree` and `newLit` the macro
``macros.dumpAstGen`` can help you with the verbosity. ``quote do:`` `macros.dumpAstGen` can help you with the verbosity.
allows you to write the code that you want to generate literally,
backticks are used to insert code from ``NimNode`` symbols into the `quote do:` allows you to write the code that you want to generate literally.
generated expression. This means that you can't use backticks within Backticks are used to insert code from `NimNode` symbols into the
``quote do:`` for anything else than injecting symbols. Make sure to generated expression.
inject only symbols of type ``NimNode`` into the generated syntax
tree. You can use ``newLit`` to convert arbitrary values into .. code-block:: nim
expressions trees of type ``NimNode`` so that it is safe to inject macro a(i) = quote do: let `i` = 0
a b
A custom prefix operator can be defined whenever backticks are needed.
.. code-block:: nim
macro a(i) = quote("@") do: assert @i == 0
let b = 0
a b
The injected symbol needs accent quoted when it resolves to a symbol.
.. code-block:: nim
macro a(i) = quote("@") do: let `@i` == 0
a b
Make sure to inject only symbols of type `NimNode` into the generated syntax
tree. You can use `newLit` to convert arbitrary values into
expressions trees of type `NimNode` so that it is safe to inject
them into the tree. them into the tree.
@ -213,7 +231,7 @@ them into the tree.
myMacro("Hallo") myMacro("Hallo")
The call to ``myMacro`` will generate the following code: The call to `myMacro` will generate the following code:
.. code-block:: nim .. code-block:: nim
echo "Hallo" echo "Hallo"
@ -224,7 +242,7 @@ Building Your First Macro
------------------------- -------------------------
To give a starting point to writing macros we will show now how to To give a starting point to writing macros we will show now how to
implement the ``myDebug`` macro mentioned earlier. The first thing to implement the `myDebug` macro mentioned earlier. The first thing to
do is to build a simple example of the macro usage, and then just do is to build a simple example of the macro usage, and then just
print the argument. This way it is possible to get an idea of what a print the argument. This way it is possible to get an idea of what a
correct argument should look like. correct argument should look like.
@ -281,7 +299,7 @@ written.
This is the code that will be generated. To debug what the macro This is the code that will be generated. To debug what the macro
actually generated, the statement ``echo result.repr`` can be used, in actually generated, the statement `echo result.repr` can be used, in
the last line of the macro. It is also the statement that has been the last line of the macro. It is also the statement that has been
used to get this output. used to get this output.
@ -324,14 +342,14 @@ possible with it.
Strformat Strformat
--------- ---------
In the Nim standard library, the ``strformat`` library provides a In the Nim standard library, the `strformat` library provides a
macro that parses a string literal at compile time. Parsing a string macro that parses a string literal at compile time. Parsing a string
in a macro like here is generally not recommended. The parsed AST in a macro like here is generally not recommended. The parsed AST
cannot have type information, and parsing implemented on the VM is cannot have type information, and parsing implemented on the VM is
generally not very fast. Working on AST nodes is almost always the generally not very fast. Working on AST nodes is almost always the
recommended way. But still ``strformat`` is a good example for a recommended way. But still `strformat` is a good example for a
practical use case for a macro that is slightly more complex than the practical use case for a macro that is slightly more complex than the
``assert`` macro. `assert` macro.
`Strformat <https://github.com/nim-lang/Nim/blob/5845716df8c96157a047c2bd6bcdd795a7a2b9b1/lib/pure/strformat.nim#L280>`_ `Strformat <https://github.com/nim-lang/Nim/blob/5845716df8c96157a047c2bd6bcdd795a7a2b9b1/lib/pure/strformat.nim#L280>`_

View file

@ -555,6 +555,9 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.} =
## operator may be obtained by escaping it (by prefixing it with itself) when used ## operator may be obtained by escaping it (by prefixing it with itself) when used
## as a unary operator: ## as a unary operator:
## e.g. `@` is escaped as `@@`, `&%` is escaped as `&%&%` and so on; see examples. ## e.g. `@` is escaped as `@@`, `&%` is escaped as `&%&%` and so on; see examples.
##
## A custom operator interpolation needs accent quoted (``) whenever it resolves
## to a symbol.
runnableExamples: runnableExamples:
macro check(ex: untyped) = macro check(ex: untyped) =
# this is a simplified version of the check macro from the # this is a simplified version of the check macro from the
@ -593,10 +596,11 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.} =
runnableExamples: runnableExamples:
# custom `op` # custom `op`
var destroyCalled = false var destroyCalled = false
macro bar() = macro bar(ident) =
var x = 1.5 var x = 1.5
result = quote("@") do: result = quote("@") do:
type Foo = object type Foo = object
let `@ident` = 0 # custom op interpolated symbols need quoted (``)
proc `=destroy`(a: var Foo) = proc `=destroy`(a: var Foo) =
doAssert @x == 1.5 doAssert @x == 1.5
doAssert compiles(@x == 1.5) doAssert compiles(@x == 1.5)
@ -607,7 +611,7 @@ proc quote*(bl: typed, op = "``"): NimNode {.magic: "QuoteAst", noSideEffect.} =
destroyCalled = true destroyCalled = true
block: block:
let a = Foo() let a = Foo()
bar() bar(someident)
doAssert destroyCalled doAssert destroyCalled
proc `&%`(x: int): int = 1 proc `&%`(x: int): int = 1