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:
Andreas Rumpf 2018-08-16 00:16:49 +02:00
commit 1061e26bc9
4 changed files with 172 additions and 79 deletions

View file

@ -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)