|
|
|
@ -1,4 +1,4 @@
|
|
|
|
|
# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
|
|
|
|
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
|
|
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
|
|
|
|
|
|
import std/[options, sequtils, tables, typetraits]
|
|
|
|
@ -10,390 +10,254 @@ from ./actors import Ref
|
|
|
|
|
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
AnyAtom* = dataspacePatterns.AnyAtom[Ref]
|
|
|
|
|
DBind* = dataspacePatterns.DBind[Ref]
|
|
|
|
|
DCompound* = dataspacePatterns.DCompound[Ref]
|
|
|
|
|
DCompoundArr* = dataspacePatterns.DCompoundArr[Ref]
|
|
|
|
|
DCompoundDict* = dataspacePatterns.DCompoundDict[Ref]
|
|
|
|
|
DCompoundRec* = dataspacePatterns.DCompoundRec[Ref]
|
|
|
|
|
DLit* = dataspacePatterns.DLit[Ref]
|
|
|
|
|
AnyAtom = dataspacePatterns.AnyAtom[Ref]
|
|
|
|
|
DBind = dataspacePatterns.DBind[Ref]
|
|
|
|
|
DCompound = dataspacePatterns.DCompound[Ref]
|
|
|
|
|
DCompoundArr = dataspacePatterns.DCompoundArr[Ref]
|
|
|
|
|
DCompoundDict = dataspacePatterns.DCompoundDict[Ref]
|
|
|
|
|
DCompoundRec = dataspacePatterns.DCompoundRec[Ref]
|
|
|
|
|
DLit = dataspacePatterns.DLit[Ref]
|
|
|
|
|
Pattern* = dataspacePatterns.Pattern[Ref]
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DBind): Pattern =
|
|
|
|
|
proc toPattern(d: sink DBind): Pattern =
|
|
|
|
|
Pattern(orKind: PatternKind.DBind, dbind: d)
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DLit): Pattern =
|
|
|
|
|
proc toPattern(d: sink DLit): Pattern =
|
|
|
|
|
Pattern(orKind: PatternKind.DLit, dlit: d)
|
|
|
|
|
|
|
|
|
|
proc `?`*(aa: sink AnyAtom): Pattern =
|
|
|
|
|
?DLit(value: aa)
|
|
|
|
|
proc toPattern(aa: sink AnyAtom): Pattern =
|
|
|
|
|
DLit(value: aa).toPattern
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DCompound): Pattern =
|
|
|
|
|
proc toPattern(d: sink DCompound): Pattern =
|
|
|
|
|
Pattern(orKind: PatternKind.DCompound, dcompound: d)
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DCompoundRec): Pattern =
|
|
|
|
|
?DCompound(orKind: DCompoundKind.rec, rec: d)
|
|
|
|
|
proc toPattern(d: sink DCompoundRec): Pattern =
|
|
|
|
|
DCompound(orKind: DCompoundKind.rec, rec: d).toPattern
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DCompoundArr): Pattern =
|
|
|
|
|
?DCompound(orKind: DCompoundKind.arr, arr: d)
|
|
|
|
|
proc toPattern(d: sink DCompoundArr): Pattern =
|
|
|
|
|
DCompound(orKind: DCompoundKind.arr, arr: d).toPattern
|
|
|
|
|
|
|
|
|
|
proc `?`*(d: sink DCompoundDict): Pattern =
|
|
|
|
|
?DCompound(orKind: DCompoundKind.dict, dict: d)
|
|
|
|
|
proc toPattern(d: sink DCompoundDict): Pattern =
|
|
|
|
|
DCompound(orKind: DCompoundKind.dict, dict: d).toPattern
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: bool): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`bool`, bool: x)
|
|
|
|
|
proc drop*(): Pattern {.inline.} = Pattern(orKind: PatternKind.DDiscard)
|
|
|
|
|
## Create a pattern to match any value without capture.
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: float32): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`float`, float: x)
|
|
|
|
|
proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
|
|
|
|
|
## Create a pattern to capture any value.
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: float64): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`double`, double: x)
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: int): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`int`, int: x)
|
|
|
|
|
|
|
|
|
|
proc `?`*(s: sink string): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`string`, string: s)
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: sink seq[byte]): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`bytes`, bytes: x)
|
|
|
|
|
|
|
|
|
|
proc `?`*(x: sink Symbol): Pattern =
|
|
|
|
|
?AnyAtom(orKind: AnyAtomKind.`symbol`, symbol: x)
|
|
|
|
|
|
|
|
|
|
proc `?`*[T](pr: Preserve[T]): Pattern =
|
|
|
|
|
proc grab*[T](pr: Preserve[T]): Pattern =
|
|
|
|
|
## Convert a `Preserve` value to a `Pattern`.
|
|
|
|
|
runnableExamples:
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
import preserves
|
|
|
|
|
check:
|
|
|
|
|
$(grab parsePreserves"""<foo "bar" #"00" [0 1 2.0] {maybe: #t} <_>>""") ==
|
|
|
|
|
"""<rec foo [<lit "bar"> <lit #"00"> <arr [<lit 0> <lit 1> <lit 2.0>]> <dict {maybe: <lit #t>}> <_>]>"""
|
|
|
|
|
|
|
|
|
|
assert not pr.embedded
|
|
|
|
|
case pr.kind
|
|
|
|
|
of pkBoolean: ?pr.bool
|
|
|
|
|
of pkFloat: ?pr.float
|
|
|
|
|
of pkDouble: ?pr.double
|
|
|
|
|
of pkSignedInteger: ?(int pr.int) # TODO: overflow!
|
|
|
|
|
of pkString: ?pr.string
|
|
|
|
|
of pkByteString: ?pr.bytes
|
|
|
|
|
of pkSymbol: ?pr.symbol
|
|
|
|
|
of pkBoolean:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`bool`, bool: pr.bool).toPattern
|
|
|
|
|
of pkFloat:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`float`, float: pr.float).toPattern
|
|
|
|
|
of pkDouble:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`double`, double: pr.double).toPattern
|
|
|
|
|
of pkSignedInteger:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`int`, int: pr.int).toPattern
|
|
|
|
|
of pkString:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`string`, string: pr.string).toPattern
|
|
|
|
|
of pkByteString:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`bytes`, bytes: pr.bytes).toPattern
|
|
|
|
|
of pkSymbol:
|
|
|
|
|
AnyAtom(orKind: AnyAtomKind.`symbol`, symbol: pr.symbol).toPattern
|
|
|
|
|
of pkRecord:
|
|
|
|
|
?DCompoundRec(
|
|
|
|
|
label: cast[Preserve[Ref]](pr.label), # TODO: don't cast like this
|
|
|
|
|
fields: map[Preserve[T], Pattern](pr.fields, `?`))
|
|
|
|
|
if (pr.isRecord("_") and pr.arity == 0) or (pr.isRecord("bind") and pr.arity == 1):
|
|
|
|
|
drop()
|
|
|
|
|
else:
|
|
|
|
|
DCompoundRec(
|
|
|
|
|
label: cast[Preserve[Ref]](pr.label), # TODO: don't cast like this
|
|
|
|
|
fields: map[Preserve[T], Pattern](pr.fields, grab)).toPattern
|
|
|
|
|
of pkSequence:
|
|
|
|
|
?DCompoundArr(items: map(pr.sequence, `?`))
|
|
|
|
|
DCompoundArr(items: map(pr.sequence, grab)).toPattern
|
|
|
|
|
of pkSet: raise newException(
|
|
|
|
|
ValueError, "cannot construct a pattern over a set literal")
|
|
|
|
|
of pkDictionary:
|
|
|
|
|
var dict = DCompoundDict()
|
|
|
|
|
for key, val in pr.pairs: dict.entries[cast[Preserve[Ref]](key)] = ?val
|
|
|
|
|
?dict
|
|
|
|
|
for key, val in pr.pairs: dict.entries[cast[Preserve[Ref]](key)] = grab val
|
|
|
|
|
dict.toPattern
|
|
|
|
|
of pkEmbedded:
|
|
|
|
|
raiseAssert "cannot construct a pattern over a embedded literal"
|
|
|
|
|
# TODO: can patterns be constructed over embedded literals?
|
|
|
|
|
drop()
|
|
|
|
|
|
|
|
|
|
proc drop*(): Pattern = Pattern(orKind: PatternKind.DDiscard)
|
|
|
|
|
## Create a pattern to match any value without capture.
|
|
|
|
|
|
|
|
|
|
proc grab*(): Pattern = ?DBind(pattern: drop())
|
|
|
|
|
## Create a pattern to capture any value.
|
|
|
|
|
|
|
|
|
|
proc `?`*[A, B](table: TableRef[A,B]): Pattern =
|
|
|
|
|
raiseAssert "not implemented"
|
|
|
|
|
|
|
|
|
|
#[
|
|
|
|
|
proc `?`*(pat: sink Pattern): Pattern =
|
|
|
|
|
## Construct a `Pattern` that matches a `Pattern`.
|
|
|
|
|
case pat.orKind
|
|
|
|
|
of PatternKind.DDiscard: result = pat
|
|
|
|
|
of PatternKind.DBind: result = drop()
|
|
|
|
|
of PatternKind.DLit: result = ?pat.toPreserve(Ref)
|
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
|
case pat.dcompound.orKind:
|
|
|
|
|
of DCompoundKind.rec:
|
|
|
|
|
var fields = move pat.dcompound.rec.fields
|
|
|
|
|
for f in fields.mitems: f = ?(move f)
|
|
|
|
|
result = ?pat.toPreserve(Ref)
|
|
|
|
|
# echo "need to stuff fields into ", result, " at ", result.dcompound.rec.fields[1].dcompound.arr.items
|
|
|
|
|
result.dcompound.rec.fields[1].dcompound.arr.items = fields
|
|
|
|
|
#[
|
|
|
|
|
of DCompoundKind.arr
|
|
|
|
|
pat.dcompound.arr
|
|
|
|
|
of DCompoundKind.dict
|
|
|
|
|
pat.dcompound.dict
|
|
|
|
|
]#
|
|
|
|
|
else: raiseAssert "`?` not implemented for pattern " & $pat
|
|
|
|
|
]#
|
|
|
|
|
|
|
|
|
|
#[
|
|
|
|
|
proc `?`*(patterns: openArray[Pattern]): Pattern =
|
|
|
|
|
raiseAssert "got it in the right place"
|
|
|
|
|
#[
|
|
|
|
|
result = DCompoundArr()
|
|
|
|
|
for e in val:
|
|
|
|
|
if i > arr.items.high: arr.items.setLen(succ i)
|
|
|
|
|
arr.items[i] = pat
|
|
|
|
|
for pat in arr.items.mitems:
|
|
|
|
|
if pat.isNil: pat = drop()
|
|
|
|
|
result = ?DCompound(
|
|
|
|
|
orKind: DCompoundKind.arr,
|
|
|
|
|
arr: arr)
|
|
|
|
|
]#
|
|
|
|
|
]#
|
|
|
|
|
|
|
|
|
|
proc `?`*[T](val: sink T): Pattern =
|
|
|
|
|
proc grab*[T](val: T): Pattern =
|
|
|
|
|
## Construct a `Pattern` from value of type `T`.
|
|
|
|
|
#[
|
|
|
|
|
when T is Pattern:
|
|
|
|
|
case val.orKind
|
|
|
|
|
of PatternKind.DDiscard, PatternKind.DBind: result = val
|
|
|
|
|
of PatternKind.DLit: result = ?val.toPreserve(Ref)
|
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
|
case val.dcompound.orKind:
|
|
|
|
|
of DCompoundKind.rec:
|
|
|
|
|
var fields = move val.dcompound.rec.fields
|
|
|
|
|
for f in fields.mitems: f = ?(move f)
|
|
|
|
|
result = ?val.toPreserve(Ref)
|
|
|
|
|
# echo "need to stuff fields into ", result, " at ", result.dcompound.rec.fields[1].dcompound.arr.items
|
|
|
|
|
result.dcompound.rec.fields[1].dcompound.arr.items = fields
|
|
|
|
|
#[
|
|
|
|
|
of DCompoundKind.arr
|
|
|
|
|
val.dcompound.arr
|
|
|
|
|
of DCompoundKind.dict
|
|
|
|
|
val.dcompound.dict
|
|
|
|
|
]#
|
|
|
|
|
else: raiseAssert "`?` not implemented for pattern " & $val
|
|
|
|
|
]#
|
|
|
|
|
when T is Ref:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.embedded,
|
|
|
|
|
embedded: embed(val))))
|
|
|
|
|
elif T is ptr | ref:
|
|
|
|
|
if system.`==`(val, nil): result = ?(Symbol "null")
|
|
|
|
|
else: result = ?(val[])
|
|
|
|
|
elif T is bool:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.bool,
|
|
|
|
|
bool: val)))
|
|
|
|
|
elif T is float32:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.float,
|
|
|
|
|
float: val)))
|
|
|
|
|
elif T is float64:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.double,
|
|
|
|
|
double: val)))
|
|
|
|
|
elif T is SomeInteger:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.int,
|
|
|
|
|
int: AnyAtomInt val)))
|
|
|
|
|
elif T is string:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.string,
|
|
|
|
|
string: val)))
|
|
|
|
|
elif T is seq[byte]:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.bytes,
|
|
|
|
|
bytes: val)))
|
|
|
|
|
elif T is array | seq:
|
|
|
|
|
let arr = DCompoundArr(items: newSeq[Pattern](val.len))
|
|
|
|
|
for i, e in val.mitems: arr.items[i] = ?(move e)
|
|
|
|
|
result = ?arr
|
|
|
|
|
elif T is Symbol:
|
|
|
|
|
result = Pattern(orKind: PatternKind.DLit, dlit: DLit(
|
|
|
|
|
value: AnyAtom(
|
|
|
|
|
orKind: AnyAtomKind.symbol,
|
|
|
|
|
symbol: Symbol $val)))
|
|
|
|
|
elif T.hasPreservesRecordPragma:
|
|
|
|
|
var
|
|
|
|
|
label = T.recordLabel.tosymbol(Ref)
|
|
|
|
|
fields = newSeq[Pattern]()
|
|
|
|
|
for f in fields(val):
|
|
|
|
|
fields.add ?f
|
|
|
|
|
result = ?DCompound(
|
|
|
|
|
orKind: DCompoundKind.rec,
|
|
|
|
|
rec: DCompoundRec(
|
|
|
|
|
label: label, fields: fields))
|
|
|
|
|
else:
|
|
|
|
|
?(toPreserve(val, Ref))
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import syndicate/protocols/simpleChatProtocol
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
check:
|
|
|
|
|
$grab(true) == "<lit #t>"
|
|
|
|
|
$grab(3.14) == "<lit 3.14>"
|
|
|
|
|
$grab([0, 1, 2, 3]) == "<arr [<lit 0> <lit 1> <lit 2> <lit 3>]>"
|
|
|
|
|
$grab(Present(username: "Carol")) == """<rec Present [<lit "Carol">]>"""
|
|
|
|
|
grab (toPreserve(val, Ref))
|
|
|
|
|
|
|
|
|
|
proc `?`*(T: static typedesc): Pattern =
|
|
|
|
|
## Derive a `Pattern` from type `T`.
|
|
|
|
|
proc patternOfType(typ: static typedesc; `bind`: static bool): Pattern =
|
|
|
|
|
when typ is ref:
|
|
|
|
|
patternOfType(pointerBase(typ), `bind`)
|
|
|
|
|
elif typ.hasPreservesRecordPragma:
|
|
|
|
|
var rec = DCompoundRec(label: typ.recordLabel.tosymbol(Ref))
|
|
|
|
|
for _, f in fieldPairs(default typ):
|
|
|
|
|
add(rec.fields, patternOfType(typeof f, `bind`))
|
|
|
|
|
result = rec.toPattern
|
|
|
|
|
elif typ.hasPreservesDictionaryPragma:
|
|
|
|
|
var dict = DCompoundDict()
|
|
|
|
|
for key, val in fieldPairs(default typ):
|
|
|
|
|
dict.entries[toSymbol(key, Ref)] = patternOfType(typeof val, `bind`)
|
|
|
|
|
dict.toPattern
|
|
|
|
|
elif typ is tuple:
|
|
|
|
|
var arr = DCompoundArr()
|
|
|
|
|
for _, f in fieldPairs(default typ):
|
|
|
|
|
add(arr.items, patternOfType(typeof f, `bind`))
|
|
|
|
|
arr.toPattern
|
|
|
|
|
elif typ is array:
|
|
|
|
|
var arr = DCompoundArr()
|
|
|
|
|
arr.items.setLen(len(typ))
|
|
|
|
|
for e in arr.items.mitems: e = grab()
|
|
|
|
|
arr.toPattern
|
|
|
|
|
else:
|
|
|
|
|
if `bind`: grab()
|
|
|
|
|
else: drop()
|
|
|
|
|
|
|
|
|
|
proc grabType*(typ: static typedesc): Pattern =
|
|
|
|
|
## Derive a `Pattern` from type `typ`.
|
|
|
|
|
## This works for `tuple` and `object` types but in the
|
|
|
|
|
## general case will return a wildcard binding.
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import preserves
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
check:
|
|
|
|
|
$grabType(array[3, int]) ==
|
|
|
|
|
"""<arr [<bind <_>> <bind <_>> <bind <_>>]>"""
|
|
|
|
|
type
|
|
|
|
|
Point = tuple[x: int; y: int]
|
|
|
|
|
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
|
|
|
|
|
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
|
|
|
|
|
check:
|
|
|
|
|
$(grabType Point) ==
|
|
|
|
|
"<arr [<bind <_>> <bind <_>>]>"
|
|
|
|
|
$(grabType Rect) ==
|
|
|
|
|
"<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
|
|
|
|
|
$(grabType ColoredRect) ==
|
|
|
|
|
"<dict {color: <bind <_>> rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
|
|
|
|
|
patternOfType(typ, true)
|
|
|
|
|
|
|
|
|
|
type Point = tuple[x: int; y: int]
|
|
|
|
|
assert $(?Point) == "<arr [<bind <_>> <bind <_>>]>"
|
|
|
|
|
|
|
|
|
|
type Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
|
|
|
|
|
assert $(?Rect) == "<rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>"
|
|
|
|
|
|
|
|
|
|
type ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
|
|
|
|
|
assert $(?ColoredRect) == "<dict {color: <bind <_>>, rect: <rec rect [<arr [<bind <_>> <bind <_>>]> <arr [<bind <_>> <bind <_>>]>]>}>"
|
|
|
|
|
when T is Pattern:
|
|
|
|
|
raiseAssert "? for pattern"
|
|
|
|
|
elif T is ref:
|
|
|
|
|
?pointerBase(T)
|
|
|
|
|
elif T is array:
|
|
|
|
|
var arr = DCompoundArr(items: newSeq[Pattern](len(T)))
|
|
|
|
|
for p in arr.items.mitems: p = grab()
|
|
|
|
|
result = ?arr
|
|
|
|
|
elif T.hasPreservesRecordPragma:
|
|
|
|
|
var
|
|
|
|
|
label = T.recordLabel.tosymbol(Ref)
|
|
|
|
|
fields = newSeq[Pattern]()
|
|
|
|
|
for key, val in fieldPairs(default T):
|
|
|
|
|
when typeOf(val) is Pattern:
|
|
|
|
|
fields.add drop()
|
|
|
|
|
else:
|
|
|
|
|
fields.add ?(typeOf val)
|
|
|
|
|
result = ?DCompound(
|
|
|
|
|
orKind: DCompoundKind.rec,
|
|
|
|
|
rec: DCompoundRec(
|
|
|
|
|
label: label, fields: fields))
|
|
|
|
|
elif T.hasPreservesDictionaryPragma:
|
|
|
|
|
var dict = DCompoundDict()
|
|
|
|
|
for key, val in fieldPairs(default T):
|
|
|
|
|
dict.entries[key.toSymbol(Ref)] = ?(typeOf val)
|
|
|
|
|
?DCompound(
|
|
|
|
|
orKind: DCompoundKind.dict,
|
|
|
|
|
dict: dict)
|
|
|
|
|
elif T.hasPreservesTuplePragma or T is tuple:
|
|
|
|
|
raiseAssert "got a tuple"
|
|
|
|
|
var arr = DCompoundArr()
|
|
|
|
|
for key, val in fieldPairs(default T):
|
|
|
|
|
arr.items.add ?(typeOf val)
|
|
|
|
|
?DCompound(
|
|
|
|
|
orKind: DCompoundKind.arr,
|
|
|
|
|
arr: arr)
|
|
|
|
|
else:
|
|
|
|
|
grab() # otherwise an abritrary capture
|
|
|
|
|
proc dropType*(typ: static typedesc): Pattern =
|
|
|
|
|
## Derive a `Pattern` from type `typ` without any bindings.
|
|
|
|
|
patternOfType(typ, false)
|
|
|
|
|
|
|
|
|
|
proc fieldCount(T: typedesc): int =
|
|
|
|
|
for _, _ in fieldPairs(default T):
|
|
|
|
|
inc result
|
|
|
|
|
|
|
|
|
|
proc `?`*(T: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
|
|
|
|
|
## Construct a `Pattern` from type `T` that selectively captures fields.
|
|
|
|
|
proc match(bindings: sink openArray[(int, Pattern)]; i: int; pat: var Pattern): bool =
|
|
|
|
|
for (j, b) in bindings:
|
|
|
|
|
if i == j:
|
|
|
|
|
pat = b
|
|
|
|
|
return true
|
|
|
|
|
|
|
|
|
|
proc grab*(typ: static typedesc; bindings: sink openArray[(int, Pattern)]): Pattern =
|
|
|
|
|
## Construct a `Pattern` from type `typ` that selectively captures fields.
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import preserves
|
|
|
|
|
|
|
|
|
|
type Point = tuple[x: int; y: int; z: int]
|
|
|
|
|
assert $(Point ? { 2: grab() }) == "<arr [<_> <_> <bind <_>>]>"
|
|
|
|
|
|
|
|
|
|
when T is ref:
|
|
|
|
|
`?`(pointerBase(T), bindings)
|
|
|
|
|
elif T.hasPreservesRecordPragma:
|
|
|
|
|
var
|
|
|
|
|
label = T.recordLabel.tosymbol(Ref)
|
|
|
|
|
fields = newSeq[Pattern](fieldCount T)
|
|
|
|
|
for (i, pat) in bindings:
|
|
|
|
|
fields[i] = pat
|
|
|
|
|
for pat in fields.mitems:
|
|
|
|
|
if pat.isNil: pat = drop()
|
|
|
|
|
result = ?DCompound(
|
|
|
|
|
orKind: DCompoundKind.rec,
|
|
|
|
|
rec: DCompoundRec(
|
|
|
|
|
label: label, fields: fields))
|
|
|
|
|
elif T is tuple:
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
type
|
|
|
|
|
Point = tuple[x: int; y: int]
|
|
|
|
|
Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point]
|
|
|
|
|
ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect]
|
|
|
|
|
check:
|
|
|
|
|
$grab(Point, { 1: grab() }) == "<arr [<_> <bind <_>>]>"
|
|
|
|
|
$grab(Rect, { 0: grab() }) == "<rec rect [<bind <_>> <arr [<_> <_>]>]>"
|
|
|
|
|
when typ is ref:
|
|
|
|
|
grab(pointerBase(typ), bindings)
|
|
|
|
|
elif typ.hasPreservesRecordPragma:
|
|
|
|
|
var rec = DCompoundRec(label: typ.recordLabel.tosymbol(Ref))
|
|
|
|
|
rec.fields.setLen(fieldCount typ)
|
|
|
|
|
var i: int
|
|
|
|
|
for _, f in fieldPairs(default typ):
|
|
|
|
|
if not match(bindings, i, rec.fields[i]):
|
|
|
|
|
rec.fields[i] = dropType(typeof f)
|
|
|
|
|
inc i
|
|
|
|
|
result = rec.toPattern
|
|
|
|
|
elif typ is tuple:
|
|
|
|
|
var arr = DCompoundArr()
|
|
|
|
|
for (i, pat) in bindings:
|
|
|
|
|
if i > arr.items.high: arr.items.setLen(succ i)
|
|
|
|
|
arr.items[i] = pat
|
|
|
|
|
for pat in arr.items.mitems:
|
|
|
|
|
if pat.isNil: pat = drop()
|
|
|
|
|
result = ?DCompound(
|
|
|
|
|
orKind: DCompoundKind.arr,
|
|
|
|
|
arr: arr)
|
|
|
|
|
arr.items.setLen(fieldCount typ)
|
|
|
|
|
var i: int
|
|
|
|
|
for _, f in fieldPairs(default typ):
|
|
|
|
|
if not match(bindings, i, arr.items[i]):
|
|
|
|
|
arr.items[i] = dropType(typeof f)
|
|
|
|
|
inc i
|
|
|
|
|
result = arr.toPattern
|
|
|
|
|
else:
|
|
|
|
|
raiseAssert("no preserves pragma on " & $T)
|
|
|
|
|
{.error: "grab with bindings not implemented for " & $typ.}
|
|
|
|
|
|
|
|
|
|
#[
|
|
|
|
|
proc `?`*(pat: sink Pattern; bindings: sink openArray[(int, Pattern)]): Pattern =
|
|
|
|
|
## Construct a `Pattern` from `pat` with overrides from `bindings`.
|
|
|
|
|
result = pat
|
|
|
|
|
assert not result.isNil
|
|
|
|
|
case result.orKind
|
|
|
|
|
of PatternKind.DDiscard, PatternKind.DBind: discard
|
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
|
case result.dcompound.orKind
|
|
|
|
|
|
|
|
|
|
of `rec`:
|
|
|
|
|
echo "need to override record fields ", result.dcompound.rec.fields, " with ", bindings
|
|
|
|
|
var foo = result.dcompound.rec.fields[1] ? bindings
|
|
|
|
|
echo "recursing into fields returned ", foo
|
|
|
|
|
result.dcompound.rec.fields[1] = foo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
of `arr`:
|
|
|
|
|
echo "need to override array items ", result.dcompound.arr.items, " with ", bindings
|
|
|
|
|
for i in result.dcompound.arr.items.mitems:
|
|
|
|
|
i = drop()
|
|
|
|
|
for (i, pat) in bindings:
|
|
|
|
|
result.dcompound.arr.items[i] = pat
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raiseAssert $result.dcompound.orKind
|
|
|
|
|
else:
|
|
|
|
|
raiseAssert $result.orKind
|
|
|
|
|
]#
|
|
|
|
|
|
|
|
|
|
proc `??`*(pat: sink Pattern; bindings: sink openArray[(int, Pattern)]): Pattern =
|
|
|
|
|
## Create a `Pattern` that matches or captures from a `Pattern` over `pat`.
|
|
|
|
|
proc grabLit*(): Pattern =
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import preserves
|
|
|
|
|
import syndicate/protocols/simpleChatProtocol
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
check:
|
|
|
|
|
$grabLit() == """<rec lit [<bind <_>>]>"""
|
|
|
|
|
grabType(dataspacePatterns.DLit[void])
|
|
|
|
|
|
|
|
|
|
type Point* {.preservesRecord: "point".} = object
|
|
|
|
|
x, y: int
|
|
|
|
|
|
|
|
|
|
assert $(?Point) == "<rec point [<bind <_>> <bind <_>>]>"
|
|
|
|
|
# Capture two values from a value of `Point`.
|
|
|
|
|
|
|
|
|
|
assert $(?Point ?? { 0: ?DLit }) == "<rec rec [<lit point> <arr [<rec lit [<bind <_>>]> <_>]>]>"
|
|
|
|
|
# Match a pattern of `Point` with a literal value set in the first field and capture that value.
|
|
|
|
|
|
|
|
|
|
assert $(?tuple[x: int, y: int] ?? { 1: ?DLit }) == "<rec arr [<arr [<_> <rec lit [<bind <_>>]>]>]>"
|
|
|
|
|
# Match a pattern over a tuple with a literal value set in the second field and capture that value.
|
|
|
|
|
|
|
|
|
|
case pat.orKind
|
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
|
case pat.dcompound.orKind:
|
|
|
|
|
|
|
|
|
|
of DCompoundKind.rec:
|
|
|
|
|
let fieldsLen = pat.dcompound.rec.fields.len
|
|
|
|
|
pat.dcompound.rec.fields.setLen 0
|
|
|
|
|
result = ?pat
|
|
|
|
|
result.dcompound.rec.fields[1].dcompound.arr.items.setLen fieldsLen
|
|
|
|
|
for (i, p) in bindings:
|
|
|
|
|
result.dcompound.rec.fields[1].dcompound.arr.items[i] = p
|
|
|
|
|
for e in result.dcompound.rec.fields[1].dcompound.arr.items.mitems:
|
|
|
|
|
if e.isNil: e = drop()
|
|
|
|
|
|
|
|
|
|
of DCompoundKind.arr:
|
|
|
|
|
let itemsLen = pat.dcompound.arr.items.len
|
|
|
|
|
pat.dcompound.arr.items.setLen 0
|
|
|
|
|
result = ?pat
|
|
|
|
|
result.dcompound.rec.fields[0].dcompound.arr.items.setLen itemsLen
|
|
|
|
|
for (i, p) in bindings:
|
|
|
|
|
result.dcompound.rec.fields[0].dcompound.arr.items[i] = p
|
|
|
|
|
for e in result.dcompound.rec.fields[0].dcompound.arr.items.mitems:
|
|
|
|
|
if e.isNil: e = drop()
|
|
|
|
|
|
|
|
|
|
of DCompoundKind.dict:
|
|
|
|
|
let keys = pat.dcompound.dict.entries.keys.toSeq
|
|
|
|
|
clear pat.dcompound.dict.entries
|
|
|
|
|
result = ?pat
|
|
|
|
|
raiseAssert "?? not implemented for dictionaries"
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
raiseAssert "cannot override " & $pat
|
|
|
|
|
proc inject*(pat: Pattern; bindings: openArray[(int, Pattern)]): Pattern =
|
|
|
|
|
## Construct a `Pattern` from `pat` with injected overrides from `bindings`.
|
|
|
|
|
## Injects are made at offsets indexed by the discard (`<_>`) patterns in `pat`.
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import syndicate/protocols/simpleChatProtocol
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
check:
|
|
|
|
|
$inject(dropType(Says), {1: grabLit()}) == """<rec Says [<_> <rec lit [<bind <_>>]>]>"""
|
|
|
|
|
proc inject(pat: Pattern; bindings: openArray[(int, Pattern)]; offset: var int): Pattern =
|
|
|
|
|
case pat.orKind
|
|
|
|
|
of PatternKind.DDiscard:
|
|
|
|
|
var replaced = false
|
|
|
|
|
for (i, injection) in bindings:
|
|
|
|
|
if i == offset:
|
|
|
|
|
result = injection
|
|
|
|
|
replaced = true
|
|
|
|
|
break
|
|
|
|
|
if not replaced:
|
|
|
|
|
result = drop()
|
|
|
|
|
inc offset
|
|
|
|
|
of PatternKind.DBind, PatternKind.DLit:
|
|
|
|
|
result = pat
|
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
|
result = pat
|
|
|
|
|
case pat.dcompound.orKind
|
|
|
|
|
of DCompoundKind.rec:
|
|
|
|
|
var fields = mapIt(pat.dcompound.rec.fields):
|
|
|
|
|
inject(it, bindings, offset)
|
|
|
|
|
result = DCompoundRec(label: result.dcompound.rec.label, fields: fields).toPattern
|
|
|
|
|
of DCompoundKind.arr:
|
|
|
|
|
var items = mapIt(pat.dcompound.arr.items):
|
|
|
|
|
inject(it, bindings, offset)
|
|
|
|
|
result = DCompoundArr(items: items).toPattern
|
|
|
|
|
of DCompoundKind.dict:
|
|
|
|
|
var dict = DCompoundDict()
|
|
|
|
|
for key, val in pat.dcompound.dict.entries:
|
|
|
|
|
dict.entries[key] = inject(val, bindings, offset)
|
|
|
|
|
result = toPattern(dict)
|
|
|
|
|
var offset = 0
|
|
|
|
|
inject(pat, bindings, offset)
|
|
|
|
|
|
|
|
|
|
proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern =
|
|
|
|
|
?DCompoundRec(label: label, fields: fields.toSeq)
|
|
|
|
|
runnableExamples:
|
|
|
|
|
from std/unittest import check
|
|
|
|
|
import syndicate/actors, preserves
|
|
|
|
|
check:
|
|
|
|
|
$recordPattern("Says".toSymbol(Ref), grab(), grab()) ==
|
|
|
|
|
"""<rec Says [<bind <_>> <bind <_>>]>"""
|
|
|
|
|
DCompoundRec(label: label, fields: fields.toSeq).toPattern
|
|
|
|
|
|
|
|
|
|
type
|
|
|
|
|
Value = Preserve[Ref]
|
|
|
|
@ -445,6 +309,17 @@ func projectPaths*(v: Value; paths: seq[Path]): seq[Value] =
|
|
|
|
|
if vv.isSome: result[i] = get(vv)
|
|
|
|
|
|
|
|
|
|
func matches*(pat: Pattern; pr: Value): bool =
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import preserves
|
|
|
|
|
from syndicate/actors import Ref
|
|
|
|
|
from std/unittest import check, checkpoint
|
|
|
|
|
|
|
|
|
|
import syndicate/protocols/simpleChatProtocol
|
|
|
|
|
|
|
|
|
|
let pat = grabType(Says)
|
|
|
|
|
let val = parsePreserves("""<Says "Mike" "Hello world!">""", Ref)
|
|
|
|
|
check matches(pat, val)
|
|
|
|
|
|
|
|
|
|
let analysis = analyse(pat)
|
|
|
|
|
assert analysis.constPaths.len == analysis.constValues.len
|
|
|
|
|
for i, path in analysis.constPaths:
|
|
|
|
@ -456,6 +331,24 @@ func matches*(pat: Pattern; pr: Value): bool =
|
|
|
|
|
true
|
|
|
|
|
|
|
|
|
|
func capture*(pat: Pattern; pr: Value): seq[Value] =
|
|
|
|
|
runnableExamples:
|
|
|
|
|
import preserves
|
|
|
|
|
from syndicate/actors import Ref
|
|
|
|
|
from std/unittest import check, checkpoint
|
|
|
|
|
from syndicate/protocols/simpleChatProtocol import Says
|
|
|
|
|
from syndicate/protocols/dataspace import Observe
|
|
|
|
|
|
|
|
|
|
type Observe = dataspace.Observe[Ref]
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
pat = grab(Says, {0: grab("Mike"), 1: grab()})
|
|
|
|
|
obs = Observe(pattern: pat)
|
|
|
|
|
obsPat = inject(grab(Observe(pattern: grabType(Says))), {0: grabLit()})
|
|
|
|
|
obsVal = toPreserve(obs, Ref)
|
|
|
|
|
checkpoint "observer pattern: " & $obsPat
|
|
|
|
|
checkpoint "value: " & $obsVal
|
|
|
|
|
check capture(obsPat, obsVal) == @[toPreserve("Mike", Ref)]
|
|
|
|
|
|
|
|
|
|
let analysis = analyse(pat)
|
|
|
|
|
assert analysis.constPaths.len == analysis.constValues.len
|
|
|
|
|
for i, path in analysis.constPaths:
|
|
|
|
@ -467,11 +360,10 @@ func capture*(pat: Pattern; pr: Value): seq[Value] =
|
|
|
|
|
if v.isNone: return @[]
|
|
|
|
|
result.add(get v)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
|
let txt = readAll stdin
|
|
|
|
|
if txt != "":
|
|
|
|
|
let
|
|
|
|
|
v = parsePreserves(txt)
|
|
|
|
|
pat = ?v
|
|
|
|
|
pat = grab v
|
|
|
|
|
stdout.writeLine(pat)
|
|
|
|
|