Skip to content

Commit a961946

Browse files
committed
Add distinct entry points for parsing txns and fragments
1 parent 2f78895 commit a961946

9 files changed

Lines changed: 266 additions & 15 deletions

File tree

meta/src/meta/templates/parser.go.template

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,8 @@ func toPascalCase(s string) string {{
501501
// --- Parse functions ---
502502
{parse_nonterminal_defns}
503503

504-
// Parse parses the input string and returns the result
505-
func Parse(input string) (result *pb.Transaction, err error) {{
504+
// ParseTransaction parses the input string and returns a Transaction
505+
func ParseTransaction(input string) (result *pb.Transaction, err error) {{
506506
defer func() {{
507507
if r := recover(); r != nil {{
508508
if pe, ok := r.(ParseError); ok {{
@@ -526,3 +526,34 @@ func Parse(input string) (result *pb.Transaction, err error) {{
526526
}}
527527
return result, nil
528528
}}
529+
530+
// ParseFragment parses the input string and returns a Fragment
531+
func ParseFragment(input string) (result *pb.Fragment, err error) {{
532+
defer func() {{
533+
if r := recover(); r != nil {{
534+
if pe, ok := r.(ParseError); ok {{
535+
err = pe
536+
return
537+
}}
538+
panic(r)
539+
}}
540+
}}()
541+
542+
lexer := NewLexer(input)
543+
parser := NewParser(lexer.tokens)
544+
result = parser.parse_fragment()
545+
546+
// Check for unconsumed tokens (except EOF)
547+
if parser.pos < len(parser.tokens) {{
548+
remainingToken := parser.lookahead(0)
549+
if remainingToken.Type != "$" {{
550+
return nil, ParseError{{msg: fmt.Sprintf("Unexpected token at end of input: %v", remainingToken)}}
551+
}}
552+
}}
553+
return result, nil
554+
}}
555+
556+
// Parse parses the input string and returns a Transaction
557+
func Parse(input string) (result *pb.Transaction, err error) {{
558+
return ParseTransaction(input)
559+
}}

meta/src/meta/templates/parser.jl.template

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ end
279279
# --- Parse functions ---
280280
{parse_nonterminal_defns}
281281

282-
function parse(input::String)
282+
function parse_transaction(input::String)
283283
lexer = Lexer(input)
284284
parser = ParserState(lexer.tokens)
285285
result = parse_{start_name}(parser)
@@ -293,8 +293,26 @@ function parse(input::String)
293293
return result
294294
end
295295

296-
# Export main parse function and error type
297-
export parse, ParseError
296+
function parse_fragment(input::String)
297+
lexer = Lexer(input)
298+
parser = ParserState(lexer.tokens)
299+
result = parse_fragment(parser)
300+
# Check for unconsumed tokens (except EOF)
301+
if parser.pos <= length(parser.tokens)
302+
remaining_token = lookahead(parser, 0)
303+
if remaining_token.type != "\$"
304+
throw(ParseError("Unexpected token at end of input: $remaining_token"))
305+
end
306+
end
307+
return result
308+
end
309+
310+
function parse(input::String)
311+
return parse_transaction(input)
312+
end
313+
314+
# Export main parse functions and error type
315+
export parse, parse_transaction, parse_fragment, ParseError
298316
# Export scanner functions for testing
299317
export scan_string, scan_int, scan_float, scan_int128, scan_uint128, scan_decimal
300318
# Export Lexer for testing

meta/src/meta/templates/parser.py.template

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,8 @@ class Parser:
271271
# --- Parse methods ---
272272
{parse_nonterminal_defns}
273273

274-
def parse(input_str: str) -> Any:
275-
"""Parse input string and return parse tree."""
274+
def parse_transaction(input_str: str) -> Any:
275+
"""Parse input string and return a Transaction."""
276276
lexer = Lexer(input_str)
277277
parser = Parser(lexer.tokens)
278278
result = parser.parse_{start_name}()
@@ -282,3 +282,21 @@ def parse(input_str: str) -> Any:
282282
if remaining_token.type != "$":
283283
raise ParseError(f"Unexpected token at end of input: {{remaining_token}}")
284284
return result
285+
286+
287+
def parse_fragment(input_str: str) -> Any:
288+
"""Parse input string and return a Fragment."""
289+
lexer = Lexer(input_str)
290+
parser = Parser(lexer.tokens)
291+
result = parser.parse_fragment()
292+
# Check for unconsumed tokens (except EOF)
293+
if parser.pos < len(parser.tokens):
294+
remaining_token = parser.lookahead(0)
295+
if remaining_token.type != "$":
296+
raise ParseError(f"Unexpected token at end of input: {{remaining_token}}")
297+
return result
298+
299+
300+
def parse(input_str: str) -> Any:
301+
"""Parse input string and return a Transaction."""
302+
return parse_transaction(input_str)

sdks/go/src/parser.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3741,8 +3741,8 @@ func (p *Parser) parse_export_csv_column() *pb.ExportCSVColumn {
37413741
}
37423742

37433743

3744-
// Parse parses the input string and returns the result
3745-
func Parse(input string) (result *pb.Transaction, err error) {
3744+
// ParseTransaction parses the input string and returns a Transaction
3745+
func ParseTransaction(input string) (result *pb.Transaction, err error) {
37463746
defer func() {
37473747
if r := recover(); r != nil {
37483748
if pe, ok := r.(ParseError); ok {
@@ -3766,3 +3766,34 @@ func Parse(input string) (result *pb.Transaction, err error) {
37663766
}
37673767
return result, nil
37683768
}
3769+
3770+
// ParseFragment parses the input string and returns a Fragment
3771+
func ParseFragment(input string) (result *pb.Fragment, err error) {
3772+
defer func() {
3773+
if r := recover(); r != nil {
3774+
if pe, ok := r.(ParseError); ok {
3775+
err = pe
3776+
return
3777+
}
3778+
panic(r)
3779+
}
3780+
}()
3781+
3782+
lexer := NewLexer(input)
3783+
parser := NewParser(lexer.tokens)
3784+
result = parser.parse_fragment()
3785+
3786+
// Check for unconsumed tokens (except EOF)
3787+
if parser.pos < len(parser.tokens) {
3788+
remainingToken := parser.lookahead(0)
3789+
if remainingToken.Type != "$" {
3790+
return nil, ParseError{msg: fmt.Sprintf("Unexpected token at end of input: %v", remainingToken)}
3791+
}
3792+
}
3793+
return result, nil
3794+
}
3795+
3796+
// Parse parses the input string and returns a Transaction
3797+
func Parse(input string) (result *pb.Transaction, err error) {
3798+
return ParseTransaction(input)
3799+
}

sdks/go/test/parser_test.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,65 @@ func TestBasicParsing(t *testing.T) {
3232
}
3333
}
3434

35+
// TestParseTransaction tests the ParseTransaction entry point.
36+
func TestParseTransaction(t *testing.T) {
37+
input := `(transaction (epoch (writes) (reads)))`
38+
result, err := lqp.ParseTransaction(input)
39+
if err != nil {
40+
t.Fatalf("Failed to parse transaction: %v", err)
41+
}
42+
if result == nil {
43+
t.Fatal("ParseTransaction returned nil")
44+
}
45+
if len(result.Epochs) != 1 {
46+
t.Errorf("Expected 1 epoch, got %d", len(result.Epochs))
47+
}
48+
}
49+
50+
// TestParseFragment tests the ParseFragment entry point.
51+
func TestParseFragment(t *testing.T) {
52+
input := `(fragment :test_frag (def :my_rel ([x::INT] (relatom :my_rel x))))`
53+
result, err := lqp.ParseFragment(input)
54+
if err != nil {
55+
t.Fatalf("Failed to parse fragment: %v", err)
56+
}
57+
if result == nil {
58+
t.Fatal("ParseFragment returned nil")
59+
}
60+
}
61+
62+
// TestParseDelegatesToParseTransaction verifies Parse and ParseTransaction return equal results.
63+
func TestParseDelegatesToParseTransaction(t *testing.T) {
64+
input := `(transaction (epoch (writes) (reads)))`
65+
r1, err := lqp.Parse(input)
66+
if err != nil {
67+
t.Fatalf("Parse failed: %v", err)
68+
}
69+
r2, err := lqp.ParseTransaction(input)
70+
if err != nil {
71+
t.Fatalf("ParseTransaction failed: %v", err)
72+
}
73+
if !proto.Equal(r1, r2) {
74+
t.Error("Parse and ParseTransaction should return equal results")
75+
}
76+
}
77+
78+
// TestParseFragmentRejectsTransaction verifies ParseFragment rejects transaction input.
79+
func TestParseFragmentRejectsTransaction(t *testing.T) {
80+
_, err := lqp.ParseFragment(`(transaction (epoch (writes) (reads)))`)
81+
if err == nil {
82+
t.Error("ParseFragment should reject transaction input")
83+
}
84+
}
85+
86+
// TestParseTransactionRejectsFragment verifies ParseTransaction rejects fragment input.
87+
func TestParseTransactionRejectsFragment(t *testing.T) {
88+
_, err := lqp.ParseTransaction(`(fragment :f (def :r ([x::INT] (relatom :r x))))`)
89+
if err == nil {
90+
t.Error("ParseTransaction should reject fragment input")
91+
}
92+
}
93+
3594
// TestParseLQPFiles parses all LQP files and compares against binary snapshots.
3695
func TestParseLQPFiles(t *testing.T) {
3796
root := repoRoot(t)

sdks/julia/LogicalQueryProtocol.jl/src/parser.jl

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3192,7 +3192,7 @@ function parse_export_csv_column(parser::ParserState)::Proto.ExportCSVColumn
31923192
end
31933193

31943194

3195-
function parse(input::String)
3195+
function parse_transaction(input::String)
31963196
lexer = Lexer(input)
31973197
parser = ParserState(lexer.tokens)
31983198
result = parse_transaction(parser)
@@ -3206,8 +3206,26 @@ function parse(input::String)
32063206
return result
32073207
end
32083208

3209-
# Export main parse function and error type
3210-
export parse, ParseError
3209+
function parse_fragment(input::String)
3210+
lexer = Lexer(input)
3211+
parser = ParserState(lexer.tokens)
3212+
result = parse_fragment(parser)
3213+
# Check for unconsumed tokens (except EOF)
3214+
if parser.pos <= length(parser.tokens)
3215+
remaining_token = lookahead(parser, 0)
3216+
if remaining_token.type != "\$"
3217+
throw(ParseError("Unexpected token at end of input: $remaining_token"))
3218+
end
3219+
end
3220+
return result
3221+
end
3222+
3223+
function parse(input::String)
3224+
return parse_transaction(input)
3225+
end
3226+
3227+
# Export main parse functions and error type
3228+
export parse, parse_transaction, parse_fragment, ParseError
32113229
# Export scanner functions for testing
32123230
export scan_string, scan_int, scan_float, scan_int128, scan_uint128, scan_decimal
32133231
# Export Lexer for testing

sdks/julia/LogicalQueryProtocol.jl/test/parser_tests.jl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,34 @@ end
112112
@test r.scale == 1
113113
end
114114

115+
@testitem "parse_transaction entry point" setup=[ParserSetup] begin
116+
input = "(transaction (epoch (writes) (reads)))"
117+
result = Parser.parse_transaction(input)
118+
@test result isa Proto.Transaction
119+
@test length(result.epochs) == 1
120+
end
121+
122+
@testitem "parse_fragment entry point" setup=[ParserSetup] begin
123+
input = "(fragment :test_frag (def :my_rel ([x::INT] (relatom :my_rel x))))"
124+
result = Parser.parse_fragment(input)
125+
@test result isa Proto.Fragment
126+
end
127+
128+
@testitem "parse delegates to parse_transaction" setup=[ParserSetup] begin
129+
input = "(transaction (epoch (writes) (reads)))"
130+
@test Parser.parse(input) == Parser.parse_transaction(input)
131+
end
132+
133+
@testitem "parse_fragment rejects transaction" setup=[ParserSetup] begin
134+
@test_throws ParseError Parser.parse_fragment("(transaction (epoch (writes) (reads)))")
135+
end
136+
137+
@testitem "parse_transaction rejects fragment" setup=[ParserSetup] begin
138+
@test_throws ParseError Parser.parse_transaction(
139+
"(fragment :f (def :r ([x::INT] (relatom :r x))))"
140+
)
141+
end
142+
115143
@testitem "Parser - Lexer tokenization" setup=[ParserSetup] begin
116144
lexer = Lexer("(transaction (epoch (writes) (reads)))")
117145
# Tokens: ( transaction ( epoch ( writes ) ( reads ) ) ) $

sdks/python/src/lqp/gen/parser.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2849,8 +2849,8 @@ def parse_export_csv_column(self) -> transactions_pb2.ExportCSVColumn:
28492849
return _t1310
28502850

28512851

2852-
def parse(input_str: str) -> Any:
2853-
"""Parse input string and return parse tree."""
2852+
def parse_transaction(input_str: str) -> Any:
2853+
"""Parse input string and return a Transaction."""
28542854
lexer = Lexer(input_str)
28552855
parser = Parser(lexer.tokens)
28562856
result = parser.parse_transaction()
@@ -2860,3 +2860,21 @@ def parse(input_str: str) -> Any:
28602860
if remaining_token.type != "$":
28612861
raise ParseError(f"Unexpected token at end of input: {remaining_token}")
28622862
return result
2863+
2864+
2865+
def parse_fragment(input_str: str) -> Any:
2866+
"""Parse input string and return a Fragment."""
2867+
lexer = Lexer(input_str)
2868+
parser = Parser(lexer.tokens)
2869+
result = parser.parse_fragment()
2870+
# Check for unconsumed tokens (except EOF)
2871+
if parser.pos < len(parser.tokens):
2872+
remaining_token = parser.lookahead(0)
2873+
if remaining_token.type != "$":
2874+
raise ParseError(f"Unexpected token at end of input: {remaining_token}")
2875+
return result
2876+
2877+
2878+
def parse(input_str: str) -> Any:
2879+
"""Parse input string and return a Transaction."""
2880+
return parse_transaction(input_str)

sdks/python/tests/test_parser.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import pytest
44
from pytest_snapshot.plugin import Snapshot
55

6-
from lqp.gen.parser import parse
6+
from lqp.gen.parser import ParseError, parse, parse_fragment, parse_transaction
7+
from lqp.proto.v1 import fragments_pb2, transactions_pb2
78

89
from .utils import BIN_SNAPSHOTS_DIR, get_lqp_input_files
910

@@ -20,3 +21,32 @@ def test_parse_lqp(snapshot: Snapshot, input_file):
2021
snapshot.snapshot_dir = BIN_SNAPSHOTS_DIR
2122
snapshot_filename = os.path.basename(input_file).replace(".lqp", ".bin")
2223
snapshot.assert_match(binary_output, snapshot_filename)
24+
25+
26+
_SIMPLE_TXN = "(transaction (epoch (writes) (reads)))"
27+
_SIMPLE_FRAGMENT = "(fragment :test_frag (def :my_rel ([x::INT] (relatom :my_rel x))))"
28+
29+
30+
def test_parse_transaction():
31+
result = parse_transaction(_SIMPLE_TXN)
32+
assert isinstance(result, transactions_pb2.Transaction)
33+
assert len(result.epochs) == 1
34+
35+
36+
def test_parse_fragment():
37+
result = parse_fragment(_SIMPLE_FRAGMENT)
38+
assert isinstance(result, fragments_pb2.Fragment)
39+
40+
41+
def test_parse_delegates_to_parse_transaction():
42+
assert parse(_SIMPLE_TXN) == parse_transaction(_SIMPLE_TXN)
43+
44+
45+
def test_parse_fragment_rejects_transaction():
46+
with pytest.raises(ParseError):
47+
parse_fragment(_SIMPLE_TXN)
48+
49+
50+
def test_parse_transaction_rejects_fragment():
51+
with pytest.raises(ParseError):
52+
parse_transaction(_SIMPLE_FRAGMENT)

0 commit comments

Comments
 (0)