documented hygienic templates; made tests green; fixed system.clamp

This commit is contained in:
Araq 2012-08-22 22:46:02 +02:00
commit f893179889
11 changed files with 123 additions and 48 deletions

View file

@ -52,7 +52,9 @@ proc symChoice(c: PContext, n: PNode, s: PSym): PNode =
if i > 1: break if i > 1: break
if i <= 1: if i <= 1:
# XXX this makes more sense but breaks bootstrapping for now: # XXX this makes more sense but breaks bootstrapping for now:
# and s.kind notin routineKinds: # (s.kind notin routineKinds or s.magic != mNone):
# for some reason 'nextTry' is copied and considered as a candidate in
# tables.nim
result = newSymNode(s, n.info) result = newSymNode(s, n.info)
markUsed(n, s) markUsed(n, s)
else: else:
@ -68,12 +70,19 @@ proc symChoice(c: PContext, n: PNode, s: PSym): PNode =
proc semBindStmt(c: PContext, n: PNode, toBind: var TIntSet): PNode = proc semBindStmt(c: PContext, n: PNode, toBind: var TIntSet): PNode =
for i in 0 .. < n.len: for i in 0 .. < n.len:
var a = n.sons[i] var a = n.sons[i]
# If 'a' is an overloaded symbol, we use the first symbol as a 'witness' # If 'a' is an overloaded symbol, we used to use the first symbol
# and use the fact that subsequent lookups will yield the same symbol! # as a 'witness' and use the fact that subsequent lookups will yield
# This is currently the case due to the hash table's implementation... # the same symbol!
# This is however not true anymore for hygienic templates as semantic
# processing for them changes the symbol table...
let s = QualifiedLookUp(c, a) let s = QualifiedLookUp(c, a)
if s != nil: if s != nil:
toBind.incl(s.id) # we need to mark all symbols:
let sc = symChoice(c, n, s)
if sc.kind == nkSym:
toBind.incl(sc.sym.id)
else:
for x in items(sc): toBind.incl(x.sym.id)
else: else:
illFormedAst(a) illFormedAst(a)
result = newNodeI(nkEmpty, n.info) result = newNodeI(nkEmpty, n.info)

View file

@ -2967,8 +2967,8 @@ In templates identifiers can be constructed with the backticks notation:
template typedef(name: expr, typ: typeDesc) {.immediate.} = template typedef(name: expr, typ: typeDesc) {.immediate.} =
type type
`T name`* = typ `T name`* {.inject.} = typ
`P name`* = ref `T name` `P name`* {.inject.} = ref `T name`
typedef(myint, int) typedef(myint, int)
var x: PMyInt var x: PMyInt
@ -2982,7 +2982,7 @@ Lookup rules for template parameters
A parameter ``p`` in a template is even substituted in the expression ``x.p``. A parameter ``p`` in a template is even substituted in the expression ``x.p``.
Thus template arguments can be used as field names and a global symbol can be Thus template arguments can be used as field names and a global symbol can be
covered by the same argument name even when fully qualified: shadowed by the same argument name even when fully qualified:
.. code-block:: nimrod .. code-block:: nimrod
# module 'm' # module 'm'
@ -3017,6 +3017,61 @@ But the global symbol can properly be captured by a ``bind`` statement:
tstLev(levA) tstLev(levA)
# produces: 'levA levB' # produces: 'levA levB'
Hygiene in templates
~~~~~~~~~~~~~~~~~~~~
Per default templates are `hygienic`:idx:\: Local identifiers declared in a
template cannot be accessed in the instantiation context:
.. code-block:: nimrod
template newException*(exceptn: typeDesc, message: string): expr =
var
e: ref exceptn # e is implicitely gensym'ed here
new(e)
e.msg = message
e
# so this works:
let e = "message"
raise newException(EIO, e)
Whether a symbol that is declared in a template is exposed to the instantiation
scope is controlled by the `inject`:idx: and `gensym`:idx: pragmas: gensym'ed
symbols are not exposed but inject'ed are.
The default for symbols of entity ``type``, ``var``, ``let`` and ``const``
is ``gensym`` and for ``proc``, ``iterator``, ``converter``, ``template``,
``macro`` is ``inject``. However, if the name of the entity is passed as a
template parameter, it is an inject'ed symbol:
.. code-block:: nimrod
template withFile(f, fn, mode: expr, actions: stmt): stmt {.immediate.} =
block:
var f: TFile # since 'f' is a template param, it's injected implicitely
...
withFile(txt, "ttempl3.txt", fmWrite):
txt.writeln("line 1")
txt.writeln("line 2")
The ``inject`` and ``gensym`` pragmas are second class annotations; they have
no semantics outside of a template definition and cannot be abstracted over:
.. code-block:: nimrod
{.pragma myInject: inject.}
template t() =
var x {.myInject.}: int # does NOT work
To get rid of hygiene in templates, one can use the `dirty`:idx: pragma for
a template. ``inject`` and ``gensym`` have no effect in ``dirty`` templates.
Macros Macros
------ ------

View file

@ -487,7 +487,7 @@ objects as `shallow`:idx:\:
.. code-block:: Nimrod .. code-block:: Nimrod
var s = "abc" var s = "abc"
shallow(s) # mark 's' as shallow string shallow(s) # mark 's' as shallow string
var x = s # now might does not copy the string! var x = s # now might not copy the string!
Usage of ``shallow`` is always safe once you know the string won't be modified Usage of ``shallow`` is always safe once you know the string won't be modified
anymore, similar to Ruby's `freeze`:idx:. anymore, similar to Ruby's `freeze`:idx:.

View file

@ -11,6 +11,9 @@
## to do so, the 'next' and 'prev' pointers are not hidden from you and can ## to do so, the 'next' and 'prev' pointers are not hidden from you and can
## be manipulated directly for efficiency. ## be manipulated directly for efficiency.
when not defined(nimhygiene):
{.pragma: dirty.}
type type
TDoublyLinkedNode* {.pure, TDoublyLinkedNode* {.pure,
final.}[T] = object ## a node a doubly linked list consists of final.}[T] = object ## a node a doubly linked list consists of
@ -62,13 +65,13 @@ proc newSinglyLinkedNode*[T](value: T): PSinglyLinkedNode[T] =
new(result) new(result)
result.value = value result.value = value
template itemsListImpl() = template itemsListImpl() {.dirty.} =
var it = L.head var it = L.head
while it != nil: while it != nil:
yield it.value yield it.value
it = it.next it = it.next
template itemsRingImpl() = template itemsRingImpl() {.dirty.} =
var it = L.head var it = L.head
if it != nil: if it != nil:
while true: while true:
@ -76,14 +79,14 @@ template itemsRingImpl() =
it = it.next it = it.next
if it == L.head: break if it == L.head: break
template nodesListImpl() = template nodesListImpl() {.dirty.} =
var it = L.head var it = L.head
while it != nil: while it != nil:
var nxt = it.next var nxt = it.next
yield it yield it
it = nxt it = nxt
template nodesRingImpl() = template nodesRingImpl() {.dirty.} =
var it = L.head var it = L.head
if it != nil: if it != nil:
while true: while true:
@ -92,7 +95,7 @@ template nodesRingImpl() =
it = nxt it = nxt
if it == L.head: break if it == L.head: break
template findImpl() = template findImpl() {.dirty.} =
for x in nodes(L): for x in nodes(L):
if x.value == value: return x if x.value == value: return x
@ -132,7 +135,7 @@ iterator nodes*[T](L: TDoublyLinkedRing[T]): PDoublyLinkedNode[T] =
## list during traversal is supported. ## list during traversal is supported.
nodesRingImpl() nodesRingImpl()
template dollarImpl() = template dollarImpl() {.dirty.} =
result = "[" result = "["
for x in nodes(L): for x in nodes(L):
if result.len > 1: result.add(", ") if result.len > 1: result.add(", ")

View file

@ -15,6 +15,9 @@
## **Note**: This interface will change as soon as the compiler supports ## **Note**: This interface will change as soon as the compiler supports
## closures and proper coroutines. ## closures and proper coroutines.
when not defined(nimhygiene):
{.pragma: dirty.}
proc concat*[T](seqs: varargs[seq[T]]): seq[T] = proc concat*[T](seqs: varargs[seq[T]]): seq[T] =
## Takes several sequences' items and returns them inside of one sequence. ## Takes several sequences' items and returns them inside of one sequence.
var L = 0 var L = 0
@ -50,7 +53,7 @@ proc filter*[T](seq1: seq[T], pred: proc(item: T): bool {.closure.}): seq[T] =
## Returns all items in a sequence that fulfilled the predicate. ## Returns all items in a sequence that fulfilled the predicate.
accumulateResult(filter(seq1, pred)) accumulateResult(filter(seq1, pred))
template filterIt*(seq1, pred: expr): expr {.immediate.} = template filterIt*(seq1, pred: expr): expr {.immediate, dirty.} =
## Finds a specific item in a sequence as long as the ## Finds a specific item in a sequence as long as the
## predicate returns true. The predicate needs to be an expression ## predicate returns true. The predicate needs to be an expression
## containing ``it``: ``filterIt("abcxyz", it == 'x')``. ## containing ``it``: ``filterIt("abcxyz", it == 'x')``.

View file

@ -16,6 +16,8 @@ import
os, hashes, math os, hashes, math
{.pragma: myShallow.} {.pragma: myShallow.}
when not defined(nimhygiene):
{.pragma: dirty.}
type type
TSlotEnum = enum seEmpty, seFilled, seDeleted TSlotEnum = enum seEmpty, seFilled, seDeleted
@ -48,7 +50,7 @@ proc mustRehash(length, counter: int): bool {.inline.} =
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash result = ((5 * h) + 1) and maxHash
template rawGetImpl() = template rawGetImpl() {.dirty.} =
var h: THash = hash(key) and high(s.data) # start with real hash value var h: THash = hash(key) and high(s.data) # start with real hash value
while s.data[h].slot != seEmpty: while s.data[h].slot != seEmpty:
if s.data[h].key == key and s.data[h].slot == seFilled: if s.data[h].key == key and s.data[h].slot == seFilled:
@ -56,7 +58,7 @@ template rawGetImpl() =
h = nextTry(h, high(s.data)) h = nextTry(h, high(s.data))
result = -1 result = -1
template rawInsertImpl() = template rawInsertImpl() {.dirty.} =
var h: THash = hash(key) and high(data) var h: THash = hash(key) and high(data)
while data[h].slot == seFilled: while data[h].slot == seFilled:
h = nextTry(h, high(data)) h = nextTry(h, high(data))
@ -81,14 +83,14 @@ proc Enlarge[A](s: var TSet[A]) =
if s.data[i].slot == seFilled: RawInsert(s, n, s.data[i].key) if s.data[i].slot == seFilled: RawInsert(s, n, s.data[i].key)
swap(s.data, n) swap(s.data, n)
template inclImpl() = template inclImpl() {.dirty.} =
var index = RawGet(s, key) var index = RawGet(s, key)
if index < 0: if index < 0:
if mustRehash(len(s.data), s.counter): Enlarge(s) if mustRehash(len(s.data), s.counter): Enlarge(s)
RawInsert(s, s.data, key) RawInsert(s, s.data, key)
inc(s.counter) inc(s.counter)
template containsOrInclImpl() = template containsOrInclImpl() {.dirty.} =
var index = RawGet(s, key) var index = RawGet(s, key)
if index >= 0: if index >= 0:
result = true result = true
@ -125,7 +127,7 @@ proc toSet*[A](keys: openarray[A]): TSet[A] =
result = initSet[A](nextPowerOfTwo(keys.len+10)) result = initSet[A](nextPowerOfTwo(keys.len+10))
for key in items(keys): result.incl(key) for key in items(keys): result.incl(key)
template dollarImpl(): stmt = template dollarImpl(): stmt {.dirty.} =
result = "{" result = "{"
for key in items(s): for key in items(s):
if result.len > 1: result.add(", ") if result.len > 1: result.add(", ")
@ -155,7 +157,7 @@ proc card*[A](s: TOrderedSet[A]): int {.inline.} =
## alias for `len`. ## alias for `len`.
result = s.counter result = s.counter
template forAllOrderedPairs(yieldStmt: stmt) = template forAllOrderedPairs(yieldStmt: stmt) {.dirty.} =
var h = s.first var h = s.first
while h >= 0: while h >= 0:
var nxt = s.data[h].next var nxt = s.data[h].next

View file

@ -26,6 +26,9 @@ type
data: TKeyValuePairSeq[A, B] data: TKeyValuePairSeq[A, B]
counter: int counter: int
when not defined(nimhygiene):
{.pragma: dirty.}
proc len*[A, B](t: TTable[A, B]): int = proc len*[A, B](t: TTable[A, B]): int =
## returns the number of keys in `t`. ## returns the number of keys in `t`.
result = t.counter result = t.counter
@ -66,7 +69,7 @@ proc mustRehash(length, counter: int): bool {.inline.} =
proc nextTry(h, maxHash: THash): THash {.inline.} = proc nextTry(h, maxHash: THash): THash {.inline.} =
result = ((5 * h) + 1) and maxHash result = ((5 * h) + 1) and maxHash
template rawGetImpl() = template rawGetImpl() {.dirty.} =
var h: THash = hash(key) and high(t.data) # start with real hash value var h: THash = hash(key) and high(t.data) # start with real hash value
while t.data[h].slot != seEmpty: while t.data[h].slot != seEmpty:
if t.data[h].key == key and t.data[h].slot == seFilled: if t.data[h].key == key and t.data[h].slot == seFilled:
@ -74,7 +77,7 @@ template rawGetImpl() =
h = nextTry(h, high(t.data)) h = nextTry(h, high(t.data))
result = -1 result = -1
template rawInsertImpl() = template rawInsertImpl() {.dirty.} =
var h: THash = hash(key) and high(data) var h: THash = hash(key) and high(data)
while data[h].slot == seFilled: while data[h].slot == seFilled:
h = nextTry(h, high(data)) h = nextTry(h, high(data))
@ -115,12 +118,12 @@ proc Enlarge[A, B](t: var TTable[A, B]) =
if t.data[i].slot == seFilled: RawInsert(t, n, t.data[i].key, t.data[i].val) if t.data[i].slot == seFilled: RawInsert(t, n, t.data[i].key, t.data[i].val)
swap(t.data, n) swap(t.data, n)
template AddImpl() = template AddImpl() {.dirty.} =
if mustRehash(len(t.data), t.counter): Enlarge(t) if mustRehash(len(t.data), t.counter): Enlarge(t)
RawInsert(t, t.data, key, val) RawInsert(t, t.data, key, val)
inc(t.counter) inc(t.counter)
template PutImpl() = template PutImpl() {.dirty.} =
var index = RawGet(t, key) var index = RawGet(t, key)
if index >= 0: if index >= 0:
t.data[index].val = val t.data[index].val = val
@ -129,7 +132,7 @@ template PutImpl() =
when false: when false:
# not yet used: # not yet used:
template HasKeyOrPutImpl() = template HasKeyOrPutImpl() {.dirty.} =
var index = RawGet(t, key) var index = RawGet(t, key)
if index >= 0: if index >= 0:
t.data[index].val = val t.data[index].val = val
@ -168,7 +171,7 @@ proc toTable*[A, B](pairs: openarray[tuple[key: A,
result = initTable[A, B](nextPowerOfTwo(pairs.len+10)) result = initTable[A, B](nextPowerOfTwo(pairs.len+10))
for key, val in items(pairs): result[key] = val for key, val in items(pairs): result[key] = val
template dollarImpl(): stmt = template dollarImpl(): stmt {.dirty.} =
if t.len == 0: if t.len == 0:
result = "{:}" result = "{:}"
else: else:
@ -199,7 +202,7 @@ proc len*[A, B](t: TOrderedTable[A, B]): int {.inline.} =
## returns the number of keys in `t`. ## returns the number of keys in `t`.
result = t.counter result = t.counter
template forAllOrderedPairs(yieldStmt: stmt) = template forAllOrderedPairs(yieldStmt: stmt) {.dirty.} =
var h = t.first var h = t.first
while h >= 0: while h >= 0:
var nxt = t.data[h].next var nxt = t.data[h].next

View file

@ -31,19 +31,19 @@ var
checkpoints: seq[string] = @[] checkpoints: seq[string] = @[]
template TestSetupIMPL*: stmt = nil template TestSetupIMPL*: stmt {.dirty.} = nil
template TestTeardownIMPL*: stmt = nil template TestTeardownIMPL*: stmt {.dirty.} = nil
proc shouldRun(testName: string): bool = proc shouldRun(testName: string): bool =
result = true result = true
template suite*(name: expr, body: stmt): stmt = template suite*(name: expr, body: stmt): stmt {.dirty.} =
block: block:
template setup*(setupBody: stmt): stmt = template setup*(setupBody: stmt): stmt {.dirty.} =
template TestSetupIMPL: stmt = setupBody template TestSetupIMPL: stmt {.dirty.} = setupBody
template teardown*(teardownBody: stmt): stmt = template teardown*(teardownBody: stmt): stmt {.dirty.} =
template TestTeardownIMPL: stmt = teardownBody template TestTeardownIMPL: stmt {.dirty.} = teardownBody
body body
@ -59,12 +59,12 @@ proc testDone(name: string, s: TTestStatus) =
else: else:
echo "[", $s, "] ", name, "\n" echo "[", $s, "] ", name, "\n"
template test*(name: expr, body: stmt): stmt = template test*(name: expr, body: stmt): stmt {.dirty.} =
bind shouldRun, checkpoints, testDone bind shouldRun, checkpoints, testDone
if shouldRun(name): if shouldRun(name):
checkpoints = @[] checkpoints = @[]
var TestStatusIMPL = OK var TestStatusIMPL {.inject.} = OK
try: try:
TestSetupIMPL() TestSetupIMPL()
@ -146,13 +146,14 @@ macro check*(conditions: stmt): stmt =
var ast = conditions.treeRepr var ast = conditions.treeRepr
error conditions.lineinfo & ": Malformed check statement:\n" & ast error conditions.lineinfo & ": Malformed check statement:\n" & ast
template require*(conditions: stmt): stmt = template require*(conditions: stmt): stmt {.dirty.} =
block: block:
const AbortOnError = true const AbortOnError {.inject.} = true
check conditions check conditions
macro expect*(exp: stmt): stmt = macro expect*(exp: stmt): stmt =
template expectBody(errorTypes, lineInfoLit: expr, body: stmt): PNimrodNode = template expectBody(errorTypes, lineInfoLit: expr,
body: stmt): PNimrodNode {.dirty.} =
try: try:
body body
checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.") checkpoint(lineInfoLit & ": Expect Failed, no exception was thrown.")

View file

@ -1259,8 +1259,8 @@ proc max*[T](x: varargs[T]): T =
proc clamp*[T](x, a, b: T): T = proc clamp*[T](x, a, b: T): T =
## limits the value ``x`` within the interval [a, b] ## limits the value ``x`` within the interval [a, b]
if x > a: return a if x < a: return a
if x < b: return b if x > b: return b
return x return x
iterator items*[T](a: openarray[T]): T {.inline.} = iterator items*[T](a: openarray[T]): T {.inline.} =
@ -1657,9 +1657,9 @@ proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", noSideEffect.}
## side effects, so that it can be used for debugging routines marked as ## side effects, so that it can be used for debugging routines marked as
## ``noSideEffect``. ## ``noSideEffect``.
template newException*(exceptn: typeDesc, message: string): expr = template newException*(exceptn: typeDesc, message: string): expr =
## creates an exception object of type ``exceptn`` and sets its ``msg`` field ## creates an exception object of type ``exceptn`` and sets its ``msg`` field
## to `message`. Returns the new exception object. ## to `message`. Returns the new exception object.
# block: # open a new scope # block: # open a new scope
var var
e: ref exceptn e: ref exceptn

View file

@ -866,7 +866,7 @@ template `=~`*(s: string, pattern: TPeg): expr =
## echo("syntax error") ## echo("syntax error")
## ##
when not definedInScope(matches): when not definedInScope(matches):
var matches: array[0..maxSubpatterns-1, string] var matches {.inject.}: array[0..maxSubpatterns-1, string]
match(s, pattern, matches) match(s, pattern, matches)
# ------------------------- more string handling ------------------------------ # ------------------------- more string handling ------------------------------

View file

@ -1,9 +1,8 @@
version 0.9.0 version 0.9.0
============= =============
- make templates hygienic by default: 'gensym', 'inject' pragmas;
document 'gensym', 'inject' and 'dirty'
- make 'bind' default for templates and introduce 'mixin' - make 'bind' default for templates and introduce 'mixin'
- implement 'bind' for macros
- use ``\`` for comment continuations - use ``\`` for comment continuations
- ``final`` should be the default for objects - ``final`` should be the default for objects
- implement "closure tuple consists of a single 'ref'" optimization - implement "closure tuple consists of a single 'ref'" optimization