term rewriting macros fully implemented; still buggy

This commit is contained in:
Araq 2012-09-03 00:55:44 +02:00
commit af7c92c003
22 changed files with 676 additions and 138 deletions

View file

@ -176,7 +176,6 @@ type
nkFromStmt, # a from * import statement nkFromStmt, # a from * import statement
nkIncludeStmt, # an include statement nkIncludeStmt, # an include statement
nkBindStmt, # a bind statement nkBindStmt, # a bind statement
nkPattern, # a pattern statement ('as' statement)
nkCommentStmt, # a comment statement nkCommentStmt, # a comment statement
nkStmtListExpr, # a statement list followed by an expr; this is used nkStmtListExpr, # a statement list followed by an expr; this is used
# to allow powerful multi-line templates # to allow powerful multi-line templates
@ -201,6 +200,8 @@ type
nkProcTy, # proc type nkProcTy, # proc type
nkEnumTy, # enum body nkEnumTy, # enum body
nkEnumFieldDef, # `ident = expr` in an enumeration nkEnumFieldDef, # `ident = expr` in an enumeration
nkArgList, # argument list
nkPattern, # a special pattern; used for matching
nkReturnToken, # token used for interpretation nkReturnToken, # token used for interpretation
nkClosure, # (prc, env)-pair (internally used for code gen) nkClosure, # (prc, env)-pair (internally used for code gen)
nkGotoState, # used for the state machine (for iterators) nkGotoState, # used for the state machine (for iterators)
@ -584,7 +585,7 @@ type
# for a conditional: # for a conditional:
# 1 iff the symbol is defined, else 0 # 1 iff the symbol is defined, else 0
# (or not in symbol table) # (or not in symbol table)
# for modules, a unique index correspinding # for modules, a unique index corresponding
# to the order of compilation # to the order of compilation
offset*: int # offset of record field offset*: int # offset of record field
loc*: TLoc loc*: TLoc
@ -616,6 +617,7 @@ type
align*: int # the type's alignment requirements align*: int # the type's alignment requirements
containerID*: int # used for type checking of generics containerID*: int # used for type checking of generics
loc*: TLoc loc*: TLoc
constraint*: PNode # additional constraints like 'lit|result'
TPair*{.final.} = object TPair*{.final.} = object
key*, val*: PObject key*, val*: PObject
@ -866,12 +868,21 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result.info = info result.info = info
proc newNodeI(kind: TNodeKind, info: TLineInfo): PNode = proc newNodeI(kind: TNodeKind, info: TLineInfo): PNode =
result = newNode(kind) new(result)
result.kind = kind
result.info = info result.info = info
proc newNodeI*(kind: TNodeKind, info: TLineInfo, children: int): PNode =
new(result)
result.kind = kind
result.info = info
if children > 0:
newSeq(result.sons, children)
proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[], proc newNode*(kind: TNodeKind, info: TLineInfo, sons: TNodeSeq = @[],
typ: PType = nil): PNode = typ: PType = nil): PNode =
result = newNode(kind) new(result)
result.kind = kind
result.info = info result.info = info
result.typ = typ result.typ = typ
# XXX use shallowCopy here for ownership transfer: # XXX use shallowCopy here for ownership transfer:
@ -1184,3 +1195,5 @@ proc hasPattern*(s: PSym): bool {.inline.} =
iterator items*(n: PNode): PNode = iterator items*(n: PNode): PNode =
for i in 0.. <n.len: yield n.sons[i] for i in 0.. <n.len: yield n.sons[i]
proc isAtom*(n: PNode): bool {.inline.} =
result = n.kind >= nkNone and n.kind <= nkNilLit

View file

@ -1246,10 +1246,10 @@ proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
of mIncl: of mIncl:
var ts = "NI" & $(size * 8) var ts = "NI" & $(size * 8)
binaryStmtInExcl(p, e, d, binaryStmtInExcl(p, e, d,
"$1 |=(1<<((" & ts & ")($2)%(sizeof(" & ts & ")*8)));$n") "$1 |=((" & ts & ")(1)<<(($2)%(sizeof(" & ts & ")*8)));$n")
of mExcl: of mExcl:
var ts = "NI" & $(size * 8) var ts = "NI" & $(size * 8)
binaryStmtInExcl(p, e, d, "$1 &= ~(1 << ((" & ts & ")($2) % (sizeof(" & binaryStmtInExcl(p, e, d, "$1 &= ~((" & ts & ")(1) << (($2) % (sizeof(" &
ts & ")*8)));$n") ts & ")*8)));$n")
of mCard: of mCard:
if size <= 4: unaryExprChar(p, e, d, "#countBits32($1)") if size <= 4: unaryExprChar(p, e, d, "#countBits32($1)")

View file

@ -19,8 +19,37 @@ type
mapping: TIdTable # every gensym'ed symbol needs to be mapped to some mapping: TIdTable # every gensym'ed symbol needs to be mapped to some
# new symbol # new symbol
proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx, result: PNode) =
case templ.kind
of nkSym:
var s = templ.sym
if s.owner.id == c.owner.id:
if s.kind == skParam:
let x = actual.sons[s.position]
if x.kind == nkArgList:
for y in items(x): result.add(y)
else:
result.add copyTree(x)
else:
InternalAssert sfGenSym in s.flags
var x = PSym(IdTableGet(c.mapping, s))
if x == nil:
x = copySym(s, false)
x.owner = c.genSymOwner
IdTablePut(c.mapping, s, x)
result.add newSymNode(x, templ.info)
else:
result.add copyNode(templ)
of nkNone..nkIdent, nkType..nkNilLit: # atom
result.add copyNode(templ)
else:
var res = copyNode(templ)
for i in countup(0, sonsLen(templ) - 1):
evalTemplateAux(templ.sons[i], actual, c, res)
result.add res
when false:
proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx): PNode = proc evalTemplateAux(templ, actual: PNode, c: var TemplCtx): PNode =
#inc genSymBaseId
case templ.kind case templ.kind
of nkSym: of nkSym:
var s = templ.sym var s = templ.sym
@ -78,6 +107,19 @@ proc evalTemplate*(n: PNode, tmpl, genSymOwner: PSym): PNode =
ctx.owner = tmpl ctx.owner = tmpl
ctx.genSymOwner = genSymOwner ctx.genSymOwner = genSymOwner
initIdTable(ctx.mapping) initIdTable(ctx.mapping)
result = evalTemplateAux(tmpl.getBody, args, ctx)
let body = tmpl.getBody
if isAtom(body):
result = newNodeI(nkPar, body.info)
evalTemplateAux(body, args, ctx, result)
if result.len == 1: result = result.sons[0]
else:
GlobalError(result.info, errIllFormedAstX,
renderTree(result, {renderNoComments}))
else:
result = copyNode(body)
#evalTemplateAux(body, args, ctx, result)
for i in countup(0, safeLen(body) - 1):
evalTemplateAux(body.sons[i], args, ctx, result)
dec(evalTemplateCounter) dec(evalTemplateCounter)

View file

@ -109,7 +109,7 @@ type
hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded, hintLineTooLong, hintXDeclaredButNotUsed, hintConvToBaseNotNeeded,
hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled, hintConvFromXtoItselfNotNeeded, hintExprAlwaysX, hintQuitCalled,
hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath, hintProcessing, hintCodeBegin, hintCodeEnd, hintConf, hintPath,
hintConditionAlwaysTrue, hintConditionAlwaysTrue, hintPattern,
hintUser hintUser
const const
@ -152,7 +152,7 @@ const
errExceptionAlreadyHandled: "exception already handled", errExceptionAlreadyHandled: "exception already handled",
errYieldNotAllowedHere: "'yield' only allowed in an iterator", errYieldNotAllowedHere: "'yield' only allowed in an iterator",
errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator", errYieldNotAllowedInTryStmt: "'yield' cannot be used within 'try' in a non-inlined iterator",
errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expresions", errInvalidNumberOfYieldExpr: "invalid number of \'yield\' expressions",
errCannotReturnExpr: "current routine cannot return an expression", errCannotReturnExpr: "current routine cannot return an expression",
errAttemptToRedefine: "redefinition of \'$1\'", errAttemptToRedefine: "redefinition of \'$1\'",
errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\' or \'raise\'", errStmtInvalidAfterReturn: "statement not allowed after \'return\', \'break\' or \'raise\'",
@ -366,6 +366,7 @@ const
hintConf: "used config file \'$1\' [Conf]", hintConf: "used config file \'$1\' [Conf]",
hintPath: "added path: '$1' [Path]", hintPath: "added path: '$1' [Path]",
hintConditionAlwaysTrue: "condition is always true: '$1' [CondTrue]", hintConditionAlwaysTrue: "condition is always true: '$1' [CondTrue]",
hintPattern: "$1 [Pattern]",
hintUser: "$1 [User]"] hintUser: "$1 [User]"]
const const
@ -378,10 +379,10 @@ const
"AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap", "AnalysisLoophole", "DifferentHeaps", "WriteToForeignHeap",
"ImplicitClosure", "EachIdentIsTuple", "User"] "ImplicitClosure", "EachIdentIsTuple", "User"]
HintsToStr*: array[0..14, string] = ["Success", "SuccessX", "LineTooLong", HintsToStr*: array[0..15, string] = ["Success", "SuccessX", "LineTooLong",
"XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded", "XDeclaredButNotUsed", "ConvToBaseNotNeeded", "ConvFromXtoItselfNotNeeded",
"ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf", "ExprAlwaysX", "QuitCalled", "Processing", "CodeBegin", "CodeEnd", "Conf",
"Path", "CondTrue", "Path", "CondTrue", "Pattern",
"User"] "User"]
const const

205
compiler/parampatterns.nim Normal file
View file

@ -0,0 +1,205 @@
#
#
# The Nimrod Compiler
# (c) Copyright 2012 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements the pattern matching features for term rewriting
## macro support.
import strutils, ast, astalgo, types, msgs, idents, renderer, wordrecg
# we precompile the pattern here for efficiency into some internal
# stack based VM :-) Why? Because it's fun; I did no benchmarks to see if that
# actually improves performance.
type
TAliasRequest* = enum # first byte of the bytecode determines alias checking
aqNone = 1, # no alias analysis requested
aqShouldAlias, # with what?
aqNoAlias # request noalias
TOpcode = enum
ppEof = 1, # end of compiled pattern
ppOr, # we could short-cut the evaluation for 'and' and 'or',
ppAnd, # but currently we don't
ppNot,
ppSym,
ppAtom,
ppLit,
ppIdent,
ppCall,
ppSymKind,
ppNodeKind,
ppSideEffect,
ppNoSideEffect
TPatternCode = string
const
MaxStackSize* = 64 ## max required stack size by the VM
proc patternError(n: PNode) =
LocalError(n.info, errIllFormedAstX, renderTree(n, {renderNoComments}))
proc add(code: var TPatternCode, op: TOpcode) {.inline.} =
add(code, chr(ord(op)))
proc whichAlias*(p: PSym): TAliasRequest =
if p.typ.constraint != nil:
result = TAliasRequest(p.typ.constraint.strVal[0].ord)
proc compileConstraints(p: PNode, result: var TPatternCode) =
case p.kind
of nkCallKinds:
if p.sons[0].kind != nkIdent:
patternError(p.sons[0])
return
let op = p.sons[0].ident
if p.len == 3:
if op.s == "|" or op.id == ord(wOr):
compileConstraints(p.sons[1], result)
compileConstraints(p.sons[2], result)
result.add(ppOr)
elif op.s == "&" or op.id == ord(wAnd):
compileConstraints(p.sons[1], result)
compileConstraints(p.sons[2], result)
result.add(ppAnd)
else:
patternError(p)
elif p.len == 2 and (op.s == "~" or op.id == ord(wNot)):
compileConstraints(p.sons[1], result)
result.add(ppNot)
else:
patternError(p)
of nkAccQuoted, nkPar:
if p.len == 1:
compileConstraints(p.sons[0], result)
else:
patternError(p)
of nkIdent:
let spec = p.ident.s.normalize
case spec
of "atom": result.add(ppAtom)
of "lit": result.add(ppLit)
of "sym": result.add(ppSym)
of "ident": result.add(ppIdent)
of "call": result.add(ppCall)
of "alias": result[0] = chr(aqShouldAlias.ord)
of "noalias": result[0] = chr(aqNoAlias.ord)
of "sideeffect": result.add(ppSideEffect)
of "nosideeffect": result.add(ppNoSideEffect)
else:
# check all symkinds:
InternalAssert int(high(TSymKind)) < 255
for i in low(TSymKind)..high(TSymKind):
if cmpIgnoreStyle(($i).substr(2), spec) == 0:
result.add(ppSymKind)
result.add(chr(i.ord))
return
# check all nodekinds:
InternalAssert int(high(TNodeKind)) < 255
for i in low(TNodeKind)..high(TNodeKind):
if cmpIgnoreStyle($i, spec) == 0:
result.add(ppSymKind)
result.add(chr(i.ord))
return
patternError(p)
else:
patternError(p)
proc semNodeKindConstraints*(p: PNode): PNode =
## does semantic checking for a node kind pattern and compiles it into an
## efficient internal format.
assert p.kind == nkCurlyExpr
result = newNodeI(nkStrLit, p.info)
result.strVal = newStringOfCap(10)
result.strVal.add(chr(aqNone.ord))
if p.len >= 2:
for i in 1.. <p.len:
compileConstraints(p.sons[i], result.strVal)
if result.strVal.len > maxStackSize-1:
InternalError(p.info, "parameter pattern too complex")
else:
patternError(p)
result.strVal.add(ppEof)
type
TSideEffectAnalysis = enum
seUnknown, seSideEffect, seNoSideEffect
proc checkForSideEffects(n: PNode): TSideEffectAnalysis =
# XXX is 'raise' a side effect?
case n.kind
of nkCallKinds:
# only calls can produce side effects:
let op = n.sons[0]
if op.kind == nkSym and isRoutine(op.sym):
let s = n.sym
if sfSideEffect in s.flags:
return seSideEffect
# assume no side effect:
result = seNoSideEffect
elif tfNoSideEffect in op.typ.flags:
# indirect call without side effects:
result = seNoSideEffect
else:
# indirect call: we don't know
result = seUnknown
# we need to check n[0] too: (FwithSideEffectButReturnsProcWithout)(args)
for i in 0 .. <n.len:
let ret = checkForSideEffects(n.sons[i])
if ret == seSideEffect: return ret
elif ret == seUnknown and result == seNoSideEffect:
result = seUnknown
of nkNone..nkNilLit:
# an atom cannot produce a side effect:
result = seNoSideEffect
else:
for i in 0 .. <n.len:
let ret = checkForSideEffects(n.sons[i])
if ret == seSideEffect: return ret
elif ret == seUnknown and result == seNoSideEffect:
result = seUnknown
proc matchNodeKinds*(p, n: PNode): bool =
# matches the parameter constraint 'p' against the concrete AST 'n'.
# Efficiency matters here.
var stack {.noinit.}: array[0..maxStackSize, bool]
# empty patterns are true:
stack[0] = true
var sp = 1
template push(x: bool) =
stack[sp] = x
inc sp
let code = p.strVal
var pc = 1
while true:
case TOpcode(code[pc])
of ppEof: break
of ppOr:
stack[sp-2] = stack[sp-1] or stack[sp-2]
dec sp
of ppAnd:
stack[sp-2] = stack[sp-1] and stack[sp-2]
dec sp
of ppNot: stack[sp-1] = not stack[sp-1]
of ppSym: push n.kind == nkSym
of ppAtom: push isAtom(n)
of ppLit: push n.kind in {nkCharLit..nkNilLit}
of ppIdent: push n.kind == nkIdent
of ppCall: push n.kind in nkCallKinds
of ppSymKind:
let kind = TSymKind(code[pc+1])
push n.kind == nkSym and n.sym.kind == kind
inc pc
of ppNodeKind:
let kind = TNodeKind(code[pc+1])
push n.kind == kind
inc pc
of ppSideEffect: push checkForSideEffects(n) != seNoSideEffect
of ppNoSideEffect: push checkForSideEffects(n) == seNoSideEffect
inc pc
result = stack[sp-1]

View file

@ -10,13 +10,16 @@
## This module implements the pattern matching features for term rewriting ## This module implements the pattern matching features for term rewriting
## macro support. ## macro support.
import ast, astalgo, types, semdata, sigmatch, msgs, idents import
ast, astalgo, types, semdata, sigmatch, msgs, idents, aliases, parampatterns,
trees
type type
TPatternContext = object TPatternContext = object
owner: PSym owner: PSym
mapping: TIdNodeTable # maps formal parameters to nodes mapping: TIdNodeTable # maps formal parameters to nodes
c: PContext c: PContext
subMatch: bool # subnode matches are special
PPatternContext = var TPatternContext PPatternContext = var TPatternContext
proc matches(c: PPatternContext, p, n: PNode): bool proc matches(c: PPatternContext, p, n: PNode): bool
@ -53,38 +56,88 @@ proc sameTrees(a, b: PNode): bool =
result = true result = true
proc inSymChoice(sc, x: PNode): bool = proc inSymChoice(sc, x: PNode): bool =
if sc.kind in {nkOpenSymChoice, nkClosedSymChoice}: if sc.kind == nkClosedSymChoice:
for i in 0.. <sc.len: for i in 0.. <sc.len:
if sc.sons[i].sym == x.sym: return true if sc.sons[i].sym == x.sym: return true
elif sc.kind == nkOpenSymChoice:
# same name suffices for open sym choices!
result = sc.sons[0].sym.name.id == x.sym.name.id
proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool = proc checkTypes(c: PPatternContext, p: PSym, n: PNode): bool =
# XXX tyVarargs is special here; lots of other special cases # check param constraints first here as this quite optimized:
if p.typ.constraint != nil:
result = matchNodeKinds(p.typ.constraint, n)
if not result: return
if isNil(n.typ): if isNil(n.typ):
result = p.typ.kind == tyStmt result = p.typ.kind in {tyEmpty, tyStmt}
else: else:
result = sigmatch.argtypeMatches(c.c, p.typ, n.typ) result = sigmatch.argtypeMatches(c.c, p.typ, n.typ)
proc matches(c: PPatternContext, p, n: PNode): bool = proc isPatternParam(c: PPatternContext, p: PNode): bool {.inline.} =
# XXX special treatment: statement list, result = p.kind == nkSym and p.sym.kind == skParam and p.sym.owner == c.owner
# ignore comments, nkPar, hidden conversions
# f(..X) ~> how can 'X' stand for all remaining parameters? -> introduce proc matchChoice(c: PPatternContext, p, n: PNode): bool =
# a new local node kind (alias of nkReturnToken or something) for i in 1 .. <p.len:
if p.kind == nkSym and p.sym.kind == skParam and p.sym.owner == c.owner: if matches(c, p.sons[i], n): return true
var pp = IdNodeTableGetLazy(c.mapping, p.sym)
proc bindOrCheck(c: PPatternContext, param: PSym, n: PNode): bool =
var pp = IdNodeTableGetLazy(c.mapping, param)
if pp != nil: if pp != nil:
# check if we got the same pattern (already unified): # check if we got the same pattern (already unified):
result = matches(c, pp, n) result = sameTrees(pp, n) #matches(c, pp, n)
elif checkTypes(c, p.sym, n) and elif checkTypes(c, param, n) and
(p.sym.ast == nil or checkConstraints(c, p.sym.ast, n)): (param.ast == nil or checkConstraints(c, param.ast, n)):
IdNodeTablePutLazy(c.mapping, p.sym, n) IdNodeTablePutLazy(c.mapping, param, n)
result = true result = true
proc matchStar(c: PPatternContext, p, n: PNode): bool =
# match ``op*param``
# this is quite hard:
# match against: f(a, ..., f(b, c, f(...)))
# we have different semantics if there is a choice as left operand:
proc matchStarAux(c: PPatternContext, op, n, arglist: PNode) =
if n.kind in nkCallKinds and matches(c, op, n.sons[0]):
for i in 1..sonsLen(n)-1: matchStarAux(c, op, n.sons[i], arglist)
else:
add(arglist, n)
if n.kind notin nkCallKinds: return false
if p.sons[0].kind != nkPattern:
if matches(c, p.sons[0], n.sons[0]):
var arglist = newNodeI(nkArgList, n.info)
arglist.typ = p.sons[1].sym.typ
matchStarAux(c, p.sons[0], n, arglist)
result = bindOrCheck(c, p.sons[1].sym, arglist)
else:
# well it matches somehow ...
if matches(c, p.sons[0], n.sons[0]):
result = bindOrCheck(c, p.sons[1].sym, n)
proc matches(c: PPatternContext, p, n: PNode): bool =
# hidden conversions (?)
if isPatternParam(c, p):
result = bindOrCheck(c, p.sym, n)
elif n.kind == nkSym and inSymChoice(p, n): elif n.kind == nkSym and inSymChoice(p, n):
result = true result = true
elif n.kind == nkSym and n.sym.kind == skConst: elif n.kind == nkSym and n.sym.kind == skConst:
# try both: # try both:
if sameTrees(p, n): result = true if p.kind == nkSym: result = p.sym == n.sym
elif matches(c, p, n.sym.ast): elif matches(c, p, n.sym.ast): result = true
result = true elif p.kind == nkPattern:
# pattern operators: | *
let opr = p.sons[0].ident.s
case opr
of "|": result = matchChoice(c, p, n)
of "*": result = matchStar(c, p, n)
of "~": result = not matches(c, p.sons[1], n)
else: InternalError(p.info, "invalid pattern")
# template {add(a, `&` * b)}(a: string{noalias}, b: varargs[string]) =
# add(a, b)
elif p.kind == nkCurlyExpr:
assert isPatternParam(c, p.sons[1])
if matches(c, p.sons[0], n):
result = bindOrCheck(c, p.sons[1].sym, n)
elif sameKinds(p, n): elif sameKinds(p, n):
case p.kind case p.kind
of nkSym: result = p.sym == n.sym of nkSym: result = p.sym == n.sym
@ -94,19 +147,50 @@ proc matches(c: PPatternContext, p, n: PNode): bool =
of nkStrLit..nkTripleStrLit: result = p.strVal == n.strVal of nkStrLit..nkTripleStrLit: result = p.strVal == n.strVal
of nkEmpty, nkNilLit, nkType: of nkEmpty, nkNilLit, nkType:
result = true result = true
# of nkStmtList:
# both are statement lists; we need to ignore comment statements and
# 'nil' statements and check whether p <: n which is however trivially
# checked as 'applyRule' is checked after every created statement
# already; We need to ensure that the matching span is passed to the
# macro and NOT simply 'n'!
# XXX
else: else:
if sonsLen(p) == sonsLen(n): var plen = sonsLen(p)
# special rule for p(X) ~ f(...); this also works for stuff like
# partial case statements, etc! - Not really ... :-/
if plen <= sonsLen(n):
let v = lastSon(p)
if isPatternParam(c, v) and v.sym.typ.kind == tyVarargs:
for i in countup(0, plen - 2):
if not matches(c, p.sons[i], n.sons[i]): return
var arglist = newNodeI(nkArgList, n.info, sonsLen(n) - plen + 1)
# f(1, 2, 3)
# p(X)
for i in countup(0, sonsLen(n) - plen):
arglist.sons[i] = n.sons[i + plen - 1]
# check or bind 'X':
return bindOrCheck(c, v.sym, arglist)
if plen == sonsLen(n):
for i in countup(0, sonsLen(p) - 1): for i in countup(0, sonsLen(p) - 1):
if not matches(c, p.sons[i], n.sons[i]): return if not matches(c, p.sons[i], n.sons[i]): return
result = true result = true
proc matchStmtList(c: PPatternContext, p, n: PNode): PNode =
proc matchRange(c: PPatternContext, p, n: PNode, i: int): bool =
for j in 0 .. <p.len:
if not matches(c, p.sons[j], n.sons[i+j]):
# we need to undo any bindings:
if not isNil(c.mapping.data): reset(c.mapping)
return false
result = true
if p.kind == nkStmtList and n.kind == p.kind and p.len < n.len:
let n = flattenStmts(n)
# no need to flatten 'p' here as that has already been done
for i in 0 .. n.len - p.len:
if matchRange(c, p, n, i):
c.subMatch = true
result = newNodeI(nkStmtList, n.info, 3)
result.sons[0] = extractRange(nkStmtList, n, 0, i-1)
result.sons[1] = extractRange(nkStmtList, n, i, i+p.len-1)
result.sons[2] = extractRange(nkStmtList, n, i+p.len, n.len-1)
break
elif matches(c, p, n):
result = n
# writeln(X, a); writeln(X, b); --> writeln(X, a, b) # writeln(X, a); writeln(X, b); --> writeln(X, a, b)
proc applyRule*(c: PContext, s: PSym, n: PNode): PNode = proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
@ -115,13 +199,10 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
ctx.owner = s ctx.owner = s
ctx.c = c ctx.c = c
# we perform 'initIdNodeTable' lazily for performance # we perform 'initIdNodeTable' lazily for performance
if matches(ctx, s.ast.sons[patternPos], n): var m = matchStmtList(ctx, s.ast.sons[patternPos], n)
if isNil(m): return nil
# each parameter should have been bound; we simply setup a call and # each parameter should have been bound; we simply setup a call and
# let semantic checking deal with the rest :-) # let semantic checking deal with the rest :-)
# this also saves type checking if we allow for type checking errors
# as in 'system.compiles' and simply discard the results. But an error
# may have been desired in the first place! Meh, it's good enough for
# a first implementation:
result = newNodeI(nkCall, n.info) result = newNodeI(nkCall, n.info)
result.add(newSymNode(s, n.info)) result.add(newSymNode(s, n.info))
let params = s.typ.n let params = s.typ.n
@ -131,4 +212,35 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode =
# couldn't bind parameter: # couldn't bind parameter:
if isNil(x): return nil if isNil(x): return nil
result.add(x) result.add(x)
# perform alias analysis here:
if params.len >= 2:
for i in 1 .. < params.len:
let param = params.sons[i].sym
case whichAlias(param)
of aqNone: nil
of aqShouldAlias:
# it suffices that it aliases for sure with *some* other param:
var ok = false
for j in 1 .. < result.len:
if j != i and result.sons[j].typ != nil:
if aliases.isPartOf(result[i], result[j]) == arYes:
ok = true
break
# constraint not fullfilled:
if not ok: return nil
of aqNoAlias:
# it MUST not alias with any other param:
var ok = true
for j in 1 .. < result.len:
if j != i and result.sons[j].typ != nil:
if aliases.isPartOf(result[i], result[j]) != arNo:
ok = false
break
# constraint not fullfilled:
if not ok: return nil
markUsed(n, s) markUsed(n, s)
if ctx.subMatch:
assert m.len == 3
m.sons[1] = result
result = m

View file

@ -367,7 +367,7 @@ proc lsub(n: PNode): int =
else: result = len(atom(n)) else: result = len(atom(n))
of succ(nkEmpty)..pred(nkTripleStrLit), succ(nkTripleStrLit)..nkNilLit: of succ(nkEmpty)..pred(nkTripleStrLit), succ(nkTripleStrLit)..nkNilLit:
result = len(atom(n)) result = len(atom(n))
of nkCall, nkBracketExpr, nkCurlyExpr, nkConv: of nkCall, nkBracketExpr, nkCurlyExpr, nkConv, nkPattern:
result = lsub(n.sons[0]) + lcomma(n, 1) + 2 result = lsub(n.sons[0]) + lcomma(n, 1) + 2
of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: result = lsub(n[1]) of nkHiddenStdConv, nkHiddenSubConv, nkHiddenCallConv: result = lsub(n[1])
of nkCast: result = lsub(n.sons[0]) + lsub(n.sons[1]) + len("cast[]()") of nkCast: result = lsub(n.sons[0]) + lsub(n.sons[1]) + len("cast[]()")
@ -377,6 +377,7 @@ proc lsub(n: PNode): int =
of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1 of nkCommand: result = lsub(n.sons[0]) + lcomma(n, 1) + 1
of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3 of nkExprEqExpr, nkAsgn, nkFastAsgn: result = lsons(n) + 3
of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(n) + 2 of nkPar, nkCurly, nkBracket, nkClosure: result = lcomma(n) + 2
of nkArgList: result = lcomma(n)
of nkTableConstr: of nkTableConstr:
result = if n.len > 0: lcomma(n) + 2 else: len("{:}") result = if n.len > 0: lcomma(n) + 2 else: len("{:}")
of nkClosedSymChoice, nkOpenSymChoice: of nkClosedSymChoice, nkOpenSymChoice:
@ -666,7 +667,9 @@ proc gproc(g: var TSrcGen, n: PNode) =
put(g, tkSymbol, renderDefinitionName(n.sons[namePos].sym)) put(g, tkSymbol, renderDefinitionName(n.sons[namePos].sym))
else: else:
gsub(g, n.sons[namePos]) gsub(g, n.sons[namePos])
gsub(g, n.sons[patternPos])
if n.sons[patternPos].kind != nkEmpty:
gpattern(g, n.sons[patternPos])
gsub(g, n.sons[genericParamsPos]) gsub(g, n.sons[genericParamsPos])
gsub(g, n.sons[paramsPos]) gsub(g, n.sons[paramsPos])
gsub(g, n.sons[pragmasPos]) gsub(g, n.sons[pragmasPos])
@ -774,7 +777,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
of nkRStrLit: put(g, tkRStrLit, atom(n)) of nkRStrLit: put(g, tkRStrLit, atom(n))
of nkCharLit: put(g, tkCharLit, atom(n)) of nkCharLit: put(g, tkCharLit, atom(n))
of nkNilLit: put(g, tkNil, atom(n)) # complex expressions of nkNilLit: put(g, tkNil, atom(n)) # complex expressions
of nkCall, nkConv, nkDotCall: of nkCall, nkConv, nkDotCall, nkPattern:
if sonsLen(n) >= 1: gsub(g, n.sons[0]) if sonsLen(n) >= 1: gsub(g, n.sons[0])
put(g, tkParLe, "(") put(g, tkParLe, "(")
gcomma(g, n, 1) gcomma(g, n, 1)
@ -859,6 +862,8 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
put(g, tkCurlyLe, "{") put(g, tkCurlyLe, "{")
gcomma(g, n, c) gcomma(g, n, c)
put(g, tkCurlyRi, "}") put(g, tkCurlyRi, "}")
of nkArgList:
gcomma(g, n, c)
of nkTableConstr: of nkTableConstr:
put(g, tkCurlyLe, "{") put(g, tkCurlyLe, "{")
if n.len > 0: gcomma(g, n, c) if n.len > 0: gcomma(g, n, c)
@ -922,6 +927,7 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
gsub(g, n.sons[2]) gsub(g, n.sons[2])
of nkPrefix: of nkPrefix:
gsub(g, n.sons[0]) gsub(g, n.sons[0])
if n.len > 1:
put(g, tkSpaces, space) put(g, tkSpaces, space)
gsub(g, n.sons[1]) gsub(g, n.sons[1])
of nkPostfix: of nkPostfix:
@ -1046,7 +1052,6 @@ proc gsub(g: var TSrcGen, n: PNode, c: TContext) =
of nkCaseStmt, nkRecCase: gcase(g, n) of nkCaseStmt, nkRecCase: gcase(g, n)
of nkMacroStmt: gmacro(g, n) of nkMacroStmt: gmacro(g, n)
of nkTryStmt: gtry(g, n) of nkTryStmt: gtry(g, n)
of nkPattern: gpattern(g, n)
of nkForStmt, nkParForStmt: gfor(g, n) of nkForStmt, nkParForStmt: gfor(g, n)
of nkBlockStmt, nkBlockExpr: gblock(g, n) of nkBlockStmt, nkBlockExpr: gblock(g, n)
of nkStaticStmt: gstaticStmt(g, n) of nkStaticStmt: gstaticStmt(g, n)

View file

@ -330,6 +330,9 @@ proc decodeType(r: PRodReader, info: TLineInfo): PType =
if r.s[r.pos] == '@': if r.s[r.pos] == '@':
inc(r.pos) inc(r.pos)
result.containerID = decodeVInt(r.s, r.pos) result.containerID = decodeVInt(r.s, r.pos)
if r.s[r.pos] == '`':
inc(r.pos)
result.constraint = decodeNode(r, UnknownLineInfo())
decodeLoc(r, result.loc, info) decodeLoc(r, result.loc, info)
while r.s[r.pos] == '^': while r.s[r.pos] == '^':
inc(r.pos) inc(r.pos)

View file

@ -233,6 +233,9 @@ proc encodeType(w: PRodWriter, t: PType, result: var string) =
if t.containerID != 0: if t.containerID != 0:
add(result, '@') add(result, '@')
encodeVInt(t.containerID, result) encodeVInt(t.containerID, result)
if t.constraint != nil:
add(result, '`')
encodeNode(w, UnknownLineInfo(), t.constraint, result)
encodeLoc(w, t.loc, result) encodeLoc(w, t.loc, result)
for i in countup(0, sonsLen(t) - 1): for i in countup(0, sonsLen(t) - 1):
if t.sons[i] == nil: if t.sons[i] == nil:

View file

@ -15,7 +15,7 @@ import
magicsys, parser, nversion, nimsets, semfold, importer, magicsys, parser, nversion, nimsets, semfold, importer,
procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch,
semthreads, intsets, transf, evals, idgen, aliases, cgmeth, lambdalifting, semthreads, intsets, transf, evals, idgen, aliases, cgmeth, lambdalifting,
evaltempl, patterns evaltempl, patterns, parampatterns
proc semPass*(): TPass proc semPass*(): TPass
# implementation # implementation
@ -83,6 +83,7 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym, semCheck = true): PNode
proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym,
semCheck: bool = true): PNode semCheck: bool = true): PNode
proc semDirectOp(c: PContext, n: PNode, flags: TExprFlags): PNode
proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode proc semWhen(c: PContext, n: PNode, semCheck: bool = true): PNode
@ -102,6 +103,26 @@ proc semConstExpr(c: PContext, n: PNode): PNode =
return n return n
result = evalTypedExpr(c, e) result = evalTypedExpr(c, e)
proc evalPattern(c: PContext, n: PNode, info: TLineInfo): PNode =
InternalAssert n.kind == nkCall and n.sons[0].kind == nkSym
# we need to ensure that the resulting AST is semchecked. However, it's
# aweful to semcheck before macro invocation, so we don't and treat
# templates and macros as immediate in this context.
var rule: string
if optHints in gOptions and hintPattern in gNotes:
rule = renderTree(n, {renderNoComments})
let s = n.sons[0].sym
case s.kind
of skMacro:
result = semMacroExpr(c, n, n, s)
of skTemplate:
result = semTemplateExpr(c, n, s)
else:
result = semDirectOp(c, n, {})
if optHints in gOptions and hintPattern in gNotes:
Message(info, hintPattern, rule & " --> '" &
renderTree(result, {renderNoComments}) & "'")
proc applyPatterns(c: PContext, n: PNode): PNode = proc applyPatterns(c: PContext, n: PNode): PNode =
# fast exit: # fast exit:
if c.patterns.len == 0 or optPatterns notin gOptions: return n if c.patterns.len == 0 or optPatterns notin gOptions: return n
@ -110,14 +131,25 @@ proc applyPatterns(c: PContext, n: PNode): PNode =
# however the resulting AST would better not trigger the old rule then # however the resulting AST would better not trigger the old rule then
# anymore ;-) # anymore ;-)
for i in countdown(<c.patterns.len, 0): for i in countdown(<c.patterns.len, 0):
let x = applyRule(c, c.patterns[i], result) let pattern = c.patterns[i]
if not isNil(pattern):
let x = applyRule(c, pattern, result)
if not isNil(x): if not isNil(x):
assert x.kind == nkCall assert x.kind in {nkStmtList, nkCall}
inc(evalTemplateCounter) inc(evalTemplateCounter)
if evalTemplateCounter > 100: if evalTemplateCounter > 100:
GlobalError(n.info, errTemplateInstantiationTooNested) GlobalError(n.info, errTemplateInstantiationTooNested)
result = semExpr(c, x) # deactivate this pattern:
c.patterns[i] = nil
if x.kind == nkStmtList:
assert x.len == 3
x.sons[1] = evalPattern(c, x.sons[1], n.info)
result = flattenStmts(x)
else:
result = evalPattern(c, x, n.info)
dec(evalTemplateCounter) dec(evalTemplateCounter)
# activate this pattern again:
c.patterns[i] = pattern
include seminst, semcall include seminst, semcall

View file

@ -65,18 +65,17 @@ proc inlineConst(n: PNode, s: PSym): PNode {.inline.} =
result.typ = s.typ result.typ = s.typ
result.info = n.info result.info = n.info
proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode = proc performProcvarCheck(c: PContext, n: PNode, s: PSym) =
case s.kind # XXX this not correct; it's valid to pass to templates and macros.
of skProc, skMethod, skIterator, skConverter: # We really need another post nkCallConv check for this. Or maybe do it
# in transform().
var smoduleId = getModule(s).id var smoduleId = getModule(s).id
if sfProcVar notin s.flags and s.typ.callConv == ccDefault and if sfProcVar notin s.flags and s.typ.callConv == ccDefault and
smoduleId != c.module.id and smoduleId != c.friendModule.id: smoduleId != c.module.id and smoduleId != c.friendModule.id:
LocalError(n.info, errXCannotBePassedToProcVar, s.name.s) LocalError(n.info, errXCannotBePassedToProcVar, s.name.s)
result = symChoice(c, n, s, scClosed)
if result.kind == nkSym: proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
markIndirect(c, result.sym) case s.kind
if isGenericRoutine(result.sym):
LocalError(n.info, errInstantiateXExplicitely, s.name.s)
of skConst: of skConst:
markUsed(n, s) markUsed(n, s)
case skipTypes(s.typ, abstractInst).kind case skipTypes(s.typ, abstractInst).kind
@ -105,7 +104,8 @@ proc semSym(c: PContext, n: PNode, s: PSym, flags: TExprFlags): PNode =
# if a proc accesses a global variable, it is not side effect free: # if a proc accesses a global variable, it is not side effect free:
if sfGlobal in s.flags: if sfGlobal in s.flags:
incl(c.p.owner.flags, sfSideEffect) incl(c.p.owner.flags, sfSideEffect)
elif s.kind == skParam and s.typ.kind == tyExpr: elif s.kind == skParam and s.typ.kind == tyExpr and s.typ.n != nil:
# XXX see the hack in sigmatch.nim ...
return s.typ.n return s.typ.n
result = newSymNode(s, n.info) result = newSymNode(s, n.info)
# We cannot check for access to outer vars for example because it's still # We cannot check for access to outer vars for example because it's still
@ -1438,6 +1438,13 @@ proc semExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode =
var s = lookUp(c, n) var s = lookUp(c, n)
semCaptureSym(s, c.p.owner) semCaptureSym(s, c.p.owner)
result = semSym(c, n, s, flags) result = semSym(c, n, s, flags)
if s.kind in {skProc, skMethod, skIterator, skConverter}:
performProcvarCheck(c, n, s)
result = symChoice(c, n, s, scClosed)
if result.kind == nkSym:
markIndirect(c, result.sym)
if isGenericRoutine(result.sym):
LocalError(n.info, errInstantiateXExplicitely, s.name.s)
of nkSym: of nkSym:
# because of the changed symbol binding, this does not mean that we # because of the changed symbol binding, this does not mean that we
# don't have to check the symbol for semantics here again! # don't have to check the symbol for semantics here again!

View file

@ -196,8 +196,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
let x = b.intVal|+|1 let x = b.intVal|+|1
if (x and -x) == x and x >= 0: if (x and -x) == x and x >= 0:
result = makeRange(a.typ, 0, b.intVal) result = makeRange(a.typ, 0, b.intVal)
of mModI, mModI64, mModU: of mModU:
# so ... if you ever wondered about modulo's signedness; this defines it:
let a = n.sons[1] let a = n.sons[1]
let b = n.sons[2] let b = n.sons[2]
if b.kind in {nkIntLit..nkUInt64Lit}: if b.kind in {nkIntLit..nkUInt64Lit}:
@ -205,6 +204,15 @@ proc getIntervalType*(m: TMagic, n: PNode): PType =
result = makeRange(a.typ, 0, b.intVal-1) result = makeRange(a.typ, 0, b.intVal-1)
else: else:
result = makeRange(a.typ, b.intVal+1, 0) result = makeRange(a.typ, b.intVal+1, 0)
of mModI, mModI64:
# so ... if you ever wondered about modulo's signedness; this defines it:
let a = n.sons[1]
let b = n.sons[2]
if b.kind in {nkIntLit..nkUInt64Lit}:
if b.intVal >= 0:
result = makeRange(a.typ, -(b.intVal-1), b.intVal-1)
else:
result = makeRange(a.typ, b.intVal+1, -(b.intVal+1))
of mDivI, mDivI64, mDivU: of mDivI, mDivI64, mDivU:
binaryOp(`|div|`) binaryOp(`|div|`)
of mMinI, mMinI64: of mMinI, mMinI64:

View file

@ -933,6 +933,8 @@ proc semStaticStmt(c: PContext, n: PNode): PNode =
result = evalStaticExpr(c.module, a) result = evalStaticExpr(c.module, a)
if result.isNil: if result.isNil:
LocalError(n.info, errCannotInterpretNodeX, renderTree(n)) LocalError(n.info, errCannotInterpretNodeX, renderTree(n))
elif result.kind == nkEmpty:
result = newNodeI(nkNilLit, n.info)
# special marker values that indicates that we are # special marker values that indicates that we are
# 1) AnalyzingDestructor: currenlty analyzing the type for destructor # 1) AnalyzingDestructor: currenlty analyzing the type for destructor

View file

@ -89,7 +89,7 @@ proc semBindStmt(c: PContext, n: PNode, toBind: var TIntSet): PNode =
for x in items(sc): toBind.incl(x.sym.id) for x in items(sc): toBind.incl(x.sym.id)
else: else:
illFormedAst(a) illFormedAst(a)
result = newNodeI(nkEmpty, n.info) result = newNodeI(nkNilLit, n.info)
proc replaceIdentBySym(n: var PNode, s: PNode) = proc replaceIdentBySym(n: var PNode, s: PNode) =
case n.kind case n.kind
@ -98,16 +98,10 @@ proc replaceIdentBySym(n: var PNode, s: PNode) =
of nkIdent, nkAccQuoted, nkSym: n = s of nkIdent, nkAccQuoted, nkSym: n = s
else: illFormedAst(n) else: illFormedAst(n)
# This code here is the first pass over a template's body. The same code also
# implements the first pass over a pattern's body:
type type
TBodyKind = enum
bkTemplate, bkPattern
TemplCtx {.pure, final.} = object TemplCtx {.pure, final.} = object
c: PContext c: PContext
toBind: TIntSet toBind: TIntSet
bodyKind: TBodyKind
owner: PSym owner: PSym
proc getIdentNode(c: var TemplCtx, n: PNode): PNode = proc getIdentNode(c: var TemplCtx, n: PNode): PNode =
@ -188,8 +182,6 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
result = newSymNode(s, n.info) result = newSymNode(s, n.info)
elif Contains(c.toBind, s.id): elif Contains(c.toBind, s.id):
result = symChoice(c.c, n, s, scClosed) result = symChoice(c.c, n, s, scClosed)
elif c.bodyKind == bkPattern:
result = symChoice(c.c, n, s, scOpen)
elif s.owner == c.owner and sfGenSym in s.flags: elif s.owner == c.owner and sfGenSym in s.flags:
# template tmp[T](x: var seq[T]) = # template tmp[T](x: var seq[T]) =
# var yz: T # var yz: T
@ -305,7 +297,8 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode =
# so we use the generic code for nkDotExpr too # so we use the generic code for nkDotExpr too
if n.kind == nkDotExpr or n.kind == nkAccQuoted: if n.kind == nkDotExpr or n.kind == nkAccQuoted:
let s = QualifiedLookUp(c.c, n, {}) let s = QualifiedLookUp(c.c, n, {})
if s != nil and Contains(c.toBind, s.id): if s != nil:
if Contains(c.toBind, s.id):
return symChoice(c.c, n, s, scClosed) return symChoice(c.c, n, s, scClosed)
result = n result = n
for i in countup(0, sonsLen(n) - 1): for i in countup(0, sonsLen(n) - 1):
@ -405,7 +398,6 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
ctx.toBind = initIntSet() ctx.toBind = initIntSet()
ctx.c = c ctx.c = c
ctx.owner = s ctx.owner = s
ctx.bodyKind = bkTemplate
if sfDirty in s.flags: if sfDirty in s.flags:
n.sons[bodyPos] = semTemplBodyDirty(ctx, n.sons[bodyPos]) n.sons[bodyPos] = semTemplBodyDirty(ctx, n.sons[bodyPos])
else: else:
@ -426,17 +418,109 @@ proc semTemplateDef(c: PContext, n: PNode): PNode =
else: else:
SymTabReplace(c.tab.stack[curScope], proto, s) SymTabReplace(c.tab.stack[curScope], proto, s)
proc semPatternBody(c: var TemplCtx, n: PNode): PNode =
template templToExpand(s: expr): expr =
s.kind == skTemplate and (s.typ.len == 1 or sfImmediate in s.flags)
proc handleSym(c: var TemplCtx, n: PNode, s: PSym): PNode =
if s != nil:
if s.owner == c.owner and s.kind == skParam:
incl(s.flags, sfUsed)
result = newSymNode(s, n.info)
elif Contains(c.toBind, s.id):
result = symChoice(c.c, n, s, scClosed)
elif templToExpand(s):
result = semPatternBody(c, semTemplateExpr(c.c, n, s, false))
else:
result = symChoice(c.c, n, s, scOpen)
else:
result = n
proc expectParam(c: var TemplCtx, n: PNode): PNode =
let s = QualifiedLookUp(c.c, n, {})
if s != nil and s.owner == c.owner and s.kind == skParam:
incl(s.flags, sfUsed)
result = newSymNode(s, n.info)
else:
localError(n.info, errInvalidExpression)
result = n
result = n
case n.kind
of nkIdent:
let s = QualifiedLookUp(c.c, n, {})
result = handleSym(c, n, s)
of nkBindStmt:
result = semBindStmt(c.c, n, c.toBind)
of nkEmpty, nkSym..nkNilLit: nil
of nkCurlyExpr:
# we support '(pattern){x}' to bind a subpattern to a parameter 'x':
if n.len != 2 or n.sons[1].kind != nkIdent:
localError(n.info, errInvalidExpression)
else:
n.sons[0] = semPatternBody(c, n.sons[0])
n.sons[1] = expectParam(c, n.sons[1])
of nkCallKinds:
let s = QualifiedLookUp(c.c, n.sons[0], {})
if s != nil:
if s.owner == c.owner and s.kind == skParam: nil
elif Contains(c.toBind, s.id): nil
elif templToExpand(s):
return semPatternBody(c, semTemplateExpr(c.c, n, s, false))
if n.kind == nkInfix and n.sons[0].kind == nkIdent:
# we interpret `*` and `|` only as pattern operators if they occur in
# infix notation, so that '`*`(a, b)' can be used for verbatim matching:
let opr = n.sons[0]
if opr.ident.s == "*":
result = newNodeI(nkPattern, n.info, n.len)
result.sons[0] = opr
result.sons[1] = semPatternBody(c, n.sons[1])
result.sons[2] = expectParam(c, n.sons[2])
return
elif opr.ident.s == "|":
result = newNodeI(nkPattern, n.info, n.len)
result.sons[0] = opr
result.sons[1] = semPatternBody(c, n.sons[1])
result.sons[2] = semPatternBody(c, n.sons[2])
return
if n.kind == nkPrefix and n.sons[0].kind == nkIdent:
let opr = n.sons[0]
if opr.ident.s == "~":
result = newNodeI(nkPattern, n.info, n.len)
result.sons[0] = opr
result.sons[1] = semPatternBody(c, n.sons[1])
return
for i in countup(0, sonsLen(n) - 1):
result.sons[i] = semPatternBody(c, n.sons[i])
else:
# dotExpr is ambiguous: note that we explicitely allow 'x.TemplateParam',
# so we use the generic code for nkDotExpr too
case n.kind
of nkDotExpr, nkAccQuoted:
let s = QualifiedLookUp(c.c, n, {})
if s != nil:
if Contains(c.toBind, s.id):
return symChoice(c.c, n, s, scClosed)
return symChoice(c.c, n, s, scOpen)
of nkPar:
if n.len == 1: return semPatternBody(c, n.sons[0])
else: nil
for i in countup(0, sonsLen(n) - 1):
result.sons[i] = semPatternBody(c, n.sons[i])
proc semPattern(c: PContext, n: PNode): PNode = proc semPattern(c: PContext, n: PNode): PNode =
# not much to do here: We don't replace operators ``$``, ``*``, ``+``,
# ``|``, ``~`` as meta operators and strip the leading ``\`` of all
# operators.
openScope(c.tab) openScope(c.tab)
var ctx: TemplCtx var ctx: TemplCtx
ctx.toBind = initIntSet() ctx.toBind = initIntSet()
ctx.c = c ctx.c = c
ctx.owner = getCurrOwner() ctx.owner = getCurrOwner()
ctx.bodyKind = bkPattern result = flattenStmts(semPatternBody(ctx, n))
result = semTemplBody(ctx, n) if result.kind in {nkStmtList, nkStmtListExpr}:
if result.kind in {nkStmtList, nkStmtListExpr} and result.len == 1: if result.len == 1:
result = result.sons[0] result = result.sons[0]
elif result.len == 0:
LocalError(n.info, errInvalidExpression)
closeScope(c.tab) closeScope(c.tab)

View file

@ -826,9 +826,8 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of nkCurlyExpr: of nkCurlyExpr:
result = semTypeNode(c, n.sons[0], nil) result = semTypeNode(c, n.sons[0], nil)
if result != nil: if result != nil:
result = copyType(result, getCurrOwner(), false) result = copyType(result, getCurrOwner(), true)
for i in countup(1, n.len - 1): result.constraint = semNodeKindConstraints(n)
result.rawAddSon(semTypeNode(c, n.sons[i], nil))
of nkWhenStmt: of nkWhenStmt:
var whenResult = semWhen(c, n, false) var whenResult = semWhen(c, n, false)
if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType if whenResult.kind == nkStmtList: whenResult.kind = nkStmtListType
@ -844,6 +843,12 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
of mOrdinal: result = semOrdinal(c, n, prev) of mOrdinal: result = semOrdinal(c, n, prev)
of mSeq: result = semContainer(c, n, tySequence, "seq", prev) of mSeq: result = semContainer(c, n, tySequence, "seq", prev)
of mVarargs: result = semVarargs(c, n, prev) of mVarargs: result = semVarargs(c, n, prev)
of mExpr, mTypeDesc:
result = semTypeNode(c, n.sons[0], nil)
if result != nil:
result = copyType(result, getCurrOwner(), false)
for i in countup(1, n.len - 1):
result.rawAddSon(semTypeNode(c, n.sons[i], nil))
else: result = semGeneric(c, n, s, prev) else: result = semGeneric(c, n, s, prev)
of nkIdent, nkDotExpr, nkAccQuoted: of nkIdent, nkDotExpr, nkAccQuoted:
var s = semTypeIdent(c, n) var s = semTypeIdent(c, n)

View file

@ -151,3 +151,19 @@ proc whichPragma*(n: PNode): TSpecialWord =
let key = if n.kind == nkExprColonExpr: n.sons[0] else: n let key = if n.kind == nkExprColonExpr: n.sons[0] else: n
if key.kind == nkIdent: result = whichKeyword(key.ident) if key.kind == nkIdent: result = whichKeyword(key.ident)
proc unnestStmts(n, result: PNode) =
if n.kind == nkStmtList:
for x in items(n): unnestStmts(x, result)
elif n.kind notin {nkCommentStmt, nkNilLit}:
result.add(n)
proc flattenStmts*(n: PNode): PNode =
## flattens a nested statement list; used for pattern matching
result = newNodeI(nkStmtList, n.info)
unnestStmts(n, result)
if result.len == 1:
result = result.sons[0]
proc extractRange*(k: TNodeKind, n: PNode, a, b: int): PNode =
result = newNodeI(k, n.info, b-a+1)
for i in 0 .. b-a: result.sons[i] = n.sons[i+a]

View file

@ -507,7 +507,8 @@ proc base(t: PType): PType =
proc firstOrd(t: PType): biggestInt = proc firstOrd(t: PType): biggestInt =
case t.kind case t.kind
of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs: result = 0 of tyBool, tyChar, tySequence, tyOpenArray, tyString, tyVarargs, tyProxy:
result = 0
of tySet, tyVar: result = firstOrd(t.sons[0]) of tySet, tyVar: result = firstOrd(t.sons[0])
of tyArray, tyArrayConstr: result = firstOrd(t.sons[0]) of tyArray, tyArrayConstr: result = firstOrd(t.sons[0])
of tyRange: of tyRange:
@ -564,6 +565,7 @@ proc lastOrd(t: PType): biggestInt =
result = t.n.sons[sonsLen(t.n) - 1].sym.position result = t.n.sons[sonsLen(t.n) - 1].sym.position
of tyGenericInst, tyDistinct, tyConst, tyMutable: of tyGenericInst, tyDistinct, tyConst, tyMutable:
result = lastOrd(lastSon(t)) result = lastOrd(lastSon(t))
of tyProxy: result = 0
else: else:
InternalError("invalid kind for last(" & $t.kind & ')') InternalError("invalid kind for last(" & $t.kind & ')')
result = 0 result = 0
@ -591,7 +593,7 @@ type
# (few elements expected) # (few elements expected)
proc initSameTypeClosure: TSameTypeClosure = proc initSameTypeClosure: TSameTypeClosure =
# we do the initialization lazy for performance (avoids memory allocations) # we do the initialization lazily for performance (avoids memory allocations)
nil nil
proc containsOrIncl(c: var TSameTypeClosure, a, b: PType): bool = proc containsOrIncl(c: var TSameTypeClosure, a, b: PType): bool =

View file

@ -621,14 +621,14 @@ determined). Assignments from the base type to one of its subrange types
A subrange type has the same size as its base type (``int`` in the example). A subrange type has the same size as its base type (``int`` in the example).
Nimrod requires `interval arithmetic`:idx: for subrange types over a set Nimrod requires `interval arithmetic`:idx: for subrange types over a set
of built-in operators that involve constants: ``x mod 3`` is of of built-in operators that involve constants: ``x %% 3`` is of
type ``range[0..2]``. The following built-in operators for integers are type ``range[0..2]``. The following built-in operators for integers are
affected by this rule: ``-``, ``+``, ``*``, ``min``, ``max``, ``succ``, affected by this rule: ``-``, ``+``, ``*``, ``min``, ``max``, ``succ``,
``pred``, ``mod``, ``div``, ``and`` (bitwise ``and``). ``pred``, ``mod``, ``div``, ``%%``, ``and`` (bitwise ``and``).
Bitwise ``and`` only produces a ``range`` if one of its operands is a Bitwise ``and`` only produces a ``range`` if one of its operands is a
constant *x* so that (x+1) is a number of two. constant *x* so that (x+1) is a number of two.
(Bitwise ``and`` is then a ``mod`` operation.) (Bitwise ``and`` is then a ``%%`` operation.)
This means that the following code is accepted: This means that the following code is accepted:

View file

@ -46,14 +46,17 @@ type
nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt, nnkYieldStmt, nnkTryStmt, nnkFinally, nnkRaiseStmt,
nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt, nnkReturnStmt, nnkBreakStmt, nnkContinueStmt, nnkBlockStmt, nnkStaticStmt,
nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt, nnkDiscardStmt, nnkStmtList, nnkImportStmt, nnkFromStmt,
nnkIncludeStmt, nnkBindStmt, nnkPattern, nnkIncludeStmt, nnkBindStmt,
nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr, nnkCommentStmt, nnkStmtListExpr, nnkBlockExpr,
nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy, nnkStmtListType, nnkBlockType, nnkTypeOfExpr, nnkObjectTy,
nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen, nnkTupleTy, nnkRecList, nnkRecCase, nnkRecWhen,
nnkRefTy, nnkPtrTy, nnkVarTy, nnkRefTy, nnkPtrTy, nnkVarTy,
nnkConstTy, nnkMutableTy, nnkConstTy, nnkMutableTy,
nnkDistinctTy, nnkDistinctTy,
nnkProcTy, nnkEnumTy, nnkEnumFieldDef, nnkReturnToken nnkProcTy, nnkEnumTy,
nnkEnumFieldDef,
nnkArglist, nnkPattern
nnkReturnToken
TNimNodeKinds* = set[TNimrodNodeKind] TNimNodeKinds* = set[TNimrodNodeKind]
TNimrodTypeKind* = enum TNimrodTypeKind* = enum
ntyNone, ntyBool, ntyChar, ntyEmpty, ntyNone, ntyBool, ntyChar, ntyEmpty,

View file

@ -1314,7 +1314,8 @@ iterator items*(a: cstring): char {.inline.} =
yield a[i] yield a[i]
inc(i) inc(i)
iterator items*(E: typedesc{enum}): E = when not defined(booting):
iterator items*(E: typedesc[enum]): E =
## iterates over the values of the enum ``E``. ## iterates over the values of the enum ``E``.
for v in low(E)..high(E): for v in low(E)..high(E):
yield v yield v
@ -1832,12 +1833,9 @@ when not defined(EcmaScript) and not defined(NimrodVM):
## ``CRLF``. The newline character(s) are not part of the returned string. ## ``CRLF``. The newline character(s) are not part of the returned string.
## Returns ``false`` if the end of the file has been reached, ``true`` ## Returns ``false`` if the end of the file has been reached, ``true``
## otherwise. If ``false`` is returned `line` contains no new data. ## otherwise. If ``false`` is returned `line` contains no new data.
proc writeln*[Ty](f: TFile, x: Ty) {.inline.}
## writes a value `x` to `f` and then writes "\n".
## May throw an IO exception.
proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline.} proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline.}
## writes a value `x` to `f` and then writes "\n". ## writes the values `x` to `f` and then writes "\n".
## May throw an IO exception. ## May throw an IO exception.
proc getFileSize*(f: TFile): int64 proc getFileSize*(f: TFile): int64

View file

@ -146,10 +146,6 @@ proc EndOfFile(f: TFile): bool =
ungetc(c, f) ungetc(c, f)
return c < 0'i32 return c < 0'i32
proc writeln[Ty](f: TFile, x: Ty) =
write(f, x)
write(f, "\n")
proc writeln[Ty](f: TFile, x: varargs[Ty, `$`]) = proc writeln[Ty](f: TFile, x: varargs[Ty, `$`]) =
for i in items(x): write(f, i) for i in items(x): write(f, i)
write(f, "\n") write(f, "\n")

View file

@ -2,8 +2,8 @@ version 0.9.0
============= =============
- make 'm: stmt' use overloading resolution - make 'm: stmt' use overloading resolution
- document pattern matching
- improve pattern matching: introduce meta operators, statement list support
- make 'bind' default for templates and introduce 'mixin' - make 'bind' default for templates and introduce 'mixin'
- implement "closure tuple consists of a single 'ref'" optimization - implement "closure tuple consists of a single 'ref'" optimization
@ -19,6 +19,7 @@ version 0.9.0
Bugs Bugs
---- ----
- sneaking with qualifiedLookup() is really broken!
- bug: aporia.nim(968, 5) Error: ambiguous identifier: 'DELETE' -- - bug: aporia.nim(968, 5) Error: ambiguous identifier: 'DELETE' --
use a qualifier use a qualifier
- bug: pragma statements in combination with symbol files are evaluated twice - bug: pragma statements in combination with symbol files are evaluated twice