Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ opentelemetry = "0.32"
opentelemetry-otlp = { version = "0.32", features = ["http-proto", "reqwest-blocking-client", "trace"] }
opentelemetry-stdout = { version = "0.32", features = ["trace"] }
opentelemetry_sdk = "0.32"
egglog = { git = "https://github.com/egraphs-good/egglog", rev = "90635860397ce710f8c0a4eeb04154a8ebc3ac05", default-features = false }
egglog-ast = { git = "https://github.com/egraphs-good/egglog", rev = "90635860397ce710f8c0a4eeb04154a8ebc3ac05" }
egglog-core-relations = { git = "https://github.com/egraphs-good/egglog", rev = "90635860397ce710f8c0a4eeb04154a8ebc3ac05" }
egglog-reports = { git = "https://github.com/egraphs-good/egglog", rev = "90635860397ce710f8c0a4eeb04154a8ebc3ac05" }
egglog-bridge = { git = "https://github.com/egraphs-good/egglog", rev = "90635860397ce710f8c0a4eeb04154a8ebc3ac05" }
egglog-experimental = { git = "https://github.com/egraphs-good/egglog-experimental.git", rev = "3d35f37a4befbfa75608aa6998408334e19b6180", default-features = false }
egglog = { git = "https://github.com/egraphs-good/egglog", rev = "998e60380e254d00b29651e88f2065fb33851a2f", default-features = false }
egglog-ast = { git = "https://github.com/egraphs-good/egglog", rev = "998e60380e254d00b29651e88f2065fb33851a2f" }
egglog-core-relations = { git = "https://github.com/egraphs-good/egglog", rev = "998e60380e254d00b29651e88f2065fb33851a2f" }
egglog-reports = { git = "https://github.com/egraphs-good/egglog", rev = "998e60380e254d00b29651e88f2065fb33851a2f" }
egglog-bridge = { git = "https://github.com/egraphs-good/egglog", rev = "998e60380e254d00b29651e88f2065fb33851a2f" }
egglog-experimental = { git = "https://github.com/egraphs-good/egglog-experimental.git", rev = "7a277e7fb217b30794b0bb2995ecc158983c5692", default-features = false }
egraph-serialize = { version = "0.3", features = ["serde", "graphviz"] }
serde_json = "1"
pyo3-log = "*"
Expand Down
11 changes: 10 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ classifiers = [
"Topic :: Software Development :: Interpreters",
"Typing :: Typed",
]
dependencies = ["typing-extensions>=4.13", "black", "graphviz", "anywidget", "cloudpickle>=3", "opentelemetry-api"]
dependencies = [
"typing-extensions>=4.13",
"black",
"graphviz",
"anywidget",
"cloudpickle>=3",
"opentelemetry-api",
"egglog-proto @ git+https://github.com/egraphs-good/egglog-experimental.git@6e0dbc97fbe9f3868c4662b9a6d73b5d01913c9d#subdirectory=gen/python",
]

[project.optional-dependencies]

Expand Down Expand Up @@ -269,6 +277,7 @@ local_partial_types = true
python-source = "python"
module-name = "egglog.bindings"
features = ["pyo3/extension-module"]
include = ["egglog/_builtin_catalog.pb"]

[tool.pytest.ini_options]
addopts = ["--import-mode=importlib", "--doctest-modules"]
Expand Down
Binary file added python/egglog/_builtin_catalog.pb
Binary file not shown.
130 changes: 130 additions & 0 deletions python/egglog/_catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""Derived Python presentation indexes over canonical declaration records."""

from __future__ import annotations

import keyword
from collections.abc import Iterable, Sequence
from functools import cache
from importlib.resources import files

from egglog_proto.egglog.v1 import egglog_pb as pb

from ._program import Builder, Ref, _definition_key


@cache
def builtin_catalog() -> Catalog:
"""Decode the native-generated package snapshot, without creating an engine."""
path = files("egglog").joinpath("_builtin_catalog.pb")
try:
encoded = path.read_bytes()
except FileNotFoundError as exc:
msg = "The native-generated builtin catalog is missing; regenerate the package resource"
raise RuntimeError(msg) from exc
return Catalog(pb.Program.from_binary(encoded))


def _python_path(path: Sequence[str]) -> tuple[str, ...]:
if not path or any(not part.isidentifier() or keyword.iskeyword(part) for part in path):
raise ValueError(f"Invalid Python path: {path!r}")
return tuple(path)


class Catalog:
"""
Own one catalog snapshot and rebuildable indexes, never copied signatures.

These indexes contain explicit supplied bindings. Absent Python bindings
require conservative wrapper derivation at generation, without installing
synthetic bindings; explicit/derived collisions are generation errors.
Engine installation and compatibility remain the shared adapter's job.
"""

def __init__(self, program: pb.Program) -> None:
self.owner = Builder.from_program(program).publish()
self.roots = tuple(self.owner.ref("declarations", index) for index in range(len(program.declarations)))
self.reindex()

@classmethod
def from_refs(cls, declarations: Iterable[Ref]) -> Catalog:
"""Index already-published definitions without copying their owners/identity."""
result = object.__new__(cls)
result.roots = tuple(dict.fromkeys(ref.owner.ref(ref.role, ref.index) for ref in declarations))
if any(reference.role != "declarations" for reference in result.roots):
msg = "Catalog roots must be canonical declaration references"
raise TypeError(msg)
result.reindex()
return result

def reindex(self) -> None: # noqa: C901, PLR0912
"""Discard all language indexes and derive them solely from owned records."""
definitions: dict[tuple[str, str], Ref] = {}
types: dict[tuple[str, ...], Ref] = {}
functions: dict[tuple[str, ...], tuple[Ref, int]] = {}
members: dict[tuple[str | None, str], tuple[Ref, int]] = {}
for ref in self.roots:
declaration: pb.Declaration = ref.read()
key = _definition_key(declaration)
assert key is not None
if key in definitions:
raise ValueError(f"Duplicate catalog definition: {key}")
definitions[key] = ref
assert declaration.kind is not None
kind = declaration.kind.value
if isinstance(kind, pb.HostSortFamily | pb.EqSort):
binding = kind.bindings
if binding is not None and binding.python is not None:
path = _python_path(binding.python.path)
if path in types or path in functions:
raise ValueError(f"Python binding collision: {path}")
types[path] = ref
continue
binding = declaration.bindings
if binding is None or binding.python is None:
continue
for view_index, view in enumerate(binding.python.views):
if view.kind == pb.PythonCallKind.FUNCTION:
path = _python_path(view.path)
if view.owner is not None or view.has_field("receiver"):
msg = "A free Python function cannot have an owner or receiver"
raise ValueError(msg)
if path in types or path in functions:
raise ValueError(f"Python binding collision: {path}")
functions[path] = ref, view_index
continue
if view.owner is None or view.owner.kind is None:
msg = "Python member requires an owner"
raise ValueError(msg)
owner_name = None
if view.owner.kind.field == "sort":
owner_sort: pb.Sort = ref.owner.ref("sorts", view.owner.kind.value).read()
if owner_sort.kind is None:
msg = "Python member owner has no sort kind"
raise ValueError(msg)
match owner_sort.kind.field:
case "eq":
owner_name = owner_sort.kind.value
case "family":
owner_name = owner_sort.kind.value.name
case _:
msg = "Python member requires an equality or host family owner"
raise ValueError(msg)
if view.kind == pb.PythonCallKind.INITIALIZER:
if view.path:
msg = "Python initializer derives its name and cannot supply a path"
raise ValueError(msg)
name = "__init__"
else:
path = _python_path(view.path)
if len(path) != 1:
msg = "Python member path must contain exactly one name"
raise ValueError(msg)
name = path[0]
member = owner_name, name
if member in members:
raise ValueError(f"Python binding collision: {member}")
members[member] = ref, view_index
self.definitions = definitions
self.types = types
self.functions = functions
self.members = members
Loading
Loading