Distinct Symbol strings

More type buracracy makes schema conversion less lossy.
This commit is contained in:
Emery Hemingway 2022-02-19 22:33:15 -06:00
parent 486443a098
commit 3924e48deb
8 changed files with 57 additions and 62 deletions

View File

@ -1,6 +1,6 @@
# Package
version = "3.0.1" # versioned in git, this version is just to confuse nimble
version = "3.1.0" # versioned in git, this version is just to confuse nimble
author = "Emery Hemingway"
description = "data model and serialization format"
license = "Unlicense"

View File

@ -23,6 +23,13 @@ const
atomKinds* = {pkBoolean, pkFloat, pkDouble, pkSignedInteger, pkString, pkByteString, pkSymbol}
compoundKinds* = {pkRecord, pkSequence, pkSet, pkDictionary}
type Symbol* = distinct string
proc `$`*(s: Symbol): string {.borrow.}
proc `<`*(x, y: Symbol): bool {.borrow.}
proc `==`*(x, y: Symbol): bool {.borrow.}
proc len*(s: Symbol): int {.borrow.}
proc hash*(s: Symbol): Hash {.borrow.}
type
Preserve*[E = void] = object
case kind*: PreserveKind
@ -39,7 +46,7 @@ type
of pkByteString:
bytes*: seq[byte]
of pkSymbol:
symbol*: string
symbol*: Symbol
of pkRecord:
record*: seq[Preserve[E]] # label is last
of pkSequence:
@ -175,7 +182,7 @@ proc hash*(pr: Preserve): Hash =
of pkByteString:
h = h !& hash(pr.bytes)
of pkSymbol:
h = h !& hash(pr.symbol)
h = h !& hash(string pr.symbol)
of pkRecord:
for val in pr.record:
h = h !& hash(val)
@ -262,7 +269,7 @@ proc `[]=`*(pr: var Preserve; key, val: Preserve) =
proc toSymbol*(s: sink string; E = void): Preserve[E] {.inline.} =
## Create a Preserves symbol value.
Preserve[E](kind: pkSymbol, symbol: s)
Preserve[E](kind: pkSymbol, symbol: Symbol s)
proc initRecord*[E](label: Preserve[E]; arity: Natural = 0): Preserve[E] =
## Create a Preserves record value.
@ -358,9 +365,9 @@ func isByteString*(pr: Preserve): bool {.inline.} = pr.kind == pkByteString
func isSymbol*(pr: Preserve): bool {.inline.} = pr.kind == pkSymbol
## Check if `pr` is a Preserves symbol.
func isSymbol*(pr: Preserve; sym: string): bool {.inline.} =
func isSymbol*(pr: Preserve; sym: string|Symbol): bool {.inline.} =
## Check if ``pr`` is a Preserves symbol of ``sym``.
(pr.kind == pkSymbol) and (pr.symbol == sym)
(pr.kind == pkSymbol) and (pr.symbol == Symbol(sym))
func isRecord*(pr: Preserve): bool {.inline.} = (pr.kind == pkRecord) and (pr.record.len > 0)
## Check if ``pr`` is a Preserves record.
@ -474,7 +481,7 @@ proc write*[E](str: Stream; pr: Preserve[E]) =
of pkSymbol:
str.write(0xb3'u8)
str.writeVarint(pr.symbol.len)
str.write(pr.symbol)
str.write(string pr.symbol)
of pkRecord:
assert(pr.record.len > 0)
str.write(0xb4'u8)
@ -547,7 +554,7 @@ proc decodePreserves*(s: Stream; E = void): Preserve[E] =
result.bytes = cast[seq[byte]](s.readStr(len))
of 0xb3:
let len = s.readVarint()
result = Preserve[E](kind: pkSymbol, symbol: s.readStr(len))
result = Preserve[E](kind: pkSymbol, symbol: Symbol s.readStr(len))
of 0xb4:
result = Preserve[E](kind: pkRecord)
var label = decodePreserves(s, E)
@ -618,10 +625,6 @@ template preservesDictionary*() {.pragma.}
## Serialize this object or tuple as a dictionary.
## See ``toPreserve``.
template preservesSymbol*() {.pragma.}
## Serialize this string as a symbol.
## See ``toPreserve``.
template preservesOr*() {.pragma.}
## Serialize this object as an ``or`` alternative.
## See ``toPreserve``.
@ -648,8 +651,6 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] =
elif T is E: result = embed(x)
elif compiles(toPreserveHook(x, E)):
result = toPreserveHook(x, E)
elif T is distinct:
result = toPreserve(x.distinctBase, E)
elif T is enum:
result = toSymbol($x, E)
elif T is seq[byte]:
@ -677,11 +678,13 @@ proc toPreserve*[T](x: T; E = void): Preserve[E] =
result = Preserve[E](kind: pkString, string: x)
elif T is SomeInteger:
result = Preserve[E](kind: pkSignedInteger, int: x.BiggestInt)
elif T is Symbol:
result = Preserve[E](kind: pkSymbol, symbol: x)
elif T is distinct:
result = toPreserve(x.distinctBase, E)
elif T is object:
template fieldToPreserve(key: string; val: typed): Preserve {.used.} =
when x.dot(key).hasCustomPragma(preservesSymbol):
toSymbol(val, E)
elif x.dot(key).hasCustomPragma(preservesLiteral):
when x.dot(key).hasCustomPragma(preservesLiteral):
const lit = parsePreserves(x.dot(key).getCustomPragmaVal(preservesLiteral))
cast[Preserve[E]](lit)
else:
@ -747,10 +750,7 @@ proc toPreserveHook*[A, B](table: Table[A, B]|TableRef[A, B], E: typedesc): Pres
## Hook for preserving ``Table``.
result = initDictionary[E]()
for k, v in table.pairs:
when A is string:
result[toPreserve(k, E)] = toPreserve(v, E)
else:
result[toPreserve(k, E)] = toPreserve(v, E)
result[toPreserve(k, E)] = toPreserve(v, E)
proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
## Inplace version of `preserveTo`. Returns ``true`` on
@ -779,12 +779,10 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
result = true
elif compiles(fromPreserveHook(v, pr)):
result = fromPreserveHook(v, pr)
elif T is distinct:
result = fromPreserve(v.distinctBase, pr)
elif T is enum:
if pr.isSymbol:
try:
v = parseEnum[T](pr.symbol)
v = parseEnum[T](string pr.symbol)
result = true
except ValueError: discard
elif T is bool:
@ -830,9 +828,16 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
v = pr.string
result = true
of pkSymbol:
v = pr.symbol
# loose convertability of Preserves symbol to Nim string
v = string pr.symbol
result = true
else: discard
elif T is Symbol:
if pr.kind == pkSymbol:
v = pr.symbol
result = true
elif T is distinct:
result = fromPreserve(v.distinctBase, pr)
elif T is tuple:
case pr.kind
of pkRecord, pkSequence:
@ -855,12 +860,7 @@ proc fromPreserve*[T, E](v: var T; pr: Preserve[E]): bool =
result = fromPreserve(v[], pr)
elif T is object:
template fieldFromPreserve(key: string; val: typed; pr: Preserve[E]): bool {.used.} =
when v.dot(key).hasCustomPragma(preservesSymbol):
if pr.isSymbol:
fromPreserve(val, pr)
else:
false
elif v.dot(key).hasCustomPragma(preservesLiteral):
when v.dot(key).hasCustomPragma(preservesLiteral):
const lit = parsePreserves(v.dot(key).getCustomPragmaVal(preservesLiteral))
pr == lit
else:
@ -1057,7 +1057,7 @@ proc concat[E](result: var string; pr: Preserve[E]) =
result.add(alphabet[int(b and 0xf)])
result.add('"')
of pkSymbol:
result.add(escapeJsonUnquoted(pr.symbol))
result.add(escapeJsonUnquoted(string pr.symbol))
of pkRecord:
assert(pr.record.len > 0)
result.add('<')

View File

@ -41,7 +41,7 @@ proc fromPreserveHook*[E](js: var JsonNode; prs: Preserve[E]): bool =
of pkString:
js = newJString(prs.string)
of pkSymbol:
case prs.symbol
case prs.symbol.string
of "false":
js = newJBool(false)
of "true":

View File

@ -87,7 +87,7 @@ proc parsePreserves*(text: string): Preserve[void] {.gcsafe.} =
pushStack Value(kind: pkByteString, bytes: cast[seq[byte]](base64.decode($1)))
Preserves.Symbol <- Preserves.Symbol:
pushStack Value(kind: pkSymbol, symbol: $0)
pushStack Value(kind: pkSymbol, symbol: Symbol $0)
Preserves.Embedded <- Preserves.Embedded:
var v = stack.pop.value

View File

@ -88,14 +88,14 @@ proc ident(pat: Pattern; fallback = string): PNode =
proc ident(np: NamedPattern; fallback: string): PNode =
case np.orKind
of NamedPatternKind.`named`:
ident(np.named.name)
ident(string np.named.name)
of NamedPatternKind.`anonymous`:
ident(fallback)
proc ident(np: NamedSimplePattern; fallback: string): PNode =
case np.orKind
of NamedSimplePatternKind.`named`:
ident(np.named.name)
ident(string np.named.name)
of NamedSimplePatternKind.`anonymous`:
ident(fallback)
@ -120,8 +120,8 @@ proc dotExtend(result: var PNode; label: string) =
else: result = nn(nkDotExpr, result, id)
proc ident(`ref`: Ref): PNode =
for m in`ref`.module: dotExtend(result, m)
dotExtend(result, `ref`.name.capitalizeAscii)
for m in`ref`.module: dotExtend(result, string m)
dotExtend(result, `ref`.name.string.capitalizeAscii)
proc deref(scm: Schema; r: Ref): Definition =
assert r.module == @[]
@ -284,7 +284,7 @@ proc typeIdent(atom: AtomKind): PNode =
of AtomKind.`Signedinteger`: ident"int"
of AtomKind.`String`: ident"string"
of AtomKind.`Bytestring`: nn(nkBracketExpr, ident"seq", ident"byte")
of AtomKind.`Symbol`: ident"string"
of AtomKind.`Symbol`: ident"Symbol"
proc typeIdent(scm: Schema; sp: SimplePattern): TypeSpec =
case sp.orKind
@ -362,7 +362,7 @@ proc label(pat: Pattern): string =
proc label(na: NamedPattern): string =
case na.orKind
of NamedPatternKind.`named`:
na.named.name
string na.named.name
of NamedPatternKind.`anonymous`:
"data" # TODO
@ -372,7 +372,7 @@ proc idStr(sp: SimplePattern): string =
of pkString:
result = sp.lit.value.string
of pkSymbol:
result = sp.lit.value.symbol
result = string sp.lit.value.symbol
else: discard
doAssert(result != "", "no idStr for " & $sp)
@ -383,7 +383,7 @@ proc idStr(pat: Pattern): string =
proc idStr(np: NamedPattern): string =
case np.orKind
of NamedPatternKind.`named`:
np.named.name
string np.named.name
of NamedPatternKind.`anonymous`:
np.anonymous.idStr
@ -449,11 +449,6 @@ proc addField(recList: PNode; scm: Schema; known: var TypeTable; sp: SimplePatte
ident"preservesLiteral",
toStrLit(scm, sp))))
recList.add identDef(id, (ident"bool", false))
elif sp.orKind == SimplePatternKind.`atom` and
sp.atom.atomKind == AtomKind.Symbol:
let id = nn(nkPragmaExpr,
id, nn(nkPragma, ident"preservesSymbol"))
recList.add identDef(id, (ident"string", false))
else:
recList.add identDef(id, nimTypeOf(scm, known, sp))
@ -501,14 +496,14 @@ proc addFields(recList: PNode; scm: Schema; known: var TypeTable; pat: Pattern;
proc addFields(recList: PNode; scm: Schema; known: var TypeTable; entries: DictionaryEntries; parentName: string): PNode {.discardable.} =
for key, val in entries.pairs:
doAssert(key.isSymbol)
let label = key.symbol
let label = string key.symbol
addField(recList, scm, known, val.pattern, label)
recList
proc nimTypeOf(scm: Schema; known: var TypeTable; nsp: NamedSimplePattern; name: string): TypeSpec =
case nsp.orKind
of NamedsimplepatternKind.named:
nimTypeOf(scm, known, nsp.named.pattern, nsp.named.name)
nimTypeOf(scm, known, nsp.named.pattern, string nsp.named.name)
of NamedsimplepatternKind.anonymous:
nimTypeOf(scm, known, nsp.anonymous, name)
@ -628,7 +623,7 @@ proc literalToPreserveCall(scm: Schema; pr: Preserve): PNode =
of pkByteString:
return nn(nkCall, ident"parsePreserves", newStrNode(nkTripleStrLit, $pr))
of pkSymbol:
constr($pr.orKind, "symbol", newStrNode(nkStrLit, pr.symbol))
constr($pr.orKind, "symbol", newStrNode(nkStrLit, string pr.symbol))
else:
raise newException(ValueError, "refusing to convert to a literal: " & $pr)
prConstr
@ -647,7 +642,7 @@ proc collectRefImports(imports: PNode; sp: SimplePattern) =
imports.add ident"std/tables"
of SimplePatternKind.Ref:
if sp.`ref`.module != @[]:
imports.add ident(sp.ref.module[0])
imports.add ident(string sp.ref.module[0])
else: discard
proc collectRefImports(imports: PNode; cp: CompoundPattern) =
@ -702,9 +697,9 @@ proc renderNimModule*(scm: Schema): string =
unembeddableType, embeddableType: PNode
for name, def in scm.data.definitions.pairs:
if isLiteral(scm, def):
generateConstProcs(procs, scm, name, def)
generateConstProcs(procs, scm, string name, def)
else:
var name = name
var name = string name
name[0] = name[0].toUpperAscii
var defIdent = parameterize(ident(name), isEmbeddable(scm, def))
if not isSymbolEnum(scm, def) and not isAny(scm, def):
@ -826,7 +821,7 @@ when isMainModule:
preserveTo(pr, Bundle).map do (bundle: Bundle):
useful = true
for modPath, scm in bundle.modules:
let path = joinPath(modPath) & ".nim"
let path = joinPath(cast[seq[string]](modPath)) & ".nim"
writeModule(scm, path)
else:
let

View File

@ -4,9 +4,9 @@ import ../preserves, std/typetraits, std/tables
type
Ref* {.preservesRecord: "ref".} = object
`module`*: ModulePath
`name`* {.preservesSymbol.}: string
`name`*: Symbol
ModulePath* = seq[string]
ModulePath* = seq[Symbol]
Bundle*[E] {.preservesRecord: "bundle".} = ref object
`modules`*: Modules[E]
@ -55,7 +55,7 @@ type
`AtomKind`* {.preservesOr, pure.} = enum
`Boolean`, `Float`, `Double`, `SignedInteger`, `String`, `ByteString`,
`Symbol`
Definitions*[E] = Table[string, Definition[E]]
Definitions*[E] = Table[Symbol, Definition[E]]
DictionaryEntries*[E] = Table[Preserve[E], NamedSimplePattern[E]]
NamedPatternKind* {.pure.} = enum
`named`, `anonymous`
@ -181,7 +181,7 @@ type
Binding*[E] {.preservesRecord: "named".} = ref object
`name`* {.preservesSymbol.}: string
`name`*: Symbol
`pattern`*: SimplePattern[E]
proc `$`*[E](x: Bundle[E] | CompoundPattern[E] | Modules[E] | Definitions[E] |

View File

@ -86,14 +86,14 @@ const parser = peg("Schema", p: ParseState):
p.schema = move ip.schema
Definition <- >id * S * '=' * S * (OrPattern | AndPattern | Pattern):
if p.schema.definitions.hasKey $1:
if p.schema.definitions.hasKey(Symbol $1):
raise newException(ValueError, "duplicate definition of " & $1)
var
node = popStack()
def: Definition
if not fromPreserve(def, node):
raise newException(ValueError, $1 & ": " & $node)
p.schema.definitions[$1] = def
p.schema.definitions[Symbol $1] = def
p.stack.setLen(0)
OrPattern <- ?('/' * S) * AltPattern * +(S * '/' * S * AltPattern):

View File

@ -52,11 +52,11 @@ proc fromPreserveHook*[E](xn: var XmlNode; pr: Preserve[E]): bool =
result = false
break
if result:
xn = newXmlTree(pr[0].symbol, children, attrs)
xn = newXmlTree(string pr[0].symbol, children, attrs)
of pkRecord:
if pr.len == 1 and pr[0].isString and pr.label.isSymbol:
result = true
case pr.label.symbol:
case pr.label.symbol.string:
of "verbatim":
xn = newVerbatimText(pr[0].string)
of "comment":