code gen bugfixes; marshal.nim implemented

This commit is contained in:
Araq
2011-06-26 17:21:52 +02:00
parent db0a4a9f86
commit 990dc2d715
21 changed files with 725 additions and 422 deletions

View File

@@ -1,275 +0,0 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2011 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains procs for serialization and deseralization of
## arbitrary Nimrod data structures. XXX This is not implemented yet!
import streams
proc load*[T](s: PStream, data: var T) =
## loads `data` from the stream `s`. Raises `EIO` in case of an error.
proc store*[T](s: PStream, data: T) =
## stores `data` into the stream `s`. Raises `EIO` in case of an error.
type
TTypeInfo = distinct whatever
TValue = object
t: TTypeInfo
x: pointer
proc rtti[T](x: T): TTypeInfo {.magic: "rtti".}
proc `[]` (a: TValue, i: int): TValue =
## works for arrays, objects, etc.
proc `[]=` (a: TValue, i: int, x: TValue) =
##
proc reprPointer(x: pointer): string {.compilerproc.} =
var buf: array [0..59, char]
c_sprintf(buf, "%p", x)
return $buf
proc reprStrAux(result: var string, s: string) =
if cast[pointer](s) == nil:
add result, "nil"
return
add result, reprPointer(cast[pointer](s)) & "\""
for c in items(s):
case c
of '"': add result, "\\\""
of '\\': add result, "\\\\" # BUGFIX: forgotten
of '\10': add result, "\\10\"\n\"" # " \n " # better readability
of '\128' .. '\255', '\0'..'\9', '\11'..'\31':
add result, "\\" & reprInt(ord(c))
else: result.add(c)
add result, "\""
proc reprStr(s: string): string {.compilerRtl.} =
result = ""
reprStrAux(result, s)
proc reprBool(x: bool): string {.compilerRtl.} =
if x: result = "true"
else: result = "false"
proc reprChar(x: char): string {.compilerRtl.} =
result = "\'"
case x
of '"': add result, "\\\""
of '\\': add result, "\\\\"
of '\128' .. '\255', '\0'..'\31': add result, "\\" & reprInt(ord(x))
else: add result, x
add result, "\'"
proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} =
if e <% typ.node.len: # BUGFIX
result = $typ.node.sons[e].name
else:
result = $e & " (invalid data!)"
type
pbyteArray = ptr array[0.. 0xffff, byte]
proc addSetElem(result: var string, elem: int, typ: PNimType) =
case typ.kind
of tyEnum: add result, reprEnum(elem, typ)
of tyBool: add result, reprBool(bool(elem))
of tyChar: add result, reprChar(chr(elem))
of tyRange: addSetElem(result, elem, typ.base)
of tyInt..tyInt64: add result, reprInt(elem)
else: # data corrupt --> inform the user
add result, " (invalid data!)"
proc reprSetAux(result: var string, p: pointer, typ: PNimType) =
# "typ.slots.len" field is for sets the "first" field
var elemCounter = 0 # we need this flag for adding the comma at
# the right places
add result, "{"
var u: int64
case typ.size
of 1: u = ze64(cast[ptr int8](p)[])
of 2: u = ze64(cast[ptr int16](p)[])
of 4: u = ze64(cast[ptr int32](p)[])
of 8: u = cast[ptr int64](p)[]
else:
var a = cast[pbyteArray](p)
for i in 0 .. typ.size*8-1:
if (ze(a[i div 8]) and (1 shl (i mod 8))) != 0:
if elemCounter > 0: add result, ", "
addSetElem(result, i+typ.node.len, typ.base)
inc(elemCounter)
if typ.size <= 8:
for i in 0..sizeof(int64)*8-1:
if (u and (1 shl i)) != 0:
if elemCounter > 0: add result, ", "
addSetElem(result, i+typ.node.len, typ.base)
inc(elemCounter)
add result, "}"
proc reprSet(p: pointer, typ: PNimType): string {.compilerRtl.} =
result = ""
reprSetAux(result, p, typ)
type
TReprClosure {.final.} = object # we cannot use a global variable here
# as this wouldn't be thread-safe
marked: TCellSet
recdepth: int # do not recurse endless
indent: int # indentation
when not defined(useNimRtl):
proc initReprClosure(cl: var TReprClosure) =
Init(cl.marked)
cl.recdepth = -1 # default is to display everything!
cl.indent = 0
proc deinitReprClosure(cl: var TReprClosure) =
Deinit(cl.marked)
proc reprBreak(result: var string, cl: TReprClosure) =
add result, "\n"
for i in 0..cl.indent-1: add result, ' '
proc reprAux(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure)
proc reprArray(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
add result, "["
var bs = typ.base.size
for i in 0..typ.size div bs - 1:
if i > 0: add result, ", "
reprAux(result, cast[pointer](cast[TAddress](p) + i*bs), typ.base, cl)
add result, "]"
proc reprSequence(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
if p == nil:
add result, "nil"
return
result.add(reprPointer(p) & "[")
var bs = typ.base.size
for i in 0..cast[PGenericSeq](p).len-1:
if i > 0: add result, ", "
reprAux(result, cast[pointer](cast[TAddress](p) + GenericSeqSize + i*bs),
typ.Base, cl)
add result, "]"
proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode,
cl: var TReprClosure) =
case n.kind
of nkNone: assert(false)
of nkSlot:
add result, $n.name
add result, " = "
reprAux(result, cast[pointer](cast[TAddress](p) + n.offset), n.typ, cl)
of nkList:
for i in 0..n.len-1:
if i > 0: add result, ",\n"
reprRecordAux(result, p, n.sons[i], cl)
of nkCase:
var m = selectBranch(p, n)
reprAux(result, cast[pointer](cast[TAddress](p) + n.offset), n.typ, cl)
if m != nil: reprRecordAux(result, p, m, cl)
proc reprRecord(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
add result, "["
reprRecordAux(result, p, typ.node, cl)
add result, "]"
proc reprRef(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
# we know that p is not nil here:
when defined(boehmGC) or defined(nogc):
var cell = cast[PCell](p)
else:
var cell = usrToCell(p)
add result, "ref " & reprPointer(p)
if cell notin cl.marked:
# only the address is shown:
incl(cl.marked, cell)
add result, " --> "
reprAux(result, p, typ.base, cl)
proc reprAux(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
if cl.recdepth == 0:
add result, "..."
return
dec(cl.recdepth)
case typ.kind
of tySet: reprSetAux(result, p, typ)
of tyArray: reprArray(result, p, typ, cl)
of tyTuple, tyPureObject: reprRecord(result, p, typ, cl)
of tyObject:
var t = cast[ptr PNimType](p)[]
reprRecord(result, p, t, cl)
of tyRef, tyPtr:
assert(p != nil)
if cast[ppointer](p)[] == nil: add result, "nil"
else: reprRef(result, cast[ppointer](p)[], typ, cl)
of tySequence:
reprSequence(result, cast[ppointer](p)[], typ, cl)
of tyInt: add result, $(cast[ptr int](p)[])
of tyInt8: add result, $int(cast[ptr Int8](p)[])
of tyInt16: add result, $int(cast[ptr Int16](p)[])
of tyInt32: add result, $int(cast[ptr Int32](p)[])
of tyInt64: add result, $(cast[ptr Int64](p)[])
of tyFloat: add result, $(cast[ptr float](p)[])
of tyFloat32: add result, $(cast[ptr float32](p)[])
of tyFloat64: add result, $(cast[ptr float64](p)[])
of tyEnum: add result, reprEnum(cast[ptr int](p)[], typ)
of tyBool: add result, reprBool(cast[ptr bool](p)[])
of tyChar: add result, reprChar(cast[ptr char](p)[])
of tyString: reprStrAux(result, cast[ptr string](p)[])
of tyCString: reprStrAux(result, $(cast[ptr cstring](p)[]))
of tyRange: reprAux(result, p, typ.base, cl)
of tyProc, tyPointer:
if cast[ppointer](p)[] == nil: add result, "nil"
else: add result, reprPointer(cast[ppointer](p)[])
else:
add result, "(invalid data!)"
inc(cl.recdepth)
proc reprOpenArray(p: pointer, length: int, elemtyp: PNimType): string {.
compilerRtl.} =
var
cl: TReprClosure
initReprClosure(cl)
result = "["
var bs = elemtyp.size
for i in 0..length - 1:
if i > 0: add result, ", "
reprAux(result, cast[pointer](cast[TAddress](p) + i*bs), elemtyp, cl)
add result, "]"
deinitReprClosure(cl)
when not defined(useNimRtl):
proc reprAny(p: pointer, typ: PNimType): string =
var
cl: TReprClosure
initReprClosure(cl)
result = ""
if typ.kind in {tyObject, tyPureObject, tyTuple, tyArray, tySet}:
reprAux(result, p, typ, cl)
else:
var p = p
reprAux(result, addr(p), typ, cl)
add result, "\n"
deinitReprClosure(cl)

View File

@@ -11,8 +11,6 @@
## Note that even though ``TAny`` and its operations hide the nasty low level
## details from its clients, it remains inherently unsafe!
# XXX raw pointer needs to be exposed somehow?
include "system/hti.nim"
type
@@ -42,7 +40,7 @@ type
akFloat32 = 37, ## any represents a float32
akFloat64 = 38, ## any represents a float64
akFloat128 = 39, ## any represents a float128
akPureObject = 40 ## any represents an object has no `type` field
akPureObject = 40 ## any represents an object that has no `type` field
TAny* = object {.pure.} ## can represent any nimrod value; NOTE: the wrapped
## value can be modified with its wrapper! This means
@@ -63,6 +61,13 @@ const
GenericSeqSize = (2 * sizeof(int))
proc genericAssign(dest, src: Pointer, mt: PNimType) {.importc.}
proc genericShallowAssign(dest, src: Pointer, mt: PNimType) {.importc.}
proc incrSeq(seq: PGenSeq, elemSize: int): PGenSeq {.importc, nodecl.}
proc newObj(typ: PNimType, size: int): pointer {.importc, nodecl.}
proc newSeq(typ: PNimType, len: int): pointer {.importc.}
proc objectInit(dest: Pointer, typ: PNimType) {.importc.}
template `+!!`(a, b: expr): expr = cast[pointer](cast[TAddress](a) + b)
proc getDiscriminant(aa: Pointer, n: ptr TNimNode): int =
assert(n.kind == nkCase)
@@ -95,28 +100,61 @@ proc toAny*[T](x: var T): TAny {.inline.} =
result.value = addr(x)
result.rawType = cast[PNimType](getTypeInfo(x))
proc getKind*(x: TAny): TAnyKind {.inline.} =
proc kind*(x: TAny): TAnyKind {.inline.} =
## get the type kind
result = TAnyKind(ord(x.rawType.kind))
proc baseTypeKind*(x: TAny): TAnyKind {.inline.} =
## get the base type's kind; akNone is returned if `x` has no base type.
if x.rawType.base != nil:
result = TAnyKind(ord(x.rawType.base.kind))
proc baseTypeSize*(x: TAny): int =
## returns the size of `x`'s basetype.
if x.rawType.base != nil:
result = x.rawType.base.size
proc invokeNew*(x: TAny) =
## performs ``new(x)``. `x` needs to represent a ``ref``.
assert x.rawType.kind == tyRef
var z = newObj(x.rawType, x.rawType.base.size)
genericAssign(x.value, addr(z), x.rawType)
proc invokeNewSeq*(x: TAny, len: int) =
## performs ``newSeq(x, len)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence
var z = newSeq(x.rawType, len)
genericShallowAssign(x.value, addr(z), x.rawType)
proc extendSeq*(x: TAny, elems = 1) =
## performs ``setLen(x, x.len+elems)``. `x` needs to represent a ``seq``.
assert x.rawType.kind == tySequence
var y = cast[ptr PGenSeq](x.value)[]
var z = incrSeq(y, x.rawType.base.size * elems)
genericShallowAssign(x.value, addr(z), x.rawType)
proc setObjectRuntimeType*(x: TAny) =
## this needs to be called to set `x`'s runtime object type field.
assert x.rawType.kind == tyObject
objectInit(x.value, x.rawType)
proc skipRange(x: PNimType): PNimType {.inline.} =
result = x
if result.kind == tyRange: result = result.base
template `+!!`(a, b: expr): expr = cast[pointer](cast[TAddress](a) + b)
proc `[]`*(x: TAny, i: int): TAny =
## accessor for an any `x` that represents an array or a sequence.
case x.rawType.kind
of tyArray:
var bs = x.rawType.base.size
if i >% (x.rawType.size div bs - 1):
if i >=% x.rawType.size div bs:
raise newException(EInvalidIndex, "index out of bounds")
return newAny(x.value +!! i*bs, x.rawType.base)
of tySequence:
var s = cast[ppointer](x.value)[]
if s == nil: raise newException(EInvalidValue, "sequence is nil")
var bs = x.rawType.base.size
if i >% (cast[PGenSeq](s).len-1):
if i >=% cast[PGenSeq](s).len:
raise newException(EInvalidIndex, "index out of bounds")
return newAny(s +!! (GenericSeqSize+i*bs), x.rawType.base)
else: assert false
@@ -126,14 +164,15 @@ proc `[]=`*(x: TAny, i: int, y: TAny) =
case x.rawType.kind
of tyArray:
var bs = x.rawType.base.size
if i >% (x.rawType.size div bs - 1):
if i >=% x.rawType.size div bs:
raise newException(EInvalidIndex, "index out of bounds")
assert y.rawType == x.rawType.base
genericAssign(x.value +!! i*bs, y.value, y.rawType)
of tySequence:
var s = cast[ppointer](x.value)[]
if s == nil: raise newException(EInvalidValue, "sequence is nil")
var bs = x.rawType.base.size
if i >% (cast[PGenSeq](s).len-1):
if i >=% cast[PGenSeq](s).len:
raise newException(EInvalidIndex, "index out of bounds")
assert y.rawType == x.rawType.base
genericAssign(s +!! (GenericSeqSize+i*bs), y.value, y.rawType)
@@ -146,6 +185,29 @@ proc len*(x: TAny): int =
of tySequence: result = cast[PGenSeq](cast[ppointer](x.value)[]).len
else: assert false
proc isNil*(x: TAny): bool =
## `isNil` for an any `x` that represents a sequence, string, cstring,
## proc or some pointer type.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
result = isNil(cast[ppointer](x.value)[])
proc getPointer*(x: TAny): pointer =
## retrieve the pointer value out of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
result = cast[ppointer](x.value)[]
proc setPointer*(x: TAny, y: pointer) =
## sets the pointer value of `x`. ``x`` needs to be of kind
## ``akString``, ``akCString``, ``akProc``, ``akRef``, ``akPtr``,
## ``akPointer``, ``akSequence``.
assert x.rawType.kind in {tyString, tyCString, tyRef, tyPtr, tyPointer,
tySequence, tyProc}
cast[ppointer](x.value)[] = y
proc fieldsAux(p: pointer, n: ptr TNimNode,
ret: var seq[tuple[name: cstring, any: TAny]]) =
case n.kind
@@ -173,6 +235,64 @@ iterator fields*(x: TAny): tuple[name: string, any: TAny] =
for name, any in items(ret):
yield ($name, any)
proc cmpIgnoreStyle(a, b: cstring): int {.noSideEffect.} =
proc toLower(c: char): char {.inline.} =
if c in {'A'..'Z'}: result = chr(ord(c) + (ord('a') - ord('A')))
else: result = c
var i = 0
var j = 0
while True:
while a[i] == '_': inc(i)
while b[j] == '_': inc(j) # BUGFIX: typo
var aa = toLower(a[i])
var bb = toLower(b[j])
result = ord(aa) - ord(bb)
if result != 0 or aa == '\0': break
inc(i)
inc(j)
proc getFieldNode(p: pointer, n: ptr TNimNode,
name: cstring): ptr TNimNode =
case n.kind
of nkNone: assert(false)
of nkSlot:
if cmpIgnoreStyle(n.name, name) == 0:
result = n
of nkList:
for i in 0..n.len-1:
result = getFieldNode(p, n.sons[i], name)
if result != nil: break
of nkCase:
if cmpIgnoreStyle(n.name, name) == 0:
result = n
else:
var m = selectBranch(p, n)
if m != nil: result = getFieldNode(p, m, name)
proc `[]=`*(x: TAny, fieldName: string, value: TAny) =
## sets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType
if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[]
assert x.rawType.kind in {tyTuple, tyPureObject, tyObject}
var n = getFieldNode(x.value, t.node, fieldname)
if n != nil:
assert n.typ == value.rawType
genericAssign(x.value +!! n.offset, value.value, value.rawType)
else:
raise newException(EInvalidValue, "invalid field name: " & fieldName)
proc `[]`*(x: TAny, fieldName: string): TAny =
## gets a field of `x`; `x` represents an object or a tuple.
var t = x.rawType
if x.rawType.kind == tyObject: t = cast[ptr PNimType](x.value)[]
assert x.rawType.kind in {tyTuple, tyPureObject, tyObject}
var n = getFieldNode(x.value, t.node, fieldname)
if n != nil:
result.value = x.value +!! n.offset
result.rawType = n.typ
else:
raise newException(EInvalidValue, "invalid field name: " & fieldName)
proc `[]`*(x: TAny): TAny =
## dereference operation for the any `x` that represents a ptr or a ref.
assert x.rawtype.kind in {tyRef, tyPtr}
@@ -232,6 +352,27 @@ proc getBiggestInt*(x: TAny): biggestInt =
else: assert false
else: assert false
proc setBiggestInt*(x: TAny, y: biggestInt) =
## sets the integer value of `x`. `x` needs to represent
## some integer, a bool, a char or an enum.
var t = skipRange(x.rawtype)
case t.kind
of tyInt: cast[ptr int](x.value)[] = int(y)
of tyInt8: cast[ptr int8](x.value)[] = int8(y)
of tyInt16: cast[ptr int16](x.value)[] = int16(y)
of tyInt32: cast[ptr int32](x.value)[] = int32(y)
of tyInt64: cast[ptr int64](x.value)[] = int64(y)
of tyBool: cast[ptr bool](x.value)[] = y != 0
of tyChar: cast[ptr char](x.value)[] = chr(y.int)
of tyEnum:
case t.size
of 1: cast[ptr int8](x.value)[] = toU8(y.int)
of 2: cast[ptr int16](x.value)[] = toU16(y.int)
of 4: cast[ptr int32](x.value)[] = int32(y)
of 8: cast[ptr int64](x.value)[] = y
else: assert false
else: assert false
proc getChar*(x: TAny): char =
## retrieve the char value out of `x`. `x` needs to represent a char.
var t = skipRange(x.rawtype)
@@ -244,11 +385,35 @@ proc getBool*(x: TAny): bool =
assert t.kind == tyBool
result = cast[ptr bool](x.value)[]
proc getEnumField*(x: TAny): string =
## gets the enum field name as a string. `x` needs to represent an enum.
proc skipRange*(x: TAny): TAny =
## skips the range information of `x`.
assert x.rawType.kind == tyRange
result.rawType = x.rawType.base
result.value = x.value
proc getEnumOrdinal*(x: TAny, name: string): int =
## gets the enum field ordinal from `name`. `x` needs to represent an enum
## but is only used to access the type information. In case of an error
## ``low(int)`` is returned.
var typ = skipRange(x.rawtype)
assert typ.kind == tyEnum
var e = int(getBiggestInt(x))
var n = typ.node
var s = n.sons
for i in 0 .. n.len-1:
if cmpIgnoreStyle($s[i].name, name) == 0:
if ntfEnumHole notin typ.flags:
return i
else:
return s[i].offset
result = low(int)
proc getEnumField*(x: TAny, ordinalValue: int): string =
## gets the enum field name as a string. `x` needs to represent an enum
## but is only used to access the type information. The field name of
## `ordinalValue` is returned.
var typ = skipRange(x.rawtype)
assert typ.kind == tyEnum
var e = ordinalValue
if ntfEnumHole notin typ.flags:
if e <% typ.node.len:
return $typ.node.sons[e].name
@@ -258,7 +423,11 @@ proc getEnumField*(x: TAny): string =
var s = n.sons
for i in 0 .. n.len-1:
if s[i].offset == e: return $s[i].name
result = $e & " (invalid data!)"
result = $e
proc getEnumField*(x: TAny): string =
## gets the enum field name as a string. `x` needs to represent an enum.
result = getEnumField(x, getBiggestInt(x).int)
proc getFloat*(x: TAny): float =
## retrieve the float value out of `x`. `x` needs to represent an float.
@@ -284,11 +453,30 @@ proc getBiggestFloat*(x: TAny): biggestFloat =
of tyFloat64: result = biggestFloat(cast[ptr Float64](x.value)[])
else: assert false
proc setBiggestFloat*(x: TAny, y: biggestFloat) =
## sets the float value of `x`. `x` needs to represent
## some float.
case skipRange(x.rawtype).kind
of tyFloat: cast[ptr Float](x.value)[] = y
of tyFloat32: cast[ptr Float32](x.value)[] = y
of tyFloat64: cast[ptr Float64](x.value)[] = y
else: assert false
proc getString*(x: TAny): string =
## retrieve the string value out of `x`. `x` needs to represent a string.
assert x.rawtype.kind == tyString
result = cast[ptr string](x.value)[]
proc setString*(x: TAny, y: string) =
## sets the string value of `x`. `x` needs to represent a string.
assert x.rawtype.kind == tyString
cast[ptr string](x.value)[] = y
proc getCString*(x: TAny): cstring =
## retrieve the cstring value out of `x`. `x` needs to represent a cstring.
assert x.rawtype.kind == tyCString
result = cast[ptr cstring](x.value)[]
proc assign*(x, y: TAny) =
## copies the value of `y` to `x`. The assignment operator for ``TAny``
## does NOT do this; it performs a shallow copy instead!
@@ -339,7 +527,7 @@ proc inclSetElement*(x: TAny, elem: int) =
a[] = a[] or (1'i64 shl e)
else:
var a = cast[pbyteArray](p)
a[e div 8] = toU8(a[e div 8] or (1 shl (e mod 8)))
a[e shr 3] = toU8(a[e shr 3] or (1 shl (e and 7)))
when isMainModule:
type
@@ -365,8 +553,8 @@ when isMainModule:
var i = 0
for n, a in fields(x2):
case i
of 0: assert n == "name" and $a.getKind == "akString"
of 1: assert n == "s" and $a.getKind == "akInt"
of 0: assert n == "name" and $a.kind == "akString"
of 1: assert n == "s" and $a.kind == "akInt"
else: assert false
inc i
@@ -377,9 +565,9 @@ when isMainModule:
i = 0
for n, a in fields(x3):
case i
of 0: assert n == "test" and $a.getKind == "akInt"
of 1: assert n == "asd" and $a.getKind == "akInt"
of 2: assert n == "test2" and $a.getKind == "akEnum"
of 0: assert n == "test" and $a.kind == "akInt"
of 1: assert n == "asd" and $a.kind == "akInt"
of 2: assert n == "test2" and $a.kind == "akEnum"
else: assert false
inc i
@@ -387,4 +575,17 @@ when isMainModule:
new(test4)
test4[] = "test"
var x4 = toAny(test4)
assert($x4[].getKind() == "akString")
assert($x4[].kind() == "akString")
block:
# gimme a new scope dammit
var myarr: array[0..4, array[0..4, string]] = [
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"], ["test", "1", "2", "3", "4"],
["test", "1", "2", "3", "4"]]
var m = toAny(myArr)
for i in 0 .. m.len-1:
for j in 0 .. m[i].len-1:
echo getString(m[i][j])