Skip to content

Commit aa9ca13

Browse files
committed
Merge remote-tracking branch 'origin/main' into dz-csv-export-table-2
2 parents 7fa4441 + 33b156b commit aa9ca13

112 files changed

Lines changed: 8394 additions & 2706 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,16 +69,9 @@ jobs:
6969
with:
7070
version: ${{ matrix.julia-version }}
7171

72-
- name: Sync test data
73-
run: |
74-
mkdir -p sdks/julia/LogicalQueryProtocol/test/lqp sdks/julia/LogicalQueryProtocol/test/pretty sdks/julia/LogicalQueryProtocol/test/bin
75-
cp tests/lqp/*.lqp sdks/julia/LogicalQueryProtocol/test/lqp/
76-
cp tests/pretty/*.lqp sdks/julia/LogicalQueryProtocol/test/pretty/
77-
cp tests/bin/*.bin sdks/julia/LogicalQueryProtocol/test/bin/
78-
7972
- name: Run tests
8073
run: |
81-
cd sdks/julia/LogicalQueryProtocol
74+
cd sdks/julia/LogicalQueryProtocol.jl
8275
julia --project=. -e 'using Pkg; Pkg.instantiate(); Pkg.test()'
8376
8477
go-test:

.gitignore

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,6 @@ sdks/python/out/
1616
sdks/python/.lsp/.cache
1717
sdks/python/.clj-kondo
1818

19-
# Julia SDK
20-
sdks/julia/LogicalQueryProtocol/Manifest.toml
21-
sdks/julia/LogicalQueryProtocol/test/lqp/**
22-
sdks/julia/LogicalQueryProtocol/test/bin/**
23-
sdks/julia/LogicalQueryProtocol/test/pretty/**
24-
sdks/julia/LogicalQueryProtocol/test/lqp_output/**
25-
sdks/julia/LogicalQueryProtocol/test/lqp_output/**
26-
sdks/julia/LogicalQueryProtocol/test/lqp_pretty_output/**
27-
2819
# Go SDK
2920
sdks/go/test/lqp_pretty_output
3021

Makefile

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ GRAMMAR := meta/src/meta/grammar.y
3030

3131
# Generated protobuf outputs
3232
PY_PROTO_DIR := sdks/python/src/lqp/proto/v1
33-
JL_PROTO_DIR := sdks/julia/LogicalQueryProtocol/src/gen/relationalai/lqp/v1
33+
JL_PROTO_DIR := sdks/julia/LogicalQueryProtocol.jl/src/gen/relationalai/lqp/v1
3434
GO_PROTO_DIR := sdks/go/src/lqp/v1
3535

3636
# Generated parser outputs
3737
PY_PARSER := sdks/python/src/lqp/gen/parser.py
38-
JL_PARSER := sdks/julia/LogicalQueryProtocol/src/parser.jl
38+
JL_PARSER := sdks/julia/LogicalQueryProtocol.jl/src/parser.jl
3939
GO_PARSER := sdks/go/src/parser.go
4040

4141
# Generated printer outputs
4242
PY_PRINTER := sdks/python/src/lqp/gen/pretty.py
4343
GO_PRINTER := sdks/go/src/pretty.go
44-
JL_PRINTER := sdks/julia/LogicalQueryProtocol/src/pretty.jl
44+
JL_PRINTER := sdks/julia/LogicalQueryProtocol.jl/src/pretty.jl
4545

4646
# Parser templates
4747
PY_TEMPLATE := meta/src/meta/templates/parser.py.template
@@ -80,8 +80,7 @@ JL_PROTO_GENERATED := \
8080
force-printers force-printer-python force-printer-julia force-printer-go \
8181
test test-python update-snapshots test-julia test-go \
8282
test-meta check-python check-meta lint-meta format-meta \
83-
lint-python format-python clean \
84-
sync-julia-test-data
83+
lint-python format-python clean
8584

8685
all: protobuf parsers printers
8786

@@ -112,7 +111,7 @@ $(PY_PROTO_GENERATED) $(GO_PROTO_GENERATED): $(PROTO_FILES)
112111
$(JL_PROTO_GENERATED): $(PROTO_FILES)
113112
buf lint
114113
buf breaking --against ".git#branch=main,subdir=proto"
115-
cd sdks/julia && julia --project=LogicalQueryProtocol generate_proto.jl
114+
cd sdks/julia && julia --project=LogicalQueryProtocol.jl generate_proto.jl
116115

117116
# ---------- parser generation ----------
118117

@@ -124,7 +123,7 @@ $(PY_PARSER): $(PROTO_FILES) $(GRAMMAR) $(PY_TEMPLATE)
124123

125124
parser-julia: $(JL_PARSER)
126125
$(JL_PARSER): $(PROTO_FILES) $(GRAMMAR) $(JL_TEMPLATE)
127-
$(META_CLI) $(META_PROTO_ARGS) --parser julia -o ../sdks/julia/LogicalQueryProtocol/src/parser.jl
126+
$(META_CLI) $(META_PROTO_ARGS) --parser julia -o ../sdks/julia/LogicalQueryProtocol.jl/src/parser.jl
128127

129128
parser-go: $(GO_PARSER)
130129
$(GO_PARSER): $(PROTO_FILES) $(GRAMMAR) $(GO_TEMPLATE)
@@ -136,7 +135,7 @@ force-parser-python:
136135
$(META_CLI) $(META_PROTO_ARGS) --parser python -o ../sdks/python/src/lqp/gen/parser.py
137136

138137
force-parser-julia:
139-
$(META_CLI) $(META_PROTO_ARGS) --parser julia -o ../sdks/julia/LogicalQueryProtocol/src/parser.jl
138+
$(META_CLI) $(META_PROTO_ARGS) --parser julia -o ../sdks/julia/LogicalQueryProtocol.jl/src/parser.jl
140139

141140
force-parser-go:
142141
$(META_CLI) $(META_PROTO_ARGS) --parser go -o ../sdks/go/src/parser.go
@@ -151,7 +150,7 @@ $(PY_PRINTER): $(PROTO_FILES) $(GRAMMAR) $(PY_PRINTER_TEMPLATE)
151150

152151
printer-julia: $(JL_PRINTER)
153152
$(JL_PRINTER): $(PROTO_FILES) $(GRAMMAR) $(JL_PRINTER_TEMPLATE)
154-
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol/src/pretty.jl
153+
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol.jl/src/pretty.jl
155154

156155
printer-go: $(GO_PRINTER)
157156
$(GO_PRINTER): $(PROTO_FILES) $(GRAMMAR) $(GO_PRINTER_TEMPLATE)
@@ -163,7 +162,7 @@ force-printer-python:
163162
$(META_CLI) $(META_PROTO_ARGS) --printer python -o ../sdks/python/src/lqp/gen/pretty.py
164163

165164
force-printer-julia:
166-
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol/src/pretty.jl
165+
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol.jl/src/pretty.jl
167166

168167
force-printer-go:
169168
$(META_CLI) $(META_PROTO_ARGS) --printer go -o ../sdks/go/src/pretty.go
@@ -178,16 +177,8 @@ test-python: $(PY_PARSER) $(PY_PROTO_GENERATED) check-python
178177
update-snapshots: $(PY_PARSER) $(PY_PROTO_GENERATED)
179178
cd sdks/python && uv run python -m pytest --snapshot-update
180179

181-
JL_TEST_DIR := sdks/julia/LogicalQueryProtocol/test
182-
183-
sync-julia-test-data:
184-
@mkdir -p $(JL_TEST_DIR)/lqp $(JL_TEST_DIR)/pretty $(JL_TEST_DIR)/bin
185-
cp tests/lqp/*.lqp $(JL_TEST_DIR)/lqp/
186-
cp tests/pretty/*.lqp $(JL_TEST_DIR)/pretty/
187-
cp tests/bin/*.bin $(JL_TEST_DIR)/bin/
188-
189-
test-julia: $(JL_PARSER) $(JL_PROTO_GENERATED) sync-julia-test-data
190-
cd sdks/julia && julia --project=LogicalQueryProtocol -e 'using Pkg; Pkg.test()'
180+
test-julia: $(JL_PARSER) $(JL_PROTO_GENERATED)
181+
cd sdks/julia && julia --project=LogicalQueryProtocol.jl -e 'using Pkg; Pkg.test()'
191182

192183
test-go: $(GO_PARSER) $(GO_PROTO_GENERATED)
193184
cd sdks/go && go test ./test/...

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ grammar in `meta/src/meta/grammar.y`. From the grammar we can automatically deri
5858
and pretty printers for the S-expression representation of LQP, which is used for testing
5959
and debugging.
6060

61-
When you have updated the grammar, you can regenerate the parsers and verify that they match
62-
by running
61+
When you have updated the grammar, you can regenerate the SDKs (protobuf bindings, parser,
62+
and pretty printer for each language) and run the tests to verify them.
6363

6464
```
65-
make parsers
65+
make
6666
make test
6767
```
6868

@@ -76,8 +76,14 @@ The Python SDK is [deployed to PyPI](https://pypi.org/project/lqp/). Publishing
7676
via GitHub Actions: creating a GitHub release triggers a workflow that builds and uploads the
7777
package to PyPI. To release a new version:
7878

79-
1. Update the version in `sdks/python/pyproject.toml`
80-
2. Create a new GitHub release
79+
1. Make sure that everything is up-to-date by running `make`.
80+
2. File a PR that updates the version in `sdks/python/pyproject.toml` and
81+
`sdks/julia/LogicalQueryProtocol.jl/Project.toml`. Note that the Go SDK version is
82+
automatically determined by the release tag in GitHub.
83+
3. Get approval and merge into `main`.
84+
4. Create a new GitHub release from `main` with a tag matching the version (e.g.
85+
`gh release create v0.3.0 --title "v0.3.0" --generate-notes`). The release must be
86+
created after merging the version bump, since the workflow checks out the default branch.
8187

8288
To publish manually instead:
8389

meta/src/meta/cli.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,9 @@ def run(args) -> int:
296296
+ ["--printer", args.printer]
297297
)
298298
gen_fn = _PRINTER_GENERATORS[args.printer]
299-
output_text = gen_fn(grammar, command_line, proto_messages)
299+
output_text = gen_fn(
300+
grammar, command_line, proto_messages, proto_enums=proto_parser.enums
301+
)
300302
write_output(
301303
output_text,
302304
args.output,

meta/src/meta/codegen_base.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ class CodegenConfig:
6565

6666

6767
PARSER_CONFIG = CodegenConfig(
68-
receiver_type="Parser", receiver_var="p", first_param="parser::Parser"
68+
receiver_type="Parser", receiver_var="p", first_param="parser::ParserState"
6969
)
7070
PRINTER_CONFIG = CodegenConfig(
7171
receiver_type="PrettyPrinter", receiver_var="p", first_param="pp::PrettyPrinter"
@@ -1145,5 +1145,25 @@ def format_command_line_comment(self, command_line: str) -> str:
11451145
"""Format a command line comment for the generated file header."""
11461146
pass
11471147

1148+
def gen_pprint_dispatch_line(self, type_str: str, func_ref: str) -> str | None:
1149+
"""Generate a pprint dispatch line for the given type and function.
1150+
1151+
Returns None if the language does not support pprint dispatch generation.
1152+
"""
1153+
return None
1154+
1155+
def gen_dispatch_function(
1156+
self,
1157+
entries: list[tuple[str, str]],
1158+
enum_entries: list[tuple[str, str]],
1159+
) -> str:
1160+
"""Generate a dispatch function for all pretty printer entries.
1161+
1162+
Returns empty string by default. Override in language-specific
1163+
generators that need a centralized dispatch function (Go, Python).
1164+
Julia uses multiple dispatch instead.
1165+
"""
1166+
return ""
1167+
11481168
# Parser generation indent for parse method definitions
11491169
parse_def_indent: str = ""

meta/src/meta/codegen_go.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -947,6 +947,25 @@ def format_named_token_spec(self, token_name: str, token_pattern: str) -> str:
947947
def format_command_line_comment(self, command_line: str) -> str:
948948
return f"Command: {command_line}"
949949

950+
def gen_dispatch_function(
951+
self, entries: list[tuple[str, str]], enum_entries: list[tuple[str, str]]
952+
) -> str:
953+
"""Generate the Go type-switch pprintDispatch method."""
954+
lines: list[str] = []
955+
lines.append("func (p *PrettyPrinter) pprintDispatch(msg interface{}) {")
956+
lines.append("\tswitch m := msg.(type) {")
957+
for type_str, func_ref in entries:
958+
lines.append(f"\tcase {type_str}:")
959+
lines.append(f"\t\t{func_ref}(m)")
960+
for enum_type, func_ref in enum_entries:
961+
lines.append(f"\tcase {enum_type}:")
962+
lines.append(f"\t\t{func_ref}(m)")
963+
lines.append("\tdefault:")
964+
lines.append('\t\tpanic(fmt.Sprintf("no pretty printer for %T", msg))')
965+
lines.append("\t}")
966+
lines.append("}")
967+
return "\n".join(lines)
968+
950969

951970
def escape_identifier(name: str) -> str:
952971
"""Escape a Go identifier if it's a keyword."""

meta/src/meta/codegen_julia.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,9 @@ def gen_parse_nonterminal_ref(self, name: str) -> str:
210210
def gen_pretty_nonterminal_ref(self, name: str) -> str:
211211
return f"pretty_{name}"
212212

213+
def gen_pprint_dispatch_line(self, type_str: str, func_ref: str) -> str | None:
214+
return f"_pprint_dispatch(pp::PrettyPrinter, x::{type_str}) = {func_ref}(pp, x)"
215+
213216
# --- Type generation ---
214217

215218
def gen_message_type(self, module: str, name: str) -> str:
@@ -218,9 +221,10 @@ def gen_message_type(self, module: str, name: str) -> str:
218221
return f"Proto.{name}"
219222

220223
def gen_enum_type(self, module: str, name: str) -> str:
224+
# EnumX enums: the dispatch type is Proto.EnumName.T
221225
if name in self.keywords:
222-
return f'Proto.var"#{name}"'
223-
return f"Proto.{name}"
226+
return f'Proto.var"#{name}".T'
227+
return f"Proto.{name}.T"
224228

225229
def gen_enum_value(self, module: str, enum_name: str, value_name: str) -> str:
226230
if enum_name in self.keywords:

meta/src/meta/codegen_python.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@ def gen_pretty_nonterminal_ref(self, name: str) -> str:
155155
def gen_message_type(self, module: str, name: str) -> str:
156156
return f"{module}_pb2.{name}"
157157

158-
def gen_enum_type(self, module: str, name: str) -> str:
159-
return f"{module}_pb2.{name}"
158+
def gen_enum_type(self, module: str, name: str) -> str: # noqa: ARG002
159+
# Proto3 enums are int-valued in Python; use int for type annotations.
160+
return "int"
160161

161162
def gen_enum_value(self, module: str, enum_name: str, value_name: str) -> str:
162163
return f"{module}_pb2.{enum_name}.{value_name}"
@@ -392,6 +393,38 @@ def format_named_token_spec(self, token_name: str, token_pattern: str) -> str:
392393
def format_command_line_comment(self, command_line: str) -> str:
393394
return f"\nCommand: {command_line}\n"
394395

396+
def gen_dispatch_function(
397+
self, entries: list[tuple[str, str]], enum_entries: list[tuple[str, str]]
398+
) -> str:
399+
"""Generate the Python isinstance-chain pprint_dispatch method."""
400+
indent = self.parse_def_indent
401+
bi = indent + self.indent_str
402+
lines: list[str] = []
403+
lines.append(f"{indent}def pprint_dispatch(self, msg):")
404+
first = True
405+
for type_str, func_ref in entries:
406+
# Skip parameterized types (Sequence[T], tuple[...]) — these are
407+
# list-typed grammar nonterminals, not individual proto messages.
408+
if "[" in type_str:
409+
continue
410+
cond = "if" if first else "elif"
411+
first = False
412+
lines.append(f"{bi}{cond} isinstance(msg, {type_str}):")
413+
lines.append(f"{bi} {func_ref}(msg)")
414+
for enum_type, func_ref in enum_entries:
415+
# Enums in Python protobuf are ints, so we need type() check
416+
cond = "elif" if not first else "if"
417+
first = False
418+
lines.append(f"{bi}# enum: {enum_type}")
419+
lines.append(f"{bi}{cond} isinstance(msg, int):")
420+
lines.append(f"{bi} {func_ref}(msg)")
421+
if not first:
422+
lines.append(f"{bi}else:")
423+
lines.append(
424+
f'{bi} raise ParseError(f"no pretty printer for {{type(msg)}}")'
425+
)
426+
return "\n".join(lines)
427+
395428

396429
def escape_identifier(name: str) -> str:
397430
"""Escape a Python identifier if it's a keyword."""

meta/src/meta/codegen_templates.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ class BuiltinTemplate:
103103
"start_pretty_fragment": BuiltinTemplate(
104104
"{0}", ["self.start_pretty_fragment({0})"]
105105
),
106+
"format_bytes": BuiltinTemplate('"0x" + {0}.hex()'),
107+
"pp_dispatch": BuiltinTemplate("None", ["self.pprint_dispatch({0})"]),
108+
"get_at": BuiltinTemplate("{0}[{1}]"),
106109
}
107110

108111

@@ -166,12 +169,12 @@ class BuiltinTemplate:
166169
"indent_sexp_io": BuiltinTemplate("nothing", ["indent_sexp!(pp)"]),
167170
"dedent_io": BuiltinTemplate("nothing", ["dedent!(pp)"]),
168171
"try_flat_io": BuiltinTemplate("try_flat(pp, {0}, {1})"),
169-
"format_int64": BuiltinTemplate("string({0})"),
170-
"format_int32": BuiltinTemplate("string({0})"),
171-
"format_float64": BuiltinTemplate("format_float64({0})"),
172-
"format_string": BuiltinTemplate("format_string_value({0})"),
172+
"format_int64": BuiltinTemplate("format_int(pp, {0})"),
173+
"format_int32": BuiltinTemplate("format_int(pp, Int64({0}))"),
174+
"format_float64": BuiltinTemplate("format_float(pp, {0})"),
175+
"format_string": BuiltinTemplate("format_string(pp, {0})"),
173176
"format_symbol": BuiltinTemplate("{0}"),
174-
"format_bool": BuiltinTemplate('({0} ? "true" : "false")'),
177+
"format_bool": BuiltinTemplate("format_bool(pp, {0})"),
175178
"format_decimal": BuiltinTemplate("format_decimal(pp, {0})"),
176179
"format_int128": BuiltinTemplate("format_int128(pp, {0})"),
177180
"format_uint128": BuiltinTemplate("format_uint128(pp, {0})"),
@@ -185,6 +188,9 @@ class BuiltinTemplate:
185188
"relation_id_to_string": BuiltinTemplate("relation_id_to_string(pp, {0})"),
186189
"relation_id_to_uint128": BuiltinTemplate("relation_id_to_uint128(pp, {0})"),
187190
"start_pretty_fragment": BuiltinTemplate("{0}", ["start_pretty_fragment(pp, {0})"]),
191+
"format_bytes": BuiltinTemplate('"0x" * bytes2hex({0})'),
192+
"pp_dispatch": BuiltinTemplate("nothing", ["_pprint_dispatch(pp, {0})"]),
193+
"get_at": BuiltinTemplate("{0}[{1} + 1]"),
188194
}
189195

190196

@@ -271,6 +277,9 @@ class BuiltinTemplate:
271277
"relation_id_to_string": BuiltinTemplate("p.relationIdToString({0})"),
272278
"relation_id_to_uint128": BuiltinTemplate("p.relationIdToUint128({0})"),
273279
"start_pretty_fragment": BuiltinTemplate("{0}", ["p.startPrettyFragment({0})"]),
280+
"format_bytes": BuiltinTemplate('fmt.Sprintf("0x%x", {0})'),
281+
"pp_dispatch": BuiltinTemplate("nil", ["p.pprintDispatch({0})"]),
282+
"get_at": BuiltinTemplate("{0}[{1}]"),
274283
}
275284

276285
__all__ = [

0 commit comments

Comments
 (0)