Skip to content

Commit 7fa4441

Browse files
committed
Merge remote-tracking branch 'origin/main' into dz-csv-export-table-2
2 parents b5c3da9 + 9027dbf commit 7fa4441

78 files changed

Lines changed: 6503 additions & 4518 deletions

Some content is hidden

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

.gitignore

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,15 @@ sdks/python/.clj-kondo
1818

1919
# Julia SDK
2020
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_pretty_output
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+
28+
# Go SDK
29+
sdks/go/test/lqp_pretty_output
2630

2731
# Other
2832
.DS_Store

Makefile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ GO_TEMPLATE := meta/src/meta/templates/parser.go.template
5151
# Printer templates
5252
PY_PRINTER_TEMPLATE := meta/src/meta/templates/pretty_printer.py.template
5353
JL_PRINTER_TEMPLATE := meta/src/meta/templates/pretty_printer.jl.template
54+
GO_PRINTER_TEMPLATE := meta/src/meta/templates/pretty_printer.go.template
5455

5556
META_CLI := cd meta && uv run python -m meta.cli
5657
META_PROTO_ARGS := \
@@ -153,8 +154,8 @@ $(JL_PRINTER): $(PROTO_FILES) $(GRAMMAR) $(JL_PRINTER_TEMPLATE)
153154
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol/src/pretty.jl
154155

155156
printer-go: $(GO_PRINTER)
156-
$(GO_PRINTER):
157-
@echo "Generating the Go pretty printer is not yet supported"
157+
$(GO_PRINTER): $(PROTO_FILES) $(GRAMMAR) $(GO_PRINTER_TEMPLATE)
158+
$(META_CLI) $(META_PROTO_ARGS) --printer go -o ../sdks/go/src/pretty.go
158159

159160
force-printers: force-printer-python force-printer-julia force-printer-go
160161

@@ -165,7 +166,7 @@ force-printer-julia:
165166
$(META_CLI) $(META_PROTO_ARGS) --printer julia -o ../sdks/julia/LogicalQueryProtocol/src/pretty.jl
166167

167168
force-printer-go:
168-
@echo "Generating the Go pretty printer is not yet supported"
169+
$(META_CLI) $(META_PROTO_ARGS) --printer go -o ../sdks/go/src/pretty.go
169170

170171
# ---------- testing ----------
171172

meta/src/meta/cli.py

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,30 @@
2727

2828
from .grammar import Grammar
2929
from .grammar_validator import validate_grammar
30+
from .parser_gen_go import generate_parser_go
31+
from .parser_gen_julia import generate_parser_julia
32+
from .parser_gen_python import generate_parser_python
33+
from .pretty_gen_go import generate_pretty_printer_go
34+
from .pretty_gen_julia import generate_pretty_printer_julia
35+
from .pretty_gen_python import generate_pretty_printer_python
3036
from .proto_parser import ProtoParser
3137
from .proto_print import format_enum, format_message
3238
from .yacc_parser import load_yacc_grammar_file
3339

40+
_PARSER_GENERATORS = {
41+
"python": generate_parser_python,
42+
"julia": generate_parser_julia,
43+
"go": generate_parser_go,
44+
}
45+
46+
_PRINTER_GENERATORS = {
47+
"python": generate_pretty_printer_python,
48+
"julia": generate_pretty_printer_julia,
49+
"go": generate_pretty_printer_go,
50+
}
51+
52+
_LANGUAGES = sorted(set(_PARSER_GENERATORS) | set(_PRINTER_GENERATORS))
53+
3454

3555
def parse_args():
3656
"""Parse command-line arguments."""
@@ -56,14 +76,14 @@ def parse_args():
5676
output_group.add_argument(
5777
"--parser",
5878
type=str,
59-
choices=["ir", "python", "julia", "go"],
60-
help="Output the generated parser (ir, python, julia, or go)",
79+
choices=["ir"] + _LANGUAGES,
80+
help="Output the generated parser",
6181
)
6282
output_group.add_argument(
6383
"--printer",
6484
type=str,
65-
choices=["ir", "python", "julia"],
66-
help="Output the generated pretty printer (ir, python, or julia)",
85+
choices=["ir"] + _LANGUAGES,
86+
help="Output the generated pretty printer",
6787
)
6888

6989
args = parser.parse_args()
@@ -240,29 +260,15 @@ def run(args) -> int:
240260
args.output,
241261
f"Generated parser IR written to {args.output}",
242262
)
243-
elif args.parser in ("python", "julia", "go"):
263+
elif args.parser in _PARSER_GENERATORS:
244264
command_line = " ".join(
245265
["python -m meta.cli"]
246266
+ [str(f) for f in args.proto_files]
247267
+ ["--grammar", str(args.grammar)]
248268
+ ["--parser", args.parser]
249269
)
250-
if args.parser == "python":
251-
from .parser_gen_python import generate_parser_python
252-
253-
output_text = generate_parser_python(
254-
grammar, command_line, proto_messages
255-
)
256-
elif args.parser == "julia":
257-
from .parser_gen_julia import generate_parser_julia
258-
259-
output_text = generate_parser_julia(
260-
grammar, command_line, proto_messages
261-
)
262-
else:
263-
from .parser_gen_go import generate_parser_go
264-
265-
output_text = generate_parser_go(grammar, command_line, proto_messages)
270+
gen_fn = _PARSER_GENERATORS[args.parser]
271+
output_text = gen_fn(grammar, command_line, proto_messages)
266272
write_output(
267273
output_text, args.output, f"Generated parser written to {args.output}"
268274
)
@@ -282,27 +288,15 @@ def run(args) -> int:
282288
args.output,
283289
f"Generated printer IR written to {args.output}",
284290
)
285-
elif args.printer in ("python", "julia"):
291+
elif args.printer in _PRINTER_GENERATORS:
286292
command_line = " ".join(
287293
["python -m meta.cli"]
288294
+ [str(f) for f in args.proto_files]
289295
+ ["--grammar", str(args.grammar)]
290296
+ ["--printer", args.printer]
291297
)
292-
if args.printer == "python":
293-
from .pretty_gen_python import generate_pretty_printer_python
294-
295-
output_text = generate_pretty_printer_python(
296-
grammar, command_line, proto_messages
297-
)
298-
elif args.printer == "julia":
299-
from .pretty_gen_julia import generate_pretty_printer_julia
300-
301-
output_text = generate_pretty_printer_julia(
302-
grammar, command_line, proto_messages
303-
)
304-
else:
305-
assert False
298+
gen_fn = _PRINTER_GENERATORS[args.printer]
299+
output_text = gen_fn(grammar, command_line, proto_messages)
306300
write_output(
307301
output_text,
308302
args.output,

meta/src/meta/codegen_base.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -829,13 +829,7 @@ def _generate_IfElse(
829829
return result.value
830830

831831
# Determine expression type for typed variable declarations.
832-
type_hint = None
833-
try:
834-
expr_type = expr.target_type()
835-
if expr_type is not None:
836-
type_hint = self.gen_type(expr_type)
837-
except (NotImplementedError, ValueError, TypeError):
838-
pass
832+
type_hint = self._ifelse_type_hint(expr)
839833

840834
tmp = gensym()
841835
var_decl = self.gen_var_declaration(tmp, type_hint)
@@ -866,6 +860,16 @@ def _generate_IfElse(
866860

867861
return tmp
868862

863+
def _ifelse_type_hint(self, expr: IfElse) -> str | None:
864+
"""Compute the type hint for the merge variable of an IfElse expression."""
865+
try:
866+
expr_type = expr.target_type()
867+
if expr_type is not None:
868+
return self.gen_type(expr_type)
869+
except (NotImplementedError, ValueError, TypeError):
870+
pass
871+
return None
872+
869873
def _generate_nil_else_branch(
870874
self,
871875
tmp: str,

meta/src/meta/codegen_go.py

Lines changed: 74 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
with proper keyword escaping and idiomatic Go style.
55
"""
66

7-
from .codegen_base import CodeGenerator
7+
from .codegen_base import PARSER_CONFIG, CodegenConfig, CodeGenerator
88
from .codegen_templates import GO_TEMPLATES
99
from .gensym import gensym
1010
from .target import (
1111
Call,
1212
FunDef,
1313
GetElement,
14+
Let,
1415
ListExpr,
1516
NewMessage,
1617
OneOf,
@@ -118,13 +119,14 @@ def _is_nullable_go_type(type_str: str) -> bool:
118119
"[]byte": "nil",
119120
}
120121

121-
def __init__(self, proto_messages=None):
122-
super().__init__(proto_messages)
122+
def __init__(self, proto_messages=None, config: CodegenConfig = PARSER_CONFIG):
123+
super().__init__(proto_messages, config)
123124
self._oneof_field_to_parent = self._build_oneof_field_map()
124125
self._declared_vars: set[str] = set()
125126
self._current_return_type: str | None = None
126127
self._current_return_is_option: bool = False
127128
self._current_return_option_needs_ptr: bool = False
129+
self._lambda_return_type_stack: list[str | None] = []
128130
self._register_builtins()
129131

130132
def reset_declared_vars(self) -> None:
@@ -384,12 +386,42 @@ def gen_func_def_header(
384386
params_str = ", ".join(f"{n} {t}" for n, t in params)
385387
ret = f" {return_type}" if return_type else ""
386388
if is_method:
387-
return f"func (p *Parser) {name}({params_str}){ret} {{"
389+
return f"func (p *{self.config.receiver_type}) {name}({params_str}){ret} {{"
388390
return f"func {name}({params_str}){ret} {{"
389391

390392
def gen_func_def_end(self) -> str:
391393
return "}"
392394

395+
def _generate_Lambda(self, expr, lines: list[str], indent: str) -> str:
396+
"""Track lambda return type for IfElse type hint inference."""
397+
from .target import Lambda
398+
399+
assert isinstance(expr, Lambda)
400+
ret_type = (
401+
self.gen_type(expr.return_type)
402+
if expr.return_type and not self._is_void_type(expr.return_type)
403+
else None
404+
)
405+
self._lambda_return_type_stack.append(ret_type)
406+
try:
407+
result = super()._generate_Lambda(expr, lines, indent)
408+
finally:
409+
self._lambda_return_type_stack.pop()
410+
return result
411+
412+
def _ifelse_type_hint(self, expr) -> str | None:
413+
"""Improve IfElse type hint when the overall type resolves to interface{}.
414+
415+
When the IfElse type has unresolved type variables (yielding interface{}),
416+
use the enclosing lambda's return type as a better hint.
417+
"""
418+
hint = super()._ifelse_type_hint(expr)
419+
if hint == "interface{}" and self._lambda_return_type_stack:
420+
lambda_ret = self._lambda_return_type_stack[-1]
421+
if lambda_ret is not None and lambda_ret != "interface{}":
422+
return lambda_ret
423+
return hint
424+
393425
def _generate_nil_else_branch(
394426
self,
395427
tmp: str,
@@ -403,9 +435,23 @@ def _generate_nil_else_branch(
403435
def _generate_GetElement(
404436
self, expr: GetElement, lines: list[str], indent: str
405437
) -> str:
406-
"""Go uses 0-based indexing with type assertion for tuple elements."""
438+
"""Go uses 0-based indexing with type assertion for tuple elements.
439+
440+
Type assertions are only needed when the container is []interface{}
441+
(Go tuple). For typed slices (protobuf repeated fields), the element
442+
type is already correct and assertion would fail.
443+
"""
444+
from .target import ListType, SequenceType
445+
407446
tuple_code = self.generate_lines(expr.tuple_expr, lines, indent)
408-
# Add type assertion since tuple elements are interface{}
447+
# Check if the container is a typed slice (no assertion needed)
448+
try:
449+
container_type = expr.tuple_expr.target_type()
450+
if isinstance(container_type, (SequenceType, ListType)):
451+
return f"{tuple_code}[{expr.index}]"
452+
except (NotImplementedError, ValueError, TypeError):
453+
pass
454+
# For tuples ([]interface{}), add type assertion
409455
try:
410456
elem_type = expr.target_type()
411457
if elem_type is not None:
@@ -444,6 +490,23 @@ def _generate_GetField(self, expr, lines: list[str], indent: str) -> str:
444490
return f"{obj_code}.{pascal_field}"
445491
return super()._generate_GetField(expr, lines, indent)
446492

493+
def _generate_Let(self, expr: Let, lines: list[str], indent: str) -> str | None:
494+
"""Generate Go let binding, suppressing unused variable errors."""
495+
var_name = self.escape_identifier(expr.var.name)
496+
init_val = self.generate_lines(expr.init, lines, indent)
497+
assert init_val is not None, "Let initializer should not contain a return"
498+
lines.append(
499+
f"{indent}{self.gen_assignment(var_name, init_val, is_declaration=True)}"
500+
)
501+
body_start = len(lines)
502+
result = self.generate_lines(expr.body, lines, indent)
503+
# Suppress unused variable if the body didn't reference it.
504+
body_lines = lines[body_start:]
505+
var_used = any(var_name in line for line in body_lines) or result == var_name
506+
if not var_used:
507+
lines.insert(body_start, f"{indent}_ = {var_name}")
508+
return result
509+
447510
def _generate_Seq(self, expr: Seq, lines: list[str], indent: str) -> str | None:
448511
"""Generate Go sequence, suppressing unused variable errors.
449512
@@ -643,6 +706,9 @@ def _generate_Call(self, expr: Call, lines: list[str], indent: str) -> str | Non
643706
)
644707
args.append(arg_code)
645708
args_code = ", ".join(args)
709+
if self._is_void_expr(expr):
710+
lines.append(f"{indent}{f}({args_code})")
711+
return self.gen_none()
646712
tmp = gensym()
647713
lines.append(
648714
f"{indent}{self.gen_assignment(tmp, f'{f}({args_code})', is_declaration=True)}"
@@ -731,11 +797,8 @@ def _generate_option_builtin(
731797
if self._is_nullable_go_type(inner_go):
732798
return arg_code
733799
else:
734-
# Non-OptionType (e.g., oneof fields): check if the Go
735-
# type is already nullable, in which case no deref needed.
736-
go_type = self.gen_type(arg_type)
737-
if self._is_nullable_go_type(go_type):
738-
return arg_code
800+
# Non-OptionType: return as-is (no deref needed).
801+
return arg_code
739802
except (NotImplementedError, ValueError, TypeError):
740803
pass
741804
return f"*{arg_code}"

meta/src/meta/codegen_templates.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class BuiltinTemplate:
194194
# The templates here serve as fallbacks.
195195
GO_TEMPLATES: dict[str, BuiltinTemplate] = {
196196
"some": BuiltinTemplate("ptr({0})"),
197-
"not": BuiltinTemplate("!{0}"),
197+
"not": BuiltinTemplate("!({0})"),
198198
"and": BuiltinTemplate("({0} && {1})"),
199199
"or": BuiltinTemplate("({0} || {1})"),
200200
"equal": BuiltinTemplate("{0} == {1}"),
@@ -224,6 +224,7 @@ class BuiltinTemplate:
224224
"map": BuiltinTemplate("mapSlice({1}, {0})"),
225225
"list_push": BuiltinTemplate("nil", ["{0} = append({0}, {1})"]),
226226
"list_concat": BuiltinTemplate("listConcat({0}, {1})"),
227+
"list_slice": BuiltinTemplate("{0}[{1}:{2}]"),
227228
"list_sort": BuiltinTemplate("listSort({0})"),
228229
"fragment_id_from_string": BuiltinTemplate("&pb.FragmentId{{Id: []byte({0})}}"),
229230
"relation_id_from_string": BuiltinTemplate("p.relationIdFromString({0})"),
@@ -250,10 +251,10 @@ class BuiltinTemplate:
250251
"indent_io": BuiltinTemplate("nil", ["p.indent()"]),
251252
"indent_sexp_io": BuiltinTemplate("nil", ["p.indentSexp()"]),
252253
"dedent_io": BuiltinTemplate("nil", ["p.dedent()"]),
253-
"try_flat_io": BuiltinTemplate("nil"),
254+
"try_flat_io": BuiltinTemplate("p.tryFlat({0}, func() {{ {1}({0}) }})"),
254255
"format_int64": BuiltinTemplate('fmt.Sprintf("%d", {0})'),
255256
"format_int32": BuiltinTemplate('fmt.Sprintf("%d", {0})'),
256-
"format_float64": BuiltinTemplate('fmt.Sprintf("%g", {0})'),
257+
"format_float64": BuiltinTemplate("formatFloat64({0})"),
257258
"format_string": BuiltinTemplate("p.formatStringValue({0})"),
258259
"format_symbol": BuiltinTemplate("{0}"),
259260
"format_bool": BuiltinTemplate("formatBool({0})"),
@@ -269,6 +270,7 @@ class BuiltinTemplate:
269270
"fragment_id_to_string": BuiltinTemplate("p.fragmentIdToString({0})"),
270271
"relation_id_to_string": BuiltinTemplate("p.relationIdToString({0})"),
271272
"relation_id_to_uint128": BuiltinTemplate("p.relationIdToUint128({0})"),
273+
"start_pretty_fragment": BuiltinTemplate("{0}", ["p.startPrettyFragment({0})"]),
272274
}
273275

274276
__all__ = [

0 commit comments

Comments
 (0)