mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-12 22:33:49 +00:00
implements 'case statement macros' in order to encourage the development of pattern matching mechanisms that are not terrible to look at
This commit is contained in:
14
changelog.md
14
changelog.md
@@ -95,7 +95,7 @@
|
||||
- Added the procs ``rationals.`div```, ``rationals.`mod```, ``rationals.floorDiv`` and ``rationals.floorMod`` for rationals.
|
||||
- Added the proc ``math.prod`` for product of elements in openArray.
|
||||
- Added the proc ``parseBinInt`` to parse a binary integer from a string, which returns the value.
|
||||
- ``parseOct`` and ``parseBin`` in parseutils now also support the ``maxLen`` argument similar to ``parseHexInt``
|
||||
- ``parseOct`` and ``parseBin`` in parseutils now also support the ``maxLen`` argument similar to ``parseHexInt``.
|
||||
- Added the proc ``flush`` for memory mapped files.
|
||||
- Added the ``MemMapFileStream``.
|
||||
- Added ``macros.copyLineInfo`` to copy lineInfo from other node.
|
||||
@@ -138,7 +138,13 @@
|
||||
- ``func`` is now an alias for ``proc {.noSideEffect.}``.
|
||||
- In order to make ``for`` loops and iterators more flexible to use Nim now
|
||||
supports so called "for-loop macros". See
|
||||
the `manual <manual.html#macros-for-loop-macros>`_ for more details.
|
||||
the [manual](manual.html#macros-for-loop-macros) for more details.
|
||||
This feature enables a Python-like generic ``enumerate`` implementation.
|
||||
|
||||
- Case statements can now be rewritten via macros. See the [manual]() for more information.
|
||||
This feature enables custom pattern matchers.
|
||||
|
||||
|
||||
- the `typedesc` special type has been renamed to just `type`.
|
||||
- `static` and `type` are now also modifiers similar to `ref` and `ptr`.
|
||||
They denote the special types `static[T]` and `type[T]`.
|
||||
@@ -171,7 +177,7 @@
|
||||
- Thread-local variables can now be declared inside procs. This implies all
|
||||
the effects of the ``global`` pragma.
|
||||
|
||||
- Nim now supports ``except`` clause in the export statement.
|
||||
- Nim now supports the ``except`` clause in the export statement.
|
||||
|
||||
- Range float types, example ``range[0.0 .. Inf]``. More details in language manual.
|
||||
- The ``{.this.}`` pragma has been deprecated. It never worked within generics and
|
||||
@@ -219,6 +225,6 @@
|
||||
- macros.bindSym now capable to accepts not only literal string or string constant expression.
|
||||
bindSym enhancement make it also can accepts computed string or ident node inside macros /
|
||||
compile time functions / static blocks. Only in templates / regular code it retains it's old behavior.
|
||||
This new feature can be accessed via {.experimental: "dynamicBindSym".} pragma/switch
|
||||
This new feature can be accessed via {.experimental: "dynamicBindSym".} pragma/switch.
|
||||
|
||||
### Bugfixes
|
||||
|
||||
@@ -119,8 +119,8 @@ type
|
||||
destructor,
|
||||
notnil,
|
||||
dynamicBindSym,
|
||||
forLoopMacros
|
||||
#caseStmtMacros
|
||||
forLoopMacros,
|
||||
caseStmtMacros
|
||||
|
||||
SymbolFilesOption* = enum
|
||||
disabledSf, writeOnlySf, readOnlySf, v2Sf
|
||||
|
||||
@@ -178,71 +178,6 @@ proc semIf(c: PContext, n: PNode): PNode =
|
||||
result.kind = nkIfExpr
|
||||
result.typ = typ
|
||||
|
||||
proc semCase(c: PContext, n: PNode): PNode =
|
||||
result = n
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
openScope(c)
|
||||
n.sons[0] = semExprWithType(c, n.sons[0])
|
||||
var chckCovered = false
|
||||
var covered: BiggestInt = 0
|
||||
var typ = commonTypeBegin
|
||||
var hasElse = false
|
||||
let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc})
|
||||
case caseTyp.kind
|
||||
of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool:
|
||||
chckCovered = true
|
||||
of tyFloat..tyFloat128, tyString, tyError:
|
||||
discard
|
||||
else:
|
||||
localError(c.config, n.info, errSelectorMustBeOfCertainTypes)
|
||||
return
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
var x = n.sons[i]
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
|
||||
suggestEnum(c, x, caseTyp)
|
||||
case x.kind
|
||||
of nkOfBranch:
|
||||
checkMinSonsLen(x, 2, c.config)
|
||||
semCaseBranch(c, n, x, i, covered)
|
||||
var last = sonsLen(x)-1
|
||||
x.sons[last] = semExprBranchScope(c, x.sons[last])
|
||||
typ = commonType(typ, x.sons[last])
|
||||
of nkElifBranch:
|
||||
chckCovered = false
|
||||
checkSonsLen(x, 2, c.config)
|
||||
openScope(c)
|
||||
x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
|
||||
x.sons[1] = semExprBranch(c, x.sons[1])
|
||||
typ = commonType(typ, x.sons[1])
|
||||
closeScope(c)
|
||||
of nkElse:
|
||||
chckCovered = false
|
||||
checkSonsLen(x, 1, c.config)
|
||||
x.sons[0] = semExprBranchScope(c, x.sons[0])
|
||||
typ = commonType(typ, x.sons[0])
|
||||
hasElse = true
|
||||
else:
|
||||
illFormedAst(x, c.config)
|
||||
if chckCovered:
|
||||
if covered == toCover(c, n.sons[0].typ):
|
||||
hasElse = true
|
||||
else:
|
||||
localError(c.config, n.info, "not all cases are covered")
|
||||
closeScope(c)
|
||||
if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
|
||||
for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
|
||||
# propagate any enforced VoidContext:
|
||||
if typ == c.enforceVoidContext:
|
||||
result.typ = c.enforceVoidContext
|
||||
else:
|
||||
for i in 1..n.len-1:
|
||||
var it = n.sons[i]
|
||||
let j = it.len-1
|
||||
if not endsInNoReturn(it.sons[j]):
|
||||
it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
|
||||
result.typ = typ
|
||||
|
||||
proc semTry(c: PContext, n: PNode): PNode =
|
||||
|
||||
var check = initIntSet()
|
||||
@@ -683,29 +618,28 @@ proc isTrivalStmtExpr(n: PNode): bool =
|
||||
return false
|
||||
result = true
|
||||
|
||||
proc handleForLoopMacro(c: PContext; n: PNode): PNode =
|
||||
let iterExpr = n[^2]
|
||||
if iterExpr.kind in nkCallKinds:
|
||||
proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string): PNode =
|
||||
if selector.kind in nkCallKinds:
|
||||
# we transform
|
||||
# n := for a, b, c in m(x, y, z): Y
|
||||
# to
|
||||
# m(n)
|
||||
let forLoopStmt = magicsys.getCompilerProc(c.graph, "ForLoopStmt")
|
||||
if forLoopStmt == nil: return
|
||||
let maType = magicsys.getCompilerProc(c.graph, magicType)
|
||||
if maType == nil: return
|
||||
|
||||
let headSymbol = iterExpr[0]
|
||||
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] == forLoopStmt.typ:
|
||||
if symx.typ.len == 2 and symx.typ[1] == maType.typ:
|
||||
if match == nil:
|
||||
match = symx
|
||||
else:
|
||||
localError(c.config, n.info, errAmbiguousCallXYZ % [
|
||||
getProcHeader(c.config, match),
|
||||
getProcHeader(c.config, symx), $iterExpr])
|
||||
getProcHeader(c.config, symx), $selector])
|
||||
symx = nextOverloadIter(o, c, headSymbol)
|
||||
|
||||
if match == nil: return
|
||||
@@ -717,6 +651,38 @@ proc handleForLoopMacro(c: PContext; n: PNode): PNode =
|
||||
of skTemplate: result = semTemplateExpr(c, callExpr, match, {})
|
||||
else: result = nil
|
||||
|
||||
proc handleForLoopMacro(c: PContext; n: PNode): PNode =
|
||||
result = handleStmtMacro(c, n, n[^2], "ForLoopStmt")
|
||||
|
||||
proc handleCaseStmtMacro(c: PContext; n: PNode): PNode =
|
||||
# n[0] has been sem'checked and has a type. We use this to resolve
|
||||
# 'match(n[0])' but then we pass 'n' to the 'match' macro. This seems to
|
||||
# be the best solution.
|
||||
var toResolve = newNodeI(nkCall, n.info)
|
||||
toResolve.add newIdentNode(getIdent(c.cache, "match"), n.info)
|
||||
toResolve.add n[0]
|
||||
|
||||
var errors: CandidateErrors
|
||||
var r = resolveOverloads(c, toResolve, toResolve, {skTemplate, skMacro}, {},
|
||||
errors, false)
|
||||
if r.state == csMatch:
|
||||
var match = r.calleeSym
|
||||
markUsed(c.config, n[0].info, match, c.graph.usageSym)
|
||||
styleCheckUse(n[0].info, match)
|
||||
|
||||
# but pass 'n' to the 'match' macro, not 'n[0]':
|
||||
r.call.sons[1] = n
|
||||
let toExpand = semResolvedCall(c, r, r.call, {})
|
||||
case match.kind
|
||||
of skMacro: result = semMacroExpr(c, toExpand, toExpand, match, {})
|
||||
of skTemplate: result = semTemplateExpr(c, toExpand, match, {})
|
||||
else: result = nil
|
||||
# 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): PNode =
|
||||
checkMinSonsLen(n, 3, c.config)
|
||||
var length = sonsLen(n)
|
||||
@@ -758,6 +724,75 @@ proc semFor(c: PContext, n: PNode): PNode =
|
||||
result.typ = c.enforceVoidContext
|
||||
closeScope(c)
|
||||
|
||||
proc semCase(c: PContext, n: PNode): PNode =
|
||||
result = n
|
||||
checkMinSonsLen(n, 2, c.config)
|
||||
openScope(c)
|
||||
n.sons[0] = semExprWithType(c, n.sons[0])
|
||||
var chckCovered = false
|
||||
var covered: BiggestInt = 0
|
||||
var typ = commonTypeBegin
|
||||
var hasElse = false
|
||||
let caseTyp = skipTypes(n.sons[0].typ, abstractVarRange-{tyTypeDesc})
|
||||
case caseTyp.kind
|
||||
of tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt32, tyBool:
|
||||
chckCovered = true
|
||||
of tyFloat..tyFloat128, tyString, tyError:
|
||||
discard
|
||||
else:
|
||||
if caseStmtMacros in c.features:
|
||||
result = handleCaseStmtMacro(c, n)
|
||||
if result != nil: return result
|
||||
|
||||
localError(c.config, n.info, errSelectorMustBeOfCertainTypes)
|
||||
return
|
||||
for i in countup(1, sonsLen(n) - 1):
|
||||
var x = n.sons[i]
|
||||
when defined(nimsuggest):
|
||||
if c.config.ideCmd == ideSug and exactEquals(c.config.m.trackPos, x.info) and caseTyp.kind == tyEnum:
|
||||
suggestEnum(c, x, caseTyp)
|
||||
case x.kind
|
||||
of nkOfBranch:
|
||||
checkMinSonsLen(x, 2, c.config)
|
||||
semCaseBranch(c, n, x, i, covered)
|
||||
var last = sonsLen(x)-1
|
||||
x.sons[last] = semExprBranchScope(c, x.sons[last])
|
||||
typ = commonType(typ, x.sons[last])
|
||||
of nkElifBranch:
|
||||
chckCovered = false
|
||||
checkSonsLen(x, 2, c.config)
|
||||
openScope(c)
|
||||
x.sons[0] = forceBool(c, semExprWithType(c, x.sons[0]))
|
||||
x.sons[1] = semExprBranch(c, x.sons[1])
|
||||
typ = commonType(typ, x.sons[1])
|
||||
closeScope(c)
|
||||
of nkElse:
|
||||
chckCovered = false
|
||||
checkSonsLen(x, 1, c.config)
|
||||
x.sons[0] = semExprBranchScope(c, x.sons[0])
|
||||
typ = commonType(typ, x.sons[0])
|
||||
hasElse = true
|
||||
else:
|
||||
illFormedAst(x, c.config)
|
||||
if chckCovered:
|
||||
if covered == toCover(c, n.sons[0].typ):
|
||||
hasElse = true
|
||||
else:
|
||||
localError(c.config, n.info, "not all cases are covered")
|
||||
closeScope(c)
|
||||
if isEmptyType(typ) or typ.kind in {tyNil, tyExpr} or not hasElse:
|
||||
for i in 1..n.len-1: discardCheck(c, n.sons[i].lastSon)
|
||||
# propagate any enforced VoidContext:
|
||||
if typ == c.enforceVoidContext:
|
||||
result.typ = c.enforceVoidContext
|
||||
else:
|
||||
for i in 1..n.len-1:
|
||||
var it = n.sons[i]
|
||||
let j = it.len-1
|
||||
if not endsInNoReturn(it.sons[j]):
|
||||
it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info)
|
||||
result.typ = typ
|
||||
|
||||
proc semRaise(c: PContext, n: PNode): PNode =
|
||||
result = n
|
||||
checkSonsLen(n, 1, c.config)
|
||||
|
||||
@@ -5411,6 +5411,58 @@ Currently for loop macros must be enabled explicitly
|
||||
via ``{.experimental: "forLoopMacros".}``.
|
||||
|
||||
|
||||
Case statement macros
|
||||
---------------------
|
||||
|
||||
A macro that needs to be called `match`:idx: can be used to
|
||||
rewrite ``case`` statements in order to
|
||||
implement `pattern matching`:idx: for certain types. The following
|
||||
example implements a simplistic form of pattern matching for tuples,
|
||||
leveraging the existing equality operator for tuples (as provided in
|
||||
``system.==``):
|
||||
|
||||
.. code-block:: nim
|
||||
:test: "nim c $1"
|
||||
|
||||
{.experimental: "caseStmtMacros".}
|
||||
|
||||
import macros
|
||||
|
||||
macro match(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 "'match' cannot handle this node", it
|
||||
echo repr result
|
||||
|
||||
case ("foo", 78)
|
||||
of ("foo", 78): echo "yes"
|
||||
of ("bar", 88): echo "no"
|
||||
else: discard
|
||||
|
||||
|
||||
Currently case statement macros must be enabled explicitly
|
||||
via ``{.experimental: "caseStmtMacros".}``.
|
||||
|
||||
``match`` macros are subject to overload resolution. First the
|
||||
``case``'s selector expression is used to determine which ``match``
|
||||
macro to call. To this macro is then the complete ``case`` statement
|
||||
body is passed 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
|
||||
=============
|
||||
|
||||
|
||||
Reference in New Issue
Block a user