# SPDX-FileCopyrightText: ☭ 2021 Emery Hemingway # SPDX-License-Identifier: Unlicense import std/[options, sequtils, tables, typetraits] import preserves import ./protocols/dataspacePatterns 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] 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) == "> >]>" type Rect {.preservesRecord: "rect".} = tuple[a: Point; B: Point] assert $(?Rect) == "> >]> > >]>]>" type ColoredRect {.preservesDictionary.} = tuple[color: string; rect: Rect] assert $(?ColoredRect) == ">, rect: > >]> > >]>]>}>" 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() }) == " <_> >]>" 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) == "> >]>" # Capture two values from a value of `Point`. assert $(?Point ?? { 0: ?DLit }) == " >]> <_>]>]>" # 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 }) == " >]>]>]>" # 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 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) 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) when isMainModule: let txt = readAll stdin if txt != "": stdout.writeLine(? parsePreserves(txt, Ref))