diff --git a/bin/widish b/bin/widish
new file mode 100755
index 0000000..8d1fb99
--- /dev/null
+++ b/bin/widish
@@ -0,0 +1,2 @@
+#!/bin/sh
+exec python -m widip "$@"
diff --git a/bin/widish.png b/bin/widish.png
new file mode 100644
index 0000000..dd106b1
Binary files /dev/null and b/bin/widish.png differ
diff --git a/bin/widish.svg b/bin/widish.svg
new file mode 100644
index 0000000..d15aa63
--- /dev/null
+++ b/bin/widish.svg
@@ -0,0 +1,612 @@
+
+
+
diff --git a/bin/yaml/python.yaml b/bin/yaml/python.yaml
new file mode 100755
index 0000000..6dd6fdf
--- /dev/null
+++ b/bin/yaml/python.yaml
@@ -0,0 +1,2 @@
+#!bin/widish
+!python
diff --git a/bin/yaml/shell.png b/bin/yaml/shell.png
new file mode 100644
index 0000000..0b8389c
Binary files /dev/null and b/bin/yaml/shell.png differ
diff --git a/bin/yaml/shell.svg b/bin/yaml/shell.svg
new file mode 100644
index 0000000..4c68cb0
--- /dev/null
+++ b/bin/yaml/shell.svg
@@ -0,0 +1,192 @@
+
+
+
diff --git a/examples/README.md b/examples/README.md
index ac92f6a..68ca981 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -7,7 +7,7 @@ $ python -m widip examples/hello-world.yaml
Hello world!
```
-
+
## Script
@@ -19,7 +19,7 @@ $ python -m widip examples/shell.yaml
? !tail -2
```
-
+
# Working with the CLI
diff --git a/examples/aoc2025/1-1.jpg b/examples/aoc2025/1-1.jpg
index 3ef213f..5a9bf95 100644
Binary files a/examples/aoc2025/1-1.jpg and b/examples/aoc2025/1-1.jpg differ
diff --git a/examples/hello-world.jpg b/examples/hello-world.jpg
deleted file mode 100644
index 382ad43..0000000
Binary files a/examples/hello-world.jpg and /dev/null differ
diff --git a/examples/hello-world.png b/examples/hello-world.png
new file mode 100644
index 0000000..6e3474f
Binary files /dev/null and b/examples/hello-world.png differ
diff --git a/examples/hello-world.svg b/examples/hello-world.svg
new file mode 100644
index 0000000..442f7fe
--- /dev/null
+++ b/examples/hello-world.svg
@@ -0,0 +1,333 @@
+
+
+
diff --git a/examples/mascarpone/crack-then-beat.png b/examples/mascarpone/crack-then-beat.png
new file mode 100644
index 0000000..7c38706
Binary files /dev/null and b/examples/mascarpone/crack-then-beat.png differ
diff --git a/examples/mascarpone/crack-then-beat.svg b/examples/mascarpone/crack-then-beat.svg
new file mode 100644
index 0000000..b6ff70e
--- /dev/null
+++ b/examples/mascarpone/crack-then-beat.svg
@@ -0,0 +1,1671 @@
+
+
+
diff --git a/examples/shell.jpg b/examples/shell.jpg
deleted file mode 100644
index da94402..0000000
Binary files a/examples/shell.jpg and /dev/null differ
diff --git a/examples/shell.png b/examples/shell.png
new file mode 100644
index 0000000..bcd3e6d
Binary files /dev/null and b/examples/shell.png differ
diff --git a/examples/shell.svg b/examples/shell.svg
new file mode 100644
index 0000000..965a746
--- /dev/null
+++ b/examples/shell.svg
@@ -0,0 +1,1316 @@
+
+
+
diff --git a/examples/test_script.sh b/examples/test_script.sh
new file mode 100755
index 0000000..b0cf654
--- /dev/null
+++ b/examples/test_script.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "Hello from Bash Script!"
diff --git a/pyproject.toml b/pyproject.toml
index 12389de..ae62060 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -10,8 +10,9 @@ name = "widip"
version = "0.1.0"
description = "Widip is an interactive environment for computing with wiring diagrams in modern systems"
dependencies = [
- "discopy>=1.2.1", "pyyaml>=6.0.1", "watchdog>=4.0.1", "nx-yaml==0.3.0",
+ "discopy>=1.2.2", "pyyaml>=6.0.1", "watchdog>=4.0.1", "nx-yaml==0.4.1",
]
[project.urls]
"Source" = "https://github.com/colltoaction/widip"
+
diff --git a/widip/composing.py b/widip/composing.py
deleted file mode 100644
index 7464cc5..0000000
--- a/widip/composing.py
+++ /dev/null
@@ -1,145 +0,0 @@
-from discopy.closed import Id, Ty, Diagram, Functor, Box
-
-
-def adapt_to_interface(diagram, box):
- return
- """adapts a diagram open ports to fit in the box"""
- left = Id(box.dom)
- right = Id(box.cod)
- return adapter_hypergraph(left, diagram) >> \
- diagram >> \
- adapter_hypergraph(diagram, right)
-
-def adapter_hypergraph(left, right):
- return
- mid = Ty().tensor(*set(left.cod + right.dom))
- mid_to_left_ports = {
- t: tuple(i for i, lt in enumerate(left.cod) if lt == t)
- for t in mid}
- mid_to_right_ports = {
- t: tuple(i + len(left.cod) for i, lt in enumerate(right.dom) if lt == t)
- for t in mid}
- boxes = tuple(
- Id(Ty().tensor(*(t for _ in range(len(mid_to_left_ports[t])))))
- if len(mid_to_left_ports[t]) == len(mid_to_right_ports[t]) else
- Spider(
- len(mid_to_left_ports[t]),
- len(mid_to_right_ports[t]),
- t)
- for t in mid)
- g = H(
- dom=left.cod, cod=right.dom,
- boxes=boxes,
- wires=(
- tuple(i for i in range(len(left.cod))),
- tuple(
- (mid_to_left_ports[t], mid_to_right_ports[t])
- for t in mid),
- tuple(i + len(left.cod) for i in range(len(right.dom))),
- ),
- )
- return g.to_diagram()
-
-def glue_diagrams(left, right):
- return
- """a diagram connecting equal objects within each type"""
- """glues two diagrams sequentially with closed generators"""
- if left.cod == right.dom:
- return left >> right
- l_dom, l_cod, r_dom, r_cod = left.dom, left.cod, right.dom, right.cod
- dw_l = {
- t
- for t in l_cod
- if t not in r_dom}
- dw_r = {
- t
- for t in r_dom
- if t not in l_cod}
- cw_l = {
- t
- for t in l_cod
- if t in r_dom}
- cw_r = {
- t
- for t in r_dom
- if t in l_cod}
- # TODO convention for repeated in both sides
- mid_names = tuple({t for t in l_cod + r_dom})
- dom_wires = l_dom_wires = tuple(
- i
- for i in range(len(l_dom) + len(dw_r))
- )
- l_cod_wires = tuple(
- (mid_names.index(t)
- + len(l_dom) + len(dw_r))
- for t in l_cod) + \
- tuple(
- (mid_names.index(t) + len(l_dom) + len(dw_r))
- for t in dw_r
- )
- r_dom_wires = tuple(
- (mid_names.index(t) + len(l_dom) + len(dw_r))
- for t in dw_l) + \
- tuple(
- (mid_names.index(t)
- + len(l_dom) + len(dw_r))
- for t in r_dom
- )
- cod_wires = r_cod_wires = tuple(
- i
- + len(l_dom) + len(dw_r)
- + len(mid_names)
- for i in range(len(dw_l) + len(r_cod))
- )
- glued = H(
- dom=l_dom @ Ty().tensor(*dw_r),
- cod=Ty().tensor(*dw_l) @ r_cod,
- boxes=(
- left @ Ty().tensor(*dw_r),
- Ty().tensor(*dw_l) @ right,
- ),
- wires=(
- dom_wires,
- (
- (l_dom_wires, l_cod_wires),
- (r_dom_wires, r_cod_wires),
- ),
- cod_wires,
- ),
- ).to_diagram()
- return glued
-
-def replace_id_f(name):
- return Functor(
- lambda ob: replace_id_ty(ob, name),
- lambda ar: replace_id_box(ar, name),)
-
-def replace_id_box(box, name):
- return Box(
- box.name,
- replace_id_ty(box.dom, name),
- replace_id_ty(box.cod, name))
-
-def replace_id_ty(ty, name):
- return Ty().tensor(*(Ty("") if t == Ty(name) else t for t in ty))
-
-def close_ty_f(name):
- return Functor(
- lambda ob: ob,#close_ty(ob, name),
- lambda ar: close_ty_box(ar, name),)
-
-def close_ty_box(box, name):
- l = Ty().tensor(*(
- t for t in box.dom
- if t != Ty(name)))
- r = Ty().tensor(*(
- t for t in box.cod
- if t != Ty(name)))
- # box.draw()
- box.draw()
- closed = adapt_to_interface(box, Box("", l, r))
- closed.draw()
- return closed
-
-def close_ty(ty, name):
- return Ty() if ty == Ty(name) else ty
\ No newline at end of file
diff --git a/widip/discopy_to_hif.py b/widip/discopy_to_hif.py
new file mode 100644
index 0000000..3db6049
--- /dev/null
+++ b/widip/discopy_to_hif.py
@@ -0,0 +1,44 @@
+from discopy.hypergraph import Hypergraph
+from nx_hif.hif import hif_create, hif_new_node, hif_new_edge, hif_add_incidence
+
+
+def discopy_to_hif(hg: Hypergraph):
+ hif_hg = hif_create()
+
+ # Create spiders (edges)
+ spider_to_eid = {}
+ for i in range(hg.n_spiders):
+ eid = hif_new_edge(hif_hg, kind="spider")
+ spider_to_eid[i] = eid
+
+ # Create boundary
+ boundary_id = hif_new_node(hif_hg, kind="boundary")
+
+ # Connect boundary
+ dom_wires = hg.wires[0]
+ for i, spider_idx in enumerate(dom_wires):
+ eid = spider_to_eid[spider_idx]
+ hif_add_incidence(hif_hg, eid, boundary_id, role="dom", index=i, key=None)
+
+ cod_wires = hg.wires[2]
+ for i, spider_idx in enumerate(cod_wires):
+ eid = spider_to_eid[spider_idx]
+ hif_add_incidence(hif_hg, eid, boundary_id, role="cod", index=i, key=None)
+
+ # Create boxes
+ box_wires = hg.wires[1]
+ for i, box in enumerate(hg.boxes):
+ data = box.data.copy() if box.data else {}
+ data["kind"] = box.name
+ nid = hif_new_node(hif_hg, **data)
+
+ ins, outs = box_wires[i]
+ for idx, spider_idx in enumerate(ins):
+ eid = spider_to_eid[spider_idx]
+ hif_add_incidence(hif_hg, eid, nid, role="dom", index=idx, key=None)
+
+ for idx, spider_idx in enumerate(outs):
+ eid = spider_to_eid[spider_idx]
+ hif_add_incidence(hif_hg, eid, nid, role="cod", index=idx, key=None)
+
+ return hif_hg
diff --git a/widip/drawing.py b/widip/drawing.py
new file mode 100644
index 0000000..f180d8a
--- /dev/null
+++ b/widip/drawing.py
@@ -0,0 +1,98 @@
+import math, sys
+from pathlib import Path
+from discopy import monoidal, closed
+
+class ComplexityProfile:
+ def __init__(self, rw, rh, rn, d_width, d_height):
+ self.max_cpw, self.max_lpl, self.rn, self.d_width, self.d_height = rw, rh, rn, d_width, d_height
+
+ def get_layout_params(self):
+ fsize = max(10, min(24, 80 / math.pow(max(1, self.rn), 0.15)))
+ w_mult = (max(3, self.max_cpw) * fsize) / 72.0 + 0.6
+ h_mult = (self.max_lpl * fsize * 1.5) / 72.0 + 0.2
+ width, height = self.d_width * w_mult, self.d_height * h_mult
+ if self.d_width == 1 and self.d_height > 3 and height > width * 2.5:
+ height = self.d_height * ((width * 2.5) / self.d_height)
+ return {"figsize": (width, height), "fontsize": int(fsize), "textpad": (0.1, 0.15), "fontsize_types": max(12, int(fsize * 0.4))}
+
+def get_recursive_stats(d, visited=None):
+ if visited is None: visited = set()
+ max_cpw, max_lpl, box_count = 1.0, 1.0, 0
+
+ def walk(obj):
+ nonlocal max_cpw, max_lpl, box_count
+ if id(obj) in visited: return
+ visited.add(id(obj))
+ if isinstance(obj, monoidal.Bubble): return walk(obj.inside)
+ if hasattr(obj, "boxes") and len(obj.boxes) == 1 and obj.boxes[0] is obj:
+ label = str(getattr(obj, "drawing_name", getattr(obj, "name", ""))) or ""
+ lines, wires = label.split('\n'), max(1, len(getattr(obj, "dom", [])), len(getattr(obj, "cod", [])))
+ max_cpw, max_lpl, box_count = max(max_cpw, max(len(l) for l in lines) / wires), max(max_lpl, len(lines)), box_count + 1
+ return
+ if hasattr(obj, "__iter__"): [walk(b) for layer in obj if hasattr(layer, "boxes") for b in layer.boxes]
+ elif hasattr(obj, "inside"): walk(obj.inside)
+ elif hasattr(obj, "boxes"): [walk(b) for b in obj.boxes]
+ walk(d)
+ return max_cpw, max_lpl, box_count
+
+STYLES = {
+ "Scalar": {"color": "#ffffff", "spider": True, "fmt": lambda n, b: f"{getattr(b, 'tag', '')} {getattr(b, 'value', '')}".strip() or ""},
+ "Alias": {"color": "#3498db", "spider": True, "fmt": lambda n, b: f"*{getattr(b, 'name', n.lstrip('*'))}"},
+ "Anchor": {"color": "#2980b9", "spider": True, "fmt": lambda n, b: f"&{getattr(b, 'name', n.lstrip('&'))}"},
+ "Label": {"color": "#ffffff"}, "Data": {"color": "#fff9c4"}, "Eval": {"color": "#ffccbc", "name": "exec"},
+ "Curry": {"color": "#d1c4e9", "name": "Λ"}, "Program": {"color": "#ffffff"},
+ "Copy": {"color": "#2ecc71", "spider": True, "name": "Δ"}, "Merge": {"color": "#27ae60", "spider": True, "name": "μ"},
+ "Discard": {"color": "#e74c3c", "spider": True, "name": "ε", "check": lambda b: b.dom.name != ""},
+ "Swap": {"color": "#f1c40f", "swap": True, "name": ""},
+ "Sequence": {"color": "#ffffff", "bubble": True, "fmt": lambda n, b: f"[{getattr(b, 'tag', '')}]" if getattr(b, 'tag', '') else ""},
+ "Mapping": {"color": "#ffffff", "bubble": True, "fmt": lambda n, b: f"{{{getattr(b, 'tag', '')}}}" if getattr(b, 'tag', '') else ""},
+}
+
+def get_style(box):
+ cls, name = type(box).__name__, str(getattr(box, "drawing_name", getattr(box, "name", "")))
+ key = next((k for k in STYLES if cls == k or name.startswith(k) or (k == "Eval" and name=="eval") or (k == "Curry" and name=="curry") or (k=="Data" and name.startswith("⌜"))), None)
+ style = STYLES.get(key, {})
+ final_name = style.get("name", name)
+ if "fmt" in style: final_name = style["fmt"](name, box)
+ if "check" in style and not style["check"](box): final_name, style = "", {"color": "#ffffff", "spider": True}
+ return final_name, style.get("color", "#f0f0f0"), style.get("spider", False), style.get("swap", False), style.get("bubble", False)
+
+def diagram_draw(path: Path, fd):
+ m_cpw, m_lpl, rn = get_recursive_stats(fd)
+ params = ComplexityProfile(m_cpw, m_lpl, rn, getattr(fd, "width", len(getattr(fd, "dom", [])) or 1), len(fd) if hasattr(fd, "__iter__") else 1).get_layout_params()
+ out_svg, out_png = (str(path), None) if path.suffix.lower() in ['.png', '.jpg', '.jpeg'] else (str(path.with_suffix(".svg")), str(path.with_suffix(".png")))
+
+ def map_ob(ob): return monoidal.Ty(*[getattr(o, "name", str(o)) for o in ob.inside])
+
+ def map_ar(box):
+ name, color, spider, swap, bubble = get_style(box)
+ dom, cod = map_ob(box.dom), map_ob(box.cod)
+ if spider: res = monoidal.Box(name, dom, cod, drawing_name=name)
+ else:
+ padded = "\n".join([l.ljust(int(m_cpw * max(1, len(dom), len(cod)))) for l in str(name).split('\n')])
+ if bubble or (isinstance(box, monoidal.Bubble) and not spider):
+ inside = standardize(box.inside) if hasattr(box, 'inside') else monoidal.Box(padded, dom, cod)
+ res = monoidal.Bubble(inside, dom, cod, drawing_name=padded)
+ else:
+ res = monoidal.Box(padded, dom, cod, drawing_name=padded)
+ if not swap: res.nodesize = (1, 1)
+ res.color, res.draw_as_spider, res.draw_as_swap = color, spider, swap
+ if spider: res.shape, res.nodesize = "circle", (1.5, 1.5)
+ return res
+
+ def standardize(diag):
+ if not hasattr(diag, "boxes_and_offsets"): return diag
+ m_dom, curr, inside = map_ob(diag.dom), map_ob(diag.dom), []
+ for box, off in diag.boxes_and_offsets:
+ mbox = map_ar(box)
+ layer = monoidal.Layer(monoidal.Id(curr[:off]), mbox, monoidal.Id(curr[off + len(mbox.dom):]))
+ inside.append(layer)
+ curr = layer.cod
+ return monoidal.Diagram(inside, m_dom, curr)
+
+ fd_draw = standardize(fd)
+ draw_params = {"aspect": "auto", "figsize": params["figsize"], "fontsize": params["fontsize"], "fontfamily": "monospace", "textpad": params["textpad"], "fontsize_types": params["fontsize_types"]}
+ fd_draw.draw(path=out_svg, **draw_params)
+ if out_png:
+ try: fd_draw.draw(path=out_png, **draw_params)
+ except: pass
diff --git a/widip/files.py b/widip/files.py
index be33b80..b8ff844 100644
--- a/widip/files.py
+++ b/widip/files.py
@@ -24,9 +24,7 @@ def file_diagram(file_name) -> Diagram:
return fd
def diagram_draw(path, fd):
- fd.draw(path=str(path.with_suffix(".jpg")),
- textpad=(0.3, 0.1),
- fontsize=12,
- fontsize_types=8)
+ from .drawing import diagram_draw as draw_svg
+ draw_svg(path, fd)
files_f = Functor(lambda x: Ty(""), files_ar)
diff --git a/widip/loader.py b/widip/loader.py
index 912e6cc..7c0ac5f 100644
--- a/widip/loader.py
+++ b/widip/loader.py
@@ -1,144 +1,83 @@
-from itertools import batched
-from nx_yaml import nx_compose_all, nx_serialize_all
-from nx_hif.hif import *
-
-from discopy.markov import Id, Ty, Box, Eval
-P = Ty("io") >> Ty("io")
-
-from .composing import glue_diagrams
-
-
-def repl_read(stream):
- incidences = nx_compose_all(stream)
- diagrams = incidences_to_diagram(incidences)
- return diagrams
-
-def incidences_to_diagram(node: HyperGraph):
- # TODO properly skip stream and document start
- diagram = _incidences_to_diagram(node, 0)
- return diagram
-
-def _incidences_to_diagram(node: HyperGraph, index):
- """
- Takes an nx_yaml rooted bipartite graph
- and returns an equivalent string diagram
- """
- tag = (hif_node(node, index).get("tag") or "")[1:]
- kind = hif_node(node, index)["kind"]
-
- match kind:
-
- case "stream":
- ob = load_stream(node, index)
- case "document":
- ob = load_document(node, index)
- case "scalar":
- ob = load_scalar(node, index, tag)
- case "sequence":
- ob = load_sequence(node, index, tag)
- case "mapping":
- ob = load_mapping(node, index, tag)
- case _:
- raise Exception(f"Kind \"{kind}\" doesn't match any.")
-
- return ob
-
-
-def load_scalar(node, index, tag):
- v = hif_node(node, index)["value"]
- if tag and v:
- return Box("G", Ty(tag) @ Ty(v), Ty() << Ty(""))
- elif tag:
- return Box("G", Ty(tag), Ty() << Ty(""))
- elif v:
- return Box("⌜−⌝", Ty(v), Ty() << Ty(""))
- else:
- return Box("⌜−⌝", Ty(), Ty() << Ty(""))
-
-def load_mapping(node, index, tag):
- ob = Id()
- i = 0
- nxt = tuple(hif_node_incidences(node, index, key="next"))
- while True:
- if not nxt:
- break
- ((k_edge, _, _, _), ) = nxt
- ((_, k, _, _), ) = hif_edge_incidences(node, k_edge, key="start")
- ((v_edge, _, _, _), ) = hif_node_incidences(node, k, key="forward")
- ((_, v, _, _), ) = hif_edge_incidences(node, v_edge, key="start")
- key = _incidences_to_diagram(node, k)
- value = _incidences_to_diagram(node, v)
-
- kv = key @ value
-
- if i==0:
- ob = kv
- else:
- ob = ob @ kv
-
- i += 1
- nxt = tuple(hif_node_incidences(node, v, key="forward"))
- bases = Ty().tensor(*map(lambda x: x.inside[0].base, ob.cod[0::2]))
- exps = Ty().tensor(*map(lambda x: x.inside[0].exponent, ob.cod[1::2]))
- par_box = Box("(||)", ob.cod, exps << bases)
- ob = ob >> par_box
- if tag:
- ob = (ob @ bases>> Eval(exps << bases))
- ob = Ty(tag) @ ob >> Box("G", Ty(tag) @ ob.cod, Ty("") << Ty(""))
- return ob
-
-def load_sequence(node, index, tag):
- ob = Id()
- i = 0
- nxt = tuple(hif_node_incidences(node, index, key="next"))
- while True:
- if not nxt:
- break
- ((v_edge, _, _, _), ) = nxt
- ((_, v, _, _), ) = hif_edge_incidences(node, v_edge, key="start")
- value = _incidences_to_diagram(node, v)
- if i==0:
- ob = value
- else:
- ob = ob @ value
- bases = ob.cod[0].inside[0].exponent
- exps = value.cod[0].inside[0].base
- ob = ob >> Box("(;)", ob.cod, bases >> exps)
-
- i += 1
- nxt = tuple(hif_node_incidences(node, v, key="forward"))
- if tag:
- bases = Ty().tensor(*map(lambda x: x.inside[0].exponent, ob.cod))
- exps = Ty().tensor(*map(lambda x: x.inside[0].base, ob.cod))
- ob = (bases @ ob >> Eval(bases >> exps))
- ob = Ty(tag) @ ob >> Box("G", Ty(tag) @ ob.cod, Ty() >> Ty(tag))
- return ob
-
-def load_document(node, index):
- nxt = tuple(hif_node_incidences(node, index, key="next"))
- ob = Id()
- if nxt:
- ((root_e, _, _, _), ) = nxt
- ((_, root, _, _), ) = hif_edge_incidences(node, root_e, key="start")
- ob = _incidences_to_diagram(node, root)
- return ob
-
-def load_stream(node, index):
- ob = Id()
- nxt = tuple(hif_node_incidences(node, index, key="next"))
- while True:
- if not nxt:
- break
- ((nxt_edge, _, _, _), ) = nxt
- starts = tuple(hif_edge_incidences(node, nxt_edge, key="start"))
- if not starts:
- break
- ((_, nxt_node, _, _), ) = starts
- doc = _incidences_to_diagram(node, nxt_node)
- if ob == Id():
- ob = doc
- else:
- ob = glue_diagrams(ob, doc)
-
- nxt = tuple(hif_node_incidences(node, nxt_node, key="forward"))
- return ob
+from nx_yaml import nx_compose_all
+from nx_hif.hif import *
+from discopy.closed import Id, Ty, Box, Eval
+
+P = Ty() << Ty("")
+
+def iter_linked_list(node, index):
+ edges = tuple(hif_node_incidences(node, index, key="next"))
+ while edges:
+ ((edge, _, _, _), ) = edges
+ ((_, target, _, _), ) = hif_edge_incidences(node, edge, key="start")
+ yield target
+ edges = tuple(hif_node_incidences(node, target, key="forward"))
+
+def repl_read(stream):
+ return _load_node(nx_compose_all(stream), 0)
+
+def _load_node(node, index, tag=None):
+ if tag is None:
+ tag = (hif_node(node, index).get("tag") or "")[1:]
+ kind = hif_node(node, index)["kind"]
+ loaders = {"stream": _load_stream, "document": _load_document,
+ "scalar": _load_scalar, "sequence": _load_sequence, "mapping": _load_mapping}
+ if kind not in loaders:
+ raise Exception(f"Unknown kind: {kind}")
+ return loaders[kind](node, index, tag)
+
+def _load_scalar(node, index, tag):
+ v = hif_node(node, index)["value"]
+ if tag == "fix" and v:
+ return Box("Ω", Ty(), Ty(v) << P) @ P >> Eval(Ty(v) << P) >> Box("e", Ty(v), Ty(v))
+ if tag and v: return Box(tag, Ty(v), Ty(tag) >> Ty(tag))
+ if tag: return Box(tag, Ty(v) if v else Ty(), Ty(tag) >> Ty(tag))
+ if v: return Box("⌜−⌝", Ty(v), Ty() >> Ty(v))
+ return Box("⌜−⌝", Ty(), Ty() >> Ty(v))
+
+def _load_mapping(node, index, tag):
+ ob, edges = Id(), tuple(hif_node_incidences(node, index, key="next"))
+ while edges:
+ ((k_edge, _, _, _), ) = edges
+ ((_, k, _, _), ) = hif_edge_incidences(node, k_edge, key="start")
+ ((v_edge, _, _, _), ) = hif_node_incidences(node, k, key="forward")
+ ((_, v, _, _), ) = hif_edge_incidences(node, v_edge, key="start")
+ key, value = _load_node(node, k, None), _load_node(node, v, None)
+ exps = Ty().tensor(*map(lambda x: x.inside[0].exponent, key.cod))
+ bases = Ty().tensor(*map(lambda x: x.inside[0].base, value.cod))
+ kv = key @ value >> Box("(;)", key.cod @ value.cod, exps >> bases)
+ ob = kv if ob == Id() else ob @ kv
+ edges = tuple(hif_node_incidences(node, v, key="forward"))
+ exps = Ty().tensor(*map(lambda x: x.inside[0].exponent, ob.cod))
+ bases = Ty().tensor(*map(lambda x: x.inside[0].base, ob.cod))
+ ob = ob >> Box("(||)", ob.cod, exps >> bases)
+ if tag:
+ ob = ob @ exps >> Eval(exps >> bases) >> Box(tag, ob.cod, Ty(tag) >> Ty(tag))
+ return ob
+
+def _load_sequence(node, index, tag):
+ ob = Id()
+ for i, v in enumerate(iter_linked_list(node, index)):
+ value = _load_node(node, v, None)
+ if i == 0: ob = value
+ else:
+ bases, exps = ob.cod[0].inside[0].exponent, value.cod[0].inside[0].base
+ ob = ob @ value >> Box("(;)", ob.cod, bases >> exps)
+ if tag:
+ bases = Ty().tensor(*map(lambda x: x.inside[0].exponent, ob.cod))
+ exps = Ty().tensor(*map(lambda x: x.inside[0].base, ob.cod))
+ ob = bases @ ob >> Eval(bases >> exps) >> Box(tag, ob.cod, Ty() >> Ty(tag))
+ return ob
+
+def _load_document(node, index, _):
+ nxt = tuple(hif_node_incidences(node, index, key="next"))
+ if not nxt: return Id()
+ ((root_e, _, _, _), ) = nxt
+ ((_, root, _, _), ) = hif_edge_incidences(node, root_e, key="start")
+ return _load_node(node, root, None)
+
+def _load_stream(node, index, _):
+ ob = Id()
+ for nxt_node in iter_linked_list(node, index):
+ doc = _load_node(node, nxt_node, None)
+ ob = doc if ob == Id() else ob @ doc
+ return ob
diff --git a/widip/test_discopy_to_hif.py b/widip/test_discopy_to_hif.py
new file mode 100644
index 0000000..d4c291b
--- /dev/null
+++ b/widip/test_discopy_to_hif.py
@@ -0,0 +1,14 @@
+from discopy.symmetric import Box, Ty
+from nx_hif.readwrite import encode_hif_data
+from widip.discopy_to_hif import discopy_to_hif
+
+
+def test_discopy_to_hif():
+ x, y, z = Ty('x'), Ty('y'), Ty('z')
+ f = Box("f", x, y @ z, data={"foo": "bar"})
+ g = Box("g", y @ z, x, data={"baz": 42})
+
+ discopy_hg = (f >> g).to_hypergraph()
+ hif_hg = discopy_to_hif(discopy_hg)
+ data = encode_hif_data(hif_hg)
+ assert data == {'incidences': [{'edge': 0, 'node': 0, 'attrs': {'key': 0, 'role': 'dom', 'index': 0}}, {'edge': 0, 'node': 1, 'attrs': {'key': 0, 'role': 'dom', 'index': 0}}, {'edge': 1, 'node': 1, 'attrs': {'key': 0, 'role': 'cod', 'index': 0}}, {'edge': 1, 'node': 2, 'attrs': {'key': 0, 'role': 'dom', 'index': 0}}, {'edge': 2, 'node': 1, 'attrs': {'key': 0, 'role': 'cod', 'index': 1}}, {'edge': 2, 'node': 2, 'attrs': {'key': 0, 'role': 'dom', 'index': 1}}, {'edge': 3, 'node': 0, 'attrs': {'key': 0, 'role': 'cod', 'index': 0}}, {'edge': 3, 'node': 2, 'attrs': {'key': 0, 'role': 'cod', 'index': 0}}], 'edges': [{'edge': 0, 'attrs': {'kind': 'spider'}}, {'edge': 1, 'attrs': {'kind': 'spider'}}, {'edge': 2, 'attrs': {'kind': 'spider'}}, {'edge': 3, 'attrs': {'kind': 'spider'}}], 'nodes': [{'node': 0, 'attrs': {'kind': 'boundary'}}, {'node': 1, 'attrs': {'foo': 'bar', 'kind': 'f'}}, {'node': 2, 'attrs': {'baz': 42, 'kind': 'g'}}]}
diff --git a/widip/test_files.py b/widip/test_files.py
deleted file mode 100644
index a5ffc60..0000000
--- a/widip/test_files.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from discopy.closed import Box, Ty, Diagram, Id
-
-from .files import stream_diagram
-
-
-def test_single_wires():
- a = Id("a")
- a0 = stream_diagram("a")
- a1 = stream_diagram("- a")
- with Diagram.hypergraph_equality:
- assert a == a0
- assert a0 == a1
-
-def test_id_boxes():
- a = Box("a", Ty(""), Ty(""))
- a0 = stream_diagram("!a")
- a1 = stream_diagram("!a :")
- a2 = stream_diagram("- !a")
- with Diagram.hypergraph_equality:
- assert a == a0
- assert a == a1
- assert a == a2
-
-def test_the_empty_value():
- a0 = stream_diagram("")
- a1 = stream_diagram("\"\":")
- a2 = stream_diagram("\"\": a")
- a3 = stream_diagram("a:")
- a4 = stream_diagram("!a :")
- a5 = stream_diagram("\"\": !a")
- with Diagram.hypergraph_equality:
- assert a0 == Id()
- assert a1 == Id("")
- assert a2 == Box("map", Ty(""), Ty("a"))
- assert a3 == Id("a")
- assert a4 == Box("a", Ty(""), Ty(""))
- assert a5 == Box("map", Ty(""), Ty("")) >> a4
diff --git a/widip/test_loader.py b/widip/test_loader.py
deleted file mode 100644
index b98664e..0000000
--- a/widip/test_loader.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from discopy.closed import Box, Ty, Diagram, Spider, Id, Spider
-
-from .loader import compose_all
-
-
-id_box = lambda i: Box("!", Ty(i), Ty(i))
-
-def test_tagged():
- a0 = compose_all("!a")
- a1 = compose_all("!a :")
- a2 = compose_all("--- !a")
- a3 = compose_all("--- !a\n--- !b")
- a4 = compose_all("\"\": !a")
- a5 = compose_all("? !a")
- with Diagram.hypergraph_equality:
- assert a0 == Box("a", Ty(""), Ty(""))
- assert a1 == a0
- assert a2 == a0
- assert a3 == a0 @ Box("b", Ty(""), Ty(""))
- assert a4 == Box("map", Ty(""), Ty("")) >> a0
- assert a5 == a0
-
-def test_untagged():
- a0 = compose_all("")
- a1 = compose_all("\"\":")
- a2 = compose_all("\"\": a")
- a3 = compose_all("a:")
- a4 = compose_all("? a")
- with Diagram.hypergraph_equality:
- assert a0 == Id()
- assert a1 == Id("")
- assert a2 == Box("map", Ty(""), Ty("a"))
- assert a3 == Id("a")
- assert a4 == a3
-
-def test_bool():
- d = Id("true") @ Id("false")
- t = compose_all(open("src/data/bool.yaml"))
- with Diagram.hypergraph_equality:
- assert t == d
-
-# u = Ty("unit")
-# m = Ty("monoid")
-
-# def test_monoid():
-# d = Box(u.name, Ty(), m) @ Box("product", m @ m, m)
-# t = compose_all(open("src/data/monoid.yaml"))
-# with Diagram.hypergraph_equality:
-# assert t == d
diff --git a/widip/watch.py b/widip/watch.py
index c46ffd2..061c633 100644
--- a/widip/watch.py
+++ b/widip/watch.py
@@ -3,62 +3,34 @@
from watchdog.events import FileSystemEventHandler
from watchdog.observers import Observer
from yaml import YAMLError
-
-from discopy.closed import Id, Ty, Box
from discopy.utils import tuplify, untuplify
-
from .loader import repl_read
from .files import diagram_draw, file_diagram
-from .widish import SHELL_RUNNER, compile_shell_program
-
-
-# TODO watch functor ??
+from .widish import SHELL_RUNNER
class ShellHandler(FileSystemEventHandler):
- """Reload the shell on change."""
def on_modified(self, event):
if event.src_path.endswith(".yaml"):
print(f"reloading {event.src_path}")
try:
fd = file_diagram(str(event.src_path))
diagram_draw(Path(event.src_path), fd)
- diagram_draw(Path(event.src_path+".2"), fd)
except YAMLError as e:
print(e)
-def watch_main():
- """the process manager for the reader and """
- # TODO watch this path to reload changed files,
- # returning an IO as always and maintaining the contract.
- print(f"watching for changes in current path")
- observer = Observer()
- shell_handler = ShellHandler()
- observer.schedule(shell_handler, ".", recursive=True)
- observer.start()
- return observer
-
def shell_main(file_name):
try:
while True:
- observer = watch_main()
+ print("watching for changes in current path")
+ observer = Observer()
+ observer.schedule(ShellHandler(), ".", recursive=True)
+ observer.start()
try:
- prompt = f"--- !{file_name}\n"
- source = input(prompt)
+ source = input(f"--- !{file_name}\n")
source_d = repl_read(source)
- # source_d.draw(
- # textpad=(0.3, 0.1),
- # fontsize=12,
- # fontsize_types=8)
- path = Path(file_name)
- diagram_draw(path, source_d)
- # source_d = compile_shell_program(source_d)
- # diagram_draw(Path(file_name+".2"), source_d)
- # source_d = Spider(0, len(source_d.dom), Ty("io")) \
- # >> source_d \
- # >> Spider(len(source_d.cod), 1, Ty("io"))
- # diagram_draw(path, source_d)
- result_ev = SHELL_RUNNER(source_d)()
- print(result_ev)
+ diagram_draw(Path(file_name), source_d)
+ constants = tuple(x.name for x in source_d.dom)
+ print(SHELL_RUNNER(source_d)(*constants)(""))
except KeyboardInterrupt:
print()
except YAMLError as e:
@@ -67,14 +39,11 @@ def shell_main(file_name):
observer.stop()
except EOFError:
print("⌁")
- exit(0)
-def widish_main(file_name, *shell_program_args: str):
+def widish_main(file_name, *args):
fd = file_diagram(file_name)
- path = Path(file_name)
- diagram_draw(path, fd)
+ diagram_draw(Path(file_name), fd)
constants = tuple(x.name for x in fd.dom)
runner = SHELL_RUNNER(fd)(*constants)
- # TODO pass stdin
- run_res = runner and runner("")
- print(*(tuple(x.rstrip() for x in tuplify(untuplify(run_res)) if x)), sep="\n")
+ result = runner("") if sys.stdin.isatty() else runner(sys.stdin.read())
+ print(*(x.rstrip() for x in tuplify(untuplify(result)) if x), sep="\n")
diff --git a/widip/widish.py b/widip/widish.py
index 997a6b4..c08f96e 100644
--- a/widip/widish.py
+++ b/widip/widish.py
@@ -1,79 +1,42 @@
from functools import partial
-from itertools import batched
-from subprocess import CalledProcessError, run
-
-from discopy.closed import Category, Functor, Ty, Box, Eval
+from subprocess import run
+import sys
from discopy.utils import tuplify, untuplify
-from discopy import python
+from discopy import closed, python
+def _run_subprocess(args, input_str=None):
+ return run(args, check=True, text=True, capture_output=True, input=input_str).stdout.rstrip("\n")
-io_ty = Ty("io")
+def _split_args(ar, args):
+ n = len(ar.dom)
+ return args[:n], args[n:]
-def run_native_subprocess(ar, *b):
- def run_native_subprocess_constant(*params):
- if not params:
- return "" if ar.dom == Ty() else ar.dom.name
- return untuplify(params)
- def run_native_subprocess_map(*params):
- # TODO cat then copy to two
- # but formal is to pass through
- mapped = []
- start = 0
- for (dk, k), (dv, v) in batched(zip(ar.dom, b), 2):
- # note that the key cod and value dom might be different
- b0 = k(*tuplify(params))
- res = untuplify(v(*tuplify(b0)))
- mapped.append(untuplify(res))
-
- return untuplify(tuple(mapped))
- def run_native_subprocess_seq(*params):
- b0 = b[0](*untuplify(params))
- res = untuplify(b[1](*tuplify(b0)))
- return res
- def run_native_subprocess_inside(*params):
- try:
- io_result = run(
- b,
- check=True, text=True, capture_output=True,
- input="\n".join(params) if params else None,
- )
- res = io_result.stdout.rstrip("\n")
- return res
- except CalledProcessError as e:
- return e.stderr
- if ar.name == "⌜−⌝":
- return run_native_subprocess_constant
- if ar.name == "(||)":
- return run_native_subprocess_map
- if ar.name == "(;)":
- return run_native_subprocess_seq
- if ar.name == "g":
- res = run_native_subprocess_inside(*b)
- return res
- if ar.name == "G":
- return run_native_subprocess_inside
+def _run_constant(ar, *args):
+ _, params = _split_args(ar, args)
+ return untuplify(params) if params else ("" if ar.dom == closed.Ty() else ar.dom.name)
-SHELL_RUNNER = Functor(
- lambda ob: str,
- lambda ar: partial(run_native_subprocess, ar),
- cod=Category(python.Ty, python.Function))
+def _run_map(ar, *args):
+ b, params = _split_args(ar, args)
+ return untuplify(tuple(untuplify(kv(*tuplify(params))) for kv in b))
+def _run_seq(ar, *args):
+ b, params = _split_args(ar, args)
+ return untuplify(b[1](*tuplify(b[0](*tuplify(params)))))
-SHELL_COMPILER = Functor(
- # lambda ob: Ty() if ob == Ty("io") else ob,
- lambda ob: ob,
- lambda ar: {
- # "ls": ar.curry().uncurry()
- }.get(ar.name, ar),)
- # TODO remove .inside[0] workaround
- # lambda ar: ar)
+def _run_default(ar, *args):
+ b, params = _split_args(ar, args)
+ return _run_subprocess((ar.name,) + b, "\n".join(params) if params else None)
+def _run_widip(ar, *args):
+ b, params = _split_args(ar, args)
+ return _run_subprocess((sys.executable, "-m", "widip") + b, "\n".join(params) if params else None)
-def compile_shell_program(diagram):
- """
- close input parameters (constants)
- drop outputs matching input parameters
- all boxes are io->[io]"""
- # TODO compile sequences and parallels to evals
- diagram = SHELL_COMPILER(diagram)
- return diagram
+SHELL_RUNNER = closed.Functor(
+ lambda ob: str,
+ lambda ar: {
+ "widip": partial(partial, _run_widip, ar),
+ "⌜−⌝": partial(partial, _run_constant, ar),
+ "(||)": partial(partial, _run_map, ar),
+ "(;)": partial(partial, _run_seq, ar),
+ }.get(ar.name, partial(partial, _run_default, ar)),
+ cod=closed.Category(python.Ty, python.Function))