I'm trying to create a small module that represents "strings" as "values"
rather than "ref objects". I had this working for a "fixed size" type (254
chars + "0" \+ "length field"), and now tried to define the size as a generic
parameter.
First, I had issues trying to get this to compile:
type
StringValue*[LEN: Natural] = distinct array[LEN+2,char]
But the compiler refuses to do "LEN+2". I even tried to define a compile-time
proc to add Naturals, but it still would not compile (did not seem to see my +
proc). So what I then tried, was to replace "[LEN: Natural]" with "[LEN:
static[Natural]]", and now the compiler crashes.
Here the stringvalue module, with no test code. It _seems_ to compile.
# Module: stringvalue
## This module can be used to pass small "strings" across threads, or store
## them globally, without having to worry about the local GC (as Nim strings
## are local GCed objects). The "strings" are meant to be used as "values",
## rather than "reference objects", but can also be allocated on the shared
## heap.
when isMainModule:
echo("COMPILING StringValue ...")
import hashes
export hash, `==`
proc c_strcmp(a, b: cstring): cint {.
importc: "strcmp", header: "<string.h>", noSideEffect.}
type
StringValue*[LEN: static[Natural]] = distinct array[LEN+Natural(2),char]
## Represents a "string value" of up to 254 characters (excluding
## terminating '\0' and length).
proc cstr*[LEN: static[Natural]](sv: var StringValue[LEN]): cstring
{.inline, noSideEffect.} =
## Returns the 'raw' cstring of the StringValue
result = cast[cstring](addr sv)
proc `[]`*[LEN: static[Natural],I: Ordinal](sv: var StringValue[LEN]; i:
I): char {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](addr sv) +% i * sizeof(char))[]
proc `[]`*[LEN: static[Natural],I: Ordinal](sv: StringValue[LEN]; i: I):
char {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](unsafeAddr sv) +% i * sizeof(char))[]
proc `[]=`*[LEN: static[Natural],I: Ordinal](sv: var StringValue[LEN]; i:
I; c: char) {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](addr sv) +% i * sizeof(char))[] = c
proc len*[LEN: static[Natural]](sv: var StringValue[LEN]): int {.inline,
noSideEffect.} =
## Returns the len of the StringValue
int(uint8(sv[LEN+1]))
proc len*[LEN: static[Natural]](sv: StringValue[LEN]): int {.inline,
noSideEffect.} =
## Returns the len of the StringValue
int(uint8(sv[LEN+1]))
proc `$`*[LEN: static[Natural]](sv: var StringValue[LEN]): string
{.inline.} =
## Returns the string representation of the StringValue
result = $sv.cstr
proc `$`*[LEN: static[Natural]](sv: StringValue[LEN]): string {.inline.} =
## Returns the string representation of the StringValue
result = $sv.cstr
proc hash*[LEN: static[Natural]](sv: var StringValue[LEN]): Hash {.inline,
noSideEffect.} =
## Returns the hash of the StringValue
result = hash($sv)
proc hash*[LEN: static[Natural]](sv: StringValue[LEN]): Hash {.inline,
noSideEffect.} =
## Returns the hash of the StringValue
result = hash($sv)
proc `==`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool
{.inline, noSideEffect.} =
## Compares NoStrings
(a.len == b.len) and (c_strcmp(a.cstr, b.cstr) == 0)
proc `==`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline,
noSideEffect.} =
## Compares NoStrings
result = false
if a.len == b.len:
result = (c_strcmp(a.cstr, b.cstr) == 0)
proc `==`*[LEN: static[Natural]](sv: var StringValue[LEN], cs: cstring):
bool {.inline, noSideEffect.} =
## Compares a StringValue to a cstring
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](sv: StringValue[LEN], cs: cstring): bool
{.inline, noSideEffect.} =
## Compares a StringValue to a cstring
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](cs: cstring, sv: var StringValue[LEN]):
bool {.inline, noSideEffect.} =
## Compares a cstring to a StringValue
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](cs: cstring, sv: StringValue[LEN]): bool
{.inline, noSideEffect.} =
## Compares a cstring to a StringValue
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `<`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool {.inline,
noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) < 0)
proc `<`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline,
noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) < 0)
proc `<=`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool
{.inline, noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) <= 0)
proc `<=`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline,
noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) <= 0)
proc initNoString*[LEN: static[Natural]](s: cstring): StringValue[LEN]
{.inline, noSideEffect.} =
## Creates a StringValue
static:
assert(LEN <= Natural(254))
let p = cast[pointer](s)
let len = if p == nil: 0 else: len(s)
if len > LEN:
raise newException(Exception, "s is too big: " & $len)
result[LEN+1] = char(len)
if len == 0:
result[0] = char(0)
else:
copyMem(result.cstr, p, len+1)
converter toNoString*[LEN: static[Natural]](s: cstring): StringValue[LEN]
{.inline, noSideEffect.} =
## Converts a cstring to a StringValue.
result = initNoString(s)
converter toNoString*[LEN: static[Natural]](s: string): StringValue[LEN]
{.inline, noSideEffect.} =
## Converts a string to a StringValue.
result = initNoString(s)
And here the test code, which crashes the compiler.
import stringvalue
type
StringValue16* = StringValue[14]
## A 16-bytes StringValue (maximum length is 14).
StringValue32* = StringValue[30]
## A 32-bytes StringValue (maximum length is 30).
StringValue64* = StringValue[62]
## A 64-bytes StringValue (maximum length is 62).
StringValue128* = StringValue[126]
## A 128-bytes StringValue (maximum length is 126).
StringValue256* = StringValue[254]
## A 256-bytes StringValue (maximum length is 254).
when isMainModule:
echo("TESTING StringValue ...")
let text1 = "abc"
let text2 = "def"
var st1 = initNoString[3](text1)
var st2 = initNoString[3](text2)
assert(st1.len == 3)
assert(st1.cstr == text1.cstring)
assert(st2.cstr == text2.cstring)
assert(st1 == text1)
assert(text1 == st1)
assert($st1 == text1)
assert(text1 == $st1)
assert(st1 != st2)
assert(st1 < st2)
assert(st1 <= st2)
assert(st2 > st1)
assert(st2 >= st1)
assert(st1[1] == 'b')
assert(hash(st1) == hash(text1))
var st3: StringValue = "abc"
assert(st3 == text1)
st3[0] = 'd'
st3[1] = 'e'
st3[2] = 'f'
assert(st3 == text2)
I'm using nim 0.17.2 on Windows 10, and vcc:
> Visual Studio 2017 Developer Command Prompt v15.0.26228.13
>
> Copyright (c) 2017 Microsoft Corporation
>
> [vcvarsall.bat] Environment initialized for: 'x64'
I think the main issue is (beyond the compiler crash), how to get this to
compile:
type
StringValue*[LEN: Natural] = distinct array[LEN+2,char]