syndicate-nim/src/syndicate/patterns.nim

435 lines
14 KiB
Nim

# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway
# SPDX-License-Identifier: Unlicense
import std/[sequtils, tables, typetraits]
import preserves
import ./protocols/dataspacePatterns
from ./actors import Ref
export dataspacePatterns.`$`, PatternKind, DCompoundKind, AnyAtomKind
type
Assertion = Preserve[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 =
Pattern(orKind: PatternKind.DBind, dbind: d)
proc `?`*(d: sink DLit): Pattern =
Pattern(orKind: PatternKind.DLit, dlit: d)
proc `?`*(aa: sink AnyAtom): Pattern =
?DLit(value: aa)
proc `?`*(d: sink DCompound): Pattern =
Pattern(orKind: PatternKind.DCompound, dcompound: d)
proc `?`*(d: sink DCompoundRec): Pattern =
?DCompound(orKind: DCompoundKind.rec, rec: d)
proc `?`*(d: sink DCompoundArr): Pattern =
?DCompound(orKind: DCompoundKind.arr, arr: d)
proc `?`*(d: sink DCompoundDict): Pattern =
?DCompound(orKind: DCompoundKind.dict, dict: d)
proc `?`*(x: bool): Pattern =
?AnyAtom(orKind: AnyAtomKind.`bool`, bool: x)
proc `?`*(x: float32): Pattern =
?AnyAtom(orKind: AnyAtomKind.`float`, float: x)
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 =
## Convert a `Preserve` value to a `Pattern`.
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 pkRecord:
?DCompoundRec(
label: pr.label,
fields: map[Preserve[T], Pattern](pr.fields, `?`))
of pkSequence:
?DCompoundArr(items: map(pr.sequence, `?`))
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[key] = ?val
?dict
of pkEmbedded:
raiseAssert "cannot construct a pattern over a embedded literal"
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 =
## 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))
proc `?`*(T: static typedesc): Pattern =
## Derive a `Pattern` from type `T`.
## This works for `tuple` and `object` types but in the
## general case will return a wildcard binding.
runnableExamples:
import preserves
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 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.
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:
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)
else:
raiseAssert("no preserves pragma on " & $T)
#[
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`.
runnableExamples:
import preserves
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
echo "override ", result.dcompound.rec.fields[0].dcompound.arr.items
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
else:
raiseAssert "cannot override " & $pat
proc recordPattern*(label: Preserve[Ref], fields: varargs[Pattern]): Pattern =
?DCompoundRec(label: label, fields: fields.toSeq)
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)