system.fields|fieldPairs for objects
This commit is contained in:
parent
8e9b39084c
commit
d15981adbc
5 changed files with 170 additions and 37 deletions
|
|
@ -301,52 +301,106 @@ proc semConst(c: PContext, n: PNode): PNode =
|
||||||
addSon(b, copyTree(def))
|
addSon(b, copyTree(def))
|
||||||
addSon(result, b)
|
addSon(result, b)
|
||||||
|
|
||||||
proc transfFieldLoopBody(n: PNode, forLoop: PNode,
|
type
|
||||||
tupleType: PType,
|
TFieldInstCtx = object # either 'tup[i]' or 'field' is valid
|
||||||
tupleIndex, first: int): PNode =
|
tupleType: PType # if != nil we're traversing a tuple
|
||||||
|
tupleIndex: int
|
||||||
|
field: PSym
|
||||||
|
replaceByFieldName: bool
|
||||||
|
|
||||||
|
proc instFieldLoopBody(c: TFieldInstCtx, n: PNode, forLoop: PNode): PNode =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkEmpty..pred(nkIdent), succ(nkIdent)..nkNilLit: result = n
|
of nkEmpty..pred(nkIdent), succ(nkIdent)..nkNilLit: result = n
|
||||||
of nkIdent:
|
of nkIdent:
|
||||||
result = n
|
result = n
|
||||||
var L = sonsLen(forLoop)
|
var L = sonsLen(forLoop)
|
||||||
# field name:
|
if c.replaceByFieldName:
|
||||||
if first > 0:
|
|
||||||
if n.ident.id == forLoop[0].ident.id:
|
if n.ident.id == forLoop[0].ident.id:
|
||||||
if tupleType.n == nil:
|
let fieldName = if c.tupleType.isNil: c.field.name.s
|
||||||
# ugh, there are no field names:
|
elif c.tupleType.n.isNil: "Field" & $c.tupleIndex
|
||||||
result = newStrNode(nkStrLit, "")
|
else: c.tupleType.n.sons[c.tupleIndex].sym.name.s
|
||||||
else:
|
result = newStrNode(nkStrLit, fieldName)
|
||||||
result = newStrNode(nkStrLit, tupleType.n.sons[tupleIndex].sym.name.s)
|
|
||||||
return
|
return
|
||||||
# other fields:
|
# other fields:
|
||||||
for i in first..L-3:
|
for i in ord(c.replaceByFieldName)..L-3:
|
||||||
if n.ident.id == forLoop[i].ident.id:
|
if n.ident.id == forLoop[i].ident.id:
|
||||||
var call = forLoop.sons[L-2]
|
var call = forLoop.sons[L-2]
|
||||||
var tupl = call.sons[i+1-first]
|
var tupl = call.sons[i+1-ord(c.replaceByFieldName)]
|
||||||
result = newNodeI(nkBracketExpr, n.info)
|
if c.field.isNil:
|
||||||
result.add(tupl)
|
result = newNodeI(nkBracketExpr, n.info)
|
||||||
result.add(newIntNode(nkIntLit, tupleIndex))
|
result.add(tupl)
|
||||||
|
result.add(newIntNode(nkIntLit, c.tupleIndex))
|
||||||
|
else:
|
||||||
|
result = newNodeI(nkDotExpr, n.info)
|
||||||
|
result.add(tupl)
|
||||||
|
result.add(newSymNode(c.field, n.info))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
result = copyNode(n)
|
result = copyNode(n)
|
||||||
newSons(result, sonsLen(n))
|
newSons(result, sonsLen(n))
|
||||||
for i in countup(0, sonsLen(n)-1):
|
for i in countup(0, sonsLen(n)-1):
|
||||||
result.sons[i] = transfFieldLoopBody(n.sons[i], forLoop,
|
result.sons[i] = instFieldLoopBody(c, n.sons[i], forLoop)
|
||||||
tupleType, tupleIndex, first)
|
|
||||||
|
|
||||||
proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
|
type
|
||||||
# so that 'break' etc. work as expected, we produce
|
TFieldsCtx = object
|
||||||
|
c: PContext
|
||||||
|
m: TMagic
|
||||||
|
|
||||||
|
proc semForObjectFields(c: TFieldsCtx, typ, forLoop, father: PNode) =
|
||||||
|
case typ.kind
|
||||||
|
of nkSym:
|
||||||
|
var fc: TFieldInstCtx # either 'tup[i]' or 'field' is valid
|
||||||
|
fc.field = typ.sym
|
||||||
|
fc.replaceByFieldName = c.m == mFieldPairs
|
||||||
|
openScope(c.c.tab)
|
||||||
|
inc c.c.InUnrolledContext
|
||||||
|
let body = instFieldLoopBody(fc, lastSon(forLoop), forLoop)
|
||||||
|
father.add(SemStmt(c.c, body))
|
||||||
|
dec c.c.InUnrolledContext
|
||||||
|
closeScope(c.c.tab)
|
||||||
|
of nkNilLit: nil
|
||||||
|
of nkRecCase:
|
||||||
|
let L = forLoop.len
|
||||||
|
let call = forLoop.sons[L-2]
|
||||||
|
if call.len > 2:
|
||||||
|
LocalError(forLoop.info, errGenerated,
|
||||||
|
"parallel 'fields' iterator does not work for 'case' objects")
|
||||||
|
return
|
||||||
|
# iterate over the selector:
|
||||||
|
semForObjectFields(c, typ[0], forLoop, father)
|
||||||
|
# we need to generate a case statement:
|
||||||
|
var caseStmt = newNodeI(nkCaseStmt, forLoop.info)
|
||||||
|
# generate selector:
|
||||||
|
var access = newNodeI(nkDotExpr, forLoop.info, 2)
|
||||||
|
access.sons[0] = call.sons[1]
|
||||||
|
access.sons[1] = newSymNode(typ.sons[0].sym, forLoop.info)
|
||||||
|
caseStmt.add(semExprWithType(c.c, access))
|
||||||
|
# copy the branches over, but replace the fields with the for loop body:
|
||||||
|
for i in 1 .. <typ.len:
|
||||||
|
var branch = copyTree(typ[i])
|
||||||
|
let L = branch.len
|
||||||
|
branch.sons[L-1] = newNodeI(nkStmtList, forLoop.info)
|
||||||
|
semForObjectFields(c, typ[i].lastSon, forLoop, branch[L-1])
|
||||||
|
caseStmt.add(branch)
|
||||||
|
father.add(caseStmt)
|
||||||
|
of nkRecList:
|
||||||
|
for t in items(typ): semForObjectFields(c, t, forLoop, father)
|
||||||
|
else:
|
||||||
|
illFormedAst(typ)
|
||||||
|
|
||||||
|
proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
|
||||||
|
# so that 'break' etc. work as expected, we produce
|
||||||
# a 'while true: stmt; break' loop ...
|
# a 'while true: stmt; break' loop ...
|
||||||
result = newNodeI(nkWhileStmt, n.info)
|
result = newNodeI(nkWhileStmt, n.info, 2)
|
||||||
var trueSymbol = StrTableGet(magicsys.systemModule.Tab, getIdent"true")
|
var trueSymbol = StrTableGet(magicsys.systemModule.Tab, getIdent"true")
|
||||||
if trueSymbol == nil:
|
if trueSymbol == nil:
|
||||||
LocalError(n.info, errSystemNeeds, "true")
|
LocalError(n.info, errSystemNeeds, "true")
|
||||||
trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(), n.info)
|
trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(), n.info)
|
||||||
trueSymbol.typ = getSysType(tyBool)
|
trueSymbol.typ = getSysType(tyBool)
|
||||||
|
|
||||||
result.add(newSymNode(trueSymbol, n.info))
|
result.sons[0] = newSymNode(trueSymbol, n.info)
|
||||||
var stmts = newNodeI(nkStmtList, n.info)
|
var stmts = newNodeI(nkStmtList, n.info)
|
||||||
result.add(stmts)
|
result.sons[1] = stmts
|
||||||
|
|
||||||
var length = sonsLen(n)
|
var length = sonsLen(n)
|
||||||
var call = n.sons[length-2]
|
var call = n.sons[length-2]
|
||||||
|
|
@ -355,22 +409,33 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode =
|
||||||
return result
|
return result
|
||||||
|
|
||||||
var tupleTypeA = skipTypes(call.sons[1].typ, abstractVar)
|
var tupleTypeA = skipTypes(call.sons[1].typ, abstractVar)
|
||||||
if tupleTypeA.kind != tyTuple: InternalError(n.info, "no tuple type!")
|
if tupleTypeA.kind notin {tyTuple, tyObject}:
|
||||||
|
localError(n.info, errGenerated, "no object or tuple type")
|
||||||
|
return result
|
||||||
for i in 1..call.len-1:
|
for i in 1..call.len-1:
|
||||||
var tupleTypeB = skipTypes(call.sons[i].typ, abstractVar)
|
var tupleTypeB = skipTypes(call.sons[i].typ, abstractVar)
|
||||||
if not SameType(tupleTypeA, tupleTypeB):
|
if not SameType(tupleTypeA, tupleTypeB):
|
||||||
typeMismatch(call.sons[i], tupleTypeA, tupleTypeB)
|
typeMismatch(call.sons[i], tupleTypeA, tupleTypeB)
|
||||||
|
|
||||||
Inc(c.p.nestedLoopCounter)
|
Inc(c.p.nestedLoopCounter)
|
||||||
var loopBody = n.sons[length-1]
|
if tupleTypeA.kind == tyTuple:
|
||||||
for i in 0..sonsLen(tupleTypeA)-1:
|
var loopBody = n.sons[length-1]
|
||||||
openScope(c.tab)
|
for i in 0..sonsLen(tupleTypeA)-1:
|
||||||
var body = transfFieldLoopBody(loopBody, n, tupleTypeA, i,
|
openScope(c.tab)
|
||||||
ord(m==mFieldPairs))
|
var fc: TFieldInstCtx
|
||||||
inc c.InUnrolledContext
|
fc.tupleType = tupleTypeA
|
||||||
stmts.add(SemStmt(c, body))
|
fc.tupleIndex = i
|
||||||
dec c.InUnrolledContext
|
fc.replaceByFieldName = m == mFieldPairs
|
||||||
closeScope(c.tab)
|
var body = instFieldLoopBody(fc, loopBody, n)
|
||||||
|
inc c.InUnrolledContext
|
||||||
|
stmts.add(SemStmt(c, body))
|
||||||
|
dec c.InUnrolledContext
|
||||||
|
closeScope(c.tab)
|
||||||
|
else:
|
||||||
|
var fc: TFieldsCtx
|
||||||
|
fc.m = m
|
||||||
|
fc.c = c
|
||||||
|
semForObjectFields(fc, tupleTypeA.n, n, stmts)
|
||||||
Dec(c.p.nestedLoopCounter)
|
Dec(c.p.nestedLoopCounter)
|
||||||
var b = newNodeI(nkBreakStmt, n.info)
|
var b = newNodeI(nkBreakStmt, n.info)
|
||||||
b.add(ast.emptyNode)
|
b.add(ast.emptyNode)
|
||||||
|
|
|
||||||
|
|
@ -938,7 +938,8 @@ proc matchTypeClass*(bindings: var TIdTable, typeClass, t: PType): bool =
|
||||||
of tyTypeClass:
|
of tyTypeClass:
|
||||||
match = matchTypeClass(bindings, req, t)
|
match = matchTypeClass(bindings, req, t)
|
||||||
else: nil
|
else: nil
|
||||||
elif t.kind in {tyObject}:
|
elif t.kind in {tyObject} and req.len != 0:
|
||||||
|
# empty 'object' is fine as constraint in a type class
|
||||||
match = sameType(t, req)
|
match = sameType(t, req)
|
||||||
|
|
||||||
if tfAny in typeClass.flags:
|
if tfAny in typeClass.flags:
|
||||||
|
|
|
||||||
|
|
@ -1508,23 +1508,24 @@ proc map*[T](data: var openArray[T], op: proc (x: var T) {.closure.}) =
|
||||||
## # --> ["142", "242", "342", "442"]
|
## # --> ["142", "242", "342", "442"]
|
||||||
for i in 0..data.len-1: op(data[i])
|
for i in 0..data.len-1: op(data[i])
|
||||||
|
|
||||||
iterator fields*[T: tuple](x: T): TObject {.
|
iterator fields*[T: tuple|object](x: T): TObject {.
|
||||||
magic: "Fields", noSideEffect.}
|
magic: "Fields", noSideEffect.}
|
||||||
## iterates over every field of `x`. Warning: This really transforms
|
## iterates over every field of `x`. Warning: This really transforms
|
||||||
## the 'for' and unrolls the loop. The current implementation also has a bug
|
## the 'for' and unrolls the loop. The current implementation also has a bug
|
||||||
## that affects symbol binding in the loop body.
|
## that affects symbol binding in the loop body.
|
||||||
iterator fields*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {.
|
iterator fields*[S:tuple|object, T:tuple|object](x: S, y: T): tuple[a,b: expr] {.
|
||||||
magic: "Fields", noSideEffect.}
|
magic: "Fields", noSideEffect.}
|
||||||
## iterates over every field of `x` and `y`.
|
## iterates over every field of `x` and `y`.
|
||||||
## Warning: This is really transforms the 'for' and unrolls the loop.
|
## Warning: This is really transforms the 'for' and unrolls the loop.
|
||||||
## The current implementation also has a bug that affects symbol binding
|
## The current implementation also has a bug that affects symbol binding
|
||||||
## in the loop body.
|
## in the loop body.
|
||||||
iterator fieldPairs*[T: tuple](x: T): TObject {.
|
iterator fieldPairs*[T: tuple|object](x: T): TObject {.
|
||||||
magic: "FieldPairs", noSideEffect.}
|
magic: "FieldPairs", noSideEffect.}
|
||||||
## iterates over every field of `x`. Warning: This really transforms
|
## iterates over every field of `x`. Warning: This really transforms
|
||||||
## the 'for' and unrolls the loop. The current implementation also has a bug
|
## the 'for' and unrolls the loop. The current implementation also has a bug
|
||||||
## that affects symbol binding in the loop body.
|
## that affects symbol binding in the loop body.
|
||||||
iterator fieldPairs*[S: tuple, T: tuple](x: S, y: T): tuple[a, b: expr] {.
|
iterator fieldPairs*[S: tuple|object, T: tuple|object](x: S, y: T): tuple[
|
||||||
|
a, b: expr] {.
|
||||||
magic: "FieldPairs", noSideEffect.}
|
magic: "FieldPairs", noSideEffect.}
|
||||||
## iterates over every field of `x` and `y`.
|
## iterates over every field of `x` and `y`.
|
||||||
## Warning: This really transforms the 'for' and unrolls the loop.
|
## Warning: This really transforms the 'for' and unrolls the loop.
|
||||||
|
|
|
||||||
64
tests/run/tfielditerator2.nim
Normal file
64
tests/run/tfielditerator2.nim
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
discard """
|
||||||
|
output: '''
|
||||||
|
a char: true
|
||||||
|
a char: false
|
||||||
|
an int: 5
|
||||||
|
an int: 6
|
||||||
|
a string: abc
|
||||||
|
false
|
||||||
|
true
|
||||||
|
true
|
||||||
|
false
|
||||||
|
true
|
||||||
|
a: a
|
||||||
|
b: b
|
||||||
|
x: 5
|
||||||
|
y: 6
|
||||||
|
z: abc
|
||||||
|
myDisc: enC
|
||||||
|
c: Z
|
||||||
|
enC
|
||||||
|
Z
|
||||||
|
'''
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
TMyObj = object
|
||||||
|
a, b: char
|
||||||
|
x, y: int
|
||||||
|
z: string
|
||||||
|
|
||||||
|
TEnum = enum enA, enB, enC
|
||||||
|
TMyCaseObj = object
|
||||||
|
case myDisc: TEnum
|
||||||
|
of enA: a: int
|
||||||
|
of enB: b: string
|
||||||
|
of enC: c: char
|
||||||
|
|
||||||
|
proc p(x: char) = echo "a char: ", x <= 'a'
|
||||||
|
proc p(x: int) = echo "an int: ", x
|
||||||
|
proc p(x: string) = echo "a string: ", x
|
||||||
|
|
||||||
|
proc myobj(a, b: char, x, y: int, z: string): TMyObj =
|
||||||
|
result.a = a; result.b = b; result.x = x; result.y = y; result.z = z
|
||||||
|
|
||||||
|
var x = myobj('a', 'b', 5, 6, "abc")
|
||||||
|
var y = myobj('A', 'b', 5, 9, "abc")
|
||||||
|
|
||||||
|
for f in fields(x):
|
||||||
|
p f
|
||||||
|
|
||||||
|
for a, b in fields(x, y):
|
||||||
|
echo a == b
|
||||||
|
|
||||||
|
for key, val in fieldPairs(x):
|
||||||
|
echo key, ": ", val
|
||||||
|
|
||||||
|
var co: TMyCaseObj
|
||||||
|
co.myDisc = enC
|
||||||
|
co.c = 'Z'
|
||||||
|
for key, val in fieldPairs(co):
|
||||||
|
echo key, ": ", val
|
||||||
|
|
||||||
|
for val in fields(co):
|
||||||
|
echo val
|
||||||
|
|
@ -26,6 +26,8 @@ Library Additions
|
||||||
- Added ``system.unsafeNew`` to support hacky variable length objects.
|
- Added ``system.unsafeNew`` to support hacky variable length objects.
|
||||||
- There is a new experimental mark&sweep GC which can be faster (or much
|
- There is a new experimental mark&sweep GC which can be faster (or much
|
||||||
slower) than the default GC. Enable with ``--gc:markAndSweep``.
|
slower) than the default GC. Enable with ``--gc:markAndSweep``.
|
||||||
|
- ``system.fields`` and ``system.fieldPairs`` support ``object`` too; they
|
||||||
|
used to only suppor tuples.
|
||||||
|
|
||||||
|
|
||||||
Changes affecting backwards compatibility
|
Changes affecting backwards compatibility
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue