2023-03-23 19:06:44 +00:00
|
|
|
# SPDX-FileCopyrightText: ☭ Emery Hemingway
|
2021-10-28 17:43:20 +00:00
|
|
|
# SPDX-License-Identifier: Unlicense
|
|
|
|
|
2022-10-27 00:24:10 +00:00
|
|
|
import std/[options, sequtils, tables, typetraits]
|
2021-10-28 17:43:20 +00:00
|
|
|
|
|
|
|
import preserves
|
2021-11-02 11:58:51 +00:00
|
|
|
import ./protocols/dataspacePatterns
|
2021-10-28 17:43:20 +00:00
|
|
|
from ./actors import Ref
|
|
|
|
|
2022-08-29 19:47:12 +00:00
|
|
|
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
|
2021-10-29 12:06:00 +00:00
|
|
|
|
2021-10-28 17:43:20 +00:00
|
|
|
type
|
2023-03-23 19:06:44 +00:00
|
|
|
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]
|
2021-10-28 17:43:20 +00:00
|
|
|
Pattern* = dataspacePatterns.Pattern[Ref]
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DBind): Pattern =
|
2021-10-29 12:06:00 +00:00
|
|
|
Pattern(orKind: PatternKind.DBind, dbind: d)
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DLit): Pattern =
|
2021-10-29 12:06:00 +00:00
|
|
|
Pattern(orKind: PatternKind.DLit, dlit: d)
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(aa: sink AnyAtom): Pattern =
|
|
|
|
DLit(value: aa).toPattern
|
2022-06-16 03:27:25 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DCompound): Pattern =
|
2021-10-29 12:06:00 +00:00
|
|
|
Pattern(orKind: PatternKind.DCompound, dcompound: d)
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DCompoundRec): Pattern =
|
|
|
|
DCompound(orKind: DCompoundKind.rec, rec: d).toPattern
|
2022-06-10 15:46:57 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DCompoundArr): Pattern =
|
|
|
|
DCompound(orKind: DCompoundKind.arr, arr: d).toPattern
|
2022-06-16 03:27:25 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc toPattern(d: sink DCompoundDict): Pattern =
|
|
|
|
DCompound(orKind: DCompoundKind.dict, dict: d).toPattern
|
2022-06-16 03:27:25 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc drop*(): Pattern {.inline.} = Pattern(orKind: PatternKind.DDiscard)
|
|
|
|
## Create a pattern to match any value without capture.
|
2022-06-16 03:27:25 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc grab*(): Pattern {.inline.} = DBind(pattern: drop()).toPattern
|
|
|
|
## Create a pattern to capture any value.
|
2022-06-16 03:27:25 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc grab*[T](pr: Preserve[T]): Pattern =
|
2022-08-29 19:47:12 +00:00
|
|
|
## Convert a `Preserve` value to a `Pattern`.
|
2023-03-23 19:06:44 +00:00
|
|
|
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>}> <_>]>"""
|
|
|
|
|
2022-06-16 03:27:25 +00:00
|
|
|
assert not pr.embedded
|
|
|
|
case pr.kind
|
2023-03-23 19:06:44 +00:00
|
|
|
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
|
2022-06-16 03:27:25 +00:00
|
|
|
of pkRecord:
|
2023-03-23 19:06:44 +00:00
|
|
|
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
|
2022-06-16 03:27:25 +00:00
|
|
|
of pkSequence:
|
2023-03-23 19:06:44 +00:00
|
|
|
DCompoundArr(items: map(pr.sequence, grab)).toPattern
|
2022-06-16 03:27:25 +00:00
|
|
|
of pkSet: raise newException(
|
|
|
|
ValueError, "cannot construct a pattern over a set literal")
|
|
|
|
of pkDictionary:
|
|
|
|
var dict = DCompoundDict()
|
2023-03-23 19:06:44 +00:00
|
|
|
for key, val in pr.pairs: dict.entries[cast[Preserve[Ref]](key)] = grab val
|
|
|
|
dict.toPattern
|
2022-06-16 03:27:25 +00:00
|
|
|
of pkEmbedded:
|
2023-03-23 19:06:44 +00:00
|
|
|
# TODO: can patterns be constructed over embedded literals?
|
|
|
|
drop()
|
2022-08-29 19:47:12 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc grab*[T](val: T): Pattern =
|
2022-08-29 19:47:12 +00:00
|
|
|
## Construct a `Pattern` from value of type `T`.
|
2023-03-23 19:06:44 +00:00
|
|
|
runnableExamples:
|
|
|
|
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 (toPreserve(val, Ref))
|
|
|
|
|
|
|
|
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
|
2022-08-29 19:47:12 +00:00
|
|
|
else:
|
2023-03-23 19:06:44 +00:00
|
|
|
if `bind`: grab()
|
|
|
|
else: drop()
|
2021-10-29 12:06:00 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc grabType*(typ: static typedesc): Pattern =
|
|
|
|
## Derive a `Pattern` from type `typ`.
|
2022-08-29 19:47:12 +00:00
|
|
|
## This works for `tuple` and `object` types but in the
|
|
|
|
## general case will return a wildcard binding.
|
|
|
|
runnableExamples:
|
|
|
|
import preserves
|
2023-03-23 19:06:44 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
proc dropType*(typ: static typedesc): Pattern =
|
|
|
|
## Derive a `Pattern` from type `typ` without any bindings.
|
|
|
|
patternOfType(typ, false)
|
2022-08-29 19:47:12 +00:00
|
|
|
|
|
|
|
proc fieldCount(T: typedesc): int =
|
|
|
|
for _, _ in fieldPairs(default T):
|
|
|
|
inc result
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
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.
|
2022-08-29 19:47:12 +00:00
|
|
|
runnableExamples:
|
|
|
|
import preserves
|
2023-03-23 19:06:44 +00:00
|
|
|
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:
|
2022-08-29 19:47:12 +00:00
|
|
|
var arr = DCompoundArr()
|
2023-03-23 19:06:44 +00:00
|
|
|
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
|
2022-08-29 19:47:12 +00:00
|
|
|
else:
|
2023-03-23 19:06:44 +00:00
|
|
|
{.error: "grab with bindings not implemented for " & $typ.}
|
2022-08-29 19:47:12 +00:00
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
proc grabLit*(): Pattern =
|
2022-08-29 19:47:12 +00:00
|
|
|
runnableExamples:
|
2023-03-23 19:06:44 +00:00
|
|
|
from std/unittest import check
|
|
|
|
check:
|
|
|
|
$grabLit() == """<rec lit [<bind <_>>]>"""
|
|
|
|
grabType(dataspacePatterns.DLit[void])
|
|
|
|
|
2023-03-29 22:59:28 +00:00
|
|
|
proc grabDict*(): Pattern =
|
|
|
|
grabType(dataspacePatterns.DCompoundDict[void])
|
|
|
|
|
2023-05-30 12:15:19 +00:00
|
|
|
proc unpackLiterals*[E](pr: Preserve[E]): Preserve[E] =
|
|
|
|
result = pr
|
|
|
|
apply(result) do (pr: var Preserve[E]):
|
2023-06-09 23:59:54 +00:00
|
|
|
if pr.isRecord("lit", 1) or pr.isRecord("dict", 1) or pr.isRecord("arr", 1) or pr.isRecord("set", 1):
|
2023-05-30 12:15:19 +00:00
|
|
|
pr = pr.record[0]
|
|
|
|
|
2023-03-23 19:06:44 +00:00
|
|
|
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`.
|
|
|
|
proc inject(pat: Pattern; bindings: openArray[(int, Pattern)]; offset: var int): Pattern =
|
|
|
|
case pat.orKind
|
|
|
|
of PatternKind.DDiscard:
|
2023-06-08 13:15:03 +00:00
|
|
|
result = pat
|
|
|
|
for (off, injection) in bindings:
|
|
|
|
if off == offset:
|
2023-03-23 19:06:44 +00:00
|
|
|
result = injection
|
|
|
|
break
|
|
|
|
inc offset
|
2023-06-08 13:15:03 +00:00
|
|
|
of PatternKind.DBind:
|
|
|
|
let bindOff = offset
|
|
|
|
result = pat
|
|
|
|
result.dbind.pattern = inject(pat.dbind.pattern, bindings, offset)
|
|
|
|
if result.orKind == PatternKind.DBind:
|
|
|
|
for (off, injection) in bindings:
|
|
|
|
if (off == bindOff) and (result.dbind.pattern == injection):
|
|
|
|
result = result.dbind.pattern
|
|
|
|
break # promote the injected pattern over the bind
|
|
|
|
of PatternKind.DLit:
|
2023-03-23 19:06:44 +00:00
|
|
|
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)
|
2022-06-09 01:19:12 +00:00
|
|
|
|
2022-06-10 15:46:57 +00:00
|
|
|
proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern =
|
2023-03-23 19:06:44 +00:00
|
|
|
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
|
2022-06-10 15:46:57 +00:00
|
|
|
|
2022-03-11 05:43:54 +00:00
|
|
|
type
|
|
|
|
Value = Preserve[Ref]
|
|
|
|
Path = seq[Value]
|
|
|
|
Analysis* = tuple
|
|
|
|
constPaths: seq[Path]
|
|
|
|
constValues: seq[Value]
|
|
|
|
capturePaths: seq[Path]
|
|
|
|
|
|
|
|
func walk(result: var Analysis; path: var Path; p: Pattern)
|
|
|
|
|
|
|
|
func walk(result: var Analysis; path: var Path; key: int|Value; pat: Pattern) =
|
|
|
|
path.add(key.toPreserve(Ref))
|
|
|
|
walk(result, path, pat)
|
|
|
|
discard path.pop
|
|
|
|
|
|
|
|
func walk(result: var Analysis; path: var Path; p: Pattern) =
|
|
|
|
case p.orKind
|
|
|
|
of PatternKind.DCompound:
|
|
|
|
case p.dcompound.orKind
|
|
|
|
of DCompoundKind.rec:
|
|
|
|
for k, e in p.dcompound.rec.fields: walk(result, path, k, e)
|
|
|
|
of DCompoundKind.arr:
|
|
|
|
for k, e in p.dcompound.arr.items: walk(result, path, k, e)
|
|
|
|
of DCompoundKind.dict:
|
|
|
|
for k, e in p.dcompound.dict.entries: walk(result, path, k, e)
|
|
|
|
of PatternKind.DBind:
|
|
|
|
result.capturePaths.add(path)
|
|
|
|
walk(result, path, p.dbind.pattern)
|
|
|
|
of PatternKind.DDiscard: discard
|
|
|
|
of PatternKind.DLit:
|
|
|
|
result.constPaths.add(path)
|
|
|
|
result.constValues.add(p.dlit.value.toPreserve(Ref))
|
|
|
|
|
|
|
|
func analyse*(p: Pattern): Analysis =
|
|
|
|
var path: Path
|
|
|
|
walk(result, path, p)
|
2022-10-27 00:24:10 +00:00
|
|
|
|
|
|
|
func projectPath*(v: Value; path: Path): Option[Value] =
|
|
|
|
result = some(v)
|
|
|
|
for index in path:
|
|
|
|
result = preserves.step(result.get, index)
|
|
|
|
if result.isNone: break
|
|
|
|
|
|
|
|
func projectPaths*(v: Value; paths: seq[Path]): seq[Value] =
|
|
|
|
result = newSeq[Value](paths.len)
|
|
|
|
for i, path in paths:
|
|
|
|
var vv = projectPath(v, path)
|
|
|
|
if vv.isSome: result[i] = get(vv)
|
|
|
|
|
|
|
|
func matches*(pat: Pattern; pr: Value): bool =
|
|
|
|
let analysis = analyse(pat)
|
|
|
|
assert analysis.constPaths.len == analysis.constValues.len
|
|
|
|
for i, path in analysis.constPaths:
|
|
|
|
let v = projectPath(pr, path)
|
|
|
|
if v.isNone : return false
|
|
|
|
if analysis.constValues[i] != v.get: return false
|
|
|
|
for path in analysis.capturePaths:
|
|
|
|
if isNone projectPath(pr, path): return false
|
|
|
|
true
|
|
|
|
|
|
|
|
func capture*(pat: Pattern; pr: Value): seq[Value] =
|
|
|
|
let analysis = analyse(pat)
|
|
|
|
assert analysis.constPaths.len == analysis.constValues.len
|
|
|
|
for i, path in analysis.constPaths:
|
|
|
|
let v = projectPath(pr, path)
|
|
|
|
if v.isNone : return @[]
|
|
|
|
if analysis.constValues[i] != v.get: return @[]
|
|
|
|
for path in analysis.capturePaths:
|
|
|
|
let v = projectPath(pr, path)
|
|
|
|
if v.isNone: return @[]
|
|
|
|
result.add(get v)
|
2022-11-08 19:24:18 +00:00
|
|
|
|
|
|
|
when isMainModule:
|
|
|
|
let txt = readAll stdin
|
|
|
|
if txt != "":
|
2022-12-08 04:43:36 +00:00
|
|
|
let
|
|
|
|
v = parsePreserves(txt)
|
2023-03-23 19:06:44 +00:00
|
|
|
pat = grab v
|
2022-12-08 04:43:36 +00:00
|
|
|
stdout.writeLine(pat)
|