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:
parent
da41fc1801
commit
1061e26bc9
4 changed files with 172 additions and 79 deletions
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 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 ``math.prod`` for product of elements in openArray.
|
||||||
- Added the proc ``parseBinInt`` to parse a binary integer from a string, which returns the value.
|
- 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 proc ``flush`` for memory mapped files.
|
||||||
- Added the ``MemMapFileStream``.
|
- Added the ``MemMapFileStream``.
|
||||||
- Added ``macros.copyLineInfo`` to copy lineInfo from other node.
|
- Added ``macros.copyLineInfo`` to copy lineInfo from other node.
|
||||||
|
|
@ -138,7 +138,13 @@
|
||||||
- ``func`` is now an alias for ``proc {.noSideEffect.}``.
|
- ``func`` is now an alias for ``proc {.noSideEffect.}``.
|
||||||
- In order to make ``for`` loops and iterators more flexible to use Nim now
|
- In order to make ``for`` loops and iterators more flexible to use Nim now
|
||||||
supports so called "for-loop macros". See
|
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`.
|
- the `typedesc` special type has been renamed to just `type`.
|
||||||
- `static` and `type` are now also modifiers similar to `ref` and `ptr`.
|
- `static` and `type` are now also modifiers similar to `ref` and `ptr`.
|
||||||
They denote the special types `static[T]` and `type[T]`.
|
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
|
- Thread-local variables can now be declared inside procs. This implies all
|
||||||
the effects of the ``global`` pragma.
|
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.
|
- 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
|
- 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.
|
- 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 /
|
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.
|
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
|
### Bugfixes
|
||||||
|
|
|
||||||
|
|
@ -119,8 +119,8 @@ type
|
||||||
destructor,
|
destructor,
|
||||||
notnil,
|
notnil,
|
||||||
dynamicBindSym,
|
dynamicBindSym,
|
||||||
forLoopMacros
|
forLoopMacros,
|
||||||
#caseStmtMacros
|
caseStmtMacros
|
||||||
|
|
||||||
SymbolFilesOption* = enum
|
SymbolFilesOption* = enum
|
||||||
disabledSf, writeOnlySf, readOnlySf, v2Sf
|
disabledSf, writeOnlySf, readOnlySf, v2Sf
|
||||||
|
|
|
||||||
|
|
@ -178,71 +178,6 @@ proc semIf(c: PContext, n: PNode): PNode =
|
||||||
result.kind = nkIfExpr
|
result.kind = nkIfExpr
|
||||||
result.typ = typ
|
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 =
|
proc semTry(c: PContext, n: PNode): PNode =
|
||||||
|
|
||||||
var check = initIntSet()
|
var check = initIntSet()
|
||||||
|
|
@ -683,29 +618,28 @@ proc isTrivalStmtExpr(n: PNode): bool =
|
||||||
return false
|
return false
|
||||||
result = true
|
result = true
|
||||||
|
|
||||||
proc handleForLoopMacro(c: PContext; n: PNode): PNode =
|
proc handleStmtMacro(c: PContext; n, selector: PNode; magicType: string): PNode =
|
||||||
let iterExpr = n[^2]
|
if selector.kind in nkCallKinds:
|
||||||
if iterExpr.kind in nkCallKinds:
|
|
||||||
# we transform
|
# we transform
|
||||||
# n := for a, b, c in m(x, y, z): Y
|
# n := for a, b, c in m(x, y, z): Y
|
||||||
# to
|
# to
|
||||||
# m(n)
|
# m(n)
|
||||||
let forLoopStmt = magicsys.getCompilerProc(c.graph, "ForLoopStmt")
|
let maType = magicsys.getCompilerProc(c.graph, magicType)
|
||||||
if forLoopStmt == nil: return
|
if maType == nil: return
|
||||||
|
|
||||||
let headSymbol = iterExpr[0]
|
let headSymbol = selector[0]
|
||||||
var o: TOverloadIter
|
var o: TOverloadIter
|
||||||
var match: PSym = nil
|
var match: PSym = nil
|
||||||
var symx = initOverloadIter(o, c, headSymbol)
|
var symx = initOverloadIter(o, c, headSymbol)
|
||||||
while symx != nil:
|
while symx != nil:
|
||||||
if symx.kind in {skTemplate, skMacro}:
|
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:
|
if match == nil:
|
||||||
match = symx
|
match = symx
|
||||||
else:
|
else:
|
||||||
localError(c.config, n.info, errAmbiguousCallXYZ % [
|
localError(c.config, n.info, errAmbiguousCallXYZ % [
|
||||||
getProcHeader(c.config, match),
|
getProcHeader(c.config, match),
|
||||||
getProcHeader(c.config, symx), $iterExpr])
|
getProcHeader(c.config, symx), $selector])
|
||||||
symx = nextOverloadIter(o, c, headSymbol)
|
symx = nextOverloadIter(o, c, headSymbol)
|
||||||
|
|
||||||
if match == nil: return
|
if match == nil: return
|
||||||
|
|
@ -717,6 +651,38 @@ proc handleForLoopMacro(c: PContext; n: PNode): PNode =
|
||||||
of skTemplate: result = semTemplateExpr(c, callExpr, match, {})
|
of skTemplate: result = semTemplateExpr(c, callExpr, match, {})
|
||||||
else: result = nil
|
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 =
|
proc semFor(c: PContext, n: PNode): PNode =
|
||||||
checkMinSonsLen(n, 3, c.config)
|
checkMinSonsLen(n, 3, c.config)
|
||||||
var length = sonsLen(n)
|
var length = sonsLen(n)
|
||||||
|
|
@ -758,6 +724,75 @@ proc semFor(c: PContext, n: PNode): PNode =
|
||||||
result.typ = c.enforceVoidContext
|
result.typ = c.enforceVoidContext
|
||||||
closeScope(c)
|
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 =
|
proc semRaise(c: PContext, n: PNode): PNode =
|
||||||
result = n
|
result = n
|
||||||
checkSonsLen(n, 1, c.config)
|
checkSonsLen(n, 1, c.config)
|
||||||
|
|
|
||||||
|
|
@ -5411,6 +5411,58 @@ Currently for loop macros must be enabled explicitly
|
||||||
via ``{.experimental: "forLoopMacros".}``.
|
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
|
Special Types
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue