Files
Nim/compiler/guards.nim
2013-06-09 23:33:32 +02:00

347 lines
11 KiB
Nim

#
#
# The Nimrod Compiler
# (c) Copyright 2013 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements the 'implies' relation for guards.
import ast, astalgo, msgs, magicsys, nimsets, trees, types, renderer
const
someEq = {mEqI, mEqI64, mEqF64, mEqEnum, mEqCh, mEqB, mEqRef, mEqProc,
mEqUntracedRef, mEqStr, mEqSet, mEqCString}
# set excluded here as the semantics are vastly different:
someLe = {mLeI, mLeI64, mLeF64, mLeU, mLeU64, mLeEnum,
mLeCh, mLeB, mLePtr, mLeStr}
someLt = {mLtI, mLtI64, mLtF64, mLtU, mLtU64, mLtEnum,
mLtCh, mLtB, mLtPtr, mLtStr}
someLen = {mLengthOpenArray, mLengthStr, mLengthArray, mLengthSeq}
someIn = {mInRange, mInSet}
proc isValue(n: PNode): bool = n.kind in {nkCharLit..nkNilLit}
proc isLocation(n: PNode): bool = not n.isValue
#n.kind in {nkSym, nkBracketExpr, nkDerefExpr, nkHiddenDeref, nkDotExpr}
proc isLet(n: PNode): bool =
if n.kind == nkSym:
# XXX allow skResult, skVar here if not re-bound
if n.sym.kind in {skLet, skTemp, skForVar}:
result = true
elif n.sym.kind == skParam and skipTypes(n.sym.typ,
abstractInst).kind != tyVar:
result = true
proc isLetLocation(m: PNode): bool =
var n = m
while true:
case n.kind
of nkDotExpr, nkCheckedFieldExpr, nkObjUpConv, nkObjDownConv:
n = n.sons[0]
of nkBracketExpr:
if isConstExpr(n.sons[1]) or isLet(n.sons[1]):
n = n.sons[0]
else: return
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
n = n.sons[1]
else:
break
result = n.isLet
proc neg(n: PNode): PNode =
if n.getMagic == mNot:
result = n.sons[1]
else:
result = newNodeI(nkCall, n.info, 2)
result.sons[0] = newSymNode(getSysMagic("not", mNot))
result.sons[1] = n
proc usefulFact(n: PNode): PNode =
case n.getMagic
of someEq+someLe+someLt:
if isLetLocation(n.sons[1]) or n.len == 3 and isLetLocation(n.sons[2]):
# XXX algebraic simplifications! 'i-1 < a.len' --> 'i < a.len+1'
result = n
of someIn, mIsNil:
if isLetLocation(n.sons[1]):
result = n
of mAnd:
let
a = usefulFact(n.sons[1])
b = usefulFact(n.sons[2])
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a
result.sons[2] = b
elif a != nil:
result = a
elif b != nil:
result = b
of mNot:
case n.sons[1].getMagic
of mNot:
# normalize 'not (not a)' into 'a':
result = usefulFact(n.sons[1].sons[1])
of mOr:
# not (a or b) --> not a and not b
let n = n.sons[1]
let
a = usefulFact(n.sons[1])
b = usefulFact(n.sons[2])
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a.neg
result.sons[2] = b.neg
else:
let a = usefulFact(n.sons[1])
if a != nil: result = n
of mOr:
# 'or' sucks! (p.isNil or q.isNil) --> hard to do anything
# with that knowledge...
# DeMorgan helps a little though:
# not a or not b --> not (a and b)
# (x == 3) or (y == 2) ---> not ( not (x==3) and not (y == 2))
# not (x != 3 and y != 2)
let
a = usefulFact(n.sons[1])
b = usefulFact(n.sons[2])
if a != nil and b != nil:
result = newNodeI(nkCall, n.info, 3)
result.sons[0] = newSymNode(getSysMagic("and", mAnd))
result.sons[1] = a.neg
result.sons[2] = b.neg
result = result.neg
elif n.kind == nkSym and n.sym.kind == skLet:
# consider:
# let a = 2 < x
# if a:
# ...
# We make can easily replace 'a' by '2 < x' here:
result = usefulFact(n.sym.ast)
elif n.kind == nkStmtListExpr:
result = usefulFact(n.lastSon)
type
TModel* = seq[PNode] # the "knowledge base"
proc addFact*(m: var TModel, n: PNode) =
let n = usefulFact(n)
if n != nil: m.add n
proc addFactNeg*(m: var TModel, n: PNode) = addFact(m, n.neg)
proc sameTree(a, b: PNode): bool =
result = false
if a == b:
result = true
elif (a != nil) and (b != nil) and (a.kind == b.kind):
case a.kind
of nkSym: result = a.sym == b.sym
of nkIdent: result = a.ident.id == b.ident.id
of nkCharLit..nkInt64Lit: result = a.intVal == b.intVal
of nkFloatLit..nkFloat64Lit: result = a.floatVal == b.floatVal
of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
of nkType: result = a.typ == b.typ
of nkEmpty, nkNilLit: result = true
else:
if sonsLen(a) == sonsLen(b):
for i in countup(0, sonsLen(a) - 1):
if not sameTree(a.sons[i], b.sons[i]): return
result = true
proc valuesUnequal(a, b: PNode): bool =
if a.isValue and b.isValue:
result = not SameValue(a, b)
type
TImplication* = enum
impUnknown, impNo, impYes
proc impliesEq(fact, eq: PNode): TImplication =
let (loc, val) = if isLocation(eq.sons[1]): (1, 2) else: (2, 1)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], eq.sons[loc]):
# this is not correct; consider: a == b; a == 1 --> unknown!
if sameTree(fact.sons[2], eq.sons[val]): result = impYes
elif valuesUnequal(fact.sons[2], eq.sons[val]): result = impNo
elif sameTree(fact.sons[2], eq.sons[loc]):
if sameTree(fact.sons[1], eq.sons[val]): result = impYes
elif valuesUnequal(fact.sons[1], eq.sons[val]): result = impNo
of mInSet:
if sameTree(fact.sons[1], eq.sons[loc]) and isValue(eq.sons[val]):
if inSet(fact.sons[2], eq.sons[val]): result = impYes
else: result = impNo
of mIsNil:
if sameTree(fact.sons[1], eq.sons[loc]):
if eq.sons[val].kind == nkNilLit:
result = impYes
of mNot, mOr, mAnd: internalError(eq.info, "impliesEq")
else: nil
proc impliesIsNil(fact, eq: PNode): TImplication =
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], eq.sons[1]):
if fact.sons[2].kind == nkNilLit: result = impYes
elif sameTree(fact.sons[2], eq.sons[1]):
if fact.sons[1].kind == nkNilLit: result = impYes
of mIsNil:
if sameTree(fact.sons[1], eq.sons[1]):
result = impYes
of mNot, mOr, mAnd: internalError(eq.info, "impliesIsNil")
else: nil
proc pred(n: PNode): PNode =
if n.kind in {nkCharLit..nkUInt64Lit} and n.intVal != low(biggestInt):
result = copyNode(n)
dec result.intVal
else:
result = n
proc impliesGe(fact, x, c: PNode): TImplication =
InternalAssert isLocation(x)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x = 4; question x >= 56? --> true iff 4 >= 56
if leValue(c, fact.sons[2]): result = impYes
else: result = impNo
elif sameTree(fact.sons[2], x):
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impYes
else: result = impNo
of someLt:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x < 4; question N <= x? --> false iff N <= 4
if leValue(fact.sons[2], c): result = impNo
# fact: x < 4; question 2 <= x? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 < x; question: N-1 < x ? --> true iff N-1 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c.pred, fact.sons[1]): result = impYes
of someLe:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x <= 4; question x >= 56? --> false iff 4 <= 56
if leValue(fact.sons[2], c): result = impNo
# fact: x <= 4; question x >= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 <= x; question: x >= 2 ? --> true iff 2 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impYes
of mNot, mOr, mAnd: internalError(x.info, "impliesGe")
else: nil
proc impliesLe(fact, x, c: PNode): TImplication =
if not isLocation(x):
return impliesGe(fact, c, x)
case fact.sons[0].sym.magic
of someEq:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x = 4; question x <= 56? --> true iff 4 <= 56
if leValue(fact.sons[2], c): result = impYes
else: result = impNo
elif sameTree(fact.sons[2], x):
if isValue(fact.sons[1]) and isValue(c):
if leValue(fact.sons[1], c): result = impYes
else: result = impNo
of someLt:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x < 4; question x <= N? --> true iff N-1 <= 4
if leValue(fact.sons[2], c.pred): result = impYes
# fact: x < 4; question x <= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 < x; question: x <= 1 ? --> false iff 1 <= 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1]): result = impNo
of someLe:
if sameTree(fact.sons[1], x):
if isValue(fact.sons[2]) and isValue(c):
# fact: x <= 4; question x <= 56? --> true iff 4 <= 56
if leValue(fact.sons[2], c): result = impYes
# fact: x <= 4; question x <= 2? --> we don't know
elif sameTree(fact.sons[2], x):
# fact: 3 <= x; question: x <= 2 ? --> false iff 2 < 3
if isValue(fact.sons[1]) and isValue(c):
if leValue(c, fact.sons[1].pred): result = impNo
of mNot, mOr, mAnd: internalError(x.info, "impliesLe")
else: nil
proc impliesLt(fact, x, c: PNode): TImplication =
# x < 3 same as x <= 2:
let p = c.pred
if p != c:
result = impliesLe(fact, x, p)
else:
# 4 < x same as 3 <= x
let q = x.pred
if q != x:
result = impliesLe(fact, q, c)
proc factImplies(fact, prop: PNode, isNegation: bool): TImplication =
case fact.getMagic
of mNot:
case factImplies(fact.sons[1], prop, not isNegation)
of impUnknown: return impUnknown
of impNo: return impYes
of impYes: return impNo
of mAnd:
if not isNegation:
result = factImplies(fact.sons[1], prop, isNegation)
if result != impUnknown: return result
return factImplies(fact.sons[2], prop, isNegation)
else:
# careful! not (a and b) means not a or not b:
# a or b --> both need to imply 'prop'
let a = factImplies(fact.sons[1], prop, isNegation)
let b = factImplies(fact.sons[2], prop, isNegation)
if a == b: return a
return impUnknown
else: discard
case prop.sons[0].sym.magic
of mNot:
case fact.factImplies(prop.sons[1], isNegation)
of impUnknown: result = impUnknown
of impNo: result = impYes
of impYes: result = impNo
of mIsNil:
result = impliesIsNil(fact, prop)
of someEq:
result = impliesEq(fact, prop)
of someLe:
result = impliesLe(fact, prop.sons[1], prop.sons[2])
of someLt:
result = impliesLt(fact, prop.sons[1], prop.sons[2])
else:
internalError(prop.info, "invalid proposition")
proc doesImply*(facts: TModel, prop: PNode): TImplication =
assert prop.kind in nkCallKinds
for f in facts:
result = f.factImplies(prop, false)
if result != impUnknown: return
proc impliesNotNil*(facts: TModel, arg: PNode): TImplication =
var x = newNodeI(nkCall, arg.info, 2)
x.sons[0] = newSymNode(getSysMagic("isNil", mIsNil))
x.sons[1] = arg
result = doesImply(facts, x.neg)