Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lang: remove for loop and case statement macros #413

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions compiler/front/in_options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -190,9 +190,6 @@ type
destructor,
notnil,
dynamicBindSym,
forLoopMacros, ## not experimental anymore; remains here for backwards
## compatibility
caseStmtMacros,## ditto
vmopsDanger,
strictFuncs,
views,
Expand Down
78 changes: 0 additions & 78 deletions compiler/sem/semstmts.nim
Original file line number Diff line number Diff line change
Expand Up @@ -1614,83 +1614,8 @@ proc isTrivalStmtExpr(n: PNode): bool =
return false
result = true

proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string;
flags: TExprFlags): PNode =
if selector.kind in nkCallKinds:
# we transform
# n := for a, b, c in m(x, y, z): Y
# to
# m(n)
let maType = magicsys.getCompilerProc(c.graph, magicType)
if maType == nil: return

let headSymbol = selector[0]
var o: TOverloadIter
var match: PSym = nil
var symx = initOverloadIter(o, c, headSymbol)
while symx != nil:
if symx.kind in {skTemplate, skMacro}:
if symx.typ.len == 2 and symx.typ[1] == maType.typ:
if match == nil:
match = symx
else:
localReport(
c.config, n.info,
reportSymbols(rsemAmbiguous, @[match, symx]).withIt do:
it.ast = selector
)
elif symx.isError:
localReport(c.config, symx.ast)

symx = nextOverloadIter(o, c, headSymbol)

if match == nil: return
var callExpr = newNodeI(nkCall, n.info)
callExpr.add newSymNode(match)
callExpr.add n
case match.kind
of skMacro: result = semMacroExpr(c, callExpr, match, flags)
of skTemplate: result = semTemplateExpr(c, callExpr, match, flags)
else: result = nil

proc handleForLoopMacro(c: PContext; n: PNode; flags: TExprFlags): PNode =
result = handleStmtMacro(c, n, n[^2], "ForLoopStmt", flags)

proc handleCaseStmtMacro(c: PContext; n: PNode; flags: TExprFlags): PNode =
# n[0] has been sem'checked and has a type. We use this to resolve
# '`case`(n[0])' but then we pass 'n' to the `case` macro. This seems to
# be the best solution.
var toResolve = newNodeI(nkCall, n.info)
toResolve.add newIdentNode(getIdent(c.cache, "case"), n.info)
toResolve.add n[0]

var errors: seq[SemCallMismatch]
var r = resolveOverloads(c, toResolve, {skTemplate, skMacro}, {}, errors)
if r.state == csMatch:
var match = r.calleeSym
markUsed(c, n[0].info, match)
onUse(n[0].info, match)

# but pass 'n' to the `case` macro, not 'n[0]':
r.call[1] = n
let toExpand = semResolvedCall(c, r, r.call, {})
case match.kind
of skMacro: result = semMacroExpr(c, toExpand, match, flags)
of skTemplate: result = semTemplateExpr(c, toExpand, match, flags)
else: result = nil
else:
assert r.call.kind == nkError
result = r.call # xxx: hope this is nkError
# this would be the perfectly consistent solution with 'for loop macros',
# but it kinda sucks for pattern matching as the matcher is not attached to
# a type then:
when false:
result = handleStmtMacro(c, n, n[0], "CaseStmt")

proc semFor(c: PContext, n: PNode; flags: TExprFlags): PNode =
checkMinSonsLen(n, 3, c.config)
result = handleForLoopMacro(c, n, flags)
if result != nil: return result
openScope(c)
result = n
n[^2] = semExprNoDeref(c, n[^2], {efWantIterator})
Expand Down Expand Up @@ -1748,9 +1673,6 @@ proc semCase(c: PContext, n: PNode; flags: TExprFlags): PNode =
else:
popCaseContext(c)
closeScope(c)
result = handleCaseStmtMacro(c, n, flags)
if result != nil:
return result
result[0] = c.config.newError(n[0], reportSem rsemSelectorMustBeOfCertainTypes)
return
for i in 1..<n.len:
Expand Down
3 changes: 0 additions & 3 deletions doc/lib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -459,9 +459,6 @@ Miscellaneous
* `colors <colors.html>`_
This module implements color handling for Nim.

* `enumerate <enumerate.html>`_
This module implements `enumerate` syntactic sugar based on Nim's macro system.

* `logging <logging.html>`_
This module implements a simple logger.

Expand Down
141 changes: 0 additions & 141 deletions doc/manual.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4429,43 +4429,6 @@ parameters of an outer factory proc:
for f in foo():
echo f

The call can be made more like an inline iterator with a for loop macro:

.. code-block:: nim
import std/macros
macro toItr(x: ForLoopStmt): untyped =
let expr = x[0]
let call = x[1][1] # Get foo out of toItr(foo)
let body = x[2]
result = quote do:
block:
let itr = `call`
for `expr` in itr():
`body`

for f in toItr(mycount(1, 4)): # using early `proc mycount`
echo f

Because of full backend function call aparatus involvment, closure iterator
invocation is typically higher cost than inline iterators. Adornment by
a macro wrapper at the call site like this is a possibly useful reminder.

The factory `proc`, as an ordinary procedure, can be recursive. The
above macro allows such recursion to look much like a recursive iterator
would. For example:

.. code-block:: nim
proc recCountDown(n: int): iterator(): int =
result = iterator(): int =
if n > 0:
yield n
for e in toItr(recCountDown(n - 1)):
yield e

for i in toItr(recCountDown(6)): # Emits: 6 5 4 3 2 1
echo i


See also see `iterable <#overloading-resolution-iterable>`_ for passing iterators to templates and macros.

Converters
Expand Down Expand Up @@ -6018,110 +5981,6 @@ as arguments if called in statement form.
echo num


For loop macro
--------------

A macro that takes as its only input parameter an expression of the special
type `system.ForLoopStmt` can rewrite the entirety of a `for` loop:

.. code-block:: nim
:test: "nim c $1"

import std/macros

macro example(loop: ForLoopStmt) =
result = newTree(nnkForStmt) # Create a new For loop.
result.add loop[^3] # This is "item".
result.add loop[^2][^1] # This is "[1, 2, 3]".
result.add newCall(bindSym"echo", loop[0])

for item in example([1, 2, 3]): discard

Expands to:

.. code-block:: nim
for item in items([1, 2, 3]):
echo item

Another example:

.. code-block:: nim
:test: "nim c $1"

import std/macros

macro enumerate(x: ForLoopStmt): untyped =
expectKind x, nnkForStmt
# check if the starting count is specified:
var countStart = if x[^2].len == 2: newLit(0) else: x[^2][1]
result = newStmtList()
# we strip off the first for loop variable and use it as an integer counter:
result.add newVarStmt(x[0], countStart)
var body = x[^1]
if body.kind != nnkStmtList:
body = newTree(nnkStmtList, body)
body.add newCall(bindSym"inc", x[0])
var newFor = newTree(nnkForStmt)
for i in 1..x.len-3:
newFor.add x[i]
# transform enumerate(X) to 'X'
newFor.add x[^2][^1]
newFor.add body
result.add newFor
# now wrap the whole macro in a block to create a new scope
result = quote do:
block: `result`

for a, b in enumerate(items([1, 2, 3])):
echo a, " ", b

# without wrapping the macro in a block, we'd need to choose different
# names for `a` and `b` here to avoid redefinition errors
for a, b in enumerate(10, [1, 2, 3, 5]):
echo a, " ", b


Case statement macros
---------------------

Macros named `` `case` `` can provide implementations of `case` statements
for certain types. The following is an example of such an implementation
for tuples, leveraging the existing equality operator for tuples
(as provided in `system.==`):

.. code-block:: nim
:test: "nim c $1"
import std/macros

macro `case`(n: tuple): untyped =
result = newTree(nnkIfStmt)
let selector = n[0]
for i in 1 ..< n.len:
let it = n[i]
case it.kind
of nnkElse, nnkElifBranch, nnkElifExpr, nnkElseExpr:
result.add it
of nnkOfBranch:
for j in 0..it.len-2:
let cond = newCall("==", selector, it[j])
result.add newTree(nnkElifBranch, cond, it[^1])
else:
error "custom 'case' for tuple cannot handle this node", it

case ("foo", 78)
of ("foo", 78): echo "yes"
of ("bar", 88): echo "no"
else: discard

`case` macros are subject to overload resolution. The type of the
`case` statement's selector expression is matched against the type
of the first argument of the `case` macro. Then the complete `case`
statement is passed in place of the argument and the macro is evaluated.

In other words, the macro needs to transform the full `case` statement
but only the statement's selector expression is used to determine which
macro to call.


Special Types
=============
Expand Down
30 changes: 23 additions & 7 deletions lib/pure/collections/lists.nim
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ iterator mitems*[T](L: var SomeLinkedRing[T]): var T =
itemsRingImpl()

iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] =
## Iterates over every node of `x`. Removing the current node from the
## Iterates over every node of `L`. Removing the current node from the
## list during traversal is supported.
##
## **See also:**
Expand All @@ -293,7 +293,7 @@ iterator nodes*[T](L: SomeLinkedList[T]): SomeLinkedNode[T] =
it = nxt

iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] =
## Iterates over every node of `x`. Removing the current node from the
## Iterates over every node of `L`. Removing the current node from the
## list during traversal is supported.
##
## **See also:**
Expand All @@ -319,6 +319,22 @@ iterator nodes*[T](L: SomeLinkedRing[T]): SomeLinkedNode[T] =
it = nxt
if it == L.head: break

iterator enumerate*[T](L: SomeLinkedList[T]): (int, T) =
## Iterates over every node of `L`, yielding `(count, value)` tuples with
## count starting at `0`.
var i = 0
for n in L.items:
yield (i, n)
inc i

iterator enumerate*[T](L: SomeLinkedRing[T]): (int, T) =
## Iterates over every node of `L`, yielding `(count, value)` tuples with
## count starting at `0`.
var i = 0
for n in L.items:
yield (i, n)
inc i

proc `$`*[T](L: SomeLinkedCollection[T]): string =
## Turns a list into its string representation for logging and printing.
runnableExamples:
Expand Down Expand Up @@ -390,7 +406,7 @@ proc prependMoved*[T: SomeLinkedList](a, b: var T) {.since: (1, 5, 1).} =
## * `prepend proc <#prepend,T,T>`_
## for prepending a copy of a list
runnableExamples:
import std/[sequtils, enumerate, sugar]
import std/[sequtils, sugar]
var
a = [4, 5].toSinglyLinkedList
b = [1, 2, 3].toSinglyLinkedList
Expand Down Expand Up @@ -515,7 +531,7 @@ proc addMoved*[T](a, b: var SinglyLinkedList[T]) {.since: (1, 5, 1).} =
## **See also:**
## * `add proc <#add,T,T>`_ for adding a copy of a list
runnableExamples:
import std/[sequtils, enumerate, sugar]
import std/[sequtils, sugar]
var
a = [1, 2, 3].toSinglyLinkedList
b = [4, 5].toSinglyLinkedList
Expand Down Expand Up @@ -659,7 +675,7 @@ proc addMoved*[T](a, b: var DoublyLinkedList[T]) {.since: (1, 5, 1).} =
## * `add proc <#add,T,T>`_
## for adding a copy of a list
runnableExamples:
import std/[sequtils, enumerate, sugar]
import std/[sequtils, sugar]
var
a = [1, 2, 3].toDoublyLinkedList
b = [4, 5].toDoublyLinkedList
Expand Down Expand Up @@ -712,7 +728,7 @@ proc remove*[T](L: var SinglyLinkedList[T], n: SinglyLinkedNode[T]): bool {.disc
## Attempting to remove an element not contained in the list is a no-op.
## When the list is cyclic, the cycle is preserved after removal.
runnableExamples:
import std/[sequtils, enumerate, sugar]
import std/[sequtils, sugar]
var a = [0, 1, 2].toSinglyLinkedList
let n = a.head.next
assert n.value == 1
Expand Down Expand Up @@ -749,7 +765,7 @@ proc remove*[T](L: var DoublyLinkedList[T], n: DoublyLinkedNode[T]) =
## otherwise the effects are undefined.
## When the list is cyclic, the cycle is preserved after removal.
runnableExamples:
import std/[sequtils, enumerate, sugar]
import std/[sequtils, sugar]
var a = [0, 1, 2].toSinglyLinkedList
let n = a.head.next
assert n.value == 1
Expand Down
23 changes: 0 additions & 23 deletions lib/pure/options.nim
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,6 @@ raises `UnpackDefect` if there is no value. Note that `UnpackDefect`
inherits from `system.Defect` and should therefore never be caught.
Instead, rely on checking if the option contains a value with the
`isSome <#isSome,Option[T]>`_ and `isNone <#isNone,Option[T]>`_ procs.


Pattern matching
================

.. note:: This requires the [fusion](https://github.com/nim-lang/fusion) package.

[fusion/matching](https://nim-lang.github.io/fusion/src/fusion/matching.html)
supports pattern matching on `Option`s, with the `Some(<pattern>)` and
`None()` patterns.

.. code-block:: nim
{.experimental: "caseStmtMacros".}

import fusion/matching

case some(42)
of Some(@a):
assert a == 42
of None():
assert false

assertMatch(some(some(none(int))), Some(Some(None())))
]##
# xxx pending https://github.com/timotheecour/Nim/issues/376 use `runnableExamples` and `whichModule`

Expand Down
Loading