summary refs log tree commit diff stats
path: root/compiler/parampatterns.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/parampatterns.nim')
-rw-r--r--compiler/parampatterns.nim205
1 files changed, 205 insertions, 0 deletions
diff --git a/compiler/parampatterns.nim b/compiler/parampatterns.nim
new file mode 100644
index 000000000..83585dbd4
--- /dev/null
+++ b/compiler/parampatterns.nim
@@ -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]