From 0fd217bd0097008521a60a45b13b040eb36c44c4 Mon Sep 17 00:00:00 2001 From: Kyle Xiao Date: Fri, 14 Feb 2025 15:14:50 +0800 Subject: [PATCH] mv to open source repo --- LICENSE-APACHE => LICENSE | 0 README.md | 53 +- go.mod | 3 + go.sum | 0 internal/desc/const.go | 54 + internal/desc/desc.go | 592 + internal/desc/desc_test.go | 325 + internal/desc/map.go | 70 + internal/desc/map_test.go | 45 + internal/desc/tag.go | 98 + internal/desc/utils.go | 150 + internal/hack/hack.go | 282 + internal/hack/hack_test.go | 25 + internal/protowire/wire.go | 546 + internal/protowire/wire_test.go | 680 + internal/prutal/decoder.go | 648 + internal/prutal/decoder_test.go | 49 + internal/prutal/encoder.go | 196 + internal/prutal/encoder_test.go | 149 + internal/prutal/errors.go | 55 + internal/prutal/prutal.go | 59 + internal/prutal/prutal_test.go | 1012 ++ internal/prutal/span.go | 58 + internal/prutal/span_test.go | 59 + internal/prutal/testdata.pb.go | 98 + internal/prutal/testdata.proto | 26 + internal/prutal/testdata.sh | 9 + internal/testutils/assert/assert.go | 172 + internal/testutils/assert/assert_test.go | 222 + internal/testutils/assert/bytes.go | 63 + internal/testutils/assert/bytes_test.go | 30 + internal/testutils/testutils.go | 56 + internal/testutils/testutils_test.go | 43 + internal/wire/builder.go | 144 + internal/wire/builder_test.go | 104 + internal/wire/encoder.go | 314 + internal/wire/encoder_list.go | 121 + internal/wire/encoder_list_test.go | 115 + internal/wire/encoder_packed.go | 111 + internal/wire/encoder_packed_test.go | 81 + internal/wire/encoder_test.go | 114 + internal/wire/utils.go | 33 + internal/wire/wire.go | 53 + internal/wire/wire_test.go | 74 + licenses/antlr4-LICENSE | 28 + licenses/protobuf-LICENSE | 24 + profile/README.md | 13 - prutal.go | 31 + prutalgen/internal/Protobuf.g4 | 663 + prutalgen/internal/antlr/LICENSE | 28 + prutalgen/internal/antlr/README | 1 + prutalgen/internal/antlr/antlrdoc.go | 102 + prutalgen/internal/antlr/atn.go | 177 + prutalgen/internal/antlr/atn_config.go | 332 + prutalgen/internal/antlr/atn_config_set.go | 301 + .../antlr/atn_deserialization_options.go | 62 + prutalgen/internal/antlr/atn_deserializer.go | 684 + prutalgen/internal/antlr/atn_simulator.go | 41 + prutalgen/internal/antlr/atn_state.go | 461 + prutalgen/internal/antlr/atn_type.go | 11 + prutalgen/internal/antlr/char_stream.go | 12 + .../internal/antlr/common_token_factory.go | 56 + .../internal/antlr/common_token_stream.go | 450 + prutalgen/internal/antlr/comparators.go | 150 + prutalgen/internal/antlr/configuration.go | 214 + prutalgen/internal/antlr/dfa.go | 175 + prutalgen/internal/antlr/dfa_serializer.go | 158 + prutalgen/internal/antlr/dfa_state.go | 170 + .../antlr/diagnostic_error_listener.go | 110 + prutalgen/internal/antlr/error_listener.go | 100 + prutalgen/internal/antlr/error_strategy.go | 702 ++ prutalgen/internal/antlr/errors.go | 259 + prutalgen/internal/antlr/file_stream.go | 67 + prutalgen/internal/antlr/input_stream.go | 157 + prutalgen/internal/antlr/int_stream.go | 16 + prutalgen/internal/antlr/interval_set.go | 330 + prutalgen/internal/antlr/jcollect.go | 684 + prutalgen/internal/antlr/lexer.go | 426 + prutalgen/internal/antlr/lexer_action.go | 452 + .../internal/antlr/lexer_action_executor.go | 174 + .../internal/antlr/lexer_atn_simulator.go | 677 + prutalgen/internal/antlr/ll1_analyzer.go | 219 + prutalgen/internal/antlr/mutex.go | 41 + prutalgen/internal/antlr/mutex_nomutex.go | 31 + prutalgen/internal/antlr/nostatistics.go | 47 + prutalgen/internal/antlr/parser.go | 700 ++ .../internal/antlr/parser_atn_simulator.go | 1666 +++ .../internal/antlr/parser_rule_context.go | 421 + .../internal/antlr/prediction_context.go | 727 ++ .../antlr/prediction_context_cache.go | 48 + prutalgen/internal/antlr/prediction_mode.go | 536 + prutalgen/internal/antlr/recognizer.go | 241 + prutalgen/internal/antlr/rule_context.go | 40 + prutalgen/internal/antlr/semantic_context.go | 464 + prutalgen/internal/antlr/statistics.go | 280 + prutalgen/internal/antlr/stats_data.go | 23 + prutalgen/internal/antlr/token.go | 213 + prutalgen/internal/antlr/token_source.go | 17 + prutalgen/internal/antlr/token_stream.go | 21 + .../internal/antlr/tokenstream_rewriter.go | 662 + prutalgen/internal/antlr/trace_listener.go | 32 + prutalgen/internal/antlr/transition.go | 439 + prutalgen/internal/antlr/tree.go | 304 + prutalgen/internal/antlr/trees.go | 142 + prutalgen/internal/antlr/utils.go | 381 + .../internal/parser/protobuf_base_listener.go | 340 + prutalgen/internal/parser/protobuf_lexer.go | 448 + .../internal/parser/protobuf_listener.go | 328 + prutalgen/internal/parser/protobuf_parser.go | 10412 ++++++++++++++++ prutalgen/internal/protobuf/strs/strings.go | 185 + .../internal/protobuf/strs/strings_test.go | 129 + prutalgen/internal/protobuf/text/decode.go | 674 + .../internal/protobuf/text/decode_number.go | 221 + .../internal/protobuf/text/decode_string.go | 170 + .../internal/protobuf/text/decode_test.go | 1944 +++ .../internal/protobuf/text/decode_token.go | 357 + prutalgen/internal/update_parser.sh | 17 + prutalgen/main.go | 76 + prutalgen/pkg/prutalgen/code.go | 363 + prutalgen/pkg/prutalgen/code_test.go | 120 + prutalgen/pkg/prutalgen/codewriter.go | 118 + prutalgen/pkg/prutalgen/codewriter_test.go | 53 + prutalgen/pkg/prutalgen/comment.go | 122 + prutalgen/pkg/prutalgen/comment_test.go | 63 + prutalgen/pkg/prutalgen/consts.go | 114 + prutalgen/pkg/prutalgen/embed.go | 83 + prutalgen/pkg/prutalgen/embed_test.go | 26 + prutalgen/pkg/prutalgen/enum.go | 201 + prutalgen/pkg/prutalgen/enum_test.go | 160 + prutalgen/pkg/prutalgen/field.go | 346 + prutalgen/pkg/prutalgen/field_test.go | 192 + prutalgen/pkg/prutalgen/logger.go | 41 + prutalgen/pkg/prutalgen/message.go | 184 + prutalgen/pkg/prutalgen/message_test.go | 134 + prutalgen/pkg/prutalgen/oneof.go | 46 + prutalgen/pkg/prutalgen/oneof_test.go | 40 + prutalgen/pkg/prutalgen/option.go | 129 + prutalgen/pkg/prutalgen/option_test.go | 101 + prutalgen/pkg/prutalgen/proto.go | 212 + prutalgen/pkg/prutalgen/proto_test.go | 67 + prutalgen/pkg/prutalgen/prutalgen.go | 268 + prutalgen/pkg/prutalgen/prutalgen_test.go | 136 + prutalgen/pkg/prutalgen/reserved.go | 86 + prutalgen/pkg/prutalgen/reserved_test.go | 107 + prutalgen/pkg/prutalgen/service.go | 116 + prutalgen/pkg/prutalgen/service_test.go | 61 + prutalgen/pkg/prutalgen/type.go | 184 + prutalgen/pkg/prutalgen/type_test.go | 117 + prutalgen/pkg/prutalgen/utils.go | 121 + prutalgen/pkg/prutalgen/utils_test.go | 39 + prutalgen/pkg/prutalgen/wellknowns/any.proto | 162 + prutalgen/pkg/prutalgen/wellknowns/api.proto | 207 + .../pkg/prutalgen/wellknowns/descriptor.proto | 1337 ++ .../pkg/prutalgen/wellknowns/duration.proto | 115 + .../pkg/prutalgen/wellknowns/empty.proto | 51 + .../pkg/prutalgen/wellknowns/field_mask.proto | 245 + .../prutalgen/wellknowns/source_context.proto | 48 + .../pkg/prutalgen/wellknowns/struct.proto | 95 + .../pkg/prutalgen/wellknowns/timestamp.proto | 144 + prutalgen/pkg/prutalgen/wellknowns/type.proto | 193 + .../pkg/prutalgen/wellknowns/update_proto.sh | 29 + .../pkg/prutalgen/wellknowns/wrappers.proto | 123 + prutalgen/pkg/utils/args/args.go | 33 + prutalgen/pkg/utils/args/go_opts.go | 59 + prutalgen/pkg/utils/args/go_opts_test.go | 44 + prutalgen/proto/prutal.proto | 23 + tests/cases/edition2023/Makefile | 8 + .../test_messages_edition2023.pb.go | 1053 ++ .../test_messages_edition2023.proto | 217 + tests/cases/oneof/Makefile | 5 + tests/cases/oneof/oneof.pb.go | 61 + tests/cases/oneof/oneof.proto | 17 + tests/cases/proto2/Makefile | 5 + tests/cases/proto2/proto2.pb.go | 135 + tests/cases/proto2/proto2.proto | 111 + tests/run.sh | 36 + 176 files changed, 47377 insertions(+), 14 deletions(-) rename LICENSE-APACHE => LICENSE (100%) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/desc/const.go create mode 100644 internal/desc/desc.go create mode 100644 internal/desc/desc_test.go create mode 100644 internal/desc/map.go create mode 100644 internal/desc/map_test.go create mode 100644 internal/desc/tag.go create mode 100644 internal/desc/utils.go create mode 100644 internal/hack/hack.go create mode 100644 internal/hack/hack_test.go create mode 100644 internal/protowire/wire.go create mode 100644 internal/protowire/wire_test.go create mode 100644 internal/prutal/decoder.go create mode 100644 internal/prutal/decoder_test.go create mode 100644 internal/prutal/encoder.go create mode 100644 internal/prutal/encoder_test.go create mode 100644 internal/prutal/errors.go create mode 100644 internal/prutal/prutal.go create mode 100644 internal/prutal/prutal_test.go create mode 100644 internal/prutal/span.go create mode 100644 internal/prutal/span_test.go create mode 100644 internal/prutal/testdata.pb.go create mode 100644 internal/prutal/testdata.proto create mode 100755 internal/prutal/testdata.sh create mode 100644 internal/testutils/assert/assert.go create mode 100644 internal/testutils/assert/assert_test.go create mode 100644 internal/testutils/assert/bytes.go create mode 100644 internal/testutils/assert/bytes_test.go create mode 100644 internal/testutils/testutils.go create mode 100644 internal/testutils/testutils_test.go create mode 100644 internal/wire/builder.go create mode 100644 internal/wire/builder_test.go create mode 100644 internal/wire/encoder.go create mode 100644 internal/wire/encoder_list.go create mode 100644 internal/wire/encoder_list_test.go create mode 100644 internal/wire/encoder_packed.go create mode 100644 internal/wire/encoder_packed_test.go create mode 100644 internal/wire/encoder_test.go create mode 100644 internal/wire/utils.go create mode 100644 internal/wire/wire.go create mode 100644 internal/wire/wire_test.go create mode 100644 licenses/antlr4-LICENSE create mode 100644 licenses/protobuf-LICENSE delete mode 100644 profile/README.md create mode 100644 prutal.go create mode 100644 prutalgen/internal/Protobuf.g4 create mode 100644 prutalgen/internal/antlr/LICENSE create mode 100644 prutalgen/internal/antlr/README create mode 100644 prutalgen/internal/antlr/antlrdoc.go create mode 100644 prutalgen/internal/antlr/atn.go create mode 100644 prutalgen/internal/antlr/atn_config.go create mode 100644 prutalgen/internal/antlr/atn_config_set.go create mode 100644 prutalgen/internal/antlr/atn_deserialization_options.go create mode 100644 prutalgen/internal/antlr/atn_deserializer.go create mode 100644 prutalgen/internal/antlr/atn_simulator.go create mode 100644 prutalgen/internal/antlr/atn_state.go create mode 100644 prutalgen/internal/antlr/atn_type.go create mode 100644 prutalgen/internal/antlr/char_stream.go create mode 100644 prutalgen/internal/antlr/common_token_factory.go create mode 100644 prutalgen/internal/antlr/common_token_stream.go create mode 100644 prutalgen/internal/antlr/comparators.go create mode 100644 prutalgen/internal/antlr/configuration.go create mode 100644 prutalgen/internal/antlr/dfa.go create mode 100644 prutalgen/internal/antlr/dfa_serializer.go create mode 100644 prutalgen/internal/antlr/dfa_state.go create mode 100644 prutalgen/internal/antlr/diagnostic_error_listener.go create mode 100644 prutalgen/internal/antlr/error_listener.go create mode 100644 prutalgen/internal/antlr/error_strategy.go create mode 100644 prutalgen/internal/antlr/errors.go create mode 100644 prutalgen/internal/antlr/file_stream.go create mode 100644 prutalgen/internal/antlr/input_stream.go create mode 100644 prutalgen/internal/antlr/int_stream.go create mode 100644 prutalgen/internal/antlr/interval_set.go create mode 100644 prutalgen/internal/antlr/jcollect.go create mode 100644 prutalgen/internal/antlr/lexer.go create mode 100644 prutalgen/internal/antlr/lexer_action.go create mode 100644 prutalgen/internal/antlr/lexer_action_executor.go create mode 100644 prutalgen/internal/antlr/lexer_atn_simulator.go create mode 100644 prutalgen/internal/antlr/ll1_analyzer.go create mode 100644 prutalgen/internal/antlr/mutex.go create mode 100644 prutalgen/internal/antlr/mutex_nomutex.go create mode 100644 prutalgen/internal/antlr/nostatistics.go create mode 100644 prutalgen/internal/antlr/parser.go create mode 100644 prutalgen/internal/antlr/parser_atn_simulator.go create mode 100644 prutalgen/internal/antlr/parser_rule_context.go create mode 100644 prutalgen/internal/antlr/prediction_context.go create mode 100644 prutalgen/internal/antlr/prediction_context_cache.go create mode 100644 prutalgen/internal/antlr/prediction_mode.go create mode 100644 prutalgen/internal/antlr/recognizer.go create mode 100644 prutalgen/internal/antlr/rule_context.go create mode 100644 prutalgen/internal/antlr/semantic_context.go create mode 100644 prutalgen/internal/antlr/statistics.go create mode 100644 prutalgen/internal/antlr/stats_data.go create mode 100644 prutalgen/internal/antlr/token.go create mode 100644 prutalgen/internal/antlr/token_source.go create mode 100644 prutalgen/internal/antlr/token_stream.go create mode 100644 prutalgen/internal/antlr/tokenstream_rewriter.go create mode 100644 prutalgen/internal/antlr/trace_listener.go create mode 100644 prutalgen/internal/antlr/transition.go create mode 100644 prutalgen/internal/antlr/tree.go create mode 100644 prutalgen/internal/antlr/trees.go create mode 100644 prutalgen/internal/antlr/utils.go create mode 100644 prutalgen/internal/parser/protobuf_base_listener.go create mode 100644 prutalgen/internal/parser/protobuf_lexer.go create mode 100644 prutalgen/internal/parser/protobuf_listener.go create mode 100644 prutalgen/internal/parser/protobuf_parser.go create mode 100644 prutalgen/internal/protobuf/strs/strings.go create mode 100644 prutalgen/internal/protobuf/strs/strings_test.go create mode 100644 prutalgen/internal/protobuf/text/decode.go create mode 100644 prutalgen/internal/protobuf/text/decode_number.go create mode 100644 prutalgen/internal/protobuf/text/decode_string.go create mode 100644 prutalgen/internal/protobuf/text/decode_test.go create mode 100644 prutalgen/internal/protobuf/text/decode_token.go create mode 100755 prutalgen/internal/update_parser.sh create mode 100644 prutalgen/main.go create mode 100644 prutalgen/pkg/prutalgen/code.go create mode 100644 prutalgen/pkg/prutalgen/code_test.go create mode 100644 prutalgen/pkg/prutalgen/codewriter.go create mode 100644 prutalgen/pkg/prutalgen/codewriter_test.go create mode 100644 prutalgen/pkg/prutalgen/comment.go create mode 100644 prutalgen/pkg/prutalgen/comment_test.go create mode 100644 prutalgen/pkg/prutalgen/consts.go create mode 100644 prutalgen/pkg/prutalgen/embed.go create mode 100644 prutalgen/pkg/prutalgen/embed_test.go create mode 100644 prutalgen/pkg/prutalgen/enum.go create mode 100644 prutalgen/pkg/prutalgen/enum_test.go create mode 100644 prutalgen/pkg/prutalgen/field.go create mode 100644 prutalgen/pkg/prutalgen/field_test.go create mode 100644 prutalgen/pkg/prutalgen/logger.go create mode 100644 prutalgen/pkg/prutalgen/message.go create mode 100644 prutalgen/pkg/prutalgen/message_test.go create mode 100644 prutalgen/pkg/prutalgen/oneof.go create mode 100644 prutalgen/pkg/prutalgen/oneof_test.go create mode 100644 prutalgen/pkg/prutalgen/option.go create mode 100644 prutalgen/pkg/prutalgen/option_test.go create mode 100644 prutalgen/pkg/prutalgen/proto.go create mode 100644 prutalgen/pkg/prutalgen/proto_test.go create mode 100644 prutalgen/pkg/prutalgen/prutalgen.go create mode 100644 prutalgen/pkg/prutalgen/prutalgen_test.go create mode 100644 prutalgen/pkg/prutalgen/reserved.go create mode 100644 prutalgen/pkg/prutalgen/reserved_test.go create mode 100644 prutalgen/pkg/prutalgen/service.go create mode 100644 prutalgen/pkg/prutalgen/service_test.go create mode 100644 prutalgen/pkg/prutalgen/type.go create mode 100644 prutalgen/pkg/prutalgen/type_test.go create mode 100644 prutalgen/pkg/prutalgen/utils.go create mode 100644 prutalgen/pkg/prutalgen/utils_test.go create mode 100644 prutalgen/pkg/prutalgen/wellknowns/any.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/api.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/descriptor.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/duration.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/empty.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/field_mask.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/source_context.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/struct.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/timestamp.proto create mode 100644 prutalgen/pkg/prutalgen/wellknowns/type.proto create mode 100755 prutalgen/pkg/prutalgen/wellknowns/update_proto.sh create mode 100644 prutalgen/pkg/prutalgen/wellknowns/wrappers.proto create mode 100644 prutalgen/pkg/utils/args/args.go create mode 100644 prutalgen/pkg/utils/args/go_opts.go create mode 100644 prutalgen/pkg/utils/args/go_opts_test.go create mode 100644 prutalgen/proto/prutal.proto create mode 100644 tests/cases/edition2023/Makefile create mode 100644 tests/cases/edition2023/test_messages_edition2023.pb.go create mode 100644 tests/cases/edition2023/test_messages_edition2023.proto create mode 100644 tests/cases/oneof/Makefile create mode 100644 tests/cases/oneof/oneof.pb.go create mode 100644 tests/cases/oneof/oneof.proto create mode 100644 tests/cases/proto2/Makefile create mode 100644 tests/cases/proto2/proto2.pb.go create mode 100644 tests/cases/proto2/proto2.proto create mode 100755 tests/run.sh diff --git a/LICENSE-APACHE b/LICENSE similarity index 100% rename from LICENSE-APACHE rename to LICENSE diff --git a/README.md b/README.md index a46ae92..109188d 100644 --- a/README.md +++ b/README.md @@ -1 +1,52 @@ -# .github \ No newline at end of file +# Prutal + +Prutal is a pure Go alternative to [protocol buffers](https://protobuf.dev), it covers most of the functionality offered by Protocol Buffers. + +Prutal aims to minimize code generation as much as possible while ensuring serialization and maintaining good performance. + +**Since Prutal is NOT yet ready for production use, we are not providing usage documentation at this time, nor do we guarantee backward compatibility of the interface.** + +| Features | Protobuf | Prutal | +| -- | -- | -- | +| Supported Languages | C++, Java, Python, Go, and more | Go | +| Code Generation | ✅ | ✅ | +| Serialization | ✅ | ✅ without generating code | +| Performance | ⭐️⭐️⭐️ | ⭐️⭐️⭐️⭐️⭐️ | +| Extensibility | 😡 Plugin | 😄 Package +| Compatibility | ✅ | ✅ (see Protobuf Compatibility) +| gRPC | ✅ | 🚧 Coming soon +| Non-Pointer Field | ❌ | ✅ (aka gogoproto.nullable) + + +## Protobuf Compatibility + +* ✅ Works with code generated by the official Protocol Buffer Go +* ✅ Parsing .proto file. syntax: proto2, proto3, edition 2023 +* ✅ Protobuf wire format + - double, float, bool, string, bytes + - int32, int64, uint32, uint64, sint32, sint64 + - fixed32, fixed64, sfixed64, sfixed64 + - enum + - repeated, map, oneof +* ✅ Packed / unpack (proto2) + - PACKED / EXPANDED (repeated field encoding, edition2023) +* ✅ Reserved +* ✅ Unknown Fields +* ⚠️ JSON support: JSON struct tag only +* ⚠️ Code generation: Go only +* ⚠️ Protocol buffers well-known types [link](https://protobuf.dev/reference/protobuf/google.protobuf/) + - Prutal is able to generate code by reusing pkg [`google.golang.org/protobuf/type`](https://pkg.go.dev/google.golang.org/protobuf/types/known) + - Features of type like `Any` would be limited. +* ❌ Groups (proto2) [link](https://protobuf.dev/programming-guides/proto2/#groups) +* ❌ Overriding default scalar values (proto2, edition2023) [link](https://protobuf.dev/programming-guides/proto2/#explicit-default) + + + +## Contributing + +Contributor guide: [Contributing](CONTRIBUTING.md). + +## License + +Prutal is licensed under the terms of the Apache license 2.0. See [LICENSE](LICENSE) for more information. +Dependencies used by `prutal` are listed under [licenses](licenses). diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4e18b16 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/cloudwego/prutal + +go 1.18 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/internal/desc/const.go b/internal/desc/const.go new file mode 100644 index 0000000..1669251 --- /dev/null +++ b/internal/desc/const.go @@ -0,0 +1,54 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "strconv" +) + +// FieldType represents types used in struct tag +type TagType uint8 + +const ( + TypeVarint TagType = iota + 1 + TypeZigZag32 + TypeZigZag64 + TypeFixed32 + TypeFixed64 + TypeBytes + TypeUnknown +) + +var typeNames = []string{ + TypeVarint: "varint", + TypeZigZag32: "zigzag32", + TypeZigZag64: "zigzag64", + TypeFixed32: "fixed32", + TypeFixed64: "fixed64", + TypeBytes: "bytes", +} + +func (t TagType) String() string { + ret := "" + if uint(t) < uint(len(typeNames)) { + ret = typeNames[uint(t)] + } + if ret == "" { + ret = "FieldType-" + strconv.Itoa(int(t)) + } + return ret +} diff --git a/internal/desc/desc.go b/internal/desc/desc.go new file mode 100644 index 0000000..1a2ec7d --- /dev/null +++ b/internal/desc/desc.go @@ -0,0 +1,592 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "errors" + "fmt" + "reflect" + "sort" + "strings" + "sync" + "unsafe" + + "github.com/cloudwego/prutal/internal/hack" + "github.com/cloudwego/prutal/internal/wire" +) + +var cache = newMapStructDesc() + +var ( + parsemu sync.Mutex + errNotStruct = errors.New("input not struct") +) + +func CacheGet(rv reflect.Value) *StructDesc { + typ := hack.ReflectValueTypePtr(rv) + return cache.Get(typ) +} + +// GetOrParse ... +func GetOrParse(rv reflect.Value) (*StructDesc, error) { + typ := hack.ReflectValueTypePtr(rv) + ret := cache.Get(typ) + if ret != nil { + return ret, nil + } + rt := rv.Type() + if rt.Kind() == reflect.Pointer { + rt = rt.Elem() + } + if rt.Kind() != reflect.Struct { + return nil, errNotStruct + } + + parsemu.Lock() + defer parsemu.Unlock() + + ret = cache.Get(typ) + if ret != nil { + return ret, nil + } + + s, err := parseStruct(rt) + if err != nil { + return nil, err + } + cache.Set(typ, s) + return s, nil +} + +var bytesType = reflect.TypeOf([]byte{}) + +const maxDirectFieldMapID = 1000 + +type StructDesc struct { + Fields []*FieldDesc // sorted by ID + + // []byte or *[]byte + // for *[]byte, see: https://go-review.googlesource.com/c/protobuf/+/244937 + HasUnknownFields bool + UnknownFieldsPointer bool + UnknownFieldsOffset uintptr + + // for GetField + mFields0 []*FieldDesc // direct ID map + mFields1 map[int32]*FieldDesc // slow hash map +} + +func (p *StructDesc) GetField(v int32) *FieldDesc { + if v < int32(len(p.mFields0)) { + return p.mFields0[v] + } + return p.mFields1[v] +} + +func (p *StructDesc) String() string { + var buf strings.Builder + buf.WriteString("StructDesc {\n") + buf.WriteString("Fields:\n") + for _, f := range p.Fields { + fmt.Fprintf(&buf, " %v\n", f) + } + buf.WriteString("}\n") + return buf.String() +} + +var wireTypes = []wire.Type{ + TypeVarint: wire.TypeVarint, + TypeZigZag32: wire.TypeVarint, + TypeZigZag64: wire.TypeVarint, + TypeFixed32: wire.TypeFixed32, + TypeFixed64: wire.TypeFixed64, + TypeBytes: wire.TypeBytes, +} + +type FieldDesc struct { + ID int32 + Name string + Offset uintptr + Repeated bool + Packed bool + TagType TagType + WireType wire.Type + + IsList bool + IsMap bool + + // only for oneof types + // Kind==reflect.Pointer, coz we always use pointer for checking + OneofType reflect.Type + IfaceTab uintptr // from OneofFieldIfaceTab + + // only for scalar types (including packed scalar types) + AppendFunc func(b []byte, p unsafe.Pointer) []byte + + // only for repeated scalar types + AppendRepeated func(b []byte, id int32, p unsafe.Pointer) []byte + + // only for map type + KeyType TagType + ValType TagType + + KeyWireType wire.Type + ValWireType wire.Type + + KeyAppendFunc func(b []byte, p unsafe.Pointer) []byte + ValAppendFunc func(b []byte, p unsafe.Pointer) []byte + + T *Type +} + +func (f *FieldDesc) String() string { + return fmt.Sprintf("ID:%d Offset:%d Repeated:%v Packed:%v TagType:%v T:%v", + f.ID, f.Offset, f.Repeated, f.Packed, f.TagType, f.T) +} + +func (f *FieldDesc) IsOneof() bool { + return f.OneofType != nil +} + +func (f *FieldDesc) parse(sf reflect.StructField) (err error) { + tag := sf.Tag.Get("protobuf") + if tag == "" { + panic("not protobuf field") + } + if err = f.parseStructTag(tag); err != nil { + return + } + f.T, err = parseType(sf.Type) + if err != nil { + return + } + f.IsList = f.Repeated && f.T.Kind != reflect.Map + f.IsMap = f.T.Kind == reflect.Map + + if f.IsMap { + f.KeyType, err = parseKVTag(sf.Tag.Get("protobuf_key")) + if err != nil { + return + } + f.ValType, err = parseKVTag(sf.Tag.Get("protobuf_val")) + if err != nil { + return + } + f.KeyWireType = wireTypes[f.KeyType] + f.ValWireType = wireTypes[f.ValType] + } + if err = f.checkTypeMatch(); err != nil { + return + } + t := f.T + f.AppendFunc = getAppendFunc(f.TagType, t.RealKind(), f.Packed) + if f.T.Kind == reflect.Map { + f.KeyAppendFunc = getAppendFunc(f.KeyType, t.K.RealKind(), false) + f.ValAppendFunc = getAppendFunc(f.ValType, t.V.RealKind(), false) + } + if f.IsList { + f.AppendRepeated = getAppendRepeatedFunc(f.TagType, t.RealKind()) + } + return +} + +func (f *FieldDesc) checkTypeMatch() error { + t := f.T + if f.Packed { + if !f.Repeated { + return errors.New("packed field is not repeated field") + } + if !t.IsSlice { + return errors.New("packed field is not slice") + } + switch f.TagType { + case TypeVarint, TypeZigZag32, TypeZigZag64, TypeFixed32, TypeFixed64: + default: + return errors.New("packed field only for scalar types except string or bytes") + } + } + if f.Repeated { + if !t.IsSlice && t.Kind != reflect.Map { + return fmt.Errorf("repeated field is not slice or map") + } + } + if err := IsFieldTypeMatchReflectKind(f.TagType, t.RealKind()); err != nil { + return err + } + if t.Kind == reflect.Map { + if !f.Repeated { + return errors.New("must be repeated field for map") + } + if err := IsFieldKeyTypeMatchReflectKind(f.KeyType, t.K.RealKind()); err != nil { + return err + } + if err := IsFieldTypeMatchReflectKind(f.ValType, t.V.RealKind()); err != nil { + return err + } + } + return nil +} + +const KindBytes reflect.Kind = 5000 // for []byte + +type Type struct { + T reflect.Type + + // true if t.Kind == reflect.Pointer + IsPointer bool + + // true if t.Kind == reflect.Slice, + // false for []byte which is considered to be scalar type + IsSlice bool + + SliceLike bool // reflect.Slice, reflect.String, KindBytes + + // cache reflect.Type returns for performance + Kind reflect.Kind + Size uintptr + Align int + + // for decoder + MallocAbiType uintptr + + K *Type // for map + V *Type // for pointer, slice or map + S *StructDesc // struct + + // for map only + MapTmpVarsPool sync.Pool // for decoder tmp vars +} + +func (t *Type) RealKind() reflect.Kind { + if t.IsPointer || t.IsSlice { + return t.V.RealKind() + } + return t.Kind +} + +// TmpMapVars contains key and value tmp vars used for updating associated map for a type +type TmpMapVars struct { + m reflect.Value + + k reflect.Value // t.K.T + kp unsafe.Pointer // *t.K.T + + v reflect.Value // t.V.T + vp unsafe.Pointer // *t.V.T + + // zero value of v, + // only used when non-pointer struct as map val + // we need to zero the tmp var before using it + z reflect.Value +} + +func (p *TmpMapVars) MapWithPtr(x unsafe.Pointer) reflect.Value { + return hack.ReflectValueWithPtr(p.m, x) +} + +func (p *TmpMapVars) KeyPointer() unsafe.Pointer { return p.kp } +func (p *TmpMapVars) ValPointer() unsafe.Pointer { return p.vp } +func (p *TmpMapVars) Update(m reflect.Value) { m.SetMapIndex(p.k, p.v) } +func (p *TmpMapVars) Reset() { + if p.z.IsValid() { + p.v.Set(p.z) + } +} + +func (t *Type) String() string { + switch t.Kind { + case reflect.Struct: + return fmt.Sprintf("%+v", t.S) + default: + return fmt.Sprintf("%+v", t.T) + } +} + +var cachedTypes = map[reflect.Type]*Type{} + +func parseType(rt reflect.Type) (ret *Type, err error) { + if t := cachedTypes[rt]; t != nil { + return t, nil + } + ret = &Type{} + + cachedTypes[rt] = ret // fix cyclic refs + + ret.T = rt + ret.Kind = rt.Kind() + ret.Size = rt.Size() + ret.Align = rt.Align() + + if rt == bytesType { // special case + ret.Kind = KindBytes + } + + if ret.Kind == reflect.Slice || ret.Kind == KindBytes || ret.Kind == reflect.String || + ret.Kind == reflect.Map || ret.Kind == reflect.Struct { + // for these types, we can't use span mem allocator + // coz then may contain pointer + ret.MallocAbiType = hack.ReflectTypePtr(ret.T) + } + + ret.IsPointer = ret.Kind == reflect.Pointer + ret.IsSlice = ret.Kind == reflect.Slice + + ret.SliceLike = ret.Kind == reflect.Slice || + ret.Kind == KindBytes || + ret.Kind == reflect.String + + switch rt.Kind() { + case reflect.Map: + ret.K, err = parseType(rt.Key()) + if err != nil { + break + } + ret.V, err = parseType(rt.Elem()) + if err != nil { + break + } + ret.MapTmpVarsPool.New = func() interface{} { + m := &TmpMapVars{} + m.m = reflect.New(rt).Elem() + m.k = reflect.New(rt.Key()) + m.kp = m.k.UnsafePointer() + m.k = m.k.Elem() + m.v = reflect.New(rt.Elem()) + m.vp = m.v.UnsafePointer() + m.v = m.v.Elem() + if rt.Elem().Kind() == reflect.Struct { + m.z = reflect.Zero(rt.Elem()) + } + return m + } + case reflect.Struct: + ret.S, err = parseStruct(rt) + case reflect.Slice: + ret.V, err = parseType(rt.Elem()) + case reflect.Pointer: + ret.V, err = parseType(rt.Elem()) + if err == nil && ret.V.IsPointer { + err = errors.New("multilevel pointer") + } + default: + } + if err != nil { + delete(cachedTypes, rt) + return nil, err + } + return ret, nil +} + +var cachedStructs = map[reflect.Type]*StructDesc{} + +func parseStruct(rt reflect.Type) (s *StructDesc, err error) { + if s = cachedStructs[rt]; s != nil { + return s, nil + } + + s = &StructDesc{} + cachedStructs[rt] = s // fix cyclic refs + defer func() { + if err != nil { + delete(cachedStructs, rt) + } + }() + + var oneofs []reflect.StructField + var fields []FieldDesc + for i, n := 0, rt.NumField(); i < n; i++ { + sf := rt.Field(i) + tag := sf.Tag.Get("protobuf") + if tag == "" { + if sf.Tag.Get("protobuf_oneof") != "" { + oneofs = append(oneofs, sf) + } + continue + } + f := FieldDesc{Name: sf.Name, Offset: sf.Offset} + if err = f.parse(sf); err != nil { + return nil, fmt.Errorf("parse field %q err: %w", sf.Name, err) + } + fields = append(fields, f) + } + + if len(oneofs) > 0 { + for _, v := range searchOneofWrappers(rt) { + rt := reflect.TypeOf(v) + for _, o := range oneofs { + if !rt.Implements(o.Type) { + continue + } + // Pointer -> Struct + rt = rt.Elem() + if rt.NumField() != 1 { // The struct must contains extractly one field + return nil, fmt.Errorf("parse field %q oneof %q err: field number != 1", o.Name, rt.String()) + } + f := FieldDesc{Name: o.Name, Offset: o.Offset, OneofType: reflect.PointerTo(rt)} + f.IfaceTab = hack.IfaceTab(o.Type, rt) + if err = f.parse(rt.Field(0)); err != nil { + return nil, fmt.Errorf("parse field %q oneof %q err: %w", o.Name, rt.String(), err) + } + fields = append(fields, f) + } + } + } + + // reduce in-use objects + ff := make([]FieldDesc, len(fields)) + copy(ff, fields) + s.Fields = make([]*FieldDesc, len(ff)) + for i := range ff { + s.Fields[i] = &ff[i] + } + + // sort by ID + sort.Slice(s.Fields, func(i, j int) bool { + return s.Fields[i].ID < s.Fields[j].ID + }) + + k := 0 // for s.mFields1 + maxn := 0 + for i, f := range s.Fields { + if f.ID > maxDirectFieldMapID { + k = len(s.Fields) - 1 - i + break + } + maxn = int(f.ID) + } + s.mFields0 = make([]*FieldDesc, maxn+1) + s.mFields1 = make(map[int32]*FieldDesc, k) + for i, f := range s.Fields { + if f.ID < maxDirectFieldMapID { + s.mFields0[int(f.ID)] = f + } else { + s.mFields1[f.ID] = f + } + + if i > 0 && f.ID == s.Fields[i-1].ID { + return nil, fmt.Errorf("duplicated field number: %d for field %q and %q", + f.ID, f.Name, s.Fields[i-1].Name) + } + } + + // unknownFields: latest version + // XXX_unrecognized: old version protobuf + for _, name := range []string{"unknownFields", "XXX_unrecognized"} { + f, ok := rt.FieldByName(name) + if !ok { + continue + } + ft := f.Type + if ft != bytesType && // not []byte nor *[]byte? + ft.Kind() == reflect.Pointer && ft.Elem() != bytesType { + continue + } + s.HasUnknownFields = true + s.UnknownFieldsPointer = ft.Kind() == reflect.Pointer + s.UnknownFieldsOffset = f.Offset + break + } + + return s, nil +} + +func getAppendFunc(t TagType, k reflect.Kind, packed bool) func(b []byte, p unsafe.Pointer) []byte { + if packed { + switch t { + case TypeVarint: + switch k { + case reflect.Int32, reflect.Uint32: + return wire.UnsafeAppendPackedVarintU32 + case reflect.Int64, reflect.Uint64: + return wire.UnsafeAppendPackedVarintU64 + case reflect.Bool: + return wire.UnsafeAppendPackedBool + } + case TypeZigZag32: + return wire.UnsafeAppendPackedZigZag32 + case TypeZigZag64: + return wire.UnsafeAppendPackedZigZag64 + case TypeFixed32: + return wire.UnsafeAppendPackedFixed32 + case TypeFixed64: + return wire.UnsafeAppendPackedFixed64 + case TypeBytes: + panic("packed on bytes field") + default: + panic(fmt.Sprintf("unknown tag type: %q", t)) + } + } + switch t { + case TypeVarint: + switch k { + case reflect.Int32, reflect.Uint32: + return wire.UnsafeAppendVarintU32 + case reflect.Int64, reflect.Uint64: + return wire.UnsafeAppendVarintU64 + case reflect.Bool: + return wire.UnsafeAppendBool + } + case TypeZigZag32: + return wire.UnsafeAppendZigZag32 + case TypeZigZag64: + return wire.UnsafeAppendZigZag64 + case TypeFixed32: + return wire.UnsafeAppendFixed32 + case TypeFixed64: + return wire.UnsafeAppendFixed64 + case TypeBytes: + switch k { + case reflect.String: // string + return wire.UnsafeAppendString + case KindBytes: // []byte + return wire.UnsafeAppendBytes + } + default: + panic(fmt.Sprintf("unknown tag type: %q", t)) + } + return nil +} + +func getAppendRepeatedFunc(t TagType, k reflect.Kind) func(b []byte, id int32, p unsafe.Pointer) []byte { + switch t { + case TypeVarint: + switch k { + case reflect.Int64, reflect.Uint64: + return wire.UnsafeAppendRepeatedVarintU64 + case reflect.Int32, reflect.Uint32: + return wire.UnsafeAppendRepeatedVarintU32 + case reflect.Bool: + return wire.UnsafeAppendRepeatedBool + } + case TypeZigZag32: + return wire.UnsafeAppendRepeatedZigZag32 + case TypeZigZag64: + return wire.UnsafeAppendRepeatedZigZag64 + case TypeFixed32: + return wire.UnsafeAppendRepeatedFixed32 + case TypeFixed64: + return wire.UnsafeAppendRepeatedFixed64 + case TypeBytes: + switch k { + case reflect.String: // string + return wire.UnsafeAppendRepeatedString + case KindBytes: // []byte + return wire.UnsafeAppendRepeatedBytes + } + } + return nil +} diff --git a/internal/desc/desc_test.go b/internal/desc/desc_test.go new file mode 100644 index 0000000..3485e21 --- /dev/null +++ b/internal/desc/desc_test.go @@ -0,0 +1,325 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "reflect" + "testing" + "unsafe" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +type TestMessage struct { + Ptr *int32 `protobuf:"varint,1,opt"` + Varint32 int32 `protobuf:"varint,2,opt"` + Varint64 int64 `protobuf:"varint,3,opt"` + Bool bool `protobuf:"varint,4,opt"` + Fixed32 uint32 `protobuf:"fixed32,5,opt"` + Fixed64 uint64 `protobuf:"fixed64,6,opt"` + ZigZag32 int32 `protobuf:"zigzag32,7,opt"` + ZigZag64 int64 `protobuf:"zigzag64,8,opt"` + + Str string `protobuf:"bytes,101,opt"` + Bytes []byte `protobuf:"bytes,102,opt"` + PtrBytes *[]byte `protobuf:"bytes,103,opt"` + RepeatedBytes [][]byte `protobuf:"bytes,104,rep"` + PackedVarint []int32 `protobuf:"varint,105,rep,packed"` + + MapVarint map[int32]int32 `protobuf:"bytes,201,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapFixed32 map[uint32]uint32 `protobuf:"bytes,202,rep" protobuf_key:"fixed32,1,opt" protobuf_val:"fixed32,2,opt"` + MapFixed64 map[uint64]uint64 `protobuf:"bytes,203,rep" protobuf_key:"fixed64,1,opt" protobuf_val:"fixed64,2,opt"` + MapZigZag32 map[int32]int32 `protobuf:"bytes,204,rep" protobuf_key:"zigzag32,1,opt" protobuf_val:"zigzag32,2,opt"` + MapZigZag64 map[int64]int64 `protobuf:"bytes,205,rep" protobuf_key:"zigzag64,1,opt" protobuf_val:"zigzag64,2,opt"` + + MapStringString map[string]string `protobuf:"bytes,211,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + MapStringBytes map[string][]byte `protobuf:"bytes,212,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + MapStringStruct map[string]*NestedMessage `protobuf:"bytes,213,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + + Nested *NestedMessage `protobuf:"bytes,301,opt"` +} + +type NestedMessage struct { + V *TestMessage `protobuf:"bytes,2,opt"` +} + +func TestGetOrParse(t *testing.T) { + s, err := GetOrParse(reflect.ValueOf(&TestMessage{})) + assert.NoError(t, err) + + expects := []struct { + ID int32 + Name string + TagType TagType + Kind reflect.Kind + RealKind reflect.Kind + KKind reflect.Kind + VKind reflect.Kind + }{ + { + ID: 1, + Name: "Ptr", + TagType: TypeVarint, + Kind: reflect.Pointer, + RealKind: reflect.Int32, + }, + { + ID: 2, + Name: "Varint32", + TagType: TypeVarint, + Kind: reflect.Int32, + RealKind: reflect.Int32, + }, + { + ID: 3, + Name: "Varint64", + TagType: TypeVarint, + Kind: reflect.Int64, + RealKind: reflect.Int64, + }, + { + ID: 4, + Name: "Bool", + TagType: TypeVarint, + Kind: reflect.Bool, + RealKind: reflect.Bool, + }, + { + ID: 5, + Name: "Fixed32", + TagType: TypeFixed32, + Kind: reflect.Uint32, + RealKind: reflect.Uint32, + }, + { + ID: 6, + Name: "Fixed64", + TagType: TypeFixed64, + Kind: reflect.Uint64, + RealKind: reflect.Uint64, + }, + { + ID: 7, + Name: "ZigZag32", + TagType: TypeZigZag32, + Kind: reflect.Int32, + RealKind: reflect.Int32, + }, + { + ID: 8, + Name: "ZigZag64", + TagType: TypeZigZag64, + Kind: reflect.Int64, + RealKind: reflect.Int64, + }, + { + ID: 101, + Name: "Str", + TagType: TypeBytes, + Kind: reflect.String, + RealKind: reflect.String, + }, + { + ID: 102, + Name: "Bytes", + TagType: TypeBytes, + Kind: KindBytes, + RealKind: KindBytes, + }, + { + ID: 103, + Name: "PtrBytes", + TagType: TypeBytes, + Kind: reflect.Pointer, + RealKind: KindBytes, + }, + { + ID: 104, + Name: "RepeatedBytes", + TagType: TypeBytes, + Kind: reflect.Slice, + RealKind: KindBytes, + }, + { + ID: 105, + Name: "PackedVarint", + TagType: TypeVarint, + Kind: reflect.Slice, + RealKind: reflect.Int32, + }, + { + ID: 201, + Name: "MapVarint", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.Int32, + VKind: reflect.Int32, + }, + { + ID: 202, + Name: "MapFixed32", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.Uint32, + VKind: reflect.Uint32, + }, + { + ID: 203, + Name: "MapFixed64", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.Uint64, + VKind: reflect.Uint64, + }, + { + ID: 204, + Name: "MapZigZag32", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.Int32, + VKind: reflect.Int32, + }, + { + ID: 205, + Name: "MapZigZag64", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.Int64, + VKind: reflect.Int64, + }, + { + ID: 211, + Name: "MapStringString", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.String, + VKind: reflect.String, + }, + { + ID: 212, + Name: "MapStringBytes", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.String, + VKind: KindBytes, + }, + { + ID: 213, + Name: "MapStringStruct", + TagType: TypeBytes, + Kind: reflect.Map, + RealKind: reflect.Map, + KKind: reflect.String, + VKind: reflect.Pointer, + }, + { + ID: 301, + Name: "Nested", + TagType: TypeBytes, + Kind: reflect.Pointer, + RealKind: reflect.Struct, + }, + } + t.Log(s) + for _, p := range expects { + t.Run(p.Name, func(t *testing.T) { + f := s.GetField(p.ID) + assert.Equal(t, p.ID, f.ID) + assert.Equal(t, p.Name, f.Name) + assert.Equal(t, p.TagType, f.TagType) + assert.Equal(t, p.Kind.String(), f.T.Kind.String()) + assert.Equal(t, p.RealKind.String(), f.T.RealKind().String()) + if p.Kind == reflect.Map { + assert.Equal(t, p.KKind.String(), f.T.K.Kind.String()) + assert.Equal(t, p.VKind.String(), f.T.V.Kind.String()) + assert.True(t, f.IsMap) + } + if p.Kind == reflect.Slice { + assert.True(t, f.IsList) + } + }) + } + + assert.Same(t, s, CacheGet(reflect.ValueOf(&TestMessage{}))) +} + +func TestMapTmpVarsPool(t *testing.T) { + type S struct { + V int32 + } + typ, err := parseType(reflect.TypeOf(map[int32]S{})) + assert.NoError(t, err) + m := map[int32]S{} + vars := typ.MapTmpVarsPool.Get().(*TmpMapVars) + *(*int32)(vars.KeyPointer()) = 1 + *(*S)(vars.ValPointer()) = S{2} + vars.Update(reflect.ValueOf(m)) + assert.MapEqual(t, map[int32]S{1: S{2}}, m) + + assert.Equal(t, int32(2), (*S)(vars.ValPointer()).V) + vars.Reset() + assert.Equal(t, int32(0), (*S)(vars.ValPointer()).V) +} + +type TestOneofMessage struct { + Int32 int32 `protobuf:"varint,1,opt"` + + // Types that are assignable to OneOfField1: + // + // *TestOneofMessage_Field1 + OneOfField1 isTestOneofMessage_OneOfField1 `protobuf_oneof:"one_of_field1"` +} + +// XXX_OneofWrappers is for the internal use of the prutal package. +func (*TestOneofMessage) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*TestOneofMessage_Field1)(nil), + } +} + +type isTestOneofMessage_OneOfField1 interface { + isTestOneofMessage_OneOfField1() +} + +type TestOneofMessage_Field1 struct { + Field1 *TestOneofMessage `protobuf:"bytes,2,opt"` +} + +func (*TestOneofMessage_Field1) isTestOneofMessage_OneOfField1() {} + +func TestOneOf(t *testing.T) { + p := &TestOneofMessage{} + sd, err := GetOrParse(reflect.ValueOf(p)) + assert.NoError(t, err) + assert.Equal(t, 2, len(sd.Fields)) + assert.Equal(t, int32(1), sd.Fields[0].ID) + assert.Equal(t, int32(2), sd.Fields[1].ID) + assert.False(t, sd.Fields[0].IsOneof()) + assert.True(t, sd.Fields[1].IsOneof()) + + f := sd.Fields[1] + assert.DeepEqual(t, reflect.TypeOf(&TestOneofMessage_Field1{}), f.OneofType) + assert.Equal(t, "OneOfField1", f.Name) + assert.Equal(t, unsafe.Offsetof((*TestOneofMessage)(nil).OneOfField1), f.Offset) +} diff --git a/internal/desc/map.go b/internal/desc/map.go new file mode 100644 index 0000000..8d374e9 --- /dev/null +++ b/internal/desc/map.go @@ -0,0 +1,70 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "sync/atomic" + "unsafe" +) + +// mapStructDesc represents a read-lock-free hashmap for *StructDesc like sync.Map. +// it's NOT designed for writes. +type mapStructDesc struct { + p unsafe.Pointer // for atomic, point to hashtable +} + +// XXX: fixed size to make it simple, +// we may not so many structs that need to rehash it +const mapStructDescBuckets = 0xffff + +type mapStructDescItem struct { + abiType uintptr + sd *StructDesc +} + +func newMapStructDesc() *mapStructDesc { + m := &mapStructDesc{} + buckets := make([][]mapStructDescItem, mapStructDescBuckets+1) // [0] - [0xffff] + atomic.StorePointer(&m.p, unsafe.Pointer(&buckets)) + return m +} + +// Get ... +func (m *mapStructDesc) Get(abiType uintptr) *StructDesc { + buckets := *(*[][]mapStructDescItem)(atomic.LoadPointer(&m.p)) + dd := buckets[abiType&mapStructDescBuckets] + for i := range dd { + if dd[i].abiType == abiType { + return dd[i].sd + } + } + return nil +} + +// Set ... +// It's slow, should be used in rare write cases +func (m *mapStructDesc) Set(abiType uintptr, sd *StructDesc) { + if m.Get(abiType) == sd { + return + } + oldBuckets := *(*[][]mapStructDescItem)(atomic.LoadPointer(&m.p)) + newBuckets := make([][]mapStructDescItem, len(oldBuckets)) + copy(newBuckets, oldBuckets) + bk := abiType & mapStructDescBuckets + newBuckets[bk] = append(newBuckets[bk], mapStructDescItem{abiType: abiType, sd: sd}) + atomic.StorePointer(&m.p, unsafe.Pointer(&newBuckets)) +} diff --git a/internal/desc/map_test.go b/internal/desc/map_test.go new file mode 100644 index 0000000..d42c524 --- /dev/null +++ b/internal/desc/map_test.go @@ -0,0 +1,45 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestMapStructDescItem(t *testing.T) { + m := newMapStructDesc() + k0 := uintptr(1) + v0 := &StructDesc{} + m.Set(k0, v0) + assert.Same(t, v0, m.Get(k0)) + + k1 := uintptr(mapStructDescBuckets + 2) // k1 & mapStructDescBuckets == k0 + v1 := &StructDesc{} + m.Set(k1, v1) + assert.Same(t, v1, m.Get(k1)) + assert.Same(t, v0, m.Get(k0)) + + k2 := uintptr(2) + v2 := &StructDesc{} + m.Set(uintptr(2), v2) + assert.Same(t, v2, m.Get(k2)) + assert.Same(t, v1, m.Get(k1)) + assert.Same(t, v0, m.Get(k0)) + +} diff --git a/internal/desc/tag.go b/internal/desc/tag.go new file mode 100644 index 0000000..c891bfb --- /dev/null +++ b/internal/desc/tag.go @@ -0,0 +1,98 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "errors" + "fmt" + "strconv" + "strings" +) + +var errGroupNotSupported = errors.New("group encoding not supported") + +func (p *FieldDesc) parseStructTag(tag string) error { + ss := strings.Split(tag, ",") + for _, s := range ss { + s = strings.TrimSpace(s) + if s == "" { + continue + } + switch { + case s == "opt": + // not in use + case s == "req": + // ignore `required` field, it's only for proto2 + case s == "rep": + p.Repeated = true + case s == "varint": + p.TagType = TypeVarint + case s == "zigzag32": + p.TagType = TypeZigZag32 + case s == "zigzag64": + p.TagType = TypeZigZag64 + case s == "fixed32": + p.TagType = TypeFixed32 + case s == "fixed64": + p.TagType = TypeFixed64 + case s == "bytes": + p.TagType = TypeBytes + case s == "group": + return errGroupNotSupported + case s == "packed": + p.Packed = true + case strings.Trim(s, "1234567890") == "": + n, err := strconv.ParseUint(s, 10, 32) + if err != nil { + return err + } + p.ID = int32(n) + } + } + if p.TagType == 0 { + return errors.New("unknown tag type") + } + p.WireType = wireTypes[p.TagType] + return nil +} + +func parseKVTag(tag string) (TagType, error) { + ss := strings.Split(tag, ",") + for _, s := range ss { + s = strings.TrimSpace(s) + if s == "" { + continue + } + switch { // ignore field num 1 for k, 2 for v, and opt, only need type + case s == "varint": + return TypeVarint, nil + case s == "zigzag32": + return TypeZigZag32, nil + case s == "zigzag64": + return TypeZigZag64, nil + case s == "fixed32": + return TypeFixed32, nil + case s == "fixed64": + return TypeFixed64, nil + case s == "bytes": + return TypeBytes, nil + case s == "group": + return 0, errGroupNotSupported + } + } + return TypeUnknown, fmt.Errorf("failed to parse tag type: %q", tag) +} diff --git a/internal/desc/utils.go b/internal/desc/utils.go new file mode 100644 index 0000000..603d419 --- /dev/null +++ b/internal/desc/utils.go @@ -0,0 +1,150 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package desc + +import ( + "fmt" + "reflect" + "unsafe" +) + +// IsFieldKeyTypeMatchReflectKind returns nil if t match k for map key type +// It's same as IsFieldTypeMatchReflectKind except k can't be `[]byte` or `struct` +func IsFieldKeyTypeMatchReflectKind(t TagType, k reflect.Kind) error { + ok := false + switch t { + case TypeVarint: + ok = k == reflect.Int32 || k == reflect.Uint32 || + k == reflect.Int64 || k == reflect.Uint64 || + k == reflect.Bool + case TypeZigZag32: + ok = k == reflect.Int32 + case TypeZigZag64: + ok = k == reflect.Int64 + case TypeFixed32: + ok = k == reflect.Int32 || k == reflect.Uint32 || k == reflect.Float32 + case TypeFixed64: + ok = k == reflect.Int64 || k == reflect.Uint64 || k == reflect.Float64 + case TypeBytes: + ok = k == reflect.String + } + if ok { + return nil + } + return fmt.Errorf("tag type %q not match field type %q", t, k) +} + +// IsFieldTypeMatchReflectKind return nil if t match k +func IsFieldTypeMatchReflectKind(t TagType, k reflect.Kind) error { + ok := false + switch t { + case TypeVarint: + ok = k == reflect.Int32 || k == reflect.Uint32 || + k == reflect.Int64 || k == reflect.Uint64 || + k == reflect.Bool + case TypeZigZag32: + ok = k == reflect.Int32 + case TypeZigZag64: + ok = k == reflect.Int64 + case TypeFixed32: + ok = k == reflect.Int32 || k == reflect.Uint32 || k == reflect.Float32 + case TypeFixed64: + ok = k == reflect.Int64 || k == reflect.Uint64 || k == reflect.Float64 + case TypeBytes: + ok = k == reflect.String || k == reflect.Map || k == reflect.Struct || k == KindBytes + } + if ok { + return nil + } + return fmt.Errorf("tag type %q not match field type %q", t, k) +} + +type sliceHeader struct { + Data unsafe.Pointer + Len int + Cap int +} + +const ( + // methodProtoReflect is the method which returns description of a proto struct. + methodProtoReflect = "ProtoReflect" + + // fieldOneofWrappers is the field name which contains oneof fields of a proto struct + // + // see: https://go-review.googlesource.com/c/protobuf/+/185239 + fieldOneofWrappers = "OneofWrappers" + + // methodOneofWrappers is the method which returns oneof fields of a proto struct + // + // It's created by old version protobuf or gogoprotobuf + methodOneofWrappers = "XXX_OneofWrappers" +) + +func searchOneofWrappers(t reflect.Type) []any { + kd := t.Kind() + for kd == reflect.Pointer || kd == reflect.Interface { + t = t.Elem() + kd = t.Kind() + } + if kd != reflect.Struct { + return nil + } + pt := reflect.PointerTo(t) + m, ok := pt.MethodByName(methodProtoReflect) + if ok { + args := []reflect.Value{reflect.NewAt(t, nil)} + return searchFieldOneofWrappers(m.Func.Call(args)[0], 5) + } + m, ok = pt.MethodByName(methodOneofWrappers) + if ok { + args := []reflect.Value{reflect.NewAt(t, nil)} + return m.Func.Call(args)[0].Interface().([]any) + } + return nil +} + +func searchFieldOneofWrappers(v reflect.Value, maxdepth int) []any { + if maxdepth <= 0 { + return nil + } + kd := v.Kind() + for kd == reflect.Pointer || kd == reflect.Interface { + v = v.Elem() + kd = v.Kind() + } + if kd != reflect.Struct { + return nil + } + oneofs := v.FieldByName(fieldOneofWrappers) + if oneofs.IsValid() { + // same as oneofs.Interface().([]any) + // fix: cannot return value obtained from unexported field or method + ret := []any{} + p := (*sliceHeader)(unsafe.Pointer(&ret)) + p.Data = oneofs.UnsafePointer() + p.Len = oneofs.Len() + p.Cap = p.Len + return ret + } + for i := 0; i < v.NumField(); i++ { + f := v.Field(i) + if ret := searchFieldOneofWrappers(f, maxdepth-1); ret != nil { + return ret + } + } + return nil +} diff --git a/internal/hack/hack.go b/internal/hack/hack.go new file mode 100644 index 0000000..5c387a7 --- /dev/null +++ b/internal/hack/hack.go @@ -0,0 +1,282 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hack + +import ( + "errors" + "fmt" + "reflect" + "runtime" + "unsafe" +) + +var ( + hackErr bool + hackErrMsg string +) + +func init() { + err := testhack() + if err != nil { + hackErr = true + hackErrMsg = fmt.Sprintf("[BUG] Please upgrade Prutal to latest version.\n"+ + "If the issue still exists kindly report to author.\n"+ + "Err: %s %s/%s %s", runtime.GOOS, runtime.Version(), runtime.GOARCH, err) + } +} + +func PanicIfHackErr() { + if hackErr { + panic(hackErrMsg) + } +} + +// this func should be called once to test compatibility with Go runtime +func testhack() error { + { // MapIter + m := map[int]string{7: "hello"} + rv := reflect.ValueOf(m) + it := NewMapIter(rv) + kp, vp := it.Next() + if *(*int)(kp) != 7 || *(*string)(vp) != "hello" { + return errors.New("compatibility issue found: MapIter") + } + } + + { // maplen + m := map[int]string{} + m[8] = "world" + m[9] = "!" + m[10] = "?" + if maplen(reflect.ValueOf(m).UnsafePointer()) != 3 { + return errors.New("compatibility issue found: maplen") + } + } + + { // ReflectValueWithPtr + m := map[int]string{7: "hello"} + rv := reflect.NewAt(reflect.TypeOf(m), unsafe.Pointer(&m)).Elem() + rv1 := ReflectValueWithPtr(rv, unsafe.Pointer(&m)) + if p0, p1 := rv.UnsafePointer(), rv1.UnsafePointer(); p0 != p1 { + return fmt.Errorf("compatibility issue found: ReflectValueWithPtr %p -> %p m=%p", p0, p1, &m) + } + m1, ok := rv1.Interface().(map[int]string) + if !ok || !reflect.DeepEqual(m, m1) { + return errors.New("compatibility issue found: ReflectValueWithPtr (Interface())") + } + } + + { // ReflectValueTypePtr, ReflectTypePtr + m1 := map[int]string{} + m2 := map[int]*string{} + m3 := map[int]*string{} + rv := reflect.New(reflect.TypeOf(m1)).Elem() + + if ReflectValueTypePtr(reflect.ValueOf(m1)) != ReflectValueTypePtr(rv) || + ReflectValueTypePtr(reflect.ValueOf(m2)) != ReflectValueTypePtr(reflect.ValueOf(m3)) || + ReflectValueTypePtr(reflect.ValueOf(m1)) == ReflectValueTypePtr(reflect.ValueOf(m2)) { + return errors.New("compatibility issue found: ReflectValueTypePtr") + } + + if ReflectTypePtr(reflect.TypeOf(m1)) != ReflectTypePtr(rv.Type()) || + ReflectTypePtr(reflect.TypeOf(m2)) != ReflectTypePtr(reflect.TypeOf(m3)) || + ReflectTypePtr(reflect.TypeOf(m1)) == ReflectTypePtr(reflect.TypeOf(m3)) { + return errors.New("compatibility issue found: ReflectTypePtr") + } + + if ReflectTypePtr(reflect.TypeOf(m1)) != ReflectValueTypePtr(rv) || + ReflectTypePtr(reflect.TypeOf(m2)) != ReflectValueTypePtr(reflect.ValueOf(m3)) { + return errors.New("compatibility issue found: ReflectTypePtr<>ReflectValueTypePtr") + } + } + + { + d0 := &dog{"woo0"} + f0 := iFoo(d0) + if IfaceTypePtr(unsafe.Pointer(&f0)) != ReflectTypePtr(reflect.TypeOf(d0)) { + return fmt.Errorf("compatibility issue found: IfaceTypePtr wrong type") + } + d1 := &dog{"woo1"} + f1 := iFoo(d1) + f2 := iFoo(&cat{"meow"}) + + if IfaceTypePtr(unsafe.Pointer(&f0)) != IfaceTypePtr(unsafe.Pointer(&f1)) { + return fmt.Errorf("compatibility issue found: IfaceTypePtr same type must equal") + } + if IfaceTypePtr(unsafe.Pointer(&f1)) == IfaceTypePtr(unsafe.Pointer(&f2)) { + return fmt.Errorf("compatibility issue found: IfaceTypePtr two types must not equal") + } + tab := IfaceTab(reflect.TypeOf((*iFoo)(nil)).Elem(), reflect.TypeOf(d0).Elem()) + if tab != *(*uintptr)(unsafe.Pointer(&f0)) { + return fmt.Errorf("compatibility issue found: IfaceTab") + } + + var i iFoo + IfaceUpdate(unsafe.Pointer(&i), tab, unsafe.Pointer(&dog{"ha"})) + if i.Foo() != "ha" { + return fmt.Errorf("compatibility issue found: IfaceUpdate") + } + + } + + return nil +} + +type hackMapIter struct { + m reflect.Value + hitter struct { + // k and v is always the 1st two fields of hitter + // it will not be changed easily even though in the future + k unsafe.Pointer + v unsafe.Pointer + } +} + +// MapIter wraps reflect.MapIter for faster unsafe Next() +type MapIter struct { + reflect.MapIter +} + +// NewMapIter creates reflect.MapIter for reflect.Value. +// for go1.18, rv.MapRange() will cause one more allocation +// for >=go1.19, can use rv.MapRange() directly. +// see: https://github.com/golang/go/commit/c5edd5f616b4ee4bbaefdb1579c6078e7ed7e84e +// TODO: remove this func, and use MapIter{rv.MapRange()} when >=go1.19 +func NewMapIter(rv reflect.Value) MapIter { + ret := MapIter{} + (*hackMapIter)(unsafe.Pointer(&ret.MapIter)).m = rv + return ret +} + +func (m *MapIter) Next() (unsafe.Pointer, unsafe.Pointer) { + p := (*hackMapIter)(unsafe.Pointer(&m.MapIter)) + if p.hitter.k != nil { + mapiternext(unsafe.Pointer(&p.hitter)) + return p.hitter.k, p.hitter.v + } + // use reflect.Next to initialize hitter + // then we no need to bind mapiterinit + m.MapIter.Next() + return p.hitter.k, p.hitter.v +} + +func maplen(p unsafe.Pointer) int { + // XXX: race detector not working with this func + type hmap struct { + count int // count is the 1st field + } + return (*hmap)(p).count +} + +type rvtype struct { // reflect.Value + abiType uintptr + ptr unsafe.Pointer // data pointer +} + +// ReflectValueWithPtr returns reflect.Value with the unsafe.Pointer. +// Same reflect.NewAt().Elem() without the cost of getting abi.Type +func ReflectValueWithPtr(rv reflect.Value, p unsafe.Pointer) reflect.Value { + (*rvtype)(unsafe.Pointer(&rv)).ptr = p + return rv +} + +type iFoo interface{ Foo() string } + +type dog struct{ sound string } + +func (d *dog) Foo() string { return d.sound } + +type cat struct{ sound string } + +func (c *cat) Foo() string { return c.sound } + +type itab struct { + _ uintptr + typ uintptr +} + +type Iface struct { + tab uintptr + data unsafe.Pointer +} + +// IfaceTypePtr returns the underlying type ptr of the given p. +// +// p MUST be an Iface +func IfaceTypePtr(p unsafe.Pointer) uintptr { + return (*itab)(unsafe.Pointer((*Iface)(p).tab)).typ +} + +// IfaceUpdate updates iface p with tab and data +func IfaceUpdate(p unsafe.Pointer, tab uintptr, data unsafe.Pointer) { + (*Iface)(p).tab = tab + (*Iface)(p).data = data +} + +// IfaceTab returns iface tab of `v` for iface `t` +func IfaceTab(t reflect.Type, v reflect.Type) uintptr { + if t.Kind() != reflect.Interface || v.Kind() != reflect.Struct { + panic("input type mismatch") + } + i := &Iface{} + reflect.NewAt(t, unsafe.Pointer(i)).Elem().Set(reflect.New(v)) + return uintptr(unsafe.Pointer(i.tab)) +} + +// ExtratIface returns the underlying type and data ptr of the given p. +// +// p MUST be an Iface +func ExtratIface(p unsafe.Pointer) (typ uintptr, data unsafe.Pointer) { + return (*itab)(unsafe.Pointer((*Iface)(p).tab)).typ, unsafe.Pointer((*Iface)(p).data) +} + +// ReflectValueTypePtr returns the abi.Type pointer of the given reflect.Value. +// It used by createOrGetStructDesc for mapping a struct type to *StructDesc, +// and also used when Malloc +func ReflectValueTypePtr(rv reflect.Value) uintptr { + return (*rvtype)(unsafe.Pointer(&rv)).abiType +} + +// ReflectTypePtr returns the abi.Type pointer of the given reflect.Type. +// *rtype of reflect pkg shares the same data struct with *abi.Type +func ReflectTypePtr(rt reflect.Type) uintptr { + type iface struct { + _ uintptr + data uintptr + } + return (*iface)(unsafe.Pointer(&rt)).data +} + +// StringHeader ... +type StringHeader struct { + Data unsafe.Pointer + Len int +} + +// SliceHeader ... +type SliceHeader struct { + Data unsafe.Pointer + Len int + Cap int +} + +//go:linkname mallocgc runtime.mallocgc +func mallocgc(size uintptr, typ unsafe.Pointer, needzero bool) unsafe.Pointer + +//go:noescape +//go:linkname mapiternext runtime.mapiternext +func mapiternext(it unsafe.Pointer) diff --git a/internal/hack/hack_test.go b/internal/hack/hack_test.go new file mode 100644 index 0000000..d7789f8 --- /dev/null +++ b/internal/hack/hack_test.go @@ -0,0 +1,25 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package hack + +import "testing" + +func TestHack(t *testing.T) { + if hackErrMsg != "" { + t.Fatal(hackErrMsg) + } +} diff --git a/internal/protowire/wire.go b/internal/protowire/wire.go new file mode 100644 index 0000000..204de0a --- /dev/null +++ b/internal/protowire/wire.go @@ -0,0 +1,546 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package protowire parses and formats the raw wire encoding. +// See https://protobuf.dev/programming-guides/encoding. +// +// For marshaling and unmarshaling entire protobuf messages, +// use the [google.golang.org/protobuf/proto] package instead. +package protowire + +import ( + "errors" + "io" + "math" + "math/bits" +) + +// Number represents the field number. +type Number int32 + +const ( + MinValidNumber Number = 1 + FirstReservedNumber Number = 19000 + LastReservedNumber Number = 19999 + MaxValidNumber Number = 1<<29 - 1 + DefaultRecursionLimit = 10000 +) + +// IsValid reports whether the field number is semantically valid. +func (n Number) IsValid() bool { + return MinValidNumber <= n && n <= MaxValidNumber +} + +// Type represents the wire type. +type Type int8 + +const ( + VarintType Type = 0 + Fixed32Type Type = 5 + Fixed64Type Type = 1 + BytesType Type = 2 + StartGroupType Type = 3 + EndGroupType Type = 4 +) + +const ( + _ = -iota + errCodeTruncated + errCodeFieldNumber + errCodeOverflow + errCodeReserved + errCodeEndGroup + errCodeRecursionDepth +) + +var ( + errFieldNumber = errors.New("invalid field number") + errOverflow = errors.New("variable length integer overflow") + errReserved = errors.New("cannot parse reserved wire type") + errEndGroup = errors.New("mismatching end group marker") + errParse = errors.New("parse error") +) + +// ParseError converts an error code into an error value. +// This returns nil if n is a non-negative number. +func ParseError(n int) error { + if n >= 0 { + return nil + } + switch n { + case errCodeTruncated: + return io.ErrUnexpectedEOF + case errCodeFieldNumber: + return errFieldNumber + case errCodeOverflow: + return errOverflow + case errCodeReserved: + return errReserved + case errCodeEndGroup: + return errEndGroup + default: + return errParse + } +} + +// ConsumeField parses an entire field record (both tag and value) and returns +// the field number, the wire type, and the total length. +// This returns a negative length upon an error (see [ParseError]). +// +// The total length includes the tag header and the end group marker (if the +// field is a group). +func ConsumeField(b []byte) (Number, Type, int) { + num, typ, n := ConsumeTag(b) + if n < 0 { + return 0, 0, n // forward error code + } + m := ConsumeFieldValue(num, typ, b[n:]) + if m < 0 { + return 0, 0, m // forward error code + } + return num, typ, n + m +} + +// ConsumeFieldValue parses a field value and returns its length. +// This assumes that the field [Number] and wire [Type] have already been parsed. +// This returns a negative length upon an error (see [ParseError]). +// +// When parsing a group, the length includes the end group marker and +// the end group is verified to match the starting field number. +func ConsumeFieldValue(num Number, typ Type, b []byte) (n int) { + return consumeFieldValueD(num, typ, b, DefaultRecursionLimit) +} + +func consumeFieldValueD(num Number, typ Type, b []byte, depth int) (n int) { + switch typ { + case VarintType: + _, n = ConsumeVarint(b) + return n + case Fixed32Type: + _, n = ConsumeFixed32(b) + return n + case Fixed64Type: + _, n = ConsumeFixed64(b) + return n + case BytesType: + _, n = ConsumeBytes(b) + return n + case StartGroupType: + if depth < 0 { + return errCodeRecursionDepth + } + n0 := len(b) + for { + num2, typ2, n := ConsumeTag(b) + if n < 0 { + return n // forward error code + } + b = b[n:] + if typ2 == EndGroupType { + if num != num2 { + return errCodeEndGroup + } + return n0 - len(b) + } + + n = consumeFieldValueD(num2, typ2, b, depth-1) + if n < 0 { + return n // forward error code + } + b = b[n:] + } + case EndGroupType: + return errCodeEndGroup + default: + return errCodeReserved + } +} + +// AppendTag encodes num and typ as a varint-encoded tag and appends it to b. +func AppendTag(b []byte, num Number, typ Type) []byte { + return AppendVarint(b, EncodeTag(num, typ)) +} + +// ConsumeTag parses b as a varint-encoded tag, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeTag(b []byte) (Number, Type, int) { + v, n := ConsumeVarint(b) + if n < 0 { + return 0, 0, n // forward error code + } + num, typ := DecodeTag(v) + if num < MinValidNumber { + return 0, 0, errCodeFieldNumber + } + return num, typ, n +} + +func SizeTag(num Number) int { + return SizeVarint(EncodeTag(num, 0)) // wire type has no effect on size +} + +// AppendVarint appends v to b as a varint-encoded uint64. +func AppendVarint(b []byte, v uint64) []byte { + switch { + case v < 1<<7: + b = append(b, byte(v)) + case v < 1<<14: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte(v>>7)) + case v < 1<<21: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte(v>>14)) + case v < 1<<28: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte(v>>21)) + case v < 1<<35: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte(v>>28)) + case v < 1<<42: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte(v>>35)) + case v < 1<<49: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte(v>>42)) + case v < 1<<56: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte(v>>49)) + case v < 1<<63: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte(v>>56)) + default: + b = append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte((v>>56)&0x7f|0x80), + 1) + } + return b +} + +// ConsumeVarint parses b as a varint-encoded uint64, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeVarint(b []byte) (v uint64, n int) { + var y uint64 + if len(b) <= 0 { + return 0, errCodeTruncated + } + v = uint64(b[0]) + if v < 0x80 { + return v, 1 + } + v -= 0x80 + + if len(b) <= 1 { + return 0, errCodeTruncated + } + y = uint64(b[1]) + v += y << 7 + if y < 0x80 { + return v, 2 + } + v -= 0x80 << 7 + + if len(b) <= 2 { + return 0, errCodeTruncated + } + y = uint64(b[2]) + v += y << 14 + if y < 0x80 { + return v, 3 + } + v -= 0x80 << 14 + + if len(b) <= 3 { + return 0, errCodeTruncated + } + y = uint64(b[3]) + v += y << 21 + if y < 0x80 { + return v, 4 + } + v -= 0x80 << 21 + + if len(b) <= 4 { + return 0, errCodeTruncated + } + y = uint64(b[4]) + v += y << 28 + if y < 0x80 { + return v, 5 + } + v -= 0x80 << 28 + + if len(b) <= 5 { + return 0, errCodeTruncated + } + y = uint64(b[5]) + v += y << 35 + if y < 0x80 { + return v, 6 + } + v -= 0x80 << 35 + + if len(b) <= 6 { + return 0, errCodeTruncated + } + y = uint64(b[6]) + v += y << 42 + if y < 0x80 { + return v, 7 + } + v -= 0x80 << 42 + + if len(b) <= 7 { + return 0, errCodeTruncated + } + y = uint64(b[7]) + v += y << 49 + if y < 0x80 { + return v, 8 + } + v -= 0x80 << 49 + + if len(b) <= 8 { + return 0, errCodeTruncated + } + y = uint64(b[8]) + v += y << 56 + if y < 0x80 { + return v, 9 + } + v -= 0x80 << 56 + + if len(b) <= 9 { + return 0, errCodeTruncated + } + y = uint64(b[9]) + v += y << 63 + if y < 2 { + return v, 10 + } + return 0, errCodeOverflow +} + +// SizeVarint returns the encoded size of a varint. +// The size is guaranteed to be within 1 and 10, inclusive. +func SizeVarint(v uint64) int { + // This computes 1 + (bits.Len64(v)-1)/7. + // 9/64 is a good enough approximation of 1/7 + return int(9*uint32(bits.Len64(v))+64) / 64 +} + +// AppendFixed32 appends v to b as a little-endian uint32. +func AppendFixed32(b []byte, v uint32) []byte { + return append(b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24)) +} + +// ConsumeFixed32 parses b as a little-endian uint32, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeFixed32(b []byte) (v uint32, n int) { + if len(b) < 4 { + return 0, errCodeTruncated + } + v = uint32(b[0])<<0 | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24 + return v, 4 +} + +// SizeFixed32 returns the encoded size of a fixed32; which is always 4. +func SizeFixed32() int { + return 4 +} + +// AppendFixed64 appends v to b as a little-endian uint64. +func AppendFixed64(b []byte, v uint64) []byte { + return append(b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56)) +} + +// ConsumeFixed64 parses b as a little-endian uint64, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeFixed64(b []byte) (v uint64, n int) { + if len(b) < 8 { + return 0, errCodeTruncated + } + v = uint64(b[0])<<0 | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56 + return v, 8 +} + +// SizeFixed64 returns the encoded size of a fixed64; which is always 8. +func SizeFixed64() int { + return 8 +} + +// AppendBytes appends v to b as a length-prefixed bytes value. +func AppendBytes(b []byte, v []byte) []byte { + return append(AppendVarint(b, uint64(len(v))), v...) +} + +// ConsumeBytes parses b as a length-prefixed bytes value, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeBytes(b []byte) (v []byte, n int) { + m, n := ConsumeVarint(b) + if n < 0 { + return nil, n // forward error code + } + if m > uint64(len(b[n:])) { + return nil, errCodeTruncated + } + return b[n:][:m], n + int(m) +} + +// SizeBytes returns the encoded size of a length-prefixed bytes value, +// given only the length. +func SizeBytes(n int) int { + return SizeVarint(uint64(n)) + n +} + +// AppendString appends v to b as a length-prefixed bytes value. +func AppendString(b []byte, v string) []byte { + return append(AppendVarint(b, uint64(len(v))), v...) +} + +// ConsumeString parses b as a length-prefixed bytes value, reporting its length. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeString(b []byte) (v string, n int) { + bb, n := ConsumeBytes(b) + return string(bb), n +} + +// AppendGroup appends v to b as group value, with a trailing end group marker. +// The value v must not contain the end marker. +func AppendGroup(b []byte, num Number, v []byte) []byte { + return AppendVarint(append(b, v...), EncodeTag(num, EndGroupType)) +} + +// ConsumeGroup parses b as a group value until the trailing end group marker, +// and verifies that the end marker matches the provided num. The value v +// does not contain the end marker, while the length does contain the end marker. +// This returns a negative length upon an error (see [ParseError]). +func ConsumeGroup(num Number, b []byte) (v []byte, n int) { + n = ConsumeFieldValue(num, StartGroupType, b) + if n < 0 { + return nil, n // forward error code + } + b = b[:n] + + // Truncate off end group marker, but need to handle denormalized varints. + // Assuming end marker is never 0 (which is always the case since + // EndGroupType is non-zero), we can truncate all trailing bytes where the + // lower 7 bits are all zero (implying that the varint is denormalized). + for len(b) > 0 && b[len(b)-1]&0x7f == 0 { + b = b[:len(b)-1] + } + b = b[:len(b)-SizeTag(num)] + return b, n +} + +// SizeGroup returns the encoded size of a group, given only the length. +func SizeGroup(num Number, n int) int { + return n + SizeTag(num) +} + +// DecodeTag decodes the field [Number] and wire [Type] from its unified form. +// The [Number] is -1 if the decoded field number overflows int32. +// Other than overflow, this does not check for field number validity. +func DecodeTag(x uint64) (Number, Type) { + // NOTE: MessageSet allows for larger field numbers than normal. + if x>>3 > uint64(math.MaxInt32) { + return -1, 0 + } + return Number(x >> 3), Type(x & 7) +} + +// EncodeTag encodes the field [Number] and wire [Type] into its unified form. +func EncodeTag(num Number, typ Type) uint64 { + return uint64(num)<<3 | uint64(typ&7) +} + +// DecodeZigZag decodes a zig-zag-encoded uint64 as an int64. +// +// Input: {…, 5, 3, 1, 0, 2, 4, 6, …} +// Output: {…, -3, -2, -1, 0, +1, +2, +3, …} +func DecodeZigZag(x uint64) int64 { + return int64(x>>1) ^ int64(x)<<63>>63 +} + +// EncodeZigZag encodes an int64 as a zig-zag-encoded uint64. +// +// Input: {…, -3, -2, -1, 0, +1, +2, +3, …} +// Output: {…, 5, 3, 1, 0, 2, 4, 6, …} +func EncodeZigZag(x int64) uint64 { + return uint64(x<<1) ^ uint64(x>>63) +} + +// DecodeBool decodes a uint64 as a bool. +// +// Input: { 0, 1, 2, …} +// Output: {false, true, true, …} +func DecodeBool(x uint64) bool { + return x != 0 +} + +// EncodeBool encodes a bool as a uint64. +// +// Input: {false, true} +// Output: { 0, 1} +func EncodeBool(x bool) uint64 { + if x { + return 1 + } + return 0 +} diff --git a/internal/protowire/wire_test.go b/internal/protowire/wire_test.go new file mode 100644 index 0000000..486d183 --- /dev/null +++ b/internal/protowire/wire_test.go @@ -0,0 +1,680 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package protowire + +import ( + "bytes" + "encoding/hex" + "io" + "math" + "strings" + "testing" +) + +type ( + testOps struct { + // appendOps is a sequence of append operations, each appending to + // the output of the previous append operation. + appendOps []appendOp + + // wantRaw (if not nil) is the bytes that the appendOps should produce. + wantRaw []byte + + // consumeOps are a sequence of consume operations, each consuming the + // remaining output after the previous consume operation. + // The first consume operation starts with the output of appendOps. + consumeOps []consumeOp + } + + // appendOp represents an Append operation. + appendOp = any + appendTag struct { + inNum Number + inType Type + } + appendVarint struct { + inVal uint64 + } + appendFixed32 struct { + inVal uint32 + } + appendFixed64 struct { + inVal uint64 + } + appendBytes struct { + inVal []byte + } + appendGroup struct { + inNum Number + inVal []byte + } + appendRaw []byte + + // consumeOp represents an Consume operation. + consumeOp = any + consumeField struct { + wantNum Number + wantType Type + wantCnt int + wantErr error + } + consumeFieldValue struct { + inNum Number + inType Type + wantCnt int + wantErr error + } + consumeTag struct { + wantNum Number + wantType Type + wantCnt int + wantErr error + } + consumeVarint struct { + wantVal uint64 + wantCnt int + wantErr error + } + consumeFixed32 struct { + wantVal uint32 + wantCnt int + wantErr error + } + consumeFixed64 struct { + wantVal uint64 + wantCnt int + wantErr error + } + consumeBytes struct { + wantVal []byte + wantCnt int + wantErr error + } + consumeGroup struct { + inNum Number + wantVal []byte + wantCnt int + wantErr error + } + + ops []any +) + +// dhex decodes a hex-string and returns the bytes and panics if s is invalid. +func dhex(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b +} + +func TestTag(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeTag{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendTag{inNum: 0, inType: Fixed32Type}}, + wantRaw: dhex("05"), + consumeOps: ops{consumeTag{wantErr: errFieldNumber}}, + }, { + appendOps: ops{appendTag{inNum: 1, inType: Fixed32Type}}, + wantRaw: dhex("0d"), + consumeOps: ops{consumeTag{wantNum: 1, wantType: Fixed32Type, wantCnt: 1}}, + }, { + appendOps: ops{appendTag{inNum: FirstReservedNumber, inType: BytesType}}, + wantRaw: dhex("c2a309"), + consumeOps: ops{consumeTag{wantNum: FirstReservedNumber, wantType: BytesType, wantCnt: 3}}, + }, { + appendOps: ops{appendTag{inNum: LastReservedNumber, inType: StartGroupType}}, + wantRaw: dhex("fbe109"), + consumeOps: ops{consumeTag{wantNum: LastReservedNumber, wantType: StartGroupType, wantCnt: 3}}, + }, { + appendOps: ops{appendTag{inNum: MaxValidNumber, inType: VarintType}}, + wantRaw: dhex("f8ffffff0f"), + consumeOps: ops{consumeTag{wantNum: MaxValidNumber, wantType: VarintType, wantCnt: 5}}, + }, { + appendOps: ops{appendVarint{inVal: ((math.MaxInt32+1)<<3 | uint64(VarintType))}}, + wantRaw: dhex("8080808040"), + consumeOps: ops{consumeTag{wantErr: errFieldNumber}}, + }}) +} + +func TestVarint(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("80"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("8080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("80808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("8080808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("808080808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("80808080808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("8080808080808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("808080808080808080"))}, + consumeOps: ops{consumeVarint{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("80808080808080808080"))}, + consumeOps: ops{consumeVarint{wantErr: errOverflow}}, + }, { + // Test varints at various boundaries where the length changes. + appendOps: ops{appendVarint{inVal: 0x0}}, + wantRaw: dhex("00"), + consumeOps: ops{consumeVarint{wantVal: 0, wantCnt: 1}}, + }, { + appendOps: ops{appendVarint{inVal: 0x1}}, + wantRaw: dhex("01"), + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7f}}, + wantRaw: dhex("7f"), + consumeOps: ops{consumeVarint{wantVal: 0x7f, wantCnt: 1}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7f + 1}}, + wantRaw: dhex("8001"), + consumeOps: ops{consumeVarint{wantVal: 0x7f + 1, wantCnt: 2}}, + }, { + appendOps: ops{appendVarint{inVal: 0x3fff}}, + wantRaw: dhex("ff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x3fff, wantCnt: 2}}, + }, { + appendOps: ops{appendVarint{inVal: 0x3fff + 1}}, + wantRaw: dhex("808001"), + consumeOps: ops{consumeVarint{wantVal: 0x3fff + 1, wantCnt: 3}}, + }, { + appendOps: ops{appendVarint{inVal: 0x1fffff}}, + wantRaw: dhex("ffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x1fffff, wantCnt: 3}}, + }, { + appendOps: ops{appendVarint{inVal: 0x1fffff + 1}}, + wantRaw: dhex("80808001"), + consumeOps: ops{consumeVarint{wantVal: 0x1fffff + 1, wantCnt: 4}}, + }, { + appendOps: ops{appendVarint{inVal: 0xfffffff}}, + wantRaw: dhex("ffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0xfffffff, wantCnt: 4}}, + }, { + appendOps: ops{appendVarint{inVal: 0xfffffff + 1}}, + wantRaw: dhex("8080808001"), + consumeOps: ops{consumeVarint{wantVal: 0xfffffff + 1, wantCnt: 5}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7ffffffff}}, + wantRaw: dhex("ffffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff, wantCnt: 5}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7ffffffff + 1}}, + wantRaw: dhex("808080808001"), + consumeOps: ops{consumeVarint{wantVal: 0x7ffffffff + 1, wantCnt: 6}}, + }, { + appendOps: ops{appendVarint{inVal: 0x3ffffffffff}}, + wantRaw: dhex("ffffffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff, wantCnt: 6}}, + }, { + appendOps: ops{appendVarint{inVal: 0x3ffffffffff + 1}}, + wantRaw: dhex("80808080808001"), + consumeOps: ops{consumeVarint{wantVal: 0x3ffffffffff + 1, wantCnt: 7}}, + }, { + appendOps: ops{appendVarint{inVal: 0x1ffffffffffff}}, + wantRaw: dhex("ffffffffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff, wantCnt: 7}}, + }, { + appendOps: ops{appendVarint{inVal: 0x1ffffffffffff + 1}}, + wantRaw: dhex("8080808080808001"), + consumeOps: ops{consumeVarint{wantVal: 0x1ffffffffffff + 1, wantCnt: 8}}, + }, { + appendOps: ops{appendVarint{inVal: 0xffffffffffffff}}, + wantRaw: dhex("ffffffffffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff, wantCnt: 8}}, + }, { + appendOps: ops{appendVarint{inVal: 0xffffffffffffff + 1}}, + wantRaw: dhex("808080808080808001"), + consumeOps: ops{consumeVarint{wantVal: 0xffffffffffffff + 1, wantCnt: 9}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7fffffffffffffff}}, + wantRaw: dhex("ffffffffffffffff7f"), + consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff, wantCnt: 9}}, + }, { + appendOps: ops{appendVarint{inVal: 0x7fffffffffffffff + 1}}, + wantRaw: dhex("80808080808080808001"), + consumeOps: ops{consumeVarint{wantVal: 0x7fffffffffffffff + 1, wantCnt: 10}}, + }, { + appendOps: ops{appendVarint{inVal: math.MaxUint64}}, + wantRaw: dhex("ffffffffffffffffff01"), + consumeOps: ops{consumeVarint{wantVal: math.MaxUint64, wantCnt: 10}}, + }, { + appendOps: ops{appendRaw(dhex("ffffffffffffffffff02"))}, + consumeOps: ops{consumeVarint{wantErr: errOverflow}}, + }, { + // Test denormalized varints; where the encoding, while valid, is + // larger than necessary. + appendOps: ops{appendRaw(dhex("01"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 1}}, + }, { + appendOps: ops{appendRaw(dhex("8100"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 2}}, + }, { + appendOps: ops{appendRaw(dhex("818000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 3}}, + }, { + appendOps: ops{appendRaw(dhex("81808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 4}}, + }, { + appendOps: ops{appendRaw(dhex("8180808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 5}}, + }, { + appendOps: ops{appendRaw(dhex("818080808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 6}}, + }, { + appendOps: ops{appendRaw(dhex("81808080808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 7}}, + }, { + appendOps: ops{appendRaw(dhex("8180808080808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 8}}, + }, { + appendOps: ops{appendRaw(dhex("818080808080808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 9}}, + }, { + appendOps: ops{appendRaw(dhex("81808080808080808000"))}, + consumeOps: ops{consumeVarint{wantVal: 1, wantCnt: 10}}, + }, { + appendOps: ops{appendRaw(dhex("8180808080808080808000"))}, + consumeOps: ops{consumeVarint{wantErr: errOverflow}}, + }}) +} + +func TestFixed32(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("000000"))}, + consumeOps: ops{consumeFixed32{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendFixed32{0}}, + wantRaw: dhex("00000000"), + consumeOps: ops{consumeFixed32{wantVal: 0, wantCnt: 4}}, + }, { + appendOps: ops{appendFixed32{math.MaxUint32}}, + wantRaw: dhex("ffffffff"), + consumeOps: ops{consumeFixed32{wantVal: math.MaxUint32, wantCnt: 4}}, + }, { + appendOps: ops{appendFixed32{0xf0e1d2c3}}, + wantRaw: dhex("c3d2e1f0"), + consumeOps: ops{consumeFixed32{wantVal: 0xf0e1d2c3, wantCnt: 4}}, + }}) +} + +func TestFixed64(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("00000000000000"))}, + consumeOps: ops{consumeFixed64{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendFixed64{0}}, + wantRaw: dhex("0000000000000000"), + consumeOps: ops{consumeFixed64{wantVal: 0, wantCnt: 8}}, + }, { + appendOps: ops{appendFixed64{math.MaxUint64}}, + wantRaw: dhex("ffffffffffffffff"), + consumeOps: ops{consumeFixed64{wantVal: math.MaxUint64, wantCnt: 8}}, + }, { + appendOps: ops{appendFixed64{0xf0e1d2c3b4a59687}}, + wantRaw: dhex("8796a5b4c3d2e1f0"), + consumeOps: ops{consumeFixed64{wantVal: 0xf0e1d2c3b4a59687, wantCnt: 8}}, + }}) +} + +func TestBytes(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendRaw(dhex("01"))}, + consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendVarint{0}, appendRaw("")}, + wantRaw: dhex("00"), + consumeOps: ops{consumeBytes{wantVal: dhex(""), wantCnt: 1}}, + }, { + appendOps: ops{appendBytes{[]byte("hello")}}, + wantRaw: []byte("\x05hello"), + consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 6}}, + }, { + appendOps: ops{appendBytes{[]byte(strings.Repeat("hello", 50))}}, + wantRaw: []byte("\xfa\x01" + strings.Repeat("hello", 50)), + consumeOps: ops{consumeBytes{wantVal: []byte(strings.Repeat("hello", 50)), wantCnt: 252}}, + }, { + appendOps: ops{appendRaw("\x85\x80\x00hello")}, + consumeOps: ops{consumeBytes{wantVal: []byte("hello"), wantCnt: 8}}, + }, { + appendOps: ops{appendRaw("\x85\x80\x00hell")}, + consumeOps: ops{consumeBytes{wantErr: io.ErrUnexpectedEOF}}, + }}) +} + +func TestGroup(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeGroup{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{appendTag{inNum: 0, inType: StartGroupType}}, + consumeOps: ops{consumeGroup{inNum: 1, wantErr: errFieldNumber}}, + }, { + appendOps: ops{appendTag{inNum: 2, inType: EndGroupType}}, + consumeOps: ops{consumeGroup{inNum: 1, wantErr: errEndGroup}}, + }, { + appendOps: ops{appendTag{inNum: 1, inType: EndGroupType}}, + consumeOps: ops{consumeGroup{inNum: 1, wantCnt: 1}}, + }, { + appendOps: ops{ + appendTag{inNum: 5, inType: Fixed32Type}, + appendFixed32{0xf0e1d2c3}, + appendTag{inNum: 5, inType: EndGroupType}, + }, + wantRaw: dhex("2dc3d2e1f02c"), + consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 6}}, + }, { + appendOps: ops{ + appendTag{inNum: 5, inType: Fixed32Type}, + appendFixed32{0xf0e1d2c3}, + appendRaw(dhex("ac808000")), + }, + consumeOps: ops{consumeGroup{inNum: 5, wantVal: dhex("2dc3d2e1f0"), wantCnt: 9}}, + }}) +} + +func TestField(t *testing.T) { + runTests(t, []testOps{{ + appendOps: ops{appendRaw(dhex(""))}, + consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{ + appendTag{inNum: 5000, inType: StartGroupType}, + appendTag{inNum: 1, inType: VarintType}, + appendVarint{123456789}, + appendTag{inNum: 12, inType: Fixed32Type}, + appendFixed32{123456789}, + appendTag{inNum: 123, inType: Fixed64Type}, + appendFixed64{123456789}, + appendTag{inNum: 1234, inType: BytesType}, + appendBytes{[]byte("hello")}, + appendTag{inNum: 12345, inType: StartGroupType}, + appendTag{inNum: 11, inType: VarintType}, + appendVarint{123456789}, + appendTag{inNum: 1212, inType: Fixed32Type}, + appendFixed32{123456789}, + appendTag{inNum: 123123, inType: Fixed64Type}, + appendFixed64{123456789}, + appendTag{inNum: 12341234, inType: BytesType}, + appendBytes{[]byte("goodbye")}, + appendTag{inNum: 12345, inType: EndGroupType}, + appendTag{inNum: 5000, inType: EndGroupType}, + }, + wantRaw: dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"), + consumeOps: ops{ + consumeTag{wantNum: 5000, wantType: StartGroupType, wantCnt: 3}, + consumeTag{wantNum: 1, wantType: VarintType, wantCnt: 1}, + consumeVarint{wantVal: 123456789, wantCnt: 4}, + consumeTag{wantNum: 12, wantType: Fixed32Type, wantCnt: 1}, + consumeFixed32{wantVal: 123456789, wantCnt: 4}, + consumeTag{wantNum: 123, wantType: Fixed64Type, wantCnt: 2}, + consumeFixed64{wantVal: 123456789, wantCnt: 8}, + consumeTag{wantNum: 1234, wantType: BytesType, wantCnt: 2}, + consumeBytes{wantVal: []byte("hello"), wantCnt: 6}, + consumeTag{wantNum: 12345, wantType: StartGroupType, wantCnt: 3}, + consumeTag{wantNum: 11, wantType: VarintType, wantCnt: 1}, + consumeVarint{wantVal: 123456789, wantCnt: 4}, + consumeTag{wantNum: 1212, wantType: Fixed32Type, wantCnt: 2}, + consumeFixed32{wantVal: 123456789, wantCnt: 4}, + consumeTag{wantNum: 123123, wantType: Fixed64Type, wantCnt: 3}, + consumeFixed64{wantVal: 123456789, wantCnt: 8}, + consumeTag{wantNum: 12341234, wantType: BytesType, wantCnt: 4}, + consumeBytes{wantVal: []byte("goodbye"), wantCnt: 8}, + consumeTag{wantNum: 12345, wantType: EndGroupType, wantCnt: 3}, + consumeTag{wantNum: 5000, wantType: EndGroupType, wantCnt: 3}, + }, + }, { + appendOps: ops{appendRaw(dhex("c3b80208959aef3a6515cd5b07d90715cd5b0700000000924d0568656c6c6fcb830658959aef3ae54b15cd5b07998f3c15cd5b070000000092ff892f07676f6f64627965cc8306c4b802"))}, + consumeOps: ops{consumeField{wantNum: 5000, wantType: StartGroupType, wantCnt: 74}}, + }, { + appendOps: ops{appendTag{inNum: 5, inType: EndGroupType}}, + wantRaw: dhex("2c"), + consumeOps: ops{consumeField{wantErr: errEndGroup}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendTag{inNum: 22, inType: StartGroupType}, + appendTag{inNum: 333, inType: StartGroupType}, + appendTag{inNum: 4444, inType: StartGroupType}, + appendTag{inNum: 4444, inType: EndGroupType}, + appendTag{inNum: 333, inType: EndGroupType}, + appendTag{inNum: 22, inType: EndGroupType}, + appendTag{inNum: 1, inType: EndGroupType}, + }, + wantRaw: dhex("0bb301eb14e39502e49502ec14b4010c"), + consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendGroup{inNum: 1, inVal: dhex("b301eb14e39502e49502ec14b401")}, + }, + wantRaw: dhex("0b" + "b301eb14e39502e49502ec14b401" + "0c"), + consumeOps: ops{consumeField{wantNum: 1, wantType: StartGroupType, wantCnt: 16}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendTag{inNum: 22, inType: StartGroupType}, + appendTag{inNum: 333, inType: StartGroupType}, + appendTag{inNum: 4444, inType: StartGroupType}, + appendTag{inNum: 333, inType: EndGroupType}, + appendTag{inNum: 22, inType: EndGroupType}, + appendTag{inNum: 1, inType: EndGroupType}, + }, + consumeOps: ops{consumeField{wantErr: errEndGroup}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendTag{inNum: 22, inType: StartGroupType}, + appendTag{inNum: 333, inType: StartGroupType}, + appendTag{inNum: 4444, inType: StartGroupType}, + appendTag{inNum: 4444, inType: EndGroupType}, + appendTag{inNum: 333, inType: EndGroupType}, + appendTag{inNum: 22, inType: EndGroupType}, + }, + consumeOps: ops{consumeField{wantErr: io.ErrUnexpectedEOF}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendTag{inNum: 22, inType: StartGroupType}, + appendTag{inNum: 333, inType: StartGroupType}, + appendTag{inNum: 4444, inType: StartGroupType}, + appendTag{inNum: 0, inType: VarintType}, + appendTag{inNum: 4444, inType: EndGroupType}, + appendTag{inNum: 333, inType: EndGroupType}, + appendTag{inNum: 22, inType: EndGroupType}, + appendTag{inNum: 1, inType: EndGroupType}, + }, + consumeOps: ops{consumeField{wantErr: errFieldNumber}}, + }, { + appendOps: ops{ + appendTag{inNum: 1, inType: StartGroupType}, + appendTag{inNum: 22, inType: StartGroupType}, + appendTag{inNum: 333, inType: StartGroupType}, + appendTag{inNum: 4444, inType: StartGroupType}, + appendTag{inNum: 1, inType: 6}, + appendTag{inNum: 4444, inType: EndGroupType}, + appendTag{inNum: 333, inType: EndGroupType}, + appendTag{inNum: 22, inType: EndGroupType}, + appendTag{inNum: 1, inType: EndGroupType}, + }, + consumeOps: ops{consumeField{wantErr: errReserved}}, + }}) +} + +func runTests(t *testing.T, tests []testOps) { + for _, tt := range tests { + t.Run("", func(t *testing.T) { + var b []byte + for _, op := range tt.appendOps { + b0 := b + switch op := op.(type) { + case appendTag: + b = AppendTag(b, op.inNum, op.inType) + case appendVarint: + b = AppendVarint(b, op.inVal) + case appendFixed32: + b = AppendFixed32(b, op.inVal) + case appendFixed64: + b = AppendFixed64(b, op.inVal) + case appendBytes: + b = AppendBytes(b, op.inVal) + case appendGroup: + b = AppendGroup(b, op.inNum, op.inVal) + case appendRaw: + b = append(b, op...) + } + + check := func(label string, want int) { + t.Helper() + if got := len(b) - len(b0); got != want { + t.Errorf("len(Append%v) and Size%v mismatch: got %v, want %v", label, label, got, want) + } + } + switch op := op.(type) { + case appendTag: + check("Tag", SizeTag(op.inNum)) + case appendVarint: + check("Varint", SizeVarint(op.inVal)) + case appendFixed32: + check("Fixed32", SizeFixed32()) + case appendFixed64: + check("Fixed64", SizeFixed64()) + case appendBytes: + check("Bytes", SizeBytes(len(op.inVal))) + case appendGroup: + check("Group", SizeGroup(op.inNum, len(op.inVal))) + } + } + + if tt.wantRaw != nil && !bytes.Equal(b, tt.wantRaw) { + t.Errorf("raw output mismatch:\ngot %x\nwant %x", b, tt.wantRaw) + } + + for _, op := range tt.consumeOps { + check := func(label string, gotCnt, wantCnt int, wantErr error) { + t.Helper() + gotErr := ParseError(gotCnt) + if gotCnt < 0 { + gotCnt = 0 + } + if gotCnt != wantCnt { + t.Errorf("Consume%v(): consumed %d bytes, want %d bytes consumed", label, gotCnt, wantCnt) + } + if gotErr != wantErr { + t.Errorf("Consume%v(): got %v error, want %v error", label, gotErr, wantErr) + } + b = b[gotCnt:] + } + switch op := op.(type) { + case consumeField: + gotNum, gotType, n := ConsumeField(b) + if gotNum != op.wantNum || gotType != op.wantType { + t.Errorf("ConsumeField() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType) + } + check("Field", n, op.wantCnt, op.wantErr) + case consumeFieldValue: + n := ConsumeFieldValue(op.inNum, op.inType, b) + check("FieldValue", n, op.wantCnt, op.wantErr) + case consumeTag: + gotNum, gotType, n := ConsumeTag(b) + if gotNum != op.wantNum || gotType != op.wantType { + t.Errorf("ConsumeTag() = (%d, %v), want (%d, %v)", gotNum, gotType, op.wantNum, op.wantType) + } + check("Tag", n, op.wantCnt, op.wantErr) + case consumeVarint: + gotVal, n := ConsumeVarint(b) + if gotVal != op.wantVal { + t.Errorf("ConsumeVarint() = %d, want %d", gotVal, op.wantVal) + } + check("Varint", n, op.wantCnt, op.wantErr) + case consumeFixed32: + gotVal, n := ConsumeFixed32(b) + if gotVal != op.wantVal { + t.Errorf("ConsumeFixed32() = %d, want %d", gotVal, op.wantVal) + } + check("Fixed32", n, op.wantCnt, op.wantErr) + case consumeFixed64: + gotVal, n := ConsumeFixed64(b) + if gotVal != op.wantVal { + t.Errorf("ConsumeFixed64() = %d, want %d", gotVal, op.wantVal) + } + check("Fixed64", n, op.wantCnt, op.wantErr) + case consumeBytes: + gotVal, n := ConsumeBytes(b) + if !bytes.Equal(gotVal, op.wantVal) { + t.Errorf("ConsumeBytes() = %x, want %x", gotVal, op.wantVal) + } + check("Bytes", n, op.wantCnt, op.wantErr) + case consumeGroup: + gotVal, n := ConsumeGroup(op.inNum, b) + if !bytes.Equal(gotVal, op.wantVal) { + t.Errorf("ConsumeGroup() = %x, want %x", gotVal, op.wantVal) + } + check("Group", n, op.wantCnt, op.wantErr) + } + } + }) + } +} + +func TestZigZag(t *testing.T) { + tests := []struct { + dec int64 + enc uint64 + }{ + {math.MinInt64 + 0, math.MaxUint64 - 0}, + {math.MinInt64 + 1, math.MaxUint64 - 2}, + {math.MinInt64 + 2, math.MaxUint64 - 4}, + {-3, 5}, + {-2, 3}, + {-1, 1}, + {0, 0}, + {+1, 2}, + {+2, 4}, + {+3, 6}, + {math.MaxInt64 - 2, math.MaxUint64 - 5}, + {math.MaxInt64 - 1, math.MaxUint64 - 3}, + {math.MaxInt64 - 0, math.MaxUint64 - 1}, + } + + for _, tt := range tests { + if enc := EncodeZigZag(tt.dec); enc != tt.enc { + t.Errorf("EncodeZigZag(%d) = %d, want %d", tt.dec, enc, tt.enc) + } + if dec := DecodeZigZag(tt.enc); dec != tt.dec { + t.Errorf("DecodeZigZag(%d) = %d, want %d", tt.enc, dec, tt.dec) + } + } +} diff --git a/internal/prutal/decoder.go b/internal/prutal/decoder.go new file mode 100644 index 0000000..dbea87d --- /dev/null +++ b/internal/prutal/decoder.go @@ -0,0 +1,648 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "fmt" + "reflect" + "runtime" + "sync" + "unsafe" + + "github.com/cloudwego/prutal/internal/desc" + "github.com/cloudwego/prutal/internal/hack" + "github.com/cloudwego/prutal/internal/protowire" + "github.com/cloudwego/prutal/internal/wire" +) + +var decoderPool = sync.Pool{ + New: func() interface{} { + d := &Decoder{} + d.s.init() + return d + }, +} + +const defaultMinMakeSliceCap = 8 + +type Decoder struct { + s span +} + +func (d *Decoder) Malloc(n, align int, abiType uintptr) unsafe.Pointer { + if n > defaultDecoderSpanSize/4 || abiType != 0 { + // too large, or it needs GC to scan (MallocAbiType != 0 of tType) + return mallocgc(uintptr(n), unsafe.Pointer(abiType), abiType != 0) + } + return d.s.Malloc(n, align) // only for noscan objects like string.Data, []int etc... +} + +func (d *Decoder) mallocIfPointer(p unsafe.Pointer, t *desc.Type) (ret unsafe.Pointer) { + if !t.IsPointer { + return p + } + + // we need to malloc the type first before assigning a value to it + // *p = new(type) + t = t.V + ret = d.Malloc(int(t.Size), t.Align, t.MallocAbiType) + *(*unsafe.Pointer)(p) = ret + return +} + +func resetUnknownFields(s *desc.StructDesc, base unsafe.Pointer) { + p := unsafe.Add(base, s.UnknownFieldsOffset) + if s.UnknownFieldsPointer { + p = *(*unsafe.Pointer)(p) + } + if p != nil { + (*hack.SliceHeader)(p).Len = 0 + } +} + +func appendToUnknownFields(s *desc.StructDesc, base unsafe.Pointer, b []byte) { + p := unsafe.Add(base, s.UnknownFieldsOffset) + var x *[]byte + if s.UnknownFieldsPointer { + if *(*unsafe.Pointer)(p) == nil { + *(*unsafe.Pointer)(p) = unsafe.Pointer(&[]byte{}) + } + x = (*[]byte)(*(*unsafe.Pointer)(p)) + } else { + x = (*[]byte)(p) + } + *x = append(*x, b...) +} + +func (d *Decoder) DecodeStruct(b []byte, base unsafe.Pointer, s *desc.StructDesc, maxdepth int) (int, error) { + if maxdepth == 0 { + return 0, errMaxDepthExceeded + } + i := 0 + + var ( + f *desc.FieldDesc // cache last field, optmize for repeated + tmv *desc.TmpMapVars // cache for map, optmize for repeated map + ) + + // reset unknownfields = unknownfields[:0] + if s.HasUnknownFields { + resetUnknownFields(s, base) + } + + for i < len(b) { + // next field tag + num, typ, n := protowire.ConsumeTag(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + i += n + + // get field or skip + if f == nil || f.ID != int32(num) { + if tmv != nil { + f.T.MapTmpVarsPool.Put(tmv) + tmv = nil + } + f = s.GetField(int32(num)) + if f == nil { + // field not found, skip bytes + m := protowire.ConsumeFieldValue(num, typ, b[i:]) + if m < 0 { + return i, protowire.ParseError(m) + } + i += m + if s.HasUnknownFields { + appendToUnknownFields(s, base, b[i-m-n:i]) + } + continue + } + } + + p := unsafe.Add(base, f.Offset) + t := f.T + tag := f.TagType + if f.IsOneof() { + data := d.Malloc(int(t.Size), t.Align, t.MallocAbiType) + hack.IfaceUpdate(p, f.IfaceTab, data) + p = data + } + + if t.IsPointer { + p = d.mallocIfPointer(p, t) + t = t.V + } + + if f.Repeated && t.IsSlice { + t = t.V + if typ == wire.TypeBytes && f.Packed { + goto DECODE_PACKED_SLICE // packed repeated fields, only scalar types except string or bytes + } + h := (*hack.SliceHeader)(p) + if h.Cap == 0 { + d.ReallocSlice(h, t, defaultMinMakeSliceCap) + } else if h.Len == h.Cap { + d.ReallocSlice(h, t, 2*h.Cap) + } + h.Len++ + p = unsafe.Add(h.Data, uintptr(h.Len-1)*t.Size) // p = &d[len(d-1)] + if t.IsPointer { + p = d.mallocIfPointer(p, t) + t = t.V + } + } + + switch typ { + case wire.TypeVarint: // case: VARINT + if tag != desc.TypeVarint && tag != desc.TypeZigZag32 && tag != desc.TypeZigZag64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeVarint(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + if tag == desc.TypeZigZag32 || tag == desc.TypeZigZag64 { + u64 = uint64(protowire.DecodeZigZag(u64)) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u64) + case reflect.Uint32: + *(*uint32)(p) = uint32(u64) + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64: + *(*uint64)(p) = u64 + case reflect.Bool: // 1 for true, 0 for false + *(*byte)(p) = byte(u64 & 0x1) + } + i += n + + case wire.TypeFixed32: // case: I32 + if tag != desc.TypeFixed32 { + return i, newWireTypeNotMatch(typ, tag) + } + + u32, n := protowire.ConsumeFixed32(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u32) + case reflect.Uint32, reflect.Float32: + *(*uint32)(p) = u32 + } + i += n + + case wire.TypeFixed64: // case: I64 + if tag != desc.TypeFixed64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeFixed64(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64, reflect.Float64: + *(*uint64)(p) = u64 + } + i += n + + case wire.TypeBytes: // case: LEN + // string, bytes, embedded messages (struct or map), packed repeated fields + fb, n := protowire.ConsumeBytes(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + i += n + switch t.Kind { + case desc.KindBytes: + if len(fb) > 0 { + data := d.Malloc(len(fb), 1, 0) + h := (*hack.SliceHeader)(p) + h.Data = data + h.Len = len(fb) + h.Cap = len(fb) + copy(*(*[]byte)(p), fb) + runtime.KeepAlive(data) + } else { + *(*[]byte)(p) = []byte{} + } + case reflect.String: + if len(fb) > 0 { + data := d.Malloc(len(fb), 1, 0) + h := (*hack.StringHeader)(p) + h.Data = data + h.Len = len(fb) + copyb(data, fb) + } else { + *(*string)(p) = "" + } + case reflect.Map: + if tmv == nil { + tmv = f.T.MapTmpVarsPool.Get().(*desc.TmpMapVars) + } + if _, err := d.DecodeMapPair(fb, p, f, tmv, maxdepth-1); err != nil { + return i, err + } + case reflect.Struct: + if _, err := d.DecodeStruct(fb, p, t.S, maxdepth-1); err != nil { + return i, err + } + default: + return i, newWireTypeNotMatch(typ, tag) + } + + default: + // unknown wiretype + return i, newWireTypeNotMatch(typ, tag) + } + + continue // skip decoding packed slice below + + DECODE_PACKED_SLICE: + { // for scalar types except string, bytes + packed, n := protowire.ConsumeBytes(b[i:]) + if n < 0 { + return i, protowire.ParseError(n) + } + i += n + + h := (*hack.SliceHeader)(p) + // only for initialization, may not accurate for varint, and may case allocing more mem than needed. + // but it's perfect for fixed32 / fixed64 + sz := len(packed) / int(t.Size) + if tag != desc.TypeFixed32 && + tag != desc.TypeFixed64 && + sz < defaultMinMakeSliceCap { + sz = defaultMinMakeSliceCap + } + h.Len = 0 // x[:0] + d.ReallocSlice(h, t, sz) + + j := 0 + var u32 uint32 + var u64 uint64 + for j < len(packed) { + switch tag { + case desc.TypeFixed32: + u32, n = protowire.ConsumeFixed32(packed[j:]) + u64 = uint64(u32) // so that we can unify the code to use u64 + case desc.TypeFixed64: + u64, n = protowire.ConsumeFixed64(packed[j:]) + default: + u64, n = protowire.ConsumeVarint(packed[j:]) + if tag == desc.TypeZigZag32 || tag == desc.TypeZigZag64 { + u64 = uint64(protowire.DecodeZigZag(u64)) + } + } + if n < 0 { + return i, protowire.ParseError(n) + } + j += n + + if h.Len == h.Cap { // slow path + d.ReallocSlice(h, t, 2*h.Cap) + } + h.Len++ + p = unsafe.Add(h.Data, uintptr(h.Len-1)*t.Size) // p = &d[len(d-1)] + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u64) + case reflect.Uint32: + *(*uint32)(p) = uint32(u64) + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64: + *(*uint64)(p) = u64 + case reflect.Float32: + *(*uint32)(p) = uint32(u64) + case reflect.Float64: + *(*uint64)(p) = u64 + case reflect.Bool: // 1 for true, 0 for false + *(*byte)(p) = byte(u64 & 0x1) + } + } + } // end of PACKED_SLICE_LOOP + + } // end of decoding field loop + + if tmv != nil { // use defer? if no performance issue + f.T.MapTmpVarsPool.Put(tmv) + } + return i, nil +} + +// DecodeMapKey ... +// keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | +// "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" +func (d *Decoder) DecodeMapKey(b []byte, p unsafe.Pointer, f *desc.FieldDesc) (int, error) { + num, typ := wire.ConsumeKVTag(b) + if num != 1 { // key num + return 0, fmt.Errorf("key field num not match, got %d expect 1", num) + } + i := 1 // one byte for map key tag + + t := f.T.K + tag := f.KeyType + + switch typ { + case wire.TypeVarint: // case: VARINT + if tag != desc.TypeVarint && tag != desc.TypeZigZag32 && tag != desc.TypeZigZag64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeVarint(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + if tag == desc.TypeZigZag32 || tag == desc.TypeZigZag64 { + u64 = uint64(protowire.DecodeZigZag(u64)) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u64) + case reflect.Uint32: + *(*uint32)(p) = uint32(u64) + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64: + *(*uint64)(p) = u64 + case reflect.Bool: // 1 for true, 0 for false + *(*byte)(p) = byte(u64 & 0x1) + } + i += n + + case wire.TypeFixed32: // case: I32 + if tag != desc.TypeFixed32 { + return i, newWireTypeNotMatch(typ, tag) + } + + u32, n := protowire.ConsumeFixed32(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u32) + case reflect.Uint32, reflect.Float32: + *(*uint32)(p) = u32 + } + i += n + + case wire.TypeFixed64: // case: I64 + if tag != desc.TypeFixed64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeFixed64(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64, reflect.Float64: + *(*uint64)(p) = u64 + } + i += n + + case wire.TypeBytes: // case: LEN + // for map, can only be string + if t.Kind != reflect.String { + return i, newWireTypeNotMatch(typ, tag) + } + fb, n := protowire.ConsumeBytes(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + i += n + if len(fb) > 0 { + data := d.Malloc(len(fb), 1, 0) + h := (*hack.StringHeader)(p) + h.Data = data + h.Len = len(fb) + copyb(data, fb) + } else { + *(*string)(p) = "" + } + + default: + // unknown wiretype + return i, newWireTypeNotMatch(typ, tag) + } + + return i, nil +} + +// DecodeMapValue ... +// like DecodeMapKey plus "bytes" | messageType | enumType +func (d *Decoder) DecodeMapValue(b []byte, p unsafe.Pointer, f *desc.FieldDesc, maxdepth int) (int, error) { + num, typ := wire.ConsumeKVTag(b) + if num != 2 { // val num + return 0, fmt.Errorf("val field num not match, got %d expect 2", num) + } + i := 1 // one byte for map value tag + + t := f.T.V + tag := f.ValType + + if t.IsPointer { + p = d.mallocIfPointer(p, t) + t = t.V + } + + switch typ { + case wire.TypeVarint: // case: VARINT + if tag != desc.TypeVarint && tag != desc.TypeZigZag32 && tag != desc.TypeZigZag64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeVarint(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + if tag == desc.TypeZigZag32 || tag == desc.TypeZigZag64 { + u64 = uint64(protowire.DecodeZigZag(u64)) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u64) + case reflect.Uint32: + *(*uint32)(p) = uint32(u64) + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64: + *(*uint64)(p) = u64 + case reflect.Bool: // 1 for true, 0 for false + *(*byte)(p) = byte(u64 & 0x1) + } + i += n + + case wire.TypeFixed32: // case: I32 + if tag != desc.TypeFixed32 { + return i, newWireTypeNotMatch(typ, tag) + } + + u32, n := protowire.ConsumeFixed32(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int32: + *(*int32)(p) = int32(u32) + case reflect.Uint32, reflect.Float32: + *(*uint32)(p) = u32 + } + i += n + + case wire.TypeFixed64: // case: I64 + if tag != desc.TypeFixed64 { + return i, newWireTypeNotMatch(typ, tag) + } + + u64, n := protowire.ConsumeFixed64(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + switch t.Kind { + case reflect.Int64: + *(*int64)(p) = int64(u64) + case reflect.Uint64, reflect.Float64: + *(*uint64)(p) = u64 + } + i += n + + case wire.TypeBytes: // case: LEN + // string, bytes, embedded messages (struct or map), packed repeated fields + fb, n := protowire.ConsumeBytes(b[i:]) + if n < 0 { + return 0, protowire.ParseError(n) + } + i += n + switch t.Kind { + case desc.KindBytes: + if len(fb) > 0 { + data := d.Malloc(len(fb), 1, 0) + h := (*hack.SliceHeader)(p) + h.Data = data + h.Len = len(fb) + h.Cap = len(fb) + copy(*(*[]byte)(p), fb) + runtime.KeepAlive(data) + } else { + *(*[]byte)(p) = []byte{} + } + case reflect.String: + if len(fb) > 0 { + data := d.Malloc(len(fb), 1, 0) + h := (*hack.StringHeader)(p) + h.Data = data + h.Len = len(fb) + copyb(data, fb) + } else { + *(*string)(p) = "" + } + case reflect.Struct: + if _, err := d.DecodeStruct(fb, p, t.S, maxdepth-1); err != nil { + return i, err + } + default: + return i, newWireTypeNotMatch(typ, tag) + } + + default: + // unknown wiretype + return i, newWireTypeNotMatch(typ, tag) + } + + return i, nil +} + +func (d *Decoder) DecodeMapPair(b []byte, p unsafe.Pointer, f *desc.FieldDesc, tmp *desc.TmpMapVars, maxdepth int) (int, error) { + tmp.Reset() + + // maps are encoded exactly like a repeated message field: + // as a sequence of LEN-typed records, with two fields each. + // #1 for key and #2 for value. + var m reflect.Value + if *(*unsafe.Pointer)(p) == nil { + m = reflect.MakeMap(f.T.T) + *(*unsafe.Pointer)(p) = m.UnsafePointer() + } else { + m = tmp.MapWithPtr(p) + } + + i := 0 + n, err := d.DecodeMapKey(b, tmp.KeyPointer(), f) + if err != nil { + return 0, err + } + i += n + + n, err = d.DecodeMapValue(b[i:], tmp.ValPointer(), f, maxdepth) + if err != nil { + return i, err + } + i += n + + tmp.Update(m) + return i, nil +} + +func (d *Decoder) ReallocSlice(h *hack.SliceHeader, t *desc.Type, c int) { + if h.Cap >= c { + return + } + data := d.Malloc(c*int(t.Size), t.Align, t.MallocAbiType) + if h.Len > 0 { + copyn(data, h.Data, h.Len*int(t.Size)) + } + h.Data = data + h.Cap = c +} + +// copyn copies n bytes from src to dst addr. +func copyn(dst, src unsafe.Pointer, n int) { + var b0, b1 []byte + h0 := (*hack.SliceHeader)(unsafe.Pointer(&b0)) + h0.Data = dst + h0.Cap = n + h0.Len = n + h1 := (*hack.SliceHeader)(unsafe.Pointer(&b1)) + h1.Data = src + h1.Cap = n + h1.Len = n + copy(b0, b1) + runtime.KeepAlive(dst) + runtime.KeepAlive(src) +} + +func copyb(p unsafe.Pointer, src []byte) { + var dst []byte + h := (*hack.SliceHeader)(unsafe.Pointer(&dst)) + h.Data = p + h.Len = len(src) + h.Cap = len(src) + copy(dst, src) + runtime.KeepAlive(p) +} diff --git a/internal/prutal/decoder_test.go b/internal/prutal/decoder_test.go new file mode 100644 index 0000000..71b00b0 --- /dev/null +++ b/internal/prutal/decoder_test.go @@ -0,0 +1,49 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" + "github.com/cloudwego/prutal/internal/wire" +) + +func TestDecodeOneof(t *testing.T) { + n := 0x0000ffff + s := "helloworld" + tmp := wire.Builder{} + buf := wire.Builder{} + buf.AppendVarintField(2, uint64(n)). + AppendStringField(4, s). + AppendBytesField(5, tmp.AppendVarintField(1, 1).Bytes()) + b := buf.Bytes() + p := &TestOneofMessage{} + err := Unmarshal(b, p) + assert.NoError(t, err) + + f2, ok := p.OneOfFieldA.(*TestOneofMessage_Field2) + assert.True(t, ok) + assert.Equal(t, int64(n), f2.Field2) + f4, ok := p.OneOfFieldB.(*TestOneofMessage_Field4) + assert.True(t, ok) + assert.Equal(t, s, f4.Field4) + + f5, ok := p.OneOfFieldC.(*TestOneofMessage_Field5) + assert.True(t, ok) + assert.Equal(t, true, f5.Field5.Field1) +} diff --git a/internal/prutal/encoder.go b/internal/prutal/encoder.go new file mode 100644 index 0000000..b95a673 --- /dev/null +++ b/internal/prutal/encoder.go @@ -0,0 +1,196 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "reflect" + "unsafe" + + "github.com/cloudwego/prutal/internal/desc" + "github.com/cloudwego/prutal/internal/hack" + "github.com/cloudwego/prutal/internal/wire" +) + +type Encoder struct{} + +func (e *Encoder) AppendStruct(b []byte, base unsafe.Pointer, s *desc.StructDesc, encodeLen bool, maxdepth int) (_ []byte, err error) { + if maxdepth == 0 { + return b, errMaxDepthExceeded + } + var beforeStructSize int + if encodeLen { + b = wire.LenReserve(b) + beforeStructSize = len(b) + } + for _, f := range s.Fields { + p := unsafe.Add(base, f.Offset) + t := f.T + + if f.IsOneof() { + typ, data := hack.ExtratIface(p) + if data == nil { + continue + } + if hack.ReflectTypePtr(f.OneofType) != typ { + continue + } + p = data + } + + // this also checks pointer types + skipzero := false + switch { + case t.Size == 8: // int64, uint64, float64 or pointer on amd64 + skipzero = *(*uint64)(p) == 0 + case t.Size == 4: // int32, uint32, float32 or pointer on 386 + skipzero = *(*uint32)(p) == 0 + case t.Size == 1: // bool? + skipzero = *(*uint8)(p) == 0 + case t.SliceLike: // for slice or string, both can use StringHeader + skipzero = ((*hack.StringHeader)(p)).Len == 0 + } + if skipzero { + continue + } + + if t.IsPointer { + t = t.V + p = *(*unsafe.Pointer)(p) // dereference + } + + if !f.Repeated && f.AppendFunc != nil { // scalar types without `repeated` + b = wire.AppendVarint(b, wire.EncodeTag(f.ID, f.WireType)) + b = f.AppendFunc(b, p) + continue + } + + if f.IsList { + // fast path for packed list + if f.Packed { + b = wire.AppendVarint(b, wire.EncodeTag(f.ID, wire.TypeBytes)) + b = f.AppendFunc(b, p) + continue + } + // fast path for repeated list except struct + if f.AppendRepeated != nil { + b = f.AppendRepeated(b, f.ID, p) + continue + } + + // pb doesn't support nested slice or map, can only be struct + vt := t.V + s := vt.S + if vt.IsPointer { + s = vt.V.S + } + if s == nil { + panic("[BUG] not struct") + } + + h := (*hack.SliceHeader)(p) + p = h.Data + for i := 0; i < h.Len; i++ { + if i != 0 { + p = unsafe.Add(p, vt.Size) + } + b = wire.AppendVarint(b, wire.EncodeTag(f.ID, f.WireType)) + base := p + if vt.IsPointer { + base = *(*unsafe.Pointer)(p) + } + b, err = e.AppendStruct(b, base, s, true, maxdepth-1) + if err != nil { + return b, err + } + } + continue + } // end of list field + + if f.IsMap { + tmp := t.MapTmpVarsPool.Get().(*desc.TmpMapVars) + m := tmp.MapWithPtr(p) + vt := t.V + it := hack.NewMapIter(m) + for { + kp, vp := it.Next() + if kp == nil { + break + } + // LEN for each map record + b = wire.AppendVarint(b, wire.EncodeTag(f.ID, wire.TypeBytes)) + b = wire.LenReserve(b) + beforesz := len(b) + + // Key + b = wire.AppendVarint(b, wire.EncodeTag(1, f.KeyWireType)) + b = f.KeyAppendFunc(b, kp) + + // Val + b = wire.AppendVarint(b, wire.EncodeTag(2, f.ValWireType)) + if f.ValAppendFunc != nil { + b = f.ValAppendFunc(b, vp) + } else { + s := vt.S + if vt.IsPointer { // likely it's a pointer for struct + s = vt.V.S + vp = *(*unsafe.Pointer)(vp) + } + b, err = e.AppendStruct(b, vp, s, true, maxdepth-1) + if err != nil { + return b, err + } + } + + b = wire.LenPack(b, len(b)-beforesz) + } + t.MapTmpVarsPool.Put(tmp) + continue + } // end of map field + + // case Struct + if t.Kind != reflect.Struct { + panic("[BUG] not struct") // assert reflect.Struct + } + + b = wire.AppendVarint(b, wire.EncodeTag(f.ID, f.WireType)) + b, err = e.AppendStruct(b, p, t.S, true, maxdepth-1) + if err != nil { + return b, err + } + + } // end of encoding field loop + + if s.HasUnknownFields { + b = appendUnknownFields(b, s, base) + } + + if encodeLen { + b = wire.LenPack(b, len(b)-beforeStructSize) + } + return b, nil +} + +func appendUnknownFields(b []byte, s *desc.StructDesc, base unsafe.Pointer) []byte { + p := unsafe.Add(base, s.UnknownFieldsOffset) + var x *[]byte + if s.UnknownFieldsPointer { + x = (*[]byte)(*(*unsafe.Pointer)(p)) + } else { + x = (*[]byte)(p) + } + return append(b, (*x)...) +} diff --git a/internal/prutal/encoder_test.go b/internal/prutal/encoder_test.go new file mode 100644 index 0000000..5481fb9 --- /dev/null +++ b/internal/prutal/encoder_test.go @@ -0,0 +1,149 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "math/rand" + "testing" + + "github.com/cloudwego/prutal/internal/testutils" + "github.com/cloudwego/prutal/internal/testutils/assert" + "github.com/cloudwego/prutal/internal/wire" +) + +func TestEncodeOneof(t *testing.T) { + a := &TestOneofMessage_Field2{Field2: 123} + b := &TestOneofMessage_Field4{Field4: "helloworld"} + c := &TestOneofMessage_Field5{&TestofNestedMessage{true}} + p := &TestOneofMessage{} + p.OneOfFieldA = a + p.OneOfFieldB = b + p.OneOfFieldC = c + + tmp := wire.Builder{} + buf := wire.Builder{} + buf.AppendVarintField(2, uint64(a.Field2)). + AppendStringField(4, b.Field4). + AppendBytesField(5, tmp.AppendVarintField(1, 1).Bytes()) + expect := buf.Bytes() + data, err := MarshalAppend(nil, p) + assert.NoError(t, err) + assert.BytesEqual(t, expect, data) +} + +type TestStruct_Benchmark_Encode_List_Packed struct { + PackedInt32s []int32 `protobuf:"varint,501,rep,packed"` + PackedInt64s []int64 `protobuf:"varint,502,rep,packed"` + PackedUint32s []uint32 `protobuf:"varint,503,rep,packed"` + PackedUint64s []uint64 `protobuf:"varint,504,rep,packed"` + PackedBools []bool `protobuf:"varint,505,rep,packed"` + + PackedFixed32 []uint32 `protobuf:"fixed32,511,rep,packed"` + PackedFixed64 []uint64 `protobuf:"fixed64,512,rep,packed"` + PackedFloat []float32 `protobuf:"fixed32,513,rep,packed"` + PackedDouble []float64 `protobuf:"fixed64,514,rep,packed"` + + PackedZigZag32 []int32 `protobuf:"zigzag32,521,rep,packed"` + PackedZigZag64 []int64 `protobuf:"zigzag64,522,rep,packed"` +} + +func Benchmark_Encode_List_Packed(b *testing.B) { + p := &TestStruct_Benchmark_Encode_List_Packed{ + PackedInt32s: testutils.Repeat(50, rand.Int31()), + PackedInt64s: testutils.Repeat(50, rand.Int63()), + PackedUint32s: testutils.Repeat(50, rand.Uint32()), + PackedUint64s: testutils.Repeat(50, rand.Uint64()), + PackedBools: testutils.RandomBoolSlice(50), + + PackedFixed32: testutils.Repeat(50, rand.Uint32()), + PackedFixed64: testutils.Repeat(50, rand.Uint64()), + PackedFloat: testutils.Repeat(50, rand.Float32()), + PackedDouble: testutils.Repeat(50, rand.Float64()), + + PackedZigZag32: testutils.Repeat(50, rand.Int31()), + PackedZigZag64: testutils.Repeat(50, rand.Int63()), + } + + buf := make([]byte, 0, 16<<10) + for i := 0; i < b.N; i++ { + _, _ = MarshalAppend(buf[:0], p) + } +} + +type TestStruct_Benchmark_Encode_List_NoPack struct { + PackedInt32s []int32 `protobuf:"varint,501,rep"` + PackedInt64s []int64 `protobuf:"varint,502,rep"` + PackedUint32s []uint32 `protobuf:"varint,503,rep"` + PackedUint64s []uint64 `protobuf:"varint,504,rep"` + PackedBools []bool `protobuf:"varint,505,rep"` + + PackedFixed32 []uint32 `protobuf:"fixed32,511,rep"` + PackedFixed64 []uint64 `protobuf:"fixed64,512,rep"` + PackedFloat []float32 `protobuf:"fixed32,513,rep"` + PackedDouble []float64 `protobuf:"fixed64,514,rep"` + + PackedZigZag32 []int32 `protobuf:"zigzag32,521,rep"` + PackedZigZag64 []int64 `protobuf:"zigzag64,522,rep"` +} + +func Benchmark_Encode_List_NoPack(b *testing.B) { + p := &TestStruct_Benchmark_Encode_List_NoPack{ + PackedInt32s: testutils.Repeat(50, rand.Int31()), + PackedInt64s: testutils.Repeat(50, rand.Int63()), + PackedUint32s: testutils.Repeat(50, rand.Uint32()), + PackedUint64s: testutils.Repeat(50, rand.Uint64()), + PackedBools: testutils.RandomBoolSlice(50), + + PackedFixed32: testutils.Repeat(50, rand.Uint32()), + PackedFixed64: testutils.Repeat(50, rand.Uint64()), + PackedFloat: testutils.Repeat(50, rand.Float32()), + PackedDouble: testutils.Repeat(50, rand.Float64()), + + PackedZigZag32: testutils.Repeat(50, rand.Int31()), + PackedZigZag64: testutils.Repeat(50, rand.Int63()), + } + + buf := make([]byte, 0, 16<<10) + for i := 0; i < b.N; i++ { + _, _ = MarshalAppend(buf[:0], p) + } +} + +type TestStruct_Benchmark_Encode_String struct { + S1 string `protobuf:"bytes,1"` + S2 string `protobuf:"bytes,2"` + S3 string `protobuf:"bytes,3"` + S4 string `protobuf:"bytes,4"` + S5 string `protobuf:"bytes,5"` + SS []string `protobuf:"bytes,6,rep"` +} + +func Benchmark_Encode_String(b *testing.B) { + p := &TestStruct_Benchmark_Encode_String{ + S1: testutils.RandomStr(10), + S2: testutils.RandomStr(50), + S3: testutils.RandomStr(100), + S4: testutils.RandomStr(200), + S5: testutils.RandomStr(400), + SS: testutils.Repeat(100, "helloworld"), + } + + buf := make([]byte, 0, 16<<10) + for i := 0; i < b.N; i++ { + _, _ = MarshalAppend(buf[:0], p) + } +} diff --git a/internal/prutal/errors.go b/internal/prutal/errors.go new file mode 100644 index 0000000..fe2a572 --- /dev/null +++ b/internal/prutal/errors.go @@ -0,0 +1,55 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "errors" + "fmt" + "strconv" + + "github.com/cloudwego/prutal/internal/desc" + "github.com/cloudwego/prutal/internal/wire" +) + +const defaultRecursionMaxDepth = 1000 + +var ( + errMaxDepthExceeded = errors.New("max depth exceeded") +) + +func wireType2Str(t wire.Type) string { + switch t { + case wire.TypeVarint: + return "varint" + case wire.TypeFixed32: + return "fixed32" + case wire.TypeFixed64: + return "fixed64" + case wire.TypeBytes: + return "bytes" + case wire.TypeSGroup: + return "sgroup" + case wire.TypeEGroup: + return "egroup" + default: + return "t-" + strconv.Itoa(int(t)) + } +} + +func newWireTypeNotMatch(t0 wire.Type, t1 desc.TagType) error { + return fmt.Errorf("wire type %q not match %q", wireType2Str(t0), t1) +} diff --git a/internal/prutal/prutal.go b/internal/prutal/prutal.go new file mode 100644 index 0000000..7735600 --- /dev/null +++ b/internal/prutal/prutal.go @@ -0,0 +1,59 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "errors" + "reflect" + + "github.com/cloudwego/prutal/internal/desc" + "github.com/cloudwego/prutal/internal/hack" +) + +func MarshalAppend(b []byte, v interface{}) ([]byte, error) { + hack.PanicIfHackErr() + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Pointer { + return nil, errors.New("input not pointer type") + } + s := desc.CacheGet(rv) + if s == nil { + var err error + s, err = desc.GetOrParse(rv) + if err != nil { + return nil, err + } + } + enc := Encoder{} + return enc.AppendStruct(b, rv.UnsafePointer(), s, false, defaultRecursionMaxDepth) +} + +func Unmarshal(b []byte, v interface{}) error { + hack.PanicIfHackErr() + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Pointer { + return errors.New("input not pointer type") + } + desc, err := desc.GetOrParse(rv) + if err != nil { + return err + } + d := decoderPool.Get().(*Decoder) + _, err = d.DecodeStruct(b, rv.UnsafePointer(), desc, defaultRecursionMaxDepth) + decoderPool.Put(d) + return err +} diff --git a/internal/prutal/prutal_test.go b/internal/prutal/prutal_test.go new file mode 100644 index 0000000..3750fd2 --- /dev/null +++ b/internal/prutal/prutal_test.go @@ -0,0 +1,1012 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "fmt" + "math" + "strconv" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" + "github.com/cloudwego/prutal/internal/wire" +) + +func P[T any](v T) *T { return &v } + +type testcase struct { + Name string + Bytes func() []byte + Struct func() interface{} +} + +func runTest(t *testing.T, tc *testcase) { + t.Helper() + + // make sure the decoder works + // and then we can use Unmarshal for verifying Marshal + // this also fixes inconsist bytes when encoding map + if runDecoderTest(t, tc) { + runEncoderTest(t, tc) + } +} + +func runEncoderTest(t *testing.T, tc *testcase) bool { + t.Helper() + return t.Run(tc.Name+"#Encoder", func(t *testing.T) { + t.Helper() + + b, err := MarshalAppend([]byte{}, tc.Struct()) + assert.NoError(t, err) + + p := &TestStruct{} + err = Unmarshal(b, p) + assert.NoError(t, err) + assert.DeepEqual(t, tc.Struct(), p) + }) +} + +func runDecoderTest(t *testing.T, tc *testcase) bool { + t.Helper() + return t.Run(tc.Name+"#Decoder", func(t *testing.T) { + t.Helper() + p := &TestStruct{} + err := Unmarshal(tc.Bytes(), p) + assert.NoError(t, err) + assert.DeepEqual(t, tc.Struct(), p) + }) +} + +// fix constant overflows +func u32(v int32) uint32 { + return uint32(v) +} + +// fix constant overflows +func u64(v int64) uint64 { + return uint64(v) +} + +type TestStructS struct { + V uint64 `protobuf:"varint,1,opt"` +} + +type TestStruct struct { + // for TestEncoderDecoderVarint + + OptionalInt32 *int32 `protobuf:"varint,101,opt"` + Int32 int32 `protobuf:"varint,102,opt"` + + OptionalInt64 *int64 `protobuf:"varint,103,opt"` + Int64 int64 `protobuf:"varint,104,opt"` + + OptionalUint32 *uint32 `protobuf:"varint,105,opt"` + Uint32 uint32 `protobuf:"varint,106,opt"` + + OptionalUint64 *uint64 `protobuf:"varint,107,opt"` + Uint64 uint64 `protobuf:"varint,108,opt"` + + OptionalBool *bool `protobuf:"varint,109,opt"` + Bool bool `protobuf:"varint,110,opt"` + + // for TestEncodedDecoderFixed + + OptionalFixed32 *uint32 `protobuf:"fixed32,200,opt"` + Fixed32 uint32 `protobuf:"fixed32,201,opt"` + + OptionalFixed64 *uint64 `protobuf:"fixed64,202,opt"` + Fixed64 uint64 `protobuf:"fixed64,203,opt"` + + OptionalSfixed32 *int32 `protobuf:"fixed32,204,opt"` + Sfixed32 int32 `protobuf:"fixed32,205,opt"` + + OptionalSfixed64 *int64 `protobuf:"fixed64,206,opt"` + Sfixed64 int64 `protobuf:"fixed64,207,opt"` + + OptionalFloat *float32 `protobuf:"fixed32,208,opt"` + Float float32 `protobuf:"fixed32,209,opt"` + + OptionalDouble *float64 `protobuf:"fixed64,210,opt"` + Double float64 `protobuf:"fixed64,211,opt"` + + // for TestEncoderDecoderZigZag + + OptionalSint32 *int32 `protobuf:"zigzag32,301,opt"` + Sint32 int32 `protobuf:"zigzag32,302,opt"` + + OptionalSint64 *int64 `protobuf:"zigzag64,303,opt"` + Sint64 int64 `protobuf:"zigzag64,304,opt"` + + // for TestEncoderDecoderBytes + + OptionalStr *string `protobuf:"bytes,401,opt"` + Str string `protobuf:"bytes,402,opt"` + + OptionalB *[]byte `protobuf:"bytes,411,opt"` + B []byte `protobuf:"bytes,412,opt"` + + StructA *TestStructS `protobuf:"bytes,421,opt"` + StructB TestStructS `protobuf:"bytes,422,opt"` + + // for TestEncoderDecoderPacked + + PackedInt32s []int32 `protobuf:"varint,501,rep,packed"` + PackedInt64s []int64 `protobuf:"varint,502,rep,packed"` + PackedUint32s []uint32 `protobuf:"varint,503,rep,packed"` + PackedUint64s []uint64 `protobuf:"varint,504,rep,packed"` + PackedBools []bool `protobuf:"varint,505,rep,packed"` + + PackedFixed32 []uint32 `protobuf:"fixed32,511,rep,packed"` + PackedFixed64 []uint64 `protobuf:"fixed64,512,rep,packed"` + PackedFloat []float32 `protobuf:"fixed32,513,rep,packed"` + PackedDouble []float64 `protobuf:"fixed64,514,rep,packed"` + + PackedZigZag32 []int32 `protobuf:"zigzag32,521,rep,packed"` + PackedZigZag64 []int64 `protobuf:"zigzag64,522,rep,packed"` + + // for TestEncoderDecoderRepeated + Repeated []uint64 `protobuf:"varint,601,rep"` + PackedRepeated []uint64 `protobuf:"varint,602,rep,packed"` + + StructsA []*TestStructS `protobuf:"bytes,611,rep"` + StructsB []TestStructS `protobuf:"bytes,612,rep"` + + // for TestEncoderDecoderMapVarint + MapBool map[bool]bool `protobuf:"bytes,701,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapInt32 map[int32]int32 `protobuf:"bytes,702,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapInt64 map[int64]int64 `protobuf:"bytes,703,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapUint32 map[uint32]uint32 `protobuf:"bytes,704,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapUint64 map[uint64]uint64 `protobuf:"bytes,705,rep" protobuf_key:"varint,1,opt" protobuf_val:"varint,2,opt"` + MapZigZag32 map[int32]int32 `protobuf:"bytes,706,rep" protobuf_key:"zigzag32,1,opt" protobuf_val:"zigzag32,2,opt"` + MapZigZag64 map[int64]int64 `protobuf:"bytes,707,rep" protobuf_key:"zigzag64,1,opt" protobuf_val:"zigzag64,2,opt"` + + // for TestEncoderDecoderMapFixed + MapFixed32 map[uint32]uint32 `protobuf:"bytes,801,rep" protobuf_key:"fixed32,1,opt" protobuf_val:"fixed32,2,opt"` + MapFixed64 map[uint64]uint64 `protobuf:"bytes,802,rep" protobuf_key:"fixed64,1,opt" protobuf_val:"fixed64,2,opt"` + MapSfixed32 map[int32]int32 `protobuf:"bytes,803,rep" protobuf_key:"fixed32,1,opt" protobuf_val:"fixed32,2,opt"` + MapSfixed64 map[int64]int64 `protobuf:"bytes,804,rep" protobuf_key:"fixed64,1,opt" protobuf_val:"fixed64,2,opt"` + MapFloat map[float32]float32 `protobuf:"bytes,805,rep" protobuf_key:"fixed32,1,opt" protobuf_val:"fixed32,2,opt"` + MapDouble map[float64]float64 `protobuf:"bytes,806,rep" protobuf_key:"fixed64,1,opt" protobuf_val:"fixed64,2,opt"` + + // for TestEncoderDecoderMapBytes + MapStringString map[string]string `protobuf:"bytes,901,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + MapStringBytes map[string][]byte `protobuf:"bytes,902,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + + MapStringStructA map[string]*TestStructS `protobuf:"bytes,903,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` + MapStringStructB map[string]TestStructS `protobuf:"bytes,904,rep" protobuf_key:"bytes,1,opt" protobuf_val:"bytes,2,opt"` +} + +func TestEncoderDecoderVarint(t *testing.T) { + x := &wire.Builder{} + + runTest(t, &testcase{Name: "Int32", + Bytes: func() []byte { + return x.Reset(). + AppendVarintField(101, 100). + AppendVarintField(102, 200).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{OptionalInt32: P(int32(100)), Int32: 200} + }, + }) + runTest(t, &testcase{ + Name: "Int64", + Bytes: func() []byte { + return x.Reset(). + AppendVarintField(103, 100). + AppendVarintField(104, 200).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{OptionalInt64: P(int64(100)), Int64: 200} + }, + }) + runTest(t, &testcase{ + Name: "Uint32", + Bytes: func() []byte { + return x.Reset(). + AppendVarintField(105, 100). + AppendVarintField(106, 200).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{OptionalUint32: P(uint32(100)), Uint32: 200} + }, + }) + runTest(t, &testcase{ + Name: "Uint64", + Bytes: func() []byte { + return x.Reset(). + AppendVarintField(107, 100). + AppendVarintField(108, 200).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{OptionalUint64: P(uint64(100)), Uint64: 200} + }, + }) + runTest(t, &testcase{ + Name: "Bool", + Bytes: func() []byte { + return x.Reset(). + AppendVarintField(109, 1). + AppendVarintField(110, 1).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{OptionalBool: P(true), Bool: true} + }, + }) +} + +func TestEncodedDecoderFixed(t *testing.T) { + x := &wire.Builder{} + runTest(t, &testcase{ + Name: "Fixed32_uint32", + Bytes: func() []byte { + return x.Reset(). + AppendFixed32Field(200, 100). + AppendFixed32Field(201, 1000).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalFixed32: P(uint32(100)), + Fixed32: uint32(1000), + } + }, + }) + runTest(t, &testcase{ + Name: "Fixed64_uint64", + Bytes: func() []byte { + return x.Reset(). + AppendFixed64Field(202, 100). + AppendFixed64Field(203, 1000).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalFixed64: P(uint64(100)), + Fixed64: uint64(1000), + } + }, + }) + + runTest(t, &testcase{ + Name: "Fixed32_int32", + Bytes: func() []byte { + return x.Reset(). + AppendFixed32Field(204, u32(-100)). + AppendFixed32Field(205, u32(-1000)).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalSfixed32: P(int32(-100)), + Sfixed32: int32(-1000), + } + }, + }) + runTest(t, &testcase{ + Name: "Fixed64_int64", + Bytes: func() []byte { + return x.Reset(). + AppendFixed64Field(206, u64(-100)). + AppendFixed64Field(207, u64(-1000)).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalSfixed64: P(int64(-100)), + Sfixed64: int64(-1000), + } + }, + }) + + runTest(t, &testcase{ + Name: "Fixed32_float32", + Bytes: func() []byte { + return x.Reset(). + AppendFixed32Field(208, math.Float32bits(100)). + AppendFixed32Field(209, math.Float32bits(1000)).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalFloat: P(float32(100)), + Float: float32(1000), + } + }, + }) + + runTest(t, &testcase{ + Name: "Fixed64_float64", + Bytes: func() []byte { + return x.Reset(). + AppendFixed64Field(210, math.Float64bits(100)). + AppendFixed64Field(211, math.Float64bits(1000)).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalDouble: P(float64(100)), + Double: float64(1000), + } + }, + }) + +} + +func TestEncoderDecoderZigZag(t *testing.T) { + x := &wire.Builder{} + + runTest(t, &testcase{ + Name: "ZigZag32", + Bytes: func() []byte { + return x.Reset(). + AppendZigZagField(301, -100). + AppendZigZagField(302, -1000).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalSint32: P(int32(-100)), + Sint32: int32(-1000), + } + }, + }) + runTest(t, &testcase{ + Name: "ZigZag64", + Bytes: func() []byte { + return x.Reset(). + AppendZigZagField(303, -100). + AppendZigZagField(304, -1000).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalSint64: P(int64(-100)), + Sint64: int64(-1000), + } + }, + }) +} + +func TestEncoderDecoderBytes(t *testing.T) { + x := &wire.Builder{} + runTest(t, &testcase{ + Name: "String", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(401, "hello"). + AppendStringField(402, "world").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalStr: P("hello"), + Str: "world", + } + }, + }) + + // only for decoder, coz encoder will skip zero value + runDecoderTest(t, &testcase{ + Name: "String_Empty", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(401, ""). + AppendStringField(402, "").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalStr: P(""), + } + }, + }) + + // only for encoder, coz the case above covers this case + runEncoderTest(t, &testcase{ + Name: "String_Empty", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(401, "").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalStr: P(""), + } + }, + }) + + runTest(t, &testcase{ + Name: "Bytes", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(411, "hello"). + AppendStringField(412, "world").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalB: P([]byte("hello")), + B: []byte("world"), + } + }, + }) + + // only for Decoder, coz encoder will not encode []byte{} which is zero value + runDecoderTest(t, &testcase{ + Name: "Bytes_Empty", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(411, ""). + AppendStringField(412, "").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalB: P([]byte("")), + B: []byte{}, + } + }, + }) + + // only for Encoder, coz the case above covers this case + runEncoderTest(t, &testcase{ + Name: "Bytes_Empty", + Bytes: func() []byte { + return x.Reset(). + AppendStringField(411, "").Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + OptionalB: P([]byte("")), + } + }, + }) + runTest(t, &testcase{ + Name: "Struct", + Bytes: func() []byte { + b := x.Reset().AppendVarintField(1, 1).Bytes() // TestStructS{V:1} + return x.Reset().AppendBytesField(421, b).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{StructA: &TestStructS{V: 1}} + }, + }) + runTest(t, &testcase{ + Name: "Struct_NoPointer", + Bytes: func() []byte { + b := x.Reset().AppendVarintField(1, 1).Bytes() // TestStructS{V:1} + return x.Reset().AppendBytesField(422, b).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{StructB: TestStructS{V: 1}} + }, + }) +} + +func TestEncoderDecoderPacked(t *testing.T) { + x := &wire.Builder{} + runTest(t, &testcase{ + Name: "Varint", + Bytes: func() []byte { + return x.Reset(). + AppendPackedVarintField(501, uint64(u32(-100)), uint64(u32(-1000))). + AppendPackedVarintField(502, u64(-200), u64(-2000)). + AppendPackedVarintField(503, 300, 3000). + AppendPackedVarintField(504, 400, 4000). + AppendPackedVarintField(505, 1, 0).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + PackedInt32s: []int32{-100, -1000}, + PackedInt64s: []int64{-200, -2000}, + PackedUint32s: []uint32{300, 3000}, + PackedUint64s: []uint64{400, 4000}, + PackedBools: []bool{true, false}, + } + }, + }) + runTest(t, &testcase{ + Name: "Fixed", + Bytes: func() []byte { + return x.Reset(). + AppendPackedFixed32Field(511, 100, 1000). + AppendPackedFixed64Field(512, 200, 2000). + AppendPackedFixed32Field(513, math.Float32bits(300), math.Float32bits(3000)). + AppendPackedFixed64Field(514, math.Float64bits(400), math.Float64bits(4000)).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + PackedFixed32: []uint32{100, 1000}, + PackedFixed64: []uint64{200, 2000}, + PackedFloat: []float32{300, 3000}, + PackedDouble: []float64{400, 4000}, + } + }, + }) + runTest(t, &testcase{ + Name: "ZigZag", + Bytes: func() []byte { + return x.Reset(). + AppendPackedZigZagField(521, -100, -1000). + AppendPackedZigZagField(522, -200, -2000).Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + PackedZigZag32: []int32{-100, -1000}, + PackedZigZag64: []int64{-200, -2000}, + } + }, + }) +} + +func TestEncoderDecoderRepeated(t *testing.T) { + x := &wire.Builder{} + tmpx := &wire.Builder{} + sizes := []int{1, 2, 15, 20} + for _, n := range sizes { + { // unpacked testcase + b := []byte{} + p := &TestStruct{Repeated: make([]uint64, 0, n)} + + x.Reset() + for i := 0; i < n; i++ { + x.AppendVarintField(601, uint64(i)) + p.Repeated = append(p.Repeated, uint64(i)) + } + b = x.Bytes() + + runTest(t, &testcase{ + Name: fmt.Sprintf("Unpacked_%d_Element", n), + Bytes: func() []byte { return b }, + Struct: func() interface{} { return p }, + }) + } + + { // packed testcase + b := []byte{} + p := &TestStruct{PackedRepeated: make([]uint64, 0, n)} + for i := 0; i < n; i++ { + p.PackedRepeated = append(p.PackedRepeated, uint64(i)) + } + b = x.Reset().AppendPackedVarintField(602, p.PackedRepeated...).Bytes() + + runTest(t, &testcase{ + Name: fmt.Sprintf("Packed_%d_Element", n), + Bytes: func() []byte { return b }, + Struct: func() interface{} { return p }, + }) + } + + { // struct testcase + b := []byte{} + p := &TestStruct{StructsA: make([]*TestStructS, 0, n), StructsB: make([]TestStructS, 0, n)} + + x.Reset() + for i := 0; i < n; i++ { + v := uint64(i + 1) + x.AppendBytesField(611, tmpx.Reset().AppendVarintField(1, v).Bytes()) + p.StructsA = append(p.StructsA, &TestStructS{V: v}) + } + for i := 0; i < n; i++ { + v := uint64(i + 1) + x.AppendBytesField(612, tmpx.Reset().AppendVarintField(1, v).Bytes()) + p.StructsB = append(p.StructsB, TestStructS{V: v}) + } + b = x.Bytes() + + runTest(t, &testcase{ + Name: fmt.Sprintf("Struct_%d_Element", n), + Bytes: func() []byte { return b }, + Struct: func() interface{} { return p }, + }) + } + + } +} + +func TestEncoderDecoderMapVarint(t *testing.T) { + x := &wire.Builder{} + tmpx := &wire.Builder{} + + runTest(t, &testcase{ + Name: "Bool", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(701, tmpx.Reset(). + AppendVarintField(1, uint64(i&1)). + AppendVarintField(2, uint64((i+1)&1)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapBool: map[bool]bool{ + true: false, + false: true, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Int32", Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(702, tmpx.Reset(). + AppendVarintField(1, uint64(100+i)). + AppendVarintField(2, uint64(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapInt32: map[int32]int32{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Int64", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(703, tmpx.Reset(). + AppendVarintField(1, uint64(100+i)). + AppendVarintField(2, uint64(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapInt64: map[int64]int64{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Uint32", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(704, tmpx.Reset(). + AppendVarintField(1, uint64(100+i)). + AppendVarintField(2, uint64(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapUint32: map[uint32]uint32{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Uint64", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(705, tmpx.Reset(). + AppendVarintField(1, uint64(100+i)). + AppendVarintField(2, uint64(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapUint64: map[uint64]uint64{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Zigzag32", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(706, tmpx.Reset(). + AppendZigZagField(1, int64(-100-i)). + AppendZigZagField(2, int64(-200-i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapZigZag32: map[int32]int32{ + -100: -200, + -101: -201, + -102: -202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "zigzag64", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(707, tmpx.Reset(). + AppendZigZagField(1, int64(-100-i)). + AppendZigZagField(2, int64(-200-i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapZigZag64: map[int64]int64{ + -100: -200, + -101: -201, + -102: -202, + }, + } + }, + }) +} + +func TestEncoderDecoderMapFixed(t *testing.T) { + x := &wire.Builder{} + tmpx := &wire.Builder{} + + runTest(t, &testcase{ + Name: "Uint32", + Bytes: func() []byte { + x.Reset() + for i := uint32(0); i < 3; i++ { + x.AppendBytesField(801, tmpx.Reset(). + AppendFixed32Field(1, 100+i). + AppendFixed32Field(2, 200+i).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapFixed32: map[uint32]uint32{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Uint64", + Bytes: func() []byte { + x.Reset() + for i := uint64(0); i < 3; i++ { + x.AppendBytesField(802, tmpx.Reset(). + AppendFixed64Field(1, 100+i). + AppendFixed64Field(2, 200+i).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapFixed64: map[uint64]uint64{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Int32", + Bytes: func() []byte { + x.Reset() + for i := int32(0); i < 3; i++ { + x.AppendBytesField(803, tmpx.Reset(). + AppendFixed32Field(1, u32(-100-i)). + AppendFixed32Field(2, u32(-200-i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapSfixed32: map[int32]int32{ + -100: -200, + -101: -201, + -102: -202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Int64", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + x.AppendBytesField(804, tmpx.Reset(). + AppendFixed64Field(1, uint64(-100-i)). + AppendFixed64Field(2, uint64(-200-i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapSfixed64: map[int64]int64{ + -100: -200, + -101: -201, + -102: -202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Float", + Bytes: func() []byte { + x.Reset() + for i := float32(0); i < 3; i++ { + x.AppendBytesField(805, tmpx.Reset(). + AppendFixed32Field(1, math.Float32bits(100+i)). + AppendFixed32Field(2, math.Float32bits(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapFloat: map[float32]float32{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) + runTest(t, &testcase{ + Name: "Double", + Bytes: func() []byte { + x.Reset() + for i := float64(0); i < 3; i++ { + x.AppendBytesField(806, tmpx.Reset(). + AppendFixed64Field(1, math.Float64bits(100+i)). + AppendFixed64Field(2, math.Float64bits(200+i)).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapDouble: map[float64]float64{ + 100: 200, + 101: 201, + 102: 202, + }, + } + }, + }) +} + +func TestEncoderDecoderMapBytes(t *testing.T) { + x := &wire.Builder{} + tmpx := &wire.Builder{} + + runTest(t, &testcase{ + Name: "String2String", + Bytes: func() []byte { + x.Reset() + x.AppendBytesField(901, tmpx.Reset(). + AppendStringField(1, "hello"). + AppendStringField(2, "world").Bytes()) + x.AppendBytesField(901, tmpx.Reset(). + AppendStringField(1, ""). + AppendStringField(2, "").Bytes()) + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapStringString: map[string]string{ + "hello": "world", + "": "", + }, + } + }, + }) + runTest(t, &testcase{ + Name: "String2Bytes", + Bytes: func() []byte { + x.Reset() + x.AppendBytesField(902, tmpx.Reset(). + AppendStringField(1, "hello"). + AppendStringField(2, "world").Bytes()) + x.AppendBytesField(902, tmpx.Reset(). + AppendStringField(1, ""). + AppendStringField(2, "").Bytes()) + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapStringBytes: map[string][]byte{ + "hello": []byte("world"), + "": []byte{}, + }, + } + }, + }) + + runTest(t, &testcase{ + Name: "String2Struct", + Bytes: func() []byte { + x.Reset() + for i := 0; i < 3; i++ { + v := tmpx.Reset().AppendVarintField(1, uint64(i)).Bytes() // TestStructS + x.AppendBytesField(903, tmpx.Reset(). + AppendStringField(1, "k-"+strconv.Itoa(i)). + AppendBytesField(2, v).Bytes()) + x.AppendBytesField(904, tmpx.Reset(). + AppendStringField(1, "k-"+strconv.Itoa(i)). + AppendBytesField(2, v).Bytes()) + } + return x.Bytes() + }, + Struct: func() interface{} { + return &TestStruct{ + MapStringStructA: map[string]*TestStructS{ + "k-0": &TestStructS{V: 0}, + "k-1": &TestStructS{V: 1}, + "k-2": &TestStructS{V: 2}, + }, + MapStringStructB: map[string]TestStructS{ + "k-0": TestStructS{V: 0}, + "k-1": TestStructS{V: 1}, + "k-2": TestStructS{V: 2}, + }, + } + }, + }) +} + +func TestUnknownFields(t *testing.T) { + x := &wire.Builder{} + x.Reset().AppendStringField(1, "hello"). + AppendStringField(2, "world") + b := x.Bytes() + + type TestUnknownFieldsStruct struct { + unknownFields []byte + } + type TestUnknownFieldsStruct2 struct { + unknownFields *[]byte + } + + { // no pointer + p := &TestUnknownFieldsStruct{} + p.unknownFields = append(p.unknownFields, byte(9)) // will reset by Unmarshal + err := Unmarshal(b, p) + assert.NoError(t, err) + assert.BytesEqual(t, b, p.unknownFields) + + newb, err := MarshalAppend([]byte{}, p) + assert.NoError(t, err) + assert.BytesEqual(t, b, newb) + } + { // pointer=true + p2 := &TestUnknownFieldsStruct2{} + err := Unmarshal(b, p2) + assert.NoError(t, err) + assert.BytesEqual(t, b, *p2.unknownFields) + + newb, err := MarshalAppend([]byte{}, p2) + assert.NoError(t, err) + assert.BytesEqual(t, b, newb) + + // test again with p.unknownFields != nil + err = Unmarshal(b, p2) + assert.NoError(t, err) + assert.BytesEqual(t, b, *p2.unknownFields) + } + +} diff --git a/internal/prutal/span.go b/internal/prutal/span.go new file mode 100644 index 0000000..a01d9a2 --- /dev/null +++ b/internal/prutal/span.go @@ -0,0 +1,58 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import "unsafe" + +//go:linkname mallocgc runtime.mallocgc +func mallocgc(size uintptr, typ unsafe.Pointer, needzero bool) unsafe.Pointer + +// defaultDecoderSpanSize controls the min block mem used to malloc, +// DO NOT increase it mindlessly which would cause mem issue, +// coz objects even use one byte of the mem, it won't be released. +const defaultDecoderSpanSize = 4 << 10 + +type span struct { + p int + b unsafe.Pointer + n int +} + +func (s *span) init() { + sz := defaultDecoderSpanSize + s.p = 0 + s.b = mallocgc(uintptr(sz), nil, false) + s.n = sz +} + +func (s *span) Malloc(n, align int) unsafe.Pointer { + mask := align - 1 + if s.p+n+mask > s.n { + sz := defaultDecoderSpanSize + if n+mask > sz { + sz = n + mask + } + s.p = 0 + s.b = mallocgc(uintptr(sz), nil, false) + s.n = sz + } + ret := unsafe.Add(s.b, s.p) // b[p:] + // memory addr alignment off: aligned(ret) - ret + off := (uintptr(ret)+uintptr(mask)) & ^uintptr(mask) - uintptr(ret) + s.p += n + int(off) + return unsafe.Add(ret, off) +} diff --git a/internal/prutal/span_test.go b/internal/prutal/span_test.go new file mode 100644 index 0000000..c7c3d25 --- /dev/null +++ b/internal/prutal/span_test.go @@ -0,0 +1,59 @@ +/* + * Copyright 2024 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestSpan(t *testing.T) { + s := &span{} + s.init() + for i := 0; i < 10; i++ { + // reset align to 8 + p := s.Malloc(16, 8) + assert.Equal(t, uintptr(0), uintptr(p)%8) + + n0 := s.p + p = s.Malloc(8, 4) + n1 := s.p + assert.Equal(t, uintptr(0), uintptr(p)%4) + assert.Equal(t, n0+8, n1) // coz it's already 8 byte aligned + + p = s.Malloc(4, 2) + n2 := s.p + assert.Equal(t, uintptr(0), uintptr(p)%2) + assert.Equal(t, n1+4, n2) // coz it's already 4 byte aligned + + _ = s.Malloc(2, 1) + n3 := s.p + assert.Equal(t, n2+2, n3) // coz it's already 2 byte aligned + + p = s.Malloc(8, 8) + n4 := s.p + assert.Equal(t, uintptr(0), uintptr(p)%8) + assert.Equal(t, n3+8+2, n4) // 4 + 2 % 8 == 6, 8 - 6 = 2 + } + + // test large malloc + p := s.Malloc(2*defaultDecoderSpanSize, 2) + assert.Equal(t, uintptr(0), uintptr(p)%2) + p = s.Malloc(4, 4) + assert.Equal(t, uintptr(0), uintptr(p)%4) +} diff --git a/internal/prutal/testdata.pb.go b/internal/prutal/testdata.pb.go new file mode 100644 index 0000000..c694106 --- /dev/null +++ b/internal/prutal/testdata.pb.go @@ -0,0 +1,98 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Code generated by prutalgen. DO NOT EDIT. +// prutalgen --proto_path=. --go_out=../ ./testdata.proto + +package prutal + +type TestofNestedMessage struct { + Field1 bool `protobuf:"varint,1,opt,name=field1" json:"field1,omitempty"` +} + +func (x *TestofNestedMessage) Reset() { *x = TestofNestedMessage{} } + +type TestOneofMessage struct { + // Types that are assignable to OneOfFieldA: + // + // *TestOneofMessage_Field1 + // *TestOneofMessage_Field2 + OneOfFieldA isTestOneofMessage_OneOfFieldA `protobuf_oneof:"one_of_field_a"` + // Types that are assignable to OneOfFieldB: + // + // *TestOneofMessage_Field3 + // *TestOneofMessage_Field4 + OneOfFieldB isTestOneofMessage_OneOfFieldB `protobuf_oneof:"one_of_field_b"` + // Types that are assignable to OneOfFieldC: + // + // *TestOneofMessage_Field5 + OneOfFieldC isTestOneofMessage_OneOfFieldC `protobuf_oneof:"one_of_field_c"` +} + +func (x *TestOneofMessage) Reset() { *x = TestOneofMessage{} } + +// XXX_OneofWrappers is for the internal use of the prutal package. +func (*TestOneofMessage) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*TestOneofMessage_Field1)(nil), + (*TestOneofMessage_Field2)(nil), + (*TestOneofMessage_Field3)(nil), + (*TestOneofMessage_Field4)(nil), + (*TestOneofMessage_Field5)(nil), + } +} + +type isTestOneofMessage_OneOfFieldA interface { + isTestOneofMessage_OneOfFieldA() +} + +type TestOneofMessage_Field1 struct { + Field1 bool `protobuf:"varint,1,opt,name=field1" json:"field1,omitempty"` +} + +func (*TestOneofMessage_Field1) isTestOneofMessage_OneOfFieldA() {} + +type TestOneofMessage_Field2 struct { + Field2 int64 `protobuf:"varint,2,opt,name=field2" json:"field2,omitempty"` +} + +func (*TestOneofMessage_Field2) isTestOneofMessage_OneOfFieldA() {} + +type isTestOneofMessage_OneOfFieldB interface { + isTestOneofMessage_OneOfFieldB() +} + +type TestOneofMessage_Field3 struct { + Field3 int32 `protobuf:"varint,3,opt,name=field3" json:"field3,omitempty"` +} + +func (*TestOneofMessage_Field3) isTestOneofMessage_OneOfFieldB() {} + +type TestOneofMessage_Field4 struct { + Field4 string `protobuf:"bytes,4,opt,name=field4" json:"field4,omitempty"` +} + +func (*TestOneofMessage_Field4) isTestOneofMessage_OneOfFieldB() {} + +type isTestOneofMessage_OneOfFieldC interface { + isTestOneofMessage_OneOfFieldC() +} + +type TestOneofMessage_Field5 struct { + Field5 *TestofNestedMessage `protobuf:"bytes,5,opt,name=field5" json:"field5,omitempty"` +} + +func (*TestOneofMessage_Field5) isTestOneofMessage_OneOfFieldC() {} diff --git a/internal/prutal/testdata.proto b/internal/prutal/testdata.proto new file mode 100644 index 0000000..1aeb67e --- /dev/null +++ b/internal/prutal/testdata.proto @@ -0,0 +1,26 @@ +syntax = "proto3"; + +package prutal; + +option go_package = "./prutal"; + +message TestofNestedMessage { + bool field1 = 1; +} + +message TestOneofMessage { + oneof one_of_field_a { + bool field1 = 1; + int64 field2 = 2; + } + + oneof one_of_field_b { + int32 field3 = 3; + string field4 = 4; + } + + oneof one_of_field_c { + TestofNestedMessage field5 = 5; + } + +} diff --git a/internal/prutal/testdata.sh b/internal/prutal/testdata.sh new file mode 100755 index 0000000..29be7de --- /dev/null +++ b/internal/prutal/testdata.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -e +unset GOARCH # fix go install + +cd ../../prutalgen/ +go install +cd - + +prutalgen --proto_path=. --go_out=../ ./testdata.proto diff --git a/internal/testutils/assert/assert.go b/internal/testutils/assert/assert.go new file mode 100644 index 0000000..c2e64f1 --- /dev/null +++ b/internal/testutils/assert/assert.go @@ -0,0 +1,172 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert + +import ( + "fmt" + "reflect" + "strings" +) + +// TestingT is an interface used by assert pkg implemented by *testing.T +type TestingT interface { + Fatalf(format string, args ...interface{}) + Helper() +} + +func True(t TestingT, v bool, msgs ...interface{}) { + if !v { + t.Helper() + t.Fatalf("not true. %s", fmt.Sprint(msgs...)) + } +} + +func False(t TestingT, v bool, msgs ...interface{}) { + if v { + t.Helper() + t.Fatalf("not false. %s", fmt.Sprint(msgs...)) + } +} + +func Same(t TestingT, a, b any, msgs ...interface{}) { + x := reflect.ValueOf(a) + y := reflect.ValueOf(b) + if x.Kind() != reflect.Pointer || y.Kind() != reflect.Pointer { + t.Helper() + t.Fatalf("not pointer. %s", fmt.Sprint(msgs...)) + return + } + if x.Pointer() != y.Pointer() { + t.Helper() + t.Fatalf("not same. %s", fmt.Sprint(msgs...)) + } +} + +func DeepEqual(t TestingT, a, b any, msgs ...interface{}) { + if !reflect.DeepEqual(a, b) { + t.Helper() + t.Fatalf("not equal: [ %v != %v ] %s", a, b, fmt.Sprint(msgs...)) + } +} + +func Equal[T comparable](t TestingT, a, b T, msgs ...interface{}) { + if a != b { + t.Helper() + t.Fatalf("not equal: [ %v != %v ] %s", a, b, fmt.Sprint(msgs...)) + } +} + +func BytesEqual(t TestingT, a, b []byte) { + if string(a) == string(b) { + return // fast path if equal + } + + reason := "" + if len(a) != len(b) { + reason = fmt.Sprintf("len(a) != len(b), %d != %d", len(a), len(b)) + goto failed + } + + for i, v := range a { + if v != b[i] { + reason = fmt.Sprintf("a[%d] != b[%d]\na\n%s\nb\n%s", i, i, hexdumpAt(a, i), hexdumpAt(b, i)) + goto failed + } + } +failed: + t.Helper() + t.Fatalf("bytes not equal: %s", reason) +} + +func SliceEqual[T comparable](t TestingT, a, b []T) { + reason := "" + if len(a) != len(b) { + reason = fmt.Sprintf("len(a) != len(b), %d != %d", len(a), len(b)) + goto failed + } + + for i, v := range a { + if v != b[i] { + reason = fmt.Sprintf("a[%d] != b[%d], %v != %v", i, i, a[i], b[i]) + goto failed + } + } + return + +failed: + t.Helper() + t.Fatalf("slice not equal: %s", reason) +} + +func SliceNotEqual[T comparable](t TestingT, a, b []T) { + if len(a) != len(b) { + return + } + for i, v := range a { + if v != b[i] { + return + } + } + t.Helper() + t.Fatalf("slice equal") +} + +func MapEqual[K, V comparable](t TestingT, a, b map[K]V) { + reason := "" + if len(a) != len(b) { + reason = fmt.Sprintf("len(a) != len(b), %d != %d", len(a), len(b)) + goto failed + } + + for k, v := range a { + vb, ok := b[k] + if !ok { + reason = fmt.Sprintf("%v not found in b", k) + goto failed + } + if v != vb { + reason = fmt.Sprintf("a[%v] != b[%v], %v != %v", k, k, v, vb) + goto failed + } + } + return + +failed: + t.Helper() + t.Fatalf("map not equal: %s", reason) +} + +func StringContains(t TestingT, s, substr string) { + if !strings.Contains(s, substr) { + t.Helper() + t.Fatalf("string %q not contains: %q", s, substr) + } +} + +func ErrorContains(t TestingT, err error, s string) { + if err == nil || !strings.Contains(err.Error(), s) { + t.Helper() + t.Fatalf("err %v not contains: %q", err, s) + } +} + +func NoError(t TestingT, err error) { + if err != nil { + t.Helper() + t.Fatalf("err: %s", err) + } +} diff --git a/internal/testutils/assert/assert_test.go b/internal/testutils/assert/assert_test.go new file mode 100644 index 0000000..480bcce --- /dev/null +++ b/internal/testutils/assert/assert_test.go @@ -0,0 +1,222 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert + +import ( + "crypto/rand" + "errors" + "io" + "testing" +) + +type mockTestingT struct { + fatal bool +} + +func (m *mockTestingT) Reset() { *m = mockTestingT{} } +func (m *mockTestingT) Helper() {} +func (m *mockTestingT) Fatalf(format string, args ...interface{}) { + m.fatal = true +} + +func (m *mockTestingT) CheckPassed(t *testing.T) { + if m.fatal { + t.Helper() + t.Fatal("Fatal called") + } +} + +func (m *mockTestingT) CheckFailed(t *testing.T) { + if !m.fatal { + t.Helper() + t.Fatal("Fatal not called") + } +} + +func TestTrueFalse(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + True(m, true) + m.CheckPassed(t) + + m.Reset() + True(m, false) + m.CheckFailed(t) + + m.Reset() + False(m, false) + m.CheckPassed(t) + + m.Reset() + False(m, true) + m.CheckFailed(t) +} + +func TestEqual(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + Equal(m, 1, 1) + m.CheckPassed(t) + + m.Reset() + Equal(m, 1, 2) + m.CheckFailed(t) + + m.Reset() + DeepEqual(m, 1, 1) + m.CheckPassed(t) + + m.Reset() + DeepEqual(m, 1, 2) + m.CheckFailed(t) +} + +func TestSame(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + Same(m, io.EOF, io.EOF) + m.CheckPassed(t) + + m.Reset() + v := 1 + var x interface{} + var y *int + x = &v + y = &v + Same(m, x, y) + m.CheckPassed(t) + + m.Reset() + x = t + Same(m, x, y) + m.CheckFailed(t) + + m.Reset() + Same(m, 1, 1) // not pointer + m.CheckFailed(t) +} + +func TestStringContains(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + StringContains(m, "helloworld", "world") + m.CheckPassed(t) + + m.Reset() + StringContains(m, "helloworld", "main") + m.CheckFailed(t) +} + +func TestErrorContains(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + ErrorContains(m, errors.New("helloworld"), "world") + m.CheckPassed(t) + + m.Reset() + ErrorContains(m, nil, "main") + m.CheckFailed(t) +} + +func TestNoError(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + NoError(m, nil) + m.CheckPassed(t) + + m.Reset() + NoError(m, errors.New("")) + m.CheckFailed(t) +} + +func TestSliceEqual(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + SliceEqual(m, []int{1}, []int{1}) + m.CheckPassed(t) + + m.Reset() + SliceEqual(m, []int{1}, []int{2}) + m.CheckFailed(t) + + m.Reset() + SliceEqual(m, []int{1}, []int{1, 1}) + m.CheckFailed(t) +} + +func TestSliceNotEqual(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + SliceNotEqual(m, []int{1}, []int{2}) + m.CheckPassed(t) + + m.Reset() + SliceNotEqual(m, []int{1}, []int{1, 1}) + m.CheckPassed(t) + + m.Reset() + SliceNotEqual(m, []int{1, 1}, []int{1, 1}) + m.CheckFailed(t) +} + +func TestBytesEqual(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + BytesEqual(m, []byte{}, []byte{}) + m.CheckPassed(t) + + a := make([]byte, 100) + b := make([]byte, 100) + _, _ = rand.Read(a) + _, _ = rand.Read(b) + m.Reset() + BytesEqual(m, a, b) + m.CheckFailed(t) + + m.Reset() + BytesEqual(m, []byte{}, []byte{1}) + m.CheckFailed(t) +} + +func TestMapEqual(t *testing.T) { + m := &mockTestingT{} + + m.Reset() + MapEqual(m, map[int]int{1: 1}, map[int]int{1: 1}) + m.CheckPassed(t) + + m.Reset() + MapEqual(m, map[int]int{1: 1}, map[int]int{}) + m.CheckFailed(t) + + m.Reset() + MapEqual(m, map[int]int{1: 1}, map[int]int{2: 2}) + m.CheckFailed(t) + + m.Reset() + MapEqual(m, map[int]int{1: 1}, map[int]int{1: 2}) + m.CheckFailed(t) +} diff --git a/internal/testutils/assert/bytes.go b/internal/testutils/assert/bytes.go new file mode 100644 index 0000000..c817c35 --- /dev/null +++ b/internal/testutils/assert/bytes.go @@ -0,0 +1,63 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert + +import ( + "fmt" + "strings" +) + +// hexdumpAt outputs like `hexdump -C`, +// but it only outputs 3 lines, 16 bytes per line, and 2nd line contains b[i]. +// +// format: addr +// 00000870 75 72 6e 0a 09 7d 0a 09 66 6f 72 20 69 2c 20 76 |urn..}..for i, v| +func hexdumpAt(b []byte, i int) string { + pos1 := i - (i % 16) + pos0 := pos1 - 16 + pos2 := pos1 + 16 + + f := &strings.Builder{} + for _, pos := range []int{pos0, pos1, pos2} { + if pos < 0 || pos >= len(b) { + continue + } + fmt.Fprintf(f, "%08x", pos) + for i := 0; i < 16; i++ { + if pos+i >= len(b) { + f.WriteString(" --") + } else { + fmt.Fprintf(f, " %02x", b[pos+i]) + } + } + f.WriteString(" |") + for i := 0; i < 16; i++ { + if pos+i >= len(b) { + f.WriteString(".") + continue + } + c := b[pos+i] + if c >= 32 && c < 127 { + f.WriteByte(c) // printable ascii + } else { + f.WriteByte('.') + } + } + f.WriteString("|\n") + } + return f.String() +} diff --git a/internal/testutils/assert/bytes_test.go b/internal/testutils/assert/bytes_test.go new file mode 100644 index 0000000..cae487d --- /dev/null +++ b/internal/testutils/assert/bytes_test.go @@ -0,0 +1,30 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package assert + +import ( + "crypto/rand" + "testing" +) + +func TestHexdumpAt(t *testing.T) { + b := make([]byte, 300) + _, _ = rand.Read(b) + t.Log(hexdumpAt(b, 0)) + t.Log(hexdumpAt(b, 150)) + t.Log(hexdumpAt(b, 300)) +} diff --git a/internal/testutils/testutils.go b/internal/testutils/testutils.go new file mode 100644 index 0000000..854b5c6 --- /dev/null +++ b/internal/testutils/testutils.go @@ -0,0 +1,56 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package testutils + +import ( + crand "crypto/rand" + "math/rand" +) + +func Repeat[T any](n int, v T) []T { + ret := make([]T, n) + for i := 0; i < len(ret); i++ { + ret[i] = v + } + return ret +} + +func RandomBoolSlice(n int) []bool { + v := uint64(0) + ret := make([]bool, n) + for i := range ret { + if v == 0 { + v = rand.Uint64() + } + ret[i] = ((v & 1) == 1) + v = v >> 1 + } + return ret +} + +func RandomStr(n int) string { + return string(RandomBytes(n)) +} + +func RandomBytes(n int) []byte { + b := make([]byte, n) + _, err := crand.Read(b) + if err != nil { + panic(err) + } + return b +} diff --git a/internal/testutils/testutils_test.go b/internal/testutils/testutils_test.go new file mode 100644 index 0000000..6c48172 --- /dev/null +++ b/internal/testutils/testutils_test.go @@ -0,0 +1,43 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package testutils + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestRepeat(t *testing.T) { + assert.SliceEqual(t, []int{1, 1, 1}, Repeat(3, 1)) +} + +func TestRandomBoolSlice(t *testing.T) { + vv := RandomBoolSlice(100) + assert.SliceNotEqual(t, vv, Repeat(100, true)) + assert.SliceNotEqual(t, vv, Repeat(100, false)) +} + +func TestRandomStr(t *testing.T) { + _ = RandomStr(100) +} + +func TestRandomBytes(t *testing.T) { + vv := RandomBytes(100) + assert.SliceNotEqual(t, vv, Repeat(100, byte(0))) + assert.SliceNotEqual(t, vv, Repeat(100, byte(1))) +} diff --git a/internal/wire/builder.go b/internal/wire/builder.go new file mode 100644 index 0000000..f58c354 --- /dev/null +++ b/internal/wire/builder.go @@ -0,0 +1,144 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +// Builder implements a wire format builder for testing +type Builder struct { + b []byte + + tmp []byte +} + +func (p *Builder) Reset() *Builder { + p.b = p.b[:0] + return p +} + +func (p *Builder) Bytes() []byte { + // copy, in case caller will ref to the []byte + // it's OK for testing + return append([]byte{}, p.b...) +} + +func xappendVarint(b []byte, v uint64) []byte { + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + return append(b, byte(v)) +} + +func (p *Builder) appendVarint(v uint64) *Builder { + p.b = xappendVarint(p.b, v) + return p +} + +func (p *Builder) appendFixed32(v uint32) *Builder { + p.b = append(p.b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24)) + return p +} + +func (p *Builder) appendFixed64(v uint64) *Builder { + p.b = append(p.b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56)) + return p +} + +func (p *Builder) appendTag(num int, t Type) *Builder { + return p.appendVarint(uint64(num)<<3 | uint64(t&7)) +} + +func (p *Builder) appendString(v string) *Builder { + p.appendVarint(uint64(len(v))) + p.b = append(p.b, v...) + return p +} + +func (p *Builder) appendBytes(v []byte) *Builder { + p.appendVarint(uint64(len(v))) + p.b = append(p.b, v...) + return p +} + +func (p *Builder) AppendVarintField(num int, v uint64) *Builder { + return p.appendTag(num, TypeVarint).appendVarint(v) +} + +func (p *Builder) AppendZigZagField(num int, v int64) *Builder { + return p.appendTag(num, TypeVarint).appendVarint(uint64(v<<1) ^ uint64(v>>63)) +} + +func (p *Builder) AppendFixed32Field(num int, v uint32) *Builder { + return p.appendTag(num, TypeFixed32).appendFixed32(v) +} + +func (p *Builder) AppendFixed64Field(num int, v uint64) *Builder { + return p.appendTag(num, TypeFixed64).appendFixed64(v) +} + +func (p *Builder) AppendStringField(num int, v string) *Builder { + return p.appendTag(num, TypeBytes).appendString(v) +} + +func (p *Builder) AppendBytesField(num int, v []byte) *Builder { + return p.appendTag(num, TypeBytes).appendBytes(v) +} + +func (p *Builder) AppendPackedVarintField(num int, vv ...uint64) *Builder { + p.appendTag(num, TypeBytes) + p.tmp = p.tmp[:0] + for _, v := range vv { + p.tmp = xappendVarint(p.tmp, v) + } + return p.appendBytes(p.tmp) +} + +func (p *Builder) AppendPackedZigZagField(num int, vv ...int64) *Builder { + p.appendTag(num, TypeBytes) + p.tmp = p.tmp[:0] + for _, v := range vv { + p.tmp = xappendVarint(p.tmp, uint64(v<<1)^uint64(v>>63)) + } + return p.appendBytes(p.tmp) +} + +func (p *Builder) AppendPackedFixed32Field(num int, vv ...uint32) *Builder { + p.appendTag(num, TypeBytes).appendVarint(uint64(len(vv) * 4)) + for _, v := range vv { + p.appendFixed32(v) + } + return p +} + +func (p *Builder) AppendPackedFixed64Field(num int, vv ...uint64) *Builder { + p.appendTag(num, TypeBytes).appendVarint(uint64(len(vv) * 8)) + for _, v := range vv { + p.appendFixed64(v) + } + return p +} diff --git a/internal/wire/builder_test.go b/internal/wire/builder_test.go new file mode 100644 index 0000000..6f65587 --- /dev/null +++ b/internal/wire/builder_test.go @@ -0,0 +1,104 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/protowire" + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestBuilder(t *testing.T) { + p := &Builder{} + b := make([]byte, 0, 100) + + resetfn := func() { + p.Reset() + b = b[:0] + } + + // AppendVarintField + resetfn() + b = protowire.AppendTag(b, 1, protowire.VarintType) + b = protowire.AppendVarint(b, 129) + assert.BytesEqual(t, b, p.AppendVarintField(1, 129).Bytes()) + + // AppendZigZagField + resetfn() + b = protowire.AppendTag(b, 1, protowire.VarintType) + b = protowire.AppendVarint(b, protowire.EncodeZigZag(-200)) + assert.BytesEqual(t, b, p.AppendZigZagField(1, -200).Bytes()) + + // AppendFixed32Field + resetfn() + b = protowire.AppendTag(b, 1, protowire.Fixed32Type) + b = protowire.AppendFixed32(b, 300) + assert.BytesEqual(t, b, p.AppendFixed32Field(1, 300).Bytes()) + + // AppendFixed64Field + resetfn() + b = protowire.AppendTag(b, 1, protowire.Fixed64Type) + b = protowire.AppendFixed64(b, 300) + assert.BytesEqual(t, b, p.AppendFixed64Field(1, 300).Bytes()) + + // AppendStringField + resetfn() + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendString(b, "hello") + assert.BytesEqual(t, b, p.AppendStringField(1, "hello").Bytes()) + + // AppendBytesField + resetfn() + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendString(b, "hello") + assert.BytesEqual(t, b, p.AppendBytesField(1, []byte("hello")).Bytes()) + + // AppendPackedVarintField + resetfn() + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendVarint(b, uint64(protowire.SizeVarint(100))+uint64(protowire.SizeVarint(1000))) + b = protowire.AppendVarint(b, 100) + b = protowire.AppendVarint(b, 1000) + assert.BytesEqual(t, b, p.AppendPackedVarintField(1, 100, 1000).Bytes()) + + // AppendPackedZigZagField + resetfn() + z1, z2 := protowire.EncodeZigZag(-100), protowire.EncodeZigZag(-1000) + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendVarint(b, uint64(protowire.SizeVarint(z1))+uint64(protowire.SizeVarint(z2))) + b = protowire.AppendVarint(b, z1) + b = protowire.AppendVarint(b, z2) + assert.BytesEqual(t, b, p.AppendPackedZigZagField(1, -100, -1000).Bytes()) + + // AppendPackedFixed32Field + resetfn() + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendVarint(b, 2*4) + b = protowire.AppendFixed32(b, 100) + b = protowire.AppendFixed32(b, 1000) + assert.BytesEqual(t, b, p.AppendPackedFixed32Field(1, 100, 1000).Bytes()) + + // AppendPackedFixed64Field + resetfn() + b = protowire.AppendTag(b, 1, protowire.BytesType) + b = protowire.AppendVarint(b, 2*8) + b = protowire.AppendFixed64(b, 100) + b = protowire.AppendFixed64(b, 1000) + assert.BytesEqual(t, b, p.AppendPackedFixed64Field(1, 100, 1000).Bytes()) + +} diff --git a/internal/wire/encoder.go b/internal/wire/encoder.go new file mode 100644 index 0000000..a57944a --- /dev/null +++ b/internal/wire/encoder.go @@ -0,0 +1,314 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "unsafe" + + "github.com/cloudwego/prutal/internal/protowire" +) + +func AppendVarint(b []byte, v uint64) []byte { + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + return append(b, byte(v)) +} + +func UnsafeAppendVarintU64(b []byte, p unsafe.Pointer) []byte { + v := *(*uint64)(p) + switch { + case v < 1<<7: + return append(b, byte(v)) + case v < 1<<14: + return append(b, + byte((v>>0)&0x7f|0x80), + byte(v>>7)) + case v < 1<<21: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte(v>>14)) + case v < 1<<28: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte(v>>21)) + case v < 1<<35: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte(v>>28)) + case v < 1<<42: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte(v>>35)) + case v < 1<<49: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte(v>>42)) + case v < 1<<56: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte(v>>49)) + case v < 1<<63: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte(v>>56)) + default: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte((v>>56)&0x7f|0x80), + 1) + } +} + +func UnsafeAppendVarintU32(b []byte, p unsafe.Pointer) []byte { + v := *(*uint32)(p) + switch { + case v < 1<<7: + return append(b, byte(v)) + case v < 1<<14: + return append(b, + byte((v>>0)|0x80), + byte(v>>7)) + case v < 1<<21: + return append(b, + byte((v>>0)|0x80), + byte((v>>7)|0x80), + byte(v>>14)) + case v < 1<<28: + return append(b, + byte((v>>0)|0x80), + byte((v>>7)|0x80), + byte((v>>14)|0x80), + byte(v>>21)) + default: // v < 1<<35: + return append(b, + byte((v>>0)|0x80), + byte((v>>7)|0x80), + byte((v>>14)|0x80), + byte((v>>21)|0x80), + byte(v>>28)) + } +} + +func UnsafeAppendZigZag64(b []byte, p unsafe.Pointer) []byte { + x := *(*int64)(p) + v := uint64(x<<1) ^ uint64(x>>63) + switch { + case v < 1<<7: + return append(b, byte(v)) + case v < 1<<14: + return append(b, + byte((v>>0)&0x7f|0x80), + byte(v>>7)) + case v < 1<<21: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte(v>>14)) + case v < 1<<28: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte(v>>21)) + case v < 1<<35: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte(v>>28)) + case v < 1<<42: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte(v>>35)) + case v < 1<<49: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte(v>>42)) + case v < 1<<56: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte(v>>49)) + case v < 1<<63: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte(v>>56)) + default: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte((v>>28)&0x7f|0x80), + byte((v>>35)&0x7f|0x80), + byte((v>>42)&0x7f|0x80), + byte((v>>49)&0x7f|0x80), + byte((v>>56)&0x7f|0x80), + 1) + } +} + +func UnsafeAppendZigZag32(b []byte, p unsafe.Pointer) []byte { + x := *(*int32)(p) + v := uint32(x<<1) ^ uint32(x>>31) + switch { + case v < 1<<7: + return append(b, byte(v)) + case v < 1<<14: + return append(b, + byte((v>>0)&0x7f|0x80), + byte(v>>7)) + case v < 1<<21: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte(v>>14)) + case v < 1<<28: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte(v>>21)) + default: + return append(b, + byte((v>>0)&0x7f|0x80), + byte((v>>7)&0x7f|0x80), + byte((v>>14)&0x7f|0x80), + byte((v>>21)&0x7f|0x80), + byte(v>>28)) + } +} + +func UnsafeAppendBool(b []byte, p unsafe.Pointer) []byte { + return append(b, *(*byte)(p)&0x1) +} + +var UnsafeAppendBytes = UnsafeAppendString // should be the same + +func UnsafeAppendString(b []byte, p unsafe.Pointer) []byte { + s := *(*string)(p) + b = AppendVarint(b, uint64(len(s))) + return append(b, s...) +} + +func UnsafeAppendFixed32(b []byte, p unsafe.Pointer) []byte { + v := *(*uint32)(p) + return append(b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24)) +} + +func UnsafeAppendFixed64(b []byte, p unsafe.Pointer) []byte { + v := *(*uint64)(p) + return append(b, + byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56)) +} + +// LenReserve reserves one byte for LEN encoding. +// use `LenPack` after bytes are appended to the end of the []byte with the size of it +func LenReserve(b []byte) []byte { + return append(b, 0) +} + +func LenPack(b []byte, sz int) []byte { + if sz < 128 { + // fast path for most cases that can be inlined with cost 79 + b[len(b)-1-sz] = byte(sz) // 1 byte varint + return b + } + return lenPackSlow(b, sz) +} + +func lenPackSlow(b []byte, sz int) []byte { + m := protowire.SizeVarint(uint64(sz)) + for i := m; i > 1; i-- { + // reserved one byte, then m-1 bytes needed + b = append(b, 0) + } + pos := len(b) - sz - m // pos varint + copy(b[pos+m:], b[pos+1:]) + protowire.AppendVarint(b[pos:][:0], uint64(sz)) + return b +} diff --git a/internal/wire/encoder_list.go b/internal/wire/encoder_list.go new file mode 100644 index 0000000..52f2d8b --- /dev/null +++ b/internal/wire/encoder_list.go @@ -0,0 +1,121 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import "unsafe" + +func UnsafeAppendRepeatedVarintU64(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]uint64)(p) { + b = AppendVarint(b, EncodeTag(id, TypeVarint)) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + return b +} + +func UnsafeAppendRepeatedVarintU32(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]uint32)(p) { + b = AppendVarint(b, EncodeTag(id, TypeVarint)) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + return b +} + +func UnsafeAppendRepeatedZigZag64(b []byte, id int32, p unsafe.Pointer) []byte { + for _, x := range *(*[]uint64)(p) { + b = AppendVarint(b, EncodeTag(id, TypeVarint)) + v := uint64(x<<1) ^ uint64(x>>63) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + return b +} + +func UnsafeAppendRepeatedZigZag32(b []byte, id int32, p unsafe.Pointer) []byte { + for _, x := range *(*[]uint32)(p) { + b = AppendVarint(b, EncodeTag(id, TypeVarint)) + v := uint32(x<<1) ^ uint32(x>>31) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + return b +} + +func UnsafeAppendRepeatedFixed64(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]uint64)(p) { + b = AppendVarint(b, EncodeTag(id, TypeFixed64)) + b = append(b, byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56)) + } + return b +} + +func UnsafeAppendRepeatedFixed32(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]uint32)(p) { + b = AppendVarint(b, EncodeTag(id, TypeFixed32)) + b = append(b, byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24)) + } + return b +} + +func UnsafeAppendRepeatedBool(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]byte)(p) { + b = AppendVarint(b, EncodeTag(id, TypeVarint)) + b = append(b, v) + } + return b +} + +func UnsafeAppendRepeatedString(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[]string)(p) { + b = AppendVarint(b, EncodeTag(id, TypeBytes)) + b = AppendVarint(b, uint64(len(v))) + b = append(b, v...) + } + return b +} + +func UnsafeAppendRepeatedBytes(b []byte, id int32, p unsafe.Pointer) []byte { + for _, v := range *(*[][]byte)(p) { + b = AppendVarint(b, EncodeTag(id, TypeBytes)) + b = AppendVarint(b, uint64(len(v))) + b = append(b, v...) + } + return b +} diff --git a/internal/wire/encoder_list_test.go b/internal/wire/encoder_list_test.go new file mode 100644 index 0000000..ad2c75a --- /dev/null +++ b/internal/wire/encoder_list_test.go @@ -0,0 +1,115 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "math/rand" + "testing" + "unsafe" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestUnsafeAppendRepeatedVarintU64(t *testing.T) { + vv := []uint64{rand.Uint64(), rand.Uint64(), rand.Uint64()} + p := &Builder{} + b0 := p.AppendVarintField(1, vv[0]). + AppendVarintField(1, vv[1]). + AppendVarintField(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedVarintU64(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedVarintU32(t *testing.T) { + vv := []uint32{rand.Uint32(), rand.Uint32(), rand.Uint32()} + p := &Builder{} + b0 := p.AppendVarintField(1, uint64(vv[0])). + AppendVarintField(1, uint64(vv[1])). + AppendVarintField(1, uint64(vv[2])).Bytes() + b1 := UnsafeAppendRepeatedVarintU32(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedZigZag64(t *testing.T) { + vv := []int64{rand.Int63(), rand.Int63(), rand.Int63()} + p := &Builder{} + b0 := p.AppendZigZagField(1, vv[0]). + AppendZigZagField(1, vv[1]). + AppendZigZagField(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedZigZag64(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedZigZag32(t *testing.T) { + vv := []int32{rand.Int31(), rand.Int31(), rand.Int31()} + p := &Builder{} + b0 := p.AppendZigZagField(1, int64(vv[0])). + AppendZigZagField(1, int64(vv[1])). + AppendZigZagField(1, int64(vv[2])).Bytes() + b1 := UnsafeAppendRepeatedZigZag32(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedFixed64(t *testing.T) { + vv := []uint64{rand.Uint64(), rand.Uint64(), rand.Uint64()} + p := &Builder{} + b0 := p.AppendFixed64Field(1, vv[0]). + AppendFixed64Field(1, vv[1]). + AppendFixed64Field(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedFixed64(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedFixed32(t *testing.T) { + vv := []uint32{rand.Uint32(), rand.Uint32(), rand.Uint32()} + p := &Builder{} + b0 := p.AppendFixed32Field(1, vv[0]). + AppendFixed32Field(1, vv[1]). + AppendFixed32Field(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedFixed32(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedBool(t *testing.T) { + vv := []bool{true, false, true} + b0 := []byte{ + 1 << 3, 1, + 1 << 3, 0, + 1 << 3, 1} + b1 := UnsafeAppendRepeatedBool(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedString(t *testing.T) { + vv := []string{"s1", "s2", "s3"} + p := &Builder{} + b0 := p.AppendStringField(1, vv[0]). + AppendStringField(1, vv[1]). + AppendStringField(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedString(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendRepeatedBytes(t *testing.T) { + vv := [][]byte{[]byte("s1"), []byte("s2"), []byte("s3")} + p := &Builder{} + b0 := p.AppendBytesField(1, vv[0]). + AppendBytesField(1, vv[1]). + AppendBytesField(1, vv[2]).Bytes() + b1 := UnsafeAppendRepeatedBytes(nil, 1, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} diff --git a/internal/wire/encoder_packed.go b/internal/wire/encoder_packed.go new file mode 100644 index 0000000..b0cf3a2 --- /dev/null +++ b/internal/wire/encoder_packed.go @@ -0,0 +1,111 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import "unsafe" + +func UnsafeAppendPackedVarintU64(b []byte, p unsafe.Pointer) []byte { + b = LenReserve(b) + sz0 := len(b) + for _, v := range *(*[]uint64)(p) { + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + b = LenPack(b, len(b)-sz0) + return b +} + +func UnsafeAppendPackedVarintU32(b []byte, p unsafe.Pointer) []byte { + b = LenReserve(b) + sz0 := len(b) + for _, v := range *(*[]uint32)(p) { + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + b = LenPack(b, len(b)-sz0) + return b +} + +func UnsafeAppendPackedZigZag64(b []byte, p unsafe.Pointer) []byte { + b = LenReserve(b) + sz0 := len(b) + for _, x := range *(*[]int64)(p) { + v := uint64(x<<1) ^ uint64(x>>63) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + b = LenPack(b, len(b)-sz0) + return b +} + +func UnsafeAppendPackedZigZag32(b []byte, p unsafe.Pointer) []byte { + b = LenReserve(b) + sz0 := len(b) + for _, x := range *(*[]int32)(p) { + v := uint32(x<<1) ^ uint32(x>>31) + for v >= 0x80 { + b = append(b, byte(v)|0x80) + v >>= 7 + } + b = append(b, byte(v)) + } + b = LenPack(b, len(b)-sz0) + return b +} + +func UnsafeAppendPackedFixed64(b []byte, p unsafe.Pointer) []byte { + vv := *(*[]uint64)(p) + b = AppendVarint(b, uint64(8*len(vv))) + for _, v := range vv { + b = append(b, byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24), + byte(v>>32), + byte(v>>40), + byte(v>>48), + byte(v>>56)) + } + return b +} + +func UnsafeAppendPackedFixed32(b []byte, p unsafe.Pointer) []byte { + vv := *(*[]uint32)(p) + b = AppendVarint(b, uint64(4*len(vv))) + for _, v := range vv { + b = append(b, byte(v>>0), + byte(v>>8), + byte(v>>16), + byte(v>>24)) + } + return b +} + +func UnsafeAppendPackedBool(b []byte, p unsafe.Pointer) []byte { + vv := *(*[]byte)(p) + b = AppendVarint(b, uint64(len(vv))) + return append(b, vv...) +} diff --git a/internal/wire/encoder_packed_test.go b/internal/wire/encoder_packed_test.go new file mode 100644 index 0000000..41b126f --- /dev/null +++ b/internal/wire/encoder_packed_test.go @@ -0,0 +1,81 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "math/rand" + "unsafe" + + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestUnsafeAppendPackedVarintU64(t *testing.T) { + vv := []uint64{rand.Uint64(), rand.Uint64(), rand.Uint64()} + p := &Builder{} + b0 := p.AppendPackedVarintField(1, vv...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedVarintU64(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedVarintU32(t *testing.T) { + vv := []uint32{rand.Uint32(), rand.Uint32(), rand.Uint32()} + p := &Builder{} + b0 := p.AppendPackedVarintField(1, toUint64s(vv)...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedVarintU32(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedZigZag64(t *testing.T) { + vv := []int64{rand.Int63(), rand.Int63(), rand.Int63()} + p := &Builder{} + b0 := p.AppendPackedZigZagField(1, vv...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedZigZag64(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedZigZag32(t *testing.T) { + vv := []int32{rand.Int31(), rand.Int31(), rand.Int31()} + p := &Builder{} + b0 := p.AppendPackedZigZagField(1, toInt64s(vv)...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedZigZag32(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedFixed64(t *testing.T) { + vv := []uint64{rand.Uint64(), rand.Uint64(), rand.Uint64()} + p := &Builder{} + b0 := p.AppendPackedFixed64Field(1, vv...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedFixed64(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedFixed32(t *testing.T) { + vv := []uint32{rand.Uint32(), rand.Uint32(), rand.Uint32()} + p := &Builder{} + b0 := p.AppendPackedFixed32Field(1, vv...).Bytes()[1:] // skip tag + b1 := UnsafeAppendPackedFixed32(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} + +func TestUnsafeAppendPackedBool(t *testing.T) { + vv := []bool{true, false, true} + b0 := []byte{3, 1, 0, 1} + b1 := UnsafeAppendPackedBool(nil, unsafe.Pointer(&vv)) + assert.BytesEqual(t, b0, b1) +} diff --git a/internal/wire/encoder_test.go b/internal/wire/encoder_test.go new file mode 100644 index 0000000..a0adff1 --- /dev/null +++ b/internal/wire/encoder_test.go @@ -0,0 +1,114 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "strings" + "testing" + "unsafe" + + "github.com/cloudwego/prutal/internal/protowire" + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestAppendVarint(t *testing.T) { + v := uint64(1) + for i := 0; i < 30; i++ { + b0 := protowire.AppendVarint([]byte{}, v) + b1 := UnsafeAppendVarintU64([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) + + v32 := uint32(v) + b0 = protowire.AppendVarint([]byte{}, uint64(v32)) + b1 = UnsafeAppendVarintU32([]byte{}, unsafe.Pointer(&v32)) + assert.BytesEqual(t, b0, b1) + + v *= 17 + } +} + +func TestAppendZigZag(t *testing.T) { + v := int64(-1) + for i := 0; i < 30; i++ { + b0 := protowire.AppendVarint([]byte{}, protowire.EncodeZigZag(v)) + b1 := UnsafeAppendZigZag64([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) + + v32 := int32(v) + b0 = protowire.AppendVarint([]byte{}, protowire.EncodeZigZag(int64(v32))) + b1 = UnsafeAppendZigZag32([]byte{}, unsafe.Pointer(&v32)) + assert.BytesEqual(t, b0, b1) + + v *= 17 + } +} + +func TestAppendFixed(t *testing.T) { + v := uint64(1) + for i := 0; i < 30; i++ { + b0 := protowire.AppendFixed64([]byte{}, v) + b1 := UnsafeAppendFixed64([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) + + v32 := uint32(v) + b0 = protowire.AppendFixed32([]byte{}, v32) + b1 = UnsafeAppendFixed32([]byte{}, unsafe.Pointer(&v32)) + assert.BytesEqual(t, b0, b1) + + v *= 17 + } +} + +func TestAppendBool(t *testing.T) { + v := true + b0 := protowire.AppendVarint([]byte{}, protowire.EncodeBool(v)) + b1 := UnsafeAppendBool([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) + + v = false + b0 = protowire.AppendVarint([]byte{}, protowire.EncodeBool(v)) + b1 = UnsafeAppendBool([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) +} + +func TestAppendString(t *testing.T) { + v := "hello" + b0 := protowire.AppendString([]byte{}, v) + b1 := UnsafeAppendString([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) + + v = strings.Repeat("hello", 1000) + b0 = protowire.AppendString([]byte{}, v) + b1 = UnsafeAppendString([]byte{}, unsafe.Pointer(&v)) + assert.BytesEqual(t, b0, b1) +} + +func TestReserveLen(t *testing.T) { + n := 300 + b := make([]byte, n) + for i := 0; i < 300; i++ { + b[i] = 'a' + byte(i)%26 + } + for i := 0; i < 300; i++ { + teststr := string(b[:i]) + b0 := LenReserve([]byte{}) + x := append(b0, teststr...) + b0 = LenPack(x, len(x)-len(b0)) + b1 := protowire.AppendString([]byte{}, teststr) + assert.BytesEqual(t, b1, b0) + } +} diff --git a/internal/wire/utils.go b/internal/wire/utils.go new file mode 100644 index 0000000..80727e1 --- /dev/null +++ b/internal/wire/utils.go @@ -0,0 +1,33 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +func toUint64s(vv []uint32) []uint64 { + xx := make([]uint64, len(vv)) + for i, v := range vv { + xx[i] = uint64(v) + } + return xx +} + +func toInt64s(vv []int32) []int64 { + xx := make([]int64, len(vv)) + for i, v := range vv { + xx[i] = int64(v) + } + return xx +} diff --git a/internal/wire/wire.go b/internal/wire/wire.go new file mode 100644 index 0000000..88dfa85 --- /dev/null +++ b/internal/wire/wire.go @@ -0,0 +1,53 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import "github.com/cloudwego/prutal/internal/protowire" + +type Type = protowire.Type + +const ( // align with protowire.Type + TypeVarint Type = 0 + TypeFixed32 Type = 5 + TypeFixed64 Type = 1 + TypeBytes Type = 2 + TypeSGroup Type = 3 + TypeEGroup Type = 4 +) + +// ConsumeKVTag implements ConsumeTag for key and value of map +// +// for map pairs, num=1 for key, and num=2 for value. +// the max int should be 2<<3 + 15 = 31 which always < 0x80 (128) +func ConsumeKVTag(b []byte) (int32, Type) { + if len(b) > 0 && uint64(b[0]) < 0x80 { + return DecodeTag(uint64(b[0])) + } + return 0, 0 +} + +// EncodeTag ... +// +// see: https://protobuf.dev/programming-guides/encoding/#structure +func EncodeTag(num int32, t Type) uint64 { + return uint64(num)<<3 | uint64(t) +} + +// DecodeTag ... +func DecodeTag(v uint64) (int32, Type) { + return int32(uint32(v >> 3)), Type(v & 0b111) +} diff --git a/internal/wire/wire_test.go b/internal/wire/wire_test.go new file mode 100644 index 0000000..8f4394c --- /dev/null +++ b/internal/wire/wire_test.go @@ -0,0 +1,74 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package wire + +import ( + "encoding/binary" + "testing" + + "github.com/cloudwego/prutal/internal/protowire" + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestEncodeTag(t *testing.T) { + v := EncodeTag(1233, TypeBytes) + n, wt := DecodeTag(v) + assert.Equal(t, int32(1233), n) + assert.Equal(t, TypeBytes, wt) +} + +func TestConsumeKVTag(t *testing.T) { + v := EncodeTag(2, TypeBytes) + n, wt := ConsumeKVTag([]byte{byte(v)}) + assert.Equal(t, int32(2), n) + assert.Equal(t, TypeBytes, wt) + + n, wt = ConsumeKVTag([]byte{}) + assert.Equal(t, int32(0), n) + assert.Equal(t, Type(0), wt) +} + +var ( + _b0 = binary.AppendUvarint([]byte{}, 1) // nolint: unused + _b1 = binary.AppendUvarint([]byte{}, 1<<7) // nolint: unused + _b2 = binary.AppendUvarint([]byte{}, 1<<14) // nolint: unused + _b3 = binary.AppendUvarint([]byte{}, 1<<21) // nolint: unused + _b4 = binary.AppendUvarint([]byte{}, 1<<28) // nolint: unused + _b5 = binary.AppendUvarint([]byte{}, 1<<35) // nolint: unused + _b6 = binary.AppendUvarint([]byte{}, 1<<42) // nolint: unused + _b7 = binary.AppendUvarint([]byte{}, 1<<49) // nolint: unused + _b8 = binary.AppendUvarint([]byte{}, 1<<56) // nolint: unused + _b9 = binary.AppendUvarint([]byte{}, 1<<63) // nolint: unused +) + +func BenchmarkUvarint(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = binary.Uvarint(_b0) + _, _ = binary.Uvarint(_b2) + _, _ = binary.Uvarint(_b4) + _, _ = binary.Uvarint(_b6) + } +} + +func BenchmarkConsumeVarint(b *testing.B) { + for i := 0; i < b.N; i++ { + _, _ = protowire.ConsumeVarint(_b0) + _, _ = protowire.ConsumeVarint(_b2) + _, _ = protowire.ConsumeVarint(_b4) + _, _ = protowire.ConsumeVarint(_b6) + } +} diff --git a/licenses/antlr4-LICENSE b/licenses/antlr4-LICENSE new file mode 100644 index 0000000..5d27694 --- /dev/null +++ b/licenses/antlr4-LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither name of copyright holders nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/licenses/protobuf-LICENSE b/licenses/protobuf-LICENSE new file mode 100644 index 0000000..219c78c --- /dev/null +++ b/licenses/protobuf-LICENSE @@ -0,0 +1,24 @@ +Copyright (c) 2018 The Go Authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/profile/README.md b/profile/README.md deleted file mode 100644 index 2127160..0000000 --- a/profile/README.md +++ /dev/null @@ -1,13 +0,0 @@ -## Hi there 👋 - -🙋‍♀️ A short introduction - CloudWeGo is an open-source middleware set launched by ByteDance that can be used to quickly build enterprise-class cloud native architectures. The common characteristics of CloudWeGo projects are high performance, high scalability, high reliability and focusing on microservices communication and governance. - -🌈 Community Membership - the [Responsibilities and Requirements](https://github.com/cloudwego/community/blob/main/COMMUNITY_MEMBERSHIP.md) of contributor roles in CloudWeGo. - -👩‍💻 Useful resources - [Portal](https://www.cloudwego.io/), [Community](https://www.cloudwego.io/zh/community/), [Blogs](https://www.cloudwego.io/zh/blog/), [Use Cases](https://www.cloudwego.io/zh/cooperation/) - -🍿 Security - [Vulnerability Reporting](https://www.cloudwego.io/zh/security/vulnerability-reporting/), [Safety Bulletin](https://www.cloudwego.io/zh/security/safety-bulletin/) - -🌲 Ecosystem - [Kitex-contrib](https://github.com/kitex-contrib), [Hertz-contrib](https://github.com/hertz-contrib), [Volo-rs](https://github.com/volo-rs) - -🎊 Example - [kitex-example](https://github.com/cloudwego/kitex-examples), [hertz-example](https://github.com/cloudwego/hertz-examples), [biz-demo](https://github.com/cloudwego/biz-demo), [netpoll-example](https://github.com/cloudwego/netpoll-examples) diff --git a/prutal.go b/prutal.go new file mode 100644 index 0000000..581ac09 --- /dev/null +++ b/prutal.go @@ -0,0 +1,31 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutal + +import ( + "github.com/cloudwego/prutal/internal/prutal" +) + +// MarshalAppend ... +func MarshalAppend(b []byte, v interface{}) ([]byte, error) { + return prutal.MarshalAppend(b, v) +} + +// Unmarshal ... +func Unmarshal(b []byte, v interface{}) error { + return prutal.Unmarshal(b, v) +} diff --git a/prutalgen/internal/Protobuf.g4 b/prutalgen/internal/Protobuf.g4 new file mode 100644 index 0000000..fef2c9d --- /dev/null +++ b/prutalgen/internal/Protobuf.g4 @@ -0,0 +1,663 @@ +// This file originates from github.com/antlr/grammars-v4 Protobuf3.g4 +// SPDX-License-Identifier: Apache-2.0 +// @author anatawa12 + +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +grammar Protobuf; + +proto + : edition? (importStatement | packageStatement | optionStatement | topLevelDef | emptyStatement)* EOF + ; + +// Edition + +edition + : (SYNTAX | EDITION) EQ strLit SEMI + ; + +// Import Statement + +importStatement + : IMPORT (WEAK | PUBLIC)? strLit SEMI + ; + +// Package + +packageStatement + : PACKAGE fullIdent SEMI + ; + +// Option + +optionStatement + : OPTION optionName EQ constant SEMI + ; + +optionName + : fullIdent + | LP fullIdent RP ( DOT fullIdent)? + ; + +// Normal Field +fieldLabel + : OPTIONAL + | REQUIRED + | REPEATED + ; + +field + : fieldLabel? fieldType fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + ; + +fieldOptions + : fieldOption (COMMA fieldOption)* + ; + +fieldOption + : optionName EQ constant + ; + +fieldNumber + : intLit + ; + +// Oneof and oneof field + +oneof + : ONEOF oneofName LC (optionStatement | oneofField | emptyStatement)* RC + ; + +oneofField + : fieldType fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + ; + +// Map field + +mapField + : MAP LT keyType COMMA fieldType GT fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + ; + +keyType + : INT32 + | INT64 + | UINT32 + | UINT64 + | SINT32 + | SINT64 + | FIXED32 + | FIXED64 + | SFIXED32 + | SFIXED64 + | BOOL + | STRING + ; + +// field types + +fieldType + : DOUBLE + | FLOAT + | INT32 + | INT64 + | UINT32 + | UINT64 + | SINT32 + | SINT64 + | FIXED32 + | FIXED64 + | SFIXED32 + | SFIXED64 + | BOOL + | STRING + | BYTES + | messageType + | enumType + ; + +// Reserved + +reserved + : RESERVED (ranges | reservedFieldNames) SEMI + ; + +// Extensions + +extensions + : EXTENSIONS ranges (LB fieldOptions RB)? SEMI + ; + +ranges + : oneRange (COMMA oneRange)* + ; + +oneRange + : intLit (TO ( intLit | MAX))? + ; + +reservedFieldNames + : strLit (COMMA strLit)* + ; + + +// Top Level definitions + +topLevelDef + : messageDef + | enumDef + | extendDef + | serviceDef + ; + +// enum + +enumDef + : ENUM enumName enumBody + ; + +enumBody + : LC enumElement* RC + ; + +enumElement + : optionStatement + | enumField + | reserved + | emptyStatement + ; + +enumField + : ident EQ (MINUS)? intLit enumValueOptions? SEMI + ; + +enumValueOptions + : LB enumValueOption (COMMA enumValueOption)* RB + ; + +enumValueOption + : optionName EQ constant + ; + +// message + +messageDef + : MESSAGE messageName messageBody + ; + +messageBody + : LC messageElement* RC + ; + +messageElement + : field + | enumDef + | messageDef + | extendDef + | optionStatement + | oneof + | mapField + | reserved + | extensions + | emptyStatement + ; + +// Extend definition +// +// NB: not defined in the spec but supported by protoc and covered by protobuf3 tests +// see e.g. php/tests/proto/test_import_descriptor_proto.proto +// of https://github.com/protocolbuffers/protobuf +// it also was discussed here: https://github.com/protocolbuffers/protobuf/issues/4610 + +extendDef + : EXTEND messageType LC (field | emptyStatement)* RC + ; + +// service + +serviceDef + : SERVICE serviceName LC serviceElement* RC + ; + +serviceElement + : optionStatement + | rpc + | emptyStatement + ; + +rpc + : RPC rpcName LP (STREAM)? messageType RP RETURNS LP (STREAM)? messageType RP ( + LC ( optionStatement | emptyStatement)* RC + | SEMI + ) + ; + +// lexical + +constant + : fullIdent + | (MINUS | PLUS)? intLit + | ( MINUS | PLUS)? floatLit + | strLit + | boolLit + | blockLit + ; + +// not specified in specification but used in tests +blockLit + : LC (ident COLON constant (SEMI | COMMA)? )* RC + ; + +emptyStatement + : SEMI + ; + +// Lexical elements + +ident + : IDENTIFIER + | keywords + ; + +fullIdent + : ident (DOT ident)* + ; + +messageName + : ident + ; + +enumName + : ident + ; + +fieldName + : ident + ; + +oneofName + : ident + ; + +serviceName + : ident + ; + +rpcName + : ident + ; + +messageType + : (DOT)? (ident DOT)* messageName + ; + +enumType + : (DOT)? (ident DOT)* enumName + ; + +intLit + : INT_LIT + ; + +strLit + : STR_LIT_SINGLE+ + ; + +boolLit + : BOOL_LIT + ; + +floatLit + : FLOAT_LIT + ; + +// keywords +SYNTAX + : 'syntax' + ; + +EDITION + : 'edition' + ; + +IMPORT + : 'import' + ; + +WEAK + : 'weak' + ; + +PUBLIC + : 'public' + ; + +PACKAGE + : 'package' + ; + +OPTION + : 'option' + ; + +OPTIONAL + : 'optional' + ; + +REQUIRED + : 'required' + ; + +REPEATED + : 'repeated' + ; + +ONEOF + : 'oneof' + ; + +MAP + : 'map' + ; + +INT32 + : 'int32' + ; + +INT64 + : 'int64' + ; + +UINT32 + : 'uint32' + ; + +UINT64 + : 'uint64' + ; + +SINT32 + : 'sint32' + ; + +SINT64 + : 'sint64' + ; + +FIXED32 + : 'fixed32' + ; + +FIXED64 + : 'fixed64' + ; + +SFIXED32 + : 'sfixed32' + ; + +SFIXED64 + : 'sfixed64' + ; + +BOOL + : 'bool' + ; + +STRING + : 'string' + ; + +DOUBLE + : 'double' + ; + +FLOAT + : 'float' + ; + +BYTES + : 'bytes' + ; + +RESERVED + : 'reserved' + ; + +EXTENSIONS + : 'extensions' + ; + +TO + : 'to' + ; + +MAX + : 'max' + ; + +ENUM + : 'enum' + ; + +MESSAGE + : 'message' + ; + +SERVICE + : 'service' + ; + +EXTEND + : 'extend' + ; + +RPC + : 'rpc' + ; + +STREAM + : 'stream' + ; + +RETURNS + : 'returns' + ; + +// symbols + +SEMI + : ';' + ; + +EQ + : '=' + ; + +LP + : '(' + ; + +RP + : ')' + ; + +LB + : '[' + ; + +RB + : ']' + ; + +LC + : '{' + ; + +RC + : '}' + ; + +LT + : '<' + ; + +GT + : '>' + ; + +DOT + : '.' + ; + +COMMA + : ',' + ; + +COLON + : ':' + ; + +PLUS + : '+' + ; + +MINUS + : '-' + ; + +STR_LIT_SINGLE + : ('\'' ( CHAR_VALUE)*? '\'') + | ( '"' ( CHAR_VALUE)*? '"') + ; + +fragment CHAR_VALUE + : HEX_ESCAPE + | OCT_ESCAPE + | CHAR_ESCAPE + | ~[\u0000\n\\] + ; + +fragment HEX_ESCAPE + : '\\' ('x' | 'X') HEX_DIGIT HEX_DIGIT + ; + +fragment OCT_ESCAPE + : '\\' OCTAL_DIGIT OCTAL_DIGIT OCTAL_DIGIT + ; + +fragment CHAR_ESCAPE + : '\\' ('a' | 'b' | 'f' | 'n' | 'r' | 't' | 'v' | '\\' | '\'' | '"') + ; + +BOOL_LIT + : 'true' + | 'false' + ; + +FLOAT_LIT + : (DECIMALS DOT DECIMALS? EXPONENT? | DECIMALS EXPONENT | DOT DECIMALS EXPONENT?) + | 'inf' + | 'nan' + ; + +fragment EXPONENT + : ('e' | 'E') (PLUS | MINUS)? DECIMALS + ; + +fragment DECIMALS + : DECIMAL_DIGIT+ + ; + +INT_LIT + : DECIMAL_LIT + | OCTAL_LIT + | HEX_LIT + ; + +fragment DECIMAL_LIT + : ([1-9]) DECIMAL_DIGIT* + ; + +fragment OCTAL_LIT + : '0' OCTAL_DIGIT* + ; + +fragment HEX_LIT + : '0' ('x' | 'X') HEX_DIGIT+ + ; + +IDENTIFIER + : LETTER (LETTER | DECIMAL_DIGIT)* + ; + +fragment LETTER + : [A-Za-z_] + ; + +fragment DECIMAL_DIGIT + : [0-9] + ; + +fragment OCTAL_DIGIT + : [0-7] + ; + +fragment HEX_DIGIT + : [0-9A-Fa-f] + ; + +// comments +// keep it in channel 2, may use it later to check empty lines +WS + : [ \t\r\n\u000C]+ -> channel(2) + ; + +LINE_COMMENT + : '//' ~[\r\n]* -> channel(3) + ; + +COMMENT + : '/*' .*? '*/' -> channel(3) + ; + +keywords + : SYNTAX + | EDITION + | IMPORT + | WEAK + | PUBLIC + | PACKAGE + | OPTION + | OPTIONAL + | REPEATED + | ONEOF + | MAP + | INT32 + | INT64 + | UINT32 + | UINT64 + | SINT32 + | SINT64 + | FIXED32 + | FIXED64 + | SFIXED32 + | SFIXED64 + | BOOL + | STRING + | DOUBLE + | FLOAT + | BYTES + | RESERVED + | EXTENSIONS + | TO + | MAX + | ENUM + | MESSAGE + | SERVICE + | EXTEND + | RPC + | STREAM + | RETURNS + | BOOL_LIT + ; diff --git a/prutalgen/internal/antlr/LICENSE b/prutalgen/internal/antlr/LICENSE new file mode 100644 index 0000000..a22292e --- /dev/null +++ b/prutalgen/internal/antlr/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2012-2023 The ANTLR Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither name of copyright holders nor the names of its contributors +may be used to endorse or promote products derived from this software +without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/prutalgen/internal/antlr/README b/prutalgen/internal/antlr/README new file mode 100644 index 0000000..e5d7981 --- /dev/null +++ b/prutalgen/internal/antlr/README @@ -0,0 +1 @@ +the code is forked from github.com/antlr/antlr4/pull/4754 diff --git a/prutalgen/internal/antlr/antlrdoc.go b/prutalgen/internal/antlr/antlrdoc.go new file mode 100644 index 0000000..fbb4ac6 --- /dev/null +++ b/prutalgen/internal/antlr/antlrdoc.go @@ -0,0 +1,102 @@ +/* +Package antlr implements the Go version of the ANTLR 4 runtime. + +# The ANTLR Tool + +ANTLR (ANother Tool for Language Recognition) is a powerful parser generator for reading, processing, executing, +or translating structured text or binary files. It's widely used to build languages, tools, and frameworks. +From a grammar, ANTLR generates a parser that can build parse trees and also generates a listener interface +(or visitor) that makes it easy to respond to the recognition of phrases of interest. + +# Go Runtime + +At version 4.11.x and prior, the Go runtime was not properly versioned for go modules. After this point, the runtime +source code to be imported was held in the `runtime/Go/antlr/v4` directory, and the go.mod file was updated to reflect the version of +ANTLR4 that it is compatible with (I.E. uses the /v4 path). + +However, this was found to be problematic, as it meant that with the runtime embedded so far underneath the root +of the repo, the `go get` and related commands could not properly resolve the location of the go runtime source code. +This meant that the reference to the runtime in your `go.mod` file would refer to the correct source code, but would not +list the release tag such as @4.13.2 - this was confusing, to say the least. + +As of 4.13.0, the runtime is now available as a go module in its own repo, and can be imported as `github.com/antlr4-go/antlr` +(the go get command should also be used with this path). See the main documentation for the ANTLR4 project for more information, +which is available at [ANTLR docs]. The documentation for using the Go runtime is available at [Go runtime docs]. + +This means that if you are using the source code without modules, you should also use the source code in the [new repo]. +Though we highly recommend that you use go modules, as they are now idiomatic for Go. + +I am aware that this change will prove Hyrum's Law, but am prepared to live with it for the common good. + +Go runtime author: [Jim Idle] jimi@idle.ws + +# Code Generation + +ANTLR supports the generation of code in a number of [target languages], and the generated code is supported by a +runtime library, written specifically to support the generated code in the target language. This library is the +runtime for the Go target. + +To generate code for the go target, it is generally recommended to place the source grammar files in a package of +their own, and use the `.sh` script method of generating code, using the go generate directive. In that same directory +it is usual, though not required, to place the antlr tool that should be used to generate the code. That does mean +that the antlr tool JAR file will be checked in to your source code control though, so you are, of course, free to use any other +way of specifying the version of the ANTLR tool to use, such as aliasing in `.zshrc` or equivalent, or a profile in +your IDE, or configuration in your CI system. Checking in the jar does mean that it is easy to reproduce the build as +it was at any point in its history. + +Here is a general/recommended template for an ANTLR based recognizer in Go: + + . + ├── parser + │ ├── mygrammar.g4 + │ ├── antlr-4.13.2-complete.jar + │ ├── generate.go + │ └── generate.sh + ├── parsing - generated code goes here + │ └── error_listeners.go + ├── go.mod + ├── go.sum + ├── main.go + └── main_test.go + +Make sure that the package statement in your grammar file(s) reflects the go package the generated code will exist in. + +The generate.go file then looks like this: + + package parser + + //go:generate ./generate.sh + +And the generate.sh file will look similar to this: + + #!/bin/sh + + alias antlr4='java -Xmx500M -cp "./antlr4-4.13.2-complete.jar:$CLASSPATH" org.antlr.v4.Tool' + antlr4 -Dlanguage=Go -no-visitor -package parsing *.g4 + +depending on whether you want visitors or listeners or any other ANTLR options. Not that another option here +is to generate the code into a + +From the command line at the root of your source package (location of go.mo)d) you can then simply issue the command: + + go generate ./... + +Which will generate the code for the parser, and place it in the parsing package. You can then use the generated code +by importing the parsing package. + +There are no hard and fast rules on this. It is just a recommendation. You can generate the code in any way and to anywhere you like. + +# Copyright Notice + +Copyright (c) 2012-2023 The ANTLR Project. All rights reserved. + +Use of this file is governed by the BSD 3-clause license, which can be found in the [LICENSE.txt] file in the project root. + +[target languages]: https://github.com/antlr/antlr4/tree/master/runtime +[LICENSE.txt]: https://github.com/antlr/antlr4/blob/master/LICENSE.txt +[ANTLR docs]: https://github.com/antlr/antlr4/blob/master/doc/index.md +[new repo]: https://github.com/antlr4-go/antlr +[Jim Idle]: https://github.com/jimidle +[Go runtime docs]: https://github.com/antlr/antlr4/blob/master/doc/go-target.md +*/ +package antlr diff --git a/prutalgen/internal/antlr/atn.go b/prutalgen/internal/antlr/atn.go new file mode 100644 index 0000000..e749ebd --- /dev/null +++ b/prutalgen/internal/antlr/atn.go @@ -0,0 +1,177 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// ATNInvalidAltNumber is used to represent an ALT number that has yet to be calculated or +// which is invalid for a particular struct such as [*antlr.BaseRuleContext] +var ATNInvalidAltNumber int + +// ATN represents an “[Augmented Transition Network]”, though general in ANTLR the term +// “Augmented Recursive Transition Network” though there are some descriptions of “[Recursive Transition Network]” +// in existence. +// +// ATNs represent the main networks in the system and are serialized by the code generator and support [ALL(*)]. +// +// [Augmented Transition Network]: https://en.wikipedia.org/wiki/Augmented_transition_network +// [ALL(*)]: https://www.antlr.org/papers/allstar-techreport.pdf +// [Recursive Transition Network]: https://en.wikipedia.org/wiki/Recursive_transition_network +type ATN struct { + + // DecisionToState is the decision points for all rules, sub-rules, optional + // blocks, ()+, ()*, etc. Each sub-rule/rule is a decision point, and we must track them, so we + // can go back later and build DFA predictors for them. This includes + // all the rules, sub-rules, optional blocks, ()+, ()* etc... + DecisionToState []DecisionState + + // grammarType is the ATN type and is used for deserializing ATNs from strings. + grammarType int + + // lexerActions is referenced by action transitions in the ATN for lexer ATNs. + lexerActions []LexerAction + + // maxTokenType is the maximum value for any symbol recognized by a transition in the ATN. + maxTokenType int + + modeNameToStartState map[string]*TokensStartState + + modeToStartState []*TokensStartState + + // ruleToStartState maps from rule index to starting state number. + ruleToStartState []*RuleStartState + + // ruleToStopState maps from rule index to stop state number. + ruleToStopState []*RuleStopState + + // ruleToTokenType maps the rule index to the resulting token type for lexer + // ATNs. For parser ATNs, it maps the rule index to the generated bypass token + // type if ATNDeserializationOptions.isGenerateRuleBypassTransitions was + // specified, and otherwise is nil. + ruleToTokenType []int + + // ATNStates is a list of all states in the ATN, ordered by state number. + // + states []ATNState + + mu Mutex + stateMu RWMutex + edgeMu RWMutex +} + +// NewATN returns a new ATN struct representing the given grammarType and is used +// for runtime deserialization of ATNs from the code generated by the ANTLR tool +func NewATN(grammarType int, maxTokenType int) *ATN { + return &ATN{ + grammarType: grammarType, + maxTokenType: maxTokenType, + modeNameToStartState: make(map[string]*TokensStartState), + } +} + +// NextTokensInContext computes and returns the set of valid tokens that can occur starting +// in state s. If ctx is nil, the set of tokens will not include what can follow +// the rule surrounding s. In other words, the set will be restricted to tokens +// reachable staying within the rule of s. +func (a *ATN) NextTokensInContext(s ATNState, ctx RuleContext) *IntervalSet { + return NewLL1Analyzer(a).Look(s, nil, ctx) +} + +// NextTokensNoContext computes and returns the set of valid tokens that can occur starting +// in state s and staying in same rule. [antlr.Token.EPSILON] is in set if we reach end of +// rule. +func (a *ATN) NextTokensNoContext(s ATNState) *IntervalSet { + a.mu.Lock() + defer a.mu.Unlock() + iset := s.GetNextTokenWithinRule() + if iset == nil { + iset = a.NextTokensInContext(s, nil) + iset.readOnly = true + s.SetNextTokenWithinRule(iset) + } + return iset +} + +// NextTokens computes and returns the set of valid tokens starting in state s, by +// calling either [NextTokensNoContext] (ctx == nil) or [NextTokensInContext] (ctx != nil). +func (a *ATN) NextTokens(s ATNState, ctx RuleContext) *IntervalSet { + if ctx == nil { + return a.NextTokensNoContext(s) + } + + return a.NextTokensInContext(s, ctx) +} + +func (a *ATN) addState(state ATNState) { + if state != nil { + state.SetATN(a) + state.SetStateNumber(len(a.states)) + } + + a.states = append(a.states, state) +} + +func (a *ATN) removeState(state ATNState) { + a.states[state.GetStateNumber()] = nil // Just free the memory; don't shift states in the slice +} + +func (a *ATN) defineDecisionState(s DecisionState) int { + a.DecisionToState = append(a.DecisionToState, s) + s.setDecision(len(a.DecisionToState) - 1) + + return s.getDecision() +} + +func (a *ATN) getDecisionState(decision int) DecisionState { + if len(a.DecisionToState) == 0 { + return nil + } + + return a.DecisionToState[decision] +} + +// getExpectedTokens computes the set of input symbols which could follow ATN +// state number stateNumber in the specified full parse context ctx and returns +// the set of potentially valid input symbols which could follow the specified +// state in the specified context. This method considers the complete parser +// context, but does not evaluate semantic predicates (i.e. all predicates +// encountered during the calculation are assumed true). If a path in the ATN +// exists from the starting state to the RuleStopState of the outermost context +// without Matching any symbols, Token.EOF is added to the returned set. +// +// A nil ctx defaults to ParserRuleContext.EMPTY. +// +// It panics if the ATN does not contain state stateNumber. +func (a *ATN) getExpectedTokens(stateNumber int, ctx RuleContext) *IntervalSet { + if stateNumber < 0 || stateNumber >= len(a.states) { + panic("Invalid state number.") + } + + s := a.states[stateNumber] + following := a.NextTokens(s, nil) + + if !following.contains(TokenEpsilon) { + return following + } + + expected := NewIntervalSet() + + expected.addSet(following) + expected.removeOne(TokenEpsilon) + + for ctx != nil && ctx.GetInvokingState() >= 0 && following.contains(TokenEpsilon) { + invokingState := a.states[ctx.GetInvokingState()] + rt := invokingState.GetTransitions()[0] + + following = a.NextTokens(rt.(*RuleTransition).followState, nil) + expected.addSet(following) + expected.removeOne(TokenEpsilon) + ctx = ctx.GetParent().(RuleContext) + } + + if following.contains(TokenEpsilon) { + expected.addOne(TokenEOF) + } + + return expected +} diff --git a/prutalgen/internal/antlr/atn_config.go b/prutalgen/internal/antlr/atn_config.go new file mode 100644 index 0000000..267308b --- /dev/null +++ b/prutalgen/internal/antlr/atn_config.go @@ -0,0 +1,332 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" +) + +const ( + lexerConfig = iota // Indicates that this ATNConfig is for a lexer + parserConfig // Indicates that this ATNConfig is for a parser +) + +// ATNConfig is a tuple: (ATN state, predicted alt, syntactic, semantic +// context). The syntactic context is a graph-structured stack node whose +// path(s) to the root is the rule invocation(s) chain used to arrive in the +// state. The semantic context is the tree of semantic predicates encountered +// before reaching an ATN state. +type ATNConfig struct { + precedenceFilterSuppressed bool + state ATNState + alt int + context *PredictionContext + semanticContext SemanticContext + reachesIntoOuterContext int + cType int // lexerConfig or parserConfig + lexerActionExecutor *LexerActionExecutor + passedThroughNonGreedyDecision bool +} + +// NewATNConfig6 creates a new ATNConfig instance given a state, alt and context only +func NewATNConfig6(state ATNState, alt int, context *PredictionContext) *ATNConfig { + return NewATNConfig5(state, alt, context, SemanticContextNone) +} + +// NewATNConfig5 creates a new ATNConfig instance given a state, alt, context and semantic context +func NewATNConfig5(state ATNState, alt int, context *PredictionContext, semanticContext SemanticContext) *ATNConfig { + if semanticContext == nil { + panic("semanticContext cannot be nil") // TODO: Necessary? + } + + pac := &ATNConfig{} + pac.state = state + pac.alt = alt + pac.context = context + pac.semanticContext = semanticContext + pac.cType = parserConfig + return pac +} + +// NewATNConfig4 creates a new ATNConfig instance given an existing config, and a state only +func NewATNConfig4(c *ATNConfig, state ATNState) *ATNConfig { + return NewATNConfig(c, state, c.GetContext(), c.GetSemanticContext()) +} + +// NewATNConfig3 creates a new ATNConfig instance given an existing config, a state and a semantic context +func NewATNConfig3(c *ATNConfig, state ATNState, semanticContext SemanticContext) *ATNConfig { + return NewATNConfig(c, state, c.GetContext(), semanticContext) +} + +// NewATNConfig2 creates a new ATNConfig instance given an existing config, and a context only +func NewATNConfig2(c *ATNConfig, semanticContext SemanticContext) *ATNConfig { + return NewATNConfig(c, c.GetState(), c.GetContext(), semanticContext) +} + +// NewATNConfig1 creates a new ATNConfig instance given an existing config, a state, and a context only +func NewATNConfig1(c *ATNConfig, state ATNState, context *PredictionContext) *ATNConfig { + return NewATNConfig(c, state, context, c.GetSemanticContext()) +} + +// NewATNConfig creates a new ATNConfig instance given an existing config, a state, a context and a semantic context, other 'constructors' +// are just wrappers around this one. +func NewATNConfig(c *ATNConfig, state ATNState, context *PredictionContext, semanticContext SemanticContext) *ATNConfig { + b := &ATNConfig{} + b.InitATNConfig(c, state, c.GetAlt(), context, semanticContext) + b.cType = parserConfig + return b +} + +func (a *ATNConfig) InitATNConfig(c *ATNConfig, state ATNState, alt int, context *PredictionContext, semanticContext SemanticContext) { + + a.state = state + a.alt = alt + a.context = context + a.semanticContext = semanticContext + a.reachesIntoOuterContext = c.GetReachesIntoOuterContext() + a.precedenceFilterSuppressed = c.getPrecedenceFilterSuppressed() +} + +func (a *ATNConfig) getPrecedenceFilterSuppressed() bool { + return a.precedenceFilterSuppressed +} + +func (a *ATNConfig) setPrecedenceFilterSuppressed(v bool) { + a.precedenceFilterSuppressed = v +} + +// GetState returns the ATN state associated with this configuration +func (a *ATNConfig) GetState() ATNState { + return a.state +} + +// GetAlt returns the alternative associated with this configuration +func (a *ATNConfig) GetAlt() int { + return a.alt +} + +// SetContext sets the rule invocation stack associated with this configuration +func (a *ATNConfig) SetContext(v *PredictionContext) { + a.context = v +} + +// GetContext returns the rule invocation stack associated with this configuration +func (a *ATNConfig) GetContext() *PredictionContext { + return a.context +} + +// GetSemanticContext returns the semantic context associated with this configuration +func (a *ATNConfig) GetSemanticContext() SemanticContext { + return a.semanticContext +} + +// GetReachesIntoOuterContext returns the count of references to an outer context from this configuration +func (a *ATNConfig) GetReachesIntoOuterContext() int { + return a.reachesIntoOuterContext +} + +// SetReachesIntoOuterContext sets the count of references to an outer context from this configuration +func (a *ATNConfig) SetReachesIntoOuterContext(v int) { + a.reachesIntoOuterContext = v +} + +// Equals is the default comparison function for an ATNConfig when no specialist implementation is required +// for a collection. +// +// An ATN configuration is equal to another if both have the same state, they +// predict the same alternative, and syntactic/semantic contexts are the same. +func (a *ATNConfig) Equals(o Collectable[*ATNConfig]) bool { + switch a.cType { + case lexerConfig: + return a.LEquals(o) + case parserConfig: + return a.PEquals(o) + default: + panic("Invalid ATNConfig type") + } +} + +// PEquals is the default comparison function for a Parser ATNConfig when no specialist implementation is required +// for a collection. +// +// An ATN configuration is equal to another if both have the same state, they +// predict the same alternative, and syntactic/semantic contexts are the same. +func (a *ATNConfig) PEquals(o Collectable[*ATNConfig]) bool { + var other, ok = o.(*ATNConfig) + + if !ok { + return false + } + if a == other { + return true + } else if other == nil { + return false + } + + var equal bool + + if a.context == nil { + equal = other.context == nil + } else { + equal = a.context.Equals(other.context) + } + + var ( + nums = a.state.GetStateNumber() == other.state.GetStateNumber() + alts = a.alt == other.alt + cons = a.semanticContext.Equals(other.semanticContext) + sups = a.precedenceFilterSuppressed == other.precedenceFilterSuppressed + ) + + return nums && alts && cons && sups && equal +} + +// Hash is the default hash function for a parser ATNConfig, when no specialist hash function +// is required for a collection +func (a *ATNConfig) Hash() int { + switch a.cType { + case lexerConfig: + return a.LHash() + case parserConfig: + return a.PHash() + default: + panic("Invalid ATNConfig type") + } +} + +// PHash is the default hash function for a parser ATNConfig, when no specialist hash function +// is required for a collection +func (a *ATNConfig) PHash() int { + var c int + if a.context != nil { + c = a.context.Hash() + } + + h := murmurInit(7) + h = murmurUpdate(h, a.state.GetStateNumber()) + h = murmurUpdate(h, a.alt) + h = murmurUpdate(h, c) + h = murmurUpdate(h, a.semanticContext.Hash()) + return murmurFinish(h, 4) +} + +// String returns a string representation of the ATNConfig, usually used for debugging purposes +func (a *ATNConfig) String() string { + var s1, s2, s3 string + + if a.context != nil { + s1 = ",[" + fmt.Sprint(a.context) + "]" + } + + if a.semanticContext != SemanticContextNone { + s2 = "," + fmt.Sprint(a.semanticContext) + } + + if a.reachesIntoOuterContext > 0 { + s3 = ",up=" + fmt.Sprint(a.reachesIntoOuterContext) + } + + return fmt.Sprintf("(%v,%v%v%v%v)", a.state, a.alt, s1, s2, s3) +} + +func NewLexerATNConfig6(state ATNState, alt int, context *PredictionContext) *ATNConfig { + lac := &ATNConfig{} + lac.state = state + lac.alt = alt + lac.context = context + lac.semanticContext = SemanticContextNone + lac.cType = lexerConfig + return lac +} + +func NewLexerATNConfig4(c *ATNConfig, state ATNState) *ATNConfig { + lac := &ATNConfig{} + lac.lexerActionExecutor = c.lexerActionExecutor + lac.passedThroughNonGreedyDecision = checkNonGreedyDecision(c, state) + lac.InitATNConfig(c, state, c.GetAlt(), c.GetContext(), c.GetSemanticContext()) + lac.cType = lexerConfig + return lac +} + +func NewLexerATNConfig3(c *ATNConfig, state ATNState, lexerActionExecutor *LexerActionExecutor) *ATNConfig { + lac := &ATNConfig{} + lac.lexerActionExecutor = lexerActionExecutor + lac.passedThroughNonGreedyDecision = checkNonGreedyDecision(c, state) + lac.InitATNConfig(c, state, c.GetAlt(), c.GetContext(), c.GetSemanticContext()) + lac.cType = lexerConfig + return lac +} + +func NewLexerATNConfig2(c *ATNConfig, state ATNState, context *PredictionContext) *ATNConfig { + lac := &ATNConfig{} + lac.lexerActionExecutor = c.lexerActionExecutor + lac.passedThroughNonGreedyDecision = checkNonGreedyDecision(c, state) + lac.InitATNConfig(c, state, c.GetAlt(), context, c.GetSemanticContext()) + lac.cType = lexerConfig + return lac +} + +//goland:noinspection GoUnusedExportedFunction +func NewLexerATNConfig1(state ATNState, alt int, context *PredictionContext) *ATNConfig { + lac := &ATNConfig{} + lac.state = state + lac.alt = alt + lac.context = context + lac.semanticContext = SemanticContextNone + lac.cType = lexerConfig + return lac +} + +// LHash is the default hash function for Lexer ATNConfig objects, it can be used directly or via +// the default comparator [ObjEqComparator]. +func (a *ATNConfig) LHash() int { + var f int + if a.passedThroughNonGreedyDecision { + f = 1 + } else { + f = 0 + } + h := murmurInit(7) + h = murmurUpdate(h, a.state.GetStateNumber()) + h = murmurUpdate(h, a.alt) + h = murmurUpdate(h, a.context.Hash()) + h = murmurUpdate(h, a.semanticContext.Hash()) + h = murmurUpdate(h, f) + h = murmurUpdate(h, a.lexerActionExecutor.Hash()) + h = murmurFinish(h, 6) + return h +} + +// LEquals is the default comparison function for Lexer ATNConfig objects, it can be used directly or via +// the default comparator [ObjEqComparator]. +func (a *ATNConfig) LEquals(other Collectable[*ATNConfig]) bool { + var otherT, ok = other.(*ATNConfig) + if !ok { + return false + } else if a == otherT { + return true + } else if a.passedThroughNonGreedyDecision != otherT.passedThroughNonGreedyDecision { + return false + } + + switch { + case a.lexerActionExecutor == nil && otherT.lexerActionExecutor == nil: + return true + case a.lexerActionExecutor != nil && otherT.lexerActionExecutor != nil: + if !a.lexerActionExecutor.Equals(otherT.lexerActionExecutor) { + return false + } + default: + return false // One but not both, are nil + } + + return a.PEquals(otherT) +} + +func checkNonGreedyDecision(source *ATNConfig, target ATNState) bool { + var ds, ok = target.(DecisionState) + + return source.passedThroughNonGreedyDecision || (ok && ds.getNonGreedy()) +} diff --git a/prutalgen/internal/antlr/atn_config_set.go b/prutalgen/internal/antlr/atn_config_set.go new file mode 100644 index 0000000..52dbaf8 --- /dev/null +++ b/prutalgen/internal/antlr/atn_config_set.go @@ -0,0 +1,301 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" +) + +// ATNConfigSet is a specialized set of ATNConfig that tracks information +// about its elements and can combine similar configurations using a +// graph-structured stack. +type ATNConfigSet struct { + cachedHash int + + // configLookup is used to determine whether two ATNConfigSets are equal. We + // need all configurations with the same (s, i, _, semctx) to be equal. A key + // effectively doubles the number of objects associated with ATNConfigs. All + // keys are hashed by (s, i, _, pi), not including the context. Wiped out when + // read-only because a set becomes a DFA state. + configLookup *JStore[*ATNConfig, Comparator[*ATNConfig]] + + // configs is the added elements that did not match an existing key in configLookup + configs []*ATNConfig + + // TODO: These fields make me pretty uncomfortable, but it is nice to pack up + // info together because it saves re-computation. Can we track conflicts as they + // are added to save scanning configs later? + conflictingAlts *BitSet + + // dipsIntoOuterContext is used by parsers and lexers. In a lexer, it indicates + // we hit a pred while computing a closure operation. Do not make a DFA state + // from the ATNConfigSet in this case. TODO: How is this used by parsers? + dipsIntoOuterContext bool + + // fullCtx is whether it is part of a full context LL prediction. Used to + // determine how to merge $. It is a wildcard with SLL, but not for an LL + // context merge. + fullCtx bool + + // Used in parser and lexer. In lexer, it indicates we hit a pred + // while computing a closure operation. Don't make a DFA state from this set. + hasSemanticContext bool + + // readOnly is whether it is read-only. Do not + // allow any code to manipulate the set if true because DFA states will point at + // sets and those must not change. It not, protect other fields; conflictingAlts + // in particular, which is assigned after readOnly. + readOnly bool + + // TODO: These fields make me pretty uncomfortable, but it is nice to pack up + // info together because it saves re-computation. Can we track conflicts as they + // are added to save scanning configs later? + uniqueAlt int +} + +// Alts returns the combined set of alts for all the configurations in this set. +func (b *ATNConfigSet) Alts() *BitSet { + alts := NewBitSet() + for _, it := range b.configs { + alts.add(it.GetAlt()) + } + return alts +} + +// NewATNConfigSet creates a new ATNConfigSet instance. +func NewATNConfigSet(fullCtx bool) *ATNConfigSet { + return &ATNConfigSet{ + cachedHash: -1, + configLookup: NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfCompInst, ATNConfigLookupCollection, "NewATNConfigSet()"), + fullCtx: fullCtx, + } +} + +// Add merges contexts with existing configs for (s, i, pi, _), +// where 's' is the ATNConfig.state, 'i' is the ATNConfig.alt, and +// 'pi' is the [ATNConfig].semanticContext. +// +// We use (s,i,pi) as the key. +// Updates dipsIntoOuterContext and hasSemanticContext when necessary. +func (b *ATNConfigSet) Add(config *ATNConfig, mergeCache *JPCMap) bool { + if b.readOnly { + panic("set is read-only") + } + + if config.GetSemanticContext() != SemanticContextNone { + b.hasSemanticContext = true + } + + if config.GetReachesIntoOuterContext() > 0 { + b.dipsIntoOuterContext = true + } + + existing, present := b.configLookup.Put(config) + + // The config was not already in the set + // + if !present { + b.cachedHash = -1 + b.configs = append(b.configs, config) // Track order here + return true + } + + // Merge a previous (s, i, pi, _) with it and save the result + rootIsWildcard := !b.fullCtx + merged := merge(existing.GetContext(), config.GetContext(), rootIsWildcard, mergeCache) + + // No need to check for existing.context because config.context is in the cache, + // since the only way to create new graphs is the "call rule" and here. We cache + // at both places. + existing.SetReachesIntoOuterContext(intMax(existing.GetReachesIntoOuterContext(), config.GetReachesIntoOuterContext())) + + // Preserve the precedence filter suppression during the merge + if config.getPrecedenceFilterSuppressed() { + existing.setPrecedenceFilterSuppressed(true) + } + + // Replace the context because there is no need to do alt mapping + existing.SetContext(merged) + + return true +} + +// GetStates returns the set of states represented by all configurations in this config set +func (b *ATNConfigSet) GetStates() *JStore[ATNState, Comparator[ATNState]] { + + // states uses the standard comparator and Hash() provided by the ATNState instance + // + states := NewJStore[ATNState, Comparator[ATNState]](aStateEqInst, ATNStateCollection, "ATNConfigSet.GetStates()") + + for i := 0; i < len(b.configs); i++ { + states.Put(b.configs[i].GetState()) + } + + return states +} + +func (b *ATNConfigSet) GetPredicates() []SemanticContext { + predicates := make([]SemanticContext, 0) + + for i := 0; i < len(b.configs); i++ { + c := b.configs[i].GetSemanticContext() + + if c != SemanticContextNone { + predicates = append(predicates, c) + } + } + + return predicates +} + +func (b *ATNConfigSet) OptimizeConfigs(interpreter *BaseATNSimulator) { + if b.readOnly { + panic("set is read-only") + } + + // Empty indicate no optimization is possible + if b.configLookup == nil || b.configLookup.Len() == 0 { + return + } + + for i := 0; i < len(b.configs); i++ { + config := b.configs[i] + config.SetContext(interpreter.getCachedContext(config.GetContext())) + } +} + +func (b *ATNConfigSet) AddAll(coll []*ATNConfig) bool { + for i := 0; i < len(coll); i++ { + b.Add(coll[i], nil) + } + + return false +} + +// Compare The configs are only equal if they are in the same order and their Equals function returns true. +// Java uses ArrayList.equals(), which requires the same order. +func (b *ATNConfigSet) Compare(bs *ATNConfigSet) bool { + if len(b.configs) != len(bs.configs) { + return false + } + for i := 0; i < len(b.configs); i++ { + if !b.configs[i].Equals(bs.configs[i]) { + return false + } + } + + return true +} + +func (b *ATNConfigSet) Equals(other Collectable[ATNConfig]) bool { + if b == other { + return true + } else if _, ok := other.(*ATNConfigSet); !ok { + return false + } + + other2 := other.(*ATNConfigSet) + var eca bool + switch { + case b.conflictingAlts == nil && other2.conflictingAlts == nil: + eca = true + case b.conflictingAlts != nil && other2.conflictingAlts != nil: + eca = b.conflictingAlts.equals(other2.conflictingAlts) + } + return b.configs != nil && + b.fullCtx == other2.fullCtx && + b.uniqueAlt == other2.uniqueAlt && + eca && + b.hasSemanticContext == other2.hasSemanticContext && + b.dipsIntoOuterContext == other2.dipsIntoOuterContext && + b.Compare(other2) +} + +func (b *ATNConfigSet) Hash() int { + if b.readOnly { + if b.cachedHash == -1 { + b.cachedHash = b.hashCodeConfigs() + } + + return b.cachedHash + } + + return b.hashCodeConfigs() +} + +func (b *ATNConfigSet) hashCodeConfigs() int { + h := 1 + for _, config := range b.configs { + h = 31*h + config.Hash() + } + return h +} + +func (b *ATNConfigSet) Contains(item *ATNConfig) bool { + if b.readOnly { + panic("not implemented for read-only sets") + } + if b.configLookup == nil { + return false + } + return b.configLookup.Contains(item) +} + +func (b *ATNConfigSet) ContainsFast(item *ATNConfig) bool { + return b.Contains(item) +} + +func (b *ATNConfigSet) Clear() { + if b.readOnly { + panic("set is read-only") + } + b.configs = make([]*ATNConfig, 0) + b.cachedHash = -1 + b.configLookup = NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfCompInst, ATNConfigLookupCollection, "NewATNConfigSet()") +} + +func (b *ATNConfigSet) String() string { + + s := "[" + + for i, c := range b.configs { + s += c.String() + + if i != len(b.configs)-1 { + s += ", " + } + } + + s += "]" + + if b.hasSemanticContext { + s += ",hasSemanticContext=" + fmt.Sprint(b.hasSemanticContext) + } + + if b.uniqueAlt != ATNInvalidAltNumber { + s += ",uniqueAlt=" + fmt.Sprint(b.uniqueAlt) + } + + if b.conflictingAlts != nil { + s += ",conflictingAlts=" + b.conflictingAlts.String() + } + + if b.dipsIntoOuterContext { + s += ",dipsIntoOuterContext" + } + + return s +} + +// NewOrderedATNConfigSet creates a config set with a slightly different Hash/Equal pair +// for use in lexers. +func NewOrderedATNConfigSet() *ATNConfigSet { + return &ATNConfigSet{ + cachedHash: -1, + // This set uses the standard Hash() and Equals() from ATNConfig + configLookup: NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ATNConfigCollection, "ATNConfigSet.NewOrderedATNConfigSet()"), + fullCtx: false, + } +} diff --git a/prutalgen/internal/antlr/atn_deserialization_options.go b/prutalgen/internal/antlr/atn_deserialization_options.go new file mode 100644 index 0000000..bdb30b3 --- /dev/null +++ b/prutalgen/internal/antlr/atn_deserialization_options.go @@ -0,0 +1,62 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import "errors" + +var defaultATNDeserializationOptions = ATNDeserializationOptions{true, true, false} + +type ATNDeserializationOptions struct { + readOnly bool + verifyATN bool + generateRuleBypassTransitions bool +} + +func (opts *ATNDeserializationOptions) ReadOnly() bool { + return opts.readOnly +} + +func (opts *ATNDeserializationOptions) SetReadOnly(readOnly bool) { + if opts.readOnly { + panic(errors.New("cannot mutate read only ATNDeserializationOptions")) + } + opts.readOnly = readOnly +} + +func (opts *ATNDeserializationOptions) VerifyATN() bool { + return opts.verifyATN +} + +func (opts *ATNDeserializationOptions) SetVerifyATN(verifyATN bool) { + if opts.readOnly { + panic(errors.New("cannot mutate read only ATNDeserializationOptions")) + } + opts.verifyATN = verifyATN +} + +func (opts *ATNDeserializationOptions) GenerateRuleBypassTransitions() bool { + return opts.generateRuleBypassTransitions +} + +func (opts *ATNDeserializationOptions) SetGenerateRuleBypassTransitions(generateRuleBypassTransitions bool) { + if opts.readOnly { + panic(errors.New("cannot mutate read only ATNDeserializationOptions")) + } + opts.generateRuleBypassTransitions = generateRuleBypassTransitions +} + +//goland:noinspection GoUnusedExportedFunction +func DefaultATNDeserializationOptions() *ATNDeserializationOptions { + return NewATNDeserializationOptions(&defaultATNDeserializationOptions) +} + +func NewATNDeserializationOptions(other *ATNDeserializationOptions) *ATNDeserializationOptions { + o := new(ATNDeserializationOptions) + if other != nil { + *o = *other + o.readOnly = false + } + return o +} diff --git a/prutalgen/internal/antlr/atn_deserializer.go b/prutalgen/internal/antlr/atn_deserializer.go new file mode 100644 index 0000000..2dcb9ae --- /dev/null +++ b/prutalgen/internal/antlr/atn_deserializer.go @@ -0,0 +1,684 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" +) + +const serializedVersion = 4 + +type loopEndStateIntPair struct { + item0 *LoopEndState + item1 int +} + +type blockStartStateIntPair struct { + item0 BlockStartState + item1 int +} + +type ATNDeserializer struct { + options *ATNDeserializationOptions + data []int32 + pos int +} + +func NewATNDeserializer(options *ATNDeserializationOptions) *ATNDeserializer { + if options == nil { + options = &defaultATNDeserializationOptions + } + + return &ATNDeserializer{options: options} +} + +//goland:noinspection GoUnusedFunction +func stringInSlice(a string, list []string) int { + for i, b := range list { + if b == a { + return i + } + } + + return -1 +} + +func (a *ATNDeserializer) Deserialize(data []int32) *ATN { + a.data = data + a.pos = 0 + a.checkVersion() + + atn := a.readATN() + + a.readStates(atn) + a.readRules(atn) + a.readModes(atn) + + sets := a.readSets(atn, nil) + + a.readEdges(atn, sets) + a.readDecisions(atn) + a.readLexerActions(atn) + a.markPrecedenceDecisions(atn) + a.verifyATN(atn) + + if a.options.GenerateRuleBypassTransitions() && atn.grammarType == ATNTypeParser { + a.generateRuleBypassTransitions(atn) + // Re-verify after modification + a.verifyATN(atn) + } + + return atn + +} + +func (a *ATNDeserializer) checkVersion() { + version := a.readInt() + + if version != serializedVersion { + panic("Could not deserialize ATN with version " + strconv.Itoa(version) + " (expected " + strconv.Itoa(serializedVersion) + ").") + } +} + +func (a *ATNDeserializer) readATN() *ATN { + grammarType := a.readInt() + maxTokenType := a.readInt() + + return NewATN(grammarType, maxTokenType) +} + +func (a *ATNDeserializer) readStates(atn *ATN) { + nstates := a.readInt() + + // Allocate worst case size. + loopBackStateNumbers := make([]loopEndStateIntPair, 0, nstates) + endStateNumbers := make([]blockStartStateIntPair, 0, nstates) + + // Preallocate states slice. + atn.states = make([]ATNState, 0, nstates) + + for i := 0; i < nstates; i++ { + stype := a.readInt() + + // Ignore bad types of states + if stype == ATNStateInvalidType { + atn.addState(nil) + continue + } + + ruleIndex := a.readInt() + + s := a.stateFactory(stype, ruleIndex) + + if stype == ATNStateLoopEnd { + loopBackStateNumber := a.readInt() + + loopBackStateNumbers = append(loopBackStateNumbers, loopEndStateIntPair{s.(*LoopEndState), loopBackStateNumber}) + } else if s2, ok := s.(BlockStartState); ok { + endStateNumber := a.readInt() + + endStateNumbers = append(endStateNumbers, blockStartStateIntPair{s2, endStateNumber}) + } + + atn.addState(s) + } + + // Delay the assignment of loop back and end states until we know all the state + // instances have been initialized + for _, pair := range loopBackStateNumbers { + pair.item0.loopBackState = atn.states[pair.item1] + } + + for _, pair := range endStateNumbers { + pair.item0.setEndState(atn.states[pair.item1].(*BlockEndState)) + } + + numNonGreedyStates := a.readInt() + for j := 0; j < numNonGreedyStates; j++ { + stateNumber := a.readInt() + + atn.states[stateNumber].(DecisionState).setNonGreedy(true) + } + + numPrecedenceStates := a.readInt() + for j := 0; j < numPrecedenceStates; j++ { + stateNumber := a.readInt() + + atn.states[stateNumber].(*RuleStartState).isPrecedenceRule = true + } +} + +func (a *ATNDeserializer) readRules(atn *ATN) { + nrules := a.readInt() + + if atn.grammarType == ATNTypeLexer { + atn.ruleToTokenType = make([]int, nrules) + } + + atn.ruleToStartState = make([]*RuleStartState, nrules) + + for i := range atn.ruleToStartState { + s := a.readInt() + startState := atn.states[s].(*RuleStartState) + + atn.ruleToStartState[i] = startState + + if atn.grammarType == ATNTypeLexer { + tokenType := a.readInt() + + atn.ruleToTokenType[i] = tokenType + } + } + + atn.ruleToStopState = make([]*RuleStopState, nrules) + + for _, state := range atn.states { + if s2, ok := state.(*RuleStopState); ok { + atn.ruleToStopState[s2.ruleIndex] = s2 + atn.ruleToStartState[s2.ruleIndex].stopState = s2 + } + } +} + +func (a *ATNDeserializer) readModes(atn *ATN) { + nmodes := a.readInt() + atn.modeToStartState = make([]*TokensStartState, nmodes) + + for i := range atn.modeToStartState { + s := a.readInt() + + atn.modeToStartState[i] = atn.states[s].(*TokensStartState) + } +} + +func (a *ATNDeserializer) readSets(_ *ATN, sets []*IntervalSet) []*IntervalSet { + m := a.readInt() + + // Preallocate the needed capacity. + if cap(sets)-len(sets) < m { + isets := make([]*IntervalSet, len(sets), len(sets)+m) + copy(isets, sets) + sets = isets + } + + for i := 0; i < m; i++ { + iset := NewIntervalSet() + + sets = append(sets, iset) + + n := a.readInt() + containsEOF := a.readInt() + + if containsEOF != 0 { + iset.addOne(-1) + } + + for j := 0; j < n; j++ { + i1 := a.readInt() + i2 := a.readInt() + + iset.addRange(i1, i2) + } + } + + return sets +} + +func (a *ATNDeserializer) readEdges(atn *ATN, sets []*IntervalSet) { + nedges := a.readInt() + + for i := 0; i < nedges; i++ { + var ( + src = a.readInt() + trg = a.readInt() + ttype = a.readInt() + arg1 = a.readInt() + arg2 = a.readInt() + arg3 = a.readInt() + trans = a.edgeFactory(atn, ttype, src, trg, arg1, arg2, arg3, sets) + srcState = atn.states[src] + ) + + srcState.AddTransition(trans, -1) + } + + // Edges for rule stop states can be derived, so they are not serialized + for _, state := range atn.states { + for _, t := range state.GetTransitions() { + var rt, ok = t.(*RuleTransition) + + if !ok { + continue + } + + outermostPrecedenceReturn := -1 + + if atn.ruleToStartState[rt.getTarget().GetRuleIndex()].isPrecedenceRule { + if rt.precedence == 0 { + outermostPrecedenceReturn = rt.getTarget().GetRuleIndex() + } + } + + trans := NewEpsilonTransition(rt.followState, outermostPrecedenceReturn) + + atn.ruleToStopState[rt.getTarget().GetRuleIndex()].AddTransition(trans, -1) + } + } + + for _, state := range atn.states { + if s2, ok := state.(BlockStartState); ok { + // We need to know the end state to set its start state + if s2.getEndState() == nil { + panic("IllegalState") + } + + // Block end states can only be associated to a single block start state + if s2.getEndState().startState != nil { + panic("IllegalState") + } + + s2.getEndState().startState = state + } + + if s2, ok := state.(*PlusLoopbackState); ok { + for _, t := range s2.GetTransitions() { + if t2, ok := t.getTarget().(*PlusBlockStartState); ok { + t2.loopBackState = state + } + } + } else if s2, ok := state.(*StarLoopbackState); ok { + for _, t := range s2.GetTransitions() { + if t2, ok := t.getTarget().(*StarLoopEntryState); ok { + t2.loopBackState = state + } + } + } + } +} + +func (a *ATNDeserializer) readDecisions(atn *ATN) { + ndecisions := a.readInt() + + for i := 0; i < ndecisions; i++ { + s := a.readInt() + decState := atn.states[s].(DecisionState) + + atn.DecisionToState = append(atn.DecisionToState, decState) + decState.setDecision(i) + } +} + +func (a *ATNDeserializer) readLexerActions(atn *ATN) { + if atn.grammarType == ATNTypeLexer { + count := a.readInt() + + atn.lexerActions = make([]LexerAction, count) + + for i := range atn.lexerActions { + actionType := a.readInt() + data1 := a.readInt() + data2 := a.readInt() + atn.lexerActions[i] = a.lexerActionFactory(actionType, data1, data2) + } + } +} + +func (a *ATNDeserializer) generateRuleBypassTransitions(atn *ATN) { + count := len(atn.ruleToStartState) + + for i := 0; i < count; i++ { + atn.ruleToTokenType[i] = atn.maxTokenType + i + 1 + } + + for i := 0; i < count; i++ { + a.generateRuleBypassTransition(atn, i) + } +} + +func (a *ATNDeserializer) generateRuleBypassTransition(atn *ATN, idx int) { + bypassStart := NewBasicBlockStartState() + + bypassStart.ruleIndex = idx + atn.addState(bypassStart) + + bypassStop := NewBlockEndState() + + bypassStop.ruleIndex = idx + atn.addState(bypassStop) + + bypassStart.endState = bypassStop + + atn.defineDecisionState(&bypassStart.BaseDecisionState) + + bypassStop.startState = bypassStart + + var excludeTransition Transition + var endState ATNState + + if atn.ruleToStartState[idx].isPrecedenceRule { + // Wrap from the beginning of the rule to the StarLoopEntryState + endState = nil + + for i := 0; i < len(atn.states); i++ { + state := atn.states[i] + + if a.stateIsEndStateFor(state, idx) != nil { + endState = state + excludeTransition = state.(*StarLoopEntryState).loopBackState.GetTransitions()[0] + + break + } + } + + if excludeTransition == nil { + panic("Couldn't identify final state of the precedence rule prefix section.") + } + } else { + endState = atn.ruleToStopState[idx] + } + + // All non-excluded transitions that currently target end state need to target + // blockEnd instead + for i := 0; i < len(atn.states); i++ { + state := atn.states[i] + + for j := 0; j < len(state.GetTransitions()); j++ { + transition := state.GetTransitions()[j] + + if transition == excludeTransition { + continue + } + + if transition.getTarget() == endState { + transition.setTarget(bypassStop) + } + } + } + + // All transitions leaving the rule start state need to leave blockStart instead + ruleToStartState := atn.ruleToStartState[idx] + count := len(ruleToStartState.GetTransitions()) + + for count > 0 { + bypassStart.AddTransition(ruleToStartState.GetTransitions()[count-1], -1) + ruleToStartState.SetTransitions([]Transition{ruleToStartState.GetTransitions()[len(ruleToStartState.GetTransitions())-1]}) + } + + // Link the new states + atn.ruleToStartState[idx].AddTransition(NewEpsilonTransition(bypassStart, -1), -1) + bypassStop.AddTransition(NewEpsilonTransition(endState, -1), -1) + + MatchState := NewBasicState() + + atn.addState(MatchState) + MatchState.AddTransition(NewAtomTransition(bypassStop, atn.ruleToTokenType[idx]), -1) + bypassStart.AddTransition(NewEpsilonTransition(MatchState, -1), -1) +} + +func (a *ATNDeserializer) stateIsEndStateFor(state ATNState, idx int) ATNState { + if state.GetRuleIndex() != idx { + return nil + } + + if _, ok := state.(*StarLoopEntryState); !ok { + return nil + } + + maybeLoopEndState := state.GetTransitions()[len(state.GetTransitions())-1].getTarget() + + if _, ok := maybeLoopEndState.(*LoopEndState); !ok { + return nil + } + + var _, ok = maybeLoopEndState.GetTransitions()[0].getTarget().(*RuleStopState) + + if maybeLoopEndState.(*LoopEndState).epsilonOnlyTransitions && ok { + return state + } + + return nil +} + +// markPrecedenceDecisions analyzes the StarLoopEntryState states in the +// specified ATN to set the StarLoopEntryState.precedenceRuleDecision field to +// the correct value. +func (a *ATNDeserializer) markPrecedenceDecisions(atn *ATN) { + for _, state := range atn.states { + if _, ok := state.(*StarLoopEntryState); !ok { + continue + } + + // We analyze the [ATN] to determine if an ATN decision state is the + // decision for the closure block that determines whether a + // precedence rule should continue or complete. + if atn.ruleToStartState[state.GetRuleIndex()].isPrecedenceRule { + maybeLoopEndState := state.GetTransitions()[len(state.GetTransitions())-1].getTarget() + + if s3, ok := maybeLoopEndState.(*LoopEndState); ok { + var _, ok2 = maybeLoopEndState.GetTransitions()[0].getTarget().(*RuleStopState) + + if s3.epsilonOnlyTransitions && ok2 { + state.(*StarLoopEntryState).precedenceRuleDecision = true + } + } + } + } +} + +func (a *ATNDeserializer) verifyATN(atn *ATN) { + if !a.options.VerifyATN() { + return + } + + // Verify assumptions + for _, state := range atn.states { + if state == nil { + continue + } + + a.checkCondition(state.GetEpsilonOnlyTransitions() || len(state.GetTransitions()) <= 1, "") + + switch s2 := state.(type) { + case *PlusBlockStartState: + a.checkCondition(s2.loopBackState != nil, "") + + case *StarLoopEntryState: + a.checkCondition(s2.loopBackState != nil, "") + a.checkCondition(len(s2.GetTransitions()) == 2, "") + + switch s2.transitions[0].getTarget().(type) { + case *StarBlockStartState: + _, ok := s2.transitions[1].getTarget().(*LoopEndState) + + a.checkCondition(ok, "") + a.checkCondition(!s2.nonGreedy, "") + + case *LoopEndState: + var _, ok = s2.transitions[1].getTarget().(*StarBlockStartState) + + a.checkCondition(ok, "") + a.checkCondition(s2.nonGreedy, "") + + default: + panic("IllegalState") + } + + case *StarLoopbackState: + a.checkCondition(len(state.GetTransitions()) == 1, "") + + var _, ok = state.GetTransitions()[0].getTarget().(*StarLoopEntryState) + + a.checkCondition(ok, "") + + case *LoopEndState: + a.checkCondition(s2.loopBackState != nil, "") + + case *RuleStartState: + a.checkCondition(s2.stopState != nil, "") + + case BlockStartState: + a.checkCondition(s2.getEndState() != nil, "") + + case *BlockEndState: + a.checkCondition(s2.startState != nil, "") + + case DecisionState: + a.checkCondition(len(s2.GetTransitions()) <= 1 || s2.getDecision() >= 0, "") + + default: + var _, ok = s2.(*RuleStopState) + + a.checkCondition(len(s2.GetTransitions()) <= 1 || ok, "") + } + } +} + +func (a *ATNDeserializer) checkCondition(condition bool, message string) { + if !condition { + if message == "" { + message = "IllegalState" + } + + panic(message) + } +} + +func (a *ATNDeserializer) readInt() int { + v := a.data[a.pos] + + a.pos++ + + return int(v) // data is 32 bits but int is at least that big +} + +func (a *ATNDeserializer) edgeFactory(atn *ATN, typeIndex, _, trg, arg1, arg2, arg3 int, sets []*IntervalSet) Transition { + target := atn.states[trg] + + switch typeIndex { + case TransitionEPSILON: + return NewEpsilonTransition(target, -1) + + case TransitionRANGE: + if arg3 != 0 { + return NewRangeTransition(target, TokenEOF, arg2) + } + + return NewRangeTransition(target, arg1, arg2) + + case TransitionRULE: + return NewRuleTransition(atn.states[arg1], arg2, arg3, target) + + case TransitionPREDICATE: + return NewPredicateTransition(target, arg1, arg2, arg3 != 0) + + case TransitionPRECEDENCE: + return NewPrecedencePredicateTransition(target, arg1) + + case TransitionATOM: + if arg3 != 0 { + return NewAtomTransition(target, TokenEOF) + } + + return NewAtomTransition(target, arg1) + + case TransitionACTION: + return NewActionTransition(target, arg1, arg2, arg3 != 0) + + case TransitionSET: + return NewSetTransition(target, sets[arg1]) + + case TransitionNOTSET: + return NewNotSetTransition(target, sets[arg1]) + + case TransitionWILDCARD: + return NewWildcardTransition(target) + } + + panic("The specified transition type is not valid.") +} + +func (a *ATNDeserializer) stateFactory(typeIndex, ruleIndex int) ATNState { + var s ATNState + + switch typeIndex { + case ATNStateInvalidType: + return nil + + case ATNStateBasic: + s = NewBasicState() + + case ATNStateRuleStart: + s = NewRuleStartState() + + case ATNStateBlockStart: + s = NewBasicBlockStartState() + + case ATNStatePlusBlockStart: + s = NewPlusBlockStartState() + + case ATNStateStarBlockStart: + s = NewStarBlockStartState() + + case ATNStateTokenStart: + s = NewTokensStartState() + + case ATNStateRuleStop: + s = NewRuleStopState() + + case ATNStateBlockEnd: + s = NewBlockEndState() + + case ATNStateStarLoopBack: + s = NewStarLoopbackState() + + case ATNStateStarLoopEntry: + s = NewStarLoopEntryState() + + case ATNStatePlusLoopBack: + s = NewPlusLoopbackState() + + case ATNStateLoopEnd: + s = NewLoopEndState() + + default: + panic(fmt.Sprintf("state type %d is invalid", typeIndex)) + } + + s.SetRuleIndex(ruleIndex) + + return s +} + +func (a *ATNDeserializer) lexerActionFactory(typeIndex, data1, data2 int) LexerAction { + switch typeIndex { + case LexerActionTypeChannel: + return NewLexerChannelAction(data1) + + case LexerActionTypeCustom: + return NewLexerCustomAction(data1, data2) + + case LexerActionTypeMode: + return NewLexerModeAction(data1) + + case LexerActionTypeMore: + return LexerMoreActionINSTANCE + + case LexerActionTypePopMode: + return LexerPopModeActionINSTANCE + + case LexerActionTypePushMode: + return NewLexerPushModeAction(data1) + + case LexerActionTypeSkip: + return LexerSkipActionINSTANCE + + case LexerActionTypeType: + return NewLexerTypeAction(data1) + + default: + panic(fmt.Sprintf("lexer action %d is invalid", typeIndex)) + } +} diff --git a/prutalgen/internal/antlr/atn_simulator.go b/prutalgen/internal/antlr/atn_simulator.go new file mode 100644 index 0000000..afe6c9f --- /dev/null +++ b/prutalgen/internal/antlr/atn_simulator.go @@ -0,0 +1,41 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +var ATNSimulatorError = NewDFAState(0x7FFFFFFF, NewATNConfigSet(false)) + +type IATNSimulator interface { + SharedContextCache() *PredictionContextCache + ATN() *ATN + DecisionToDFA() []*DFA +} + +type BaseATNSimulator struct { + atn *ATN + sharedContextCache *PredictionContextCache + decisionToDFA []*DFA +} + +func (b *BaseATNSimulator) getCachedContext(context *PredictionContext) *PredictionContext { + if b.sharedContextCache == nil { + return context + } + + //visited := NewJMap[*PredictionContext, *PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionVisitedCollection, "Visit map in getCachedContext()") + visited := NewVisitRecord() + return getCachedBasePredictionContext(context, b.sharedContextCache, visited) +} + +func (b *BaseATNSimulator) SharedContextCache() *PredictionContextCache { + return b.sharedContextCache +} + +func (b *BaseATNSimulator) ATN() *ATN { + return b.atn +} + +func (b *BaseATNSimulator) DecisionToDFA() []*DFA { + return b.decisionToDFA +} diff --git a/prutalgen/internal/antlr/atn_state.go b/prutalgen/internal/antlr/atn_state.go new file mode 100644 index 0000000..2ae5807 --- /dev/null +++ b/prutalgen/internal/antlr/atn_state.go @@ -0,0 +1,461 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "os" + "strconv" +) + +// Constants for serialization. +const ( + ATNStateInvalidType = 0 + ATNStateBasic = 1 + ATNStateRuleStart = 2 + ATNStateBlockStart = 3 + ATNStatePlusBlockStart = 4 + ATNStateStarBlockStart = 5 + ATNStateTokenStart = 6 + ATNStateRuleStop = 7 + ATNStateBlockEnd = 8 + ATNStateStarLoopBack = 9 + ATNStateStarLoopEntry = 10 + ATNStatePlusLoopBack = 11 + ATNStateLoopEnd = 12 + + ATNStateInvalidStateNumber = -1 +) + +//goland:noinspection GoUnusedGlobalVariable +var ATNStateInitialNumTransitions = 4 + +type ATNState interface { + GetEpsilonOnlyTransitions() bool + + GetRuleIndex() int + SetRuleIndex(int) + + GetNextTokenWithinRule() *IntervalSet + SetNextTokenWithinRule(*IntervalSet) + + GetATN() *ATN + SetATN(*ATN) + + GetStateType() int + + GetStateNumber() int + SetStateNumber(int) + + GetTransitions() []Transition + SetTransitions([]Transition) + AddTransition(Transition, int) + + String() string + Hash() int + Equals(Collectable[ATNState]) bool +} + +type BaseATNState struct { + // NextTokenWithinRule caches lookahead during parsing. Not used during construction. + NextTokenWithinRule *IntervalSet + + // atn is the current ATN. + atn *ATN + + epsilonOnlyTransitions bool + + // ruleIndex tracks the Rule index because there are no Rule objects at runtime. + ruleIndex int + + stateNumber int + + stateType int + + // Track the transitions emanating from this ATN state. + transitions []Transition +} + +func NewATNState() *BaseATNState { + return &BaseATNState{stateNumber: ATNStateInvalidStateNumber, stateType: ATNStateInvalidType} +} + +func (as *BaseATNState) GetRuleIndex() int { + return as.ruleIndex +} + +func (as *BaseATNState) SetRuleIndex(v int) { + as.ruleIndex = v +} +func (as *BaseATNState) GetEpsilonOnlyTransitions() bool { + return as.epsilonOnlyTransitions +} + +func (as *BaseATNState) GetATN() *ATN { + return as.atn +} + +func (as *BaseATNState) SetATN(atn *ATN) { + as.atn = atn +} + +func (as *BaseATNState) GetTransitions() []Transition { + return as.transitions +} + +func (as *BaseATNState) SetTransitions(t []Transition) { + as.transitions = t +} + +func (as *BaseATNState) GetStateType() int { + return as.stateType +} + +func (as *BaseATNState) GetStateNumber() int { + return as.stateNumber +} + +func (as *BaseATNState) SetStateNumber(stateNumber int) { + as.stateNumber = stateNumber +} + +func (as *BaseATNState) GetNextTokenWithinRule() *IntervalSet { + return as.NextTokenWithinRule +} + +func (as *BaseATNState) SetNextTokenWithinRule(v *IntervalSet) { + as.NextTokenWithinRule = v +} + +func (as *BaseATNState) Hash() int { + return as.stateNumber +} + +func (as *BaseATNState) String() string { + return strconv.Itoa(as.stateNumber) +} + +func (as *BaseATNState) Equals(other Collectable[ATNState]) bool { + if ot, ok := other.(ATNState); ok { + return as.stateNumber == ot.GetStateNumber() + } + + return false +} + +func (as *BaseATNState) isNonGreedyExitState() bool { + return false +} + +func (as *BaseATNState) AddTransition(trans Transition, index int) { + if len(as.transitions) == 0 { + as.epsilonOnlyTransitions = trans.getIsEpsilon() + } else if as.epsilonOnlyTransitions != trans.getIsEpsilon() { + _, _ = fmt.Fprintf(os.Stdin, "ATN state %d has both epsilon and non-epsilon transitions.\n", as.stateNumber) + as.epsilonOnlyTransitions = false + } + + // TODO: Check code for already present compared to the Java equivalent + //alreadyPresent := false + //for _, t := range as.transitions { + // if t.getTarget().GetStateNumber() == trans.getTarget().GetStateNumber() { + // if t.getLabel() != nil && trans.getLabel() != nil && trans.getLabel().Equals(t.getLabel()) { + // alreadyPresent = true + // break + // } + // } else if t.getIsEpsilon() && trans.getIsEpsilon() { + // alreadyPresent = true + // break + // } + //} + //if !alreadyPresent { + if index == -1 { + as.transitions = append(as.transitions, trans) + } else { + as.transitions = append(as.transitions[:index], append([]Transition{trans}, as.transitions[index:]...)...) + // TODO: as.transitions.splice(index, 1, trans) + } + //} else { + // _, _ = fmt.Fprintf(os.Stderr, "Transition already present in state %d\n", as.stateNumber) + //} +} + +type BasicState struct { + BaseATNState +} + +func NewBasicState() *BasicState { + return &BasicState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateBasic, + }, + } +} + +type DecisionState interface { + ATNState + + getDecision() int + setDecision(int) + + getNonGreedy() bool + setNonGreedy(bool) +} + +type BaseDecisionState struct { + BaseATNState + decision int + nonGreedy bool +} + +func NewBaseDecisionState() *BaseDecisionState { + return &BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateBasic, + }, + decision: -1, + } +} + +func (s *BaseDecisionState) getDecision() int { + return s.decision +} + +func (s *BaseDecisionState) setDecision(b int) { + s.decision = b +} + +func (s *BaseDecisionState) getNonGreedy() bool { + return s.nonGreedy +} + +func (s *BaseDecisionState) setNonGreedy(b bool) { + s.nonGreedy = b +} + +type BlockStartState interface { + DecisionState + + getEndState() *BlockEndState + setEndState(*BlockEndState) +} + +// BaseBlockStartState is the start of a regular (...) block. +type BaseBlockStartState struct { + BaseDecisionState + endState *BlockEndState +} + +func NewBlockStartState() *BaseBlockStartState { + return &BaseBlockStartState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateBasic, + }, + decision: -1, + }, + } +} + +func (s *BaseBlockStartState) getEndState() *BlockEndState { + return s.endState +} + +func (s *BaseBlockStartState) setEndState(b *BlockEndState) { + s.endState = b +} + +type BasicBlockStartState struct { + BaseBlockStartState +} + +func NewBasicBlockStartState() *BasicBlockStartState { + return &BasicBlockStartState{ + BaseBlockStartState: BaseBlockStartState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateBlockStart, + }, + }, + }, + } +} + +var _ BlockStartState = &BasicBlockStartState{} + +// BlockEndState is a terminal node of a simple (a|b|c) block. +type BlockEndState struct { + BaseATNState + startState ATNState +} + +func NewBlockEndState() *BlockEndState { + return &BlockEndState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateBlockEnd, + }, + startState: nil, + } +} + +// RuleStopState is the last node in the ATN for a rule, unless that rule is the +// start symbol. In that case, there is one transition to EOF. Later, we might +// encode references to all calls to this rule to compute FOLLOW sets for error +// handling. +type RuleStopState struct { + BaseATNState +} + +func NewRuleStopState() *RuleStopState { + return &RuleStopState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateRuleStop, + }, + } +} + +type RuleStartState struct { + BaseATNState + stopState ATNState + isPrecedenceRule bool +} + +func NewRuleStartState() *RuleStartState { + return &RuleStartState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateRuleStart, + }, + } +} + +// PlusLoopbackState is a decision state for A+ and (A|B)+. It has two +// transitions: one to the loop back to start of the block, and one to exit. +type PlusLoopbackState struct { + BaseDecisionState +} + +func NewPlusLoopbackState() *PlusLoopbackState { + return &PlusLoopbackState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStatePlusLoopBack, + }, + }, + } +} + +// PlusBlockStartState is the start of a (A|B|...)+ loop. Technically it is a +// decision state; we don't use it for code generation. Somebody might need it, +// it is included for completeness. In reality, PlusLoopbackState is the real +// decision-making node for A+. +type PlusBlockStartState struct { + BaseBlockStartState + loopBackState ATNState +} + +func NewPlusBlockStartState() *PlusBlockStartState { + return &PlusBlockStartState{ + BaseBlockStartState: BaseBlockStartState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStatePlusBlockStart, + }, + }, + }, + } +} + +var _ BlockStartState = &PlusBlockStartState{} + +// StarBlockStartState is the block that begins a closure loop. +type StarBlockStartState struct { + BaseBlockStartState +} + +func NewStarBlockStartState() *StarBlockStartState { + return &StarBlockStartState{ + BaseBlockStartState: BaseBlockStartState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateStarBlockStart, + }, + }, + }, + } +} + +var _ BlockStartState = &StarBlockStartState{} + +type StarLoopbackState struct { + BaseATNState +} + +func NewStarLoopbackState() *StarLoopbackState { + return &StarLoopbackState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateStarLoopBack, + }, + } +} + +type StarLoopEntryState struct { + BaseDecisionState + loopBackState ATNState + precedenceRuleDecision bool +} + +func NewStarLoopEntryState() *StarLoopEntryState { + // False precedenceRuleDecision indicates whether s state can benefit from a precedence DFA during SLL decision making. + return &StarLoopEntryState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateStarLoopEntry, + }, + }, + } +} + +// LoopEndState marks the end of a * or + loop. +type LoopEndState struct { + BaseATNState + loopBackState ATNState +} + +func NewLoopEndState() *LoopEndState { + return &LoopEndState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateLoopEnd, + }, + } +} + +// TokensStartState is the Tokens rule start state linking to each lexer rule start state. +type TokensStartState struct { + BaseDecisionState +} + +func NewTokensStartState() *TokensStartState { + return &TokensStartState{ + BaseDecisionState: BaseDecisionState{ + BaseATNState: BaseATNState{ + stateNumber: ATNStateInvalidStateNumber, + stateType: ATNStateTokenStart, + }, + }, + } +} diff --git a/prutalgen/internal/antlr/atn_type.go b/prutalgen/internal/antlr/atn_type.go new file mode 100644 index 0000000..3a515a1 --- /dev/null +++ b/prutalgen/internal/antlr/atn_type.go @@ -0,0 +1,11 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// Represent the type of recognizer an ATN applies to. +const ( + ATNTypeLexer = 0 + ATNTypeParser = 1 +) diff --git a/prutalgen/internal/antlr/char_stream.go b/prutalgen/internal/antlr/char_stream.go new file mode 100644 index 0000000..bd8127b --- /dev/null +++ b/prutalgen/internal/antlr/char_stream.go @@ -0,0 +1,12 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +type CharStream interface { + IntStream + GetText(int, int) string + GetTextFromTokens(start, end Token) string + GetTextFromInterval(Interval) string +} diff --git a/prutalgen/internal/antlr/common_token_factory.go b/prutalgen/internal/antlr/common_token_factory.go new file mode 100644 index 0000000..1bb0314 --- /dev/null +++ b/prutalgen/internal/antlr/common_token_factory.go @@ -0,0 +1,56 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// TokenFactory creates CommonToken objects. +type TokenFactory interface { + Create(source *TokenSourceCharStreamPair, ttype int, text string, channel, start, stop, line, column int) Token +} + +// CommonTokenFactory is the default TokenFactory implementation. +type CommonTokenFactory struct { + // copyText indicates whether CommonToken.setText should be called after + // constructing tokens to explicitly set the text. This is useful for cases + // where the input stream might not be able to provide arbitrary substrings of + // text from the input after the lexer creates a token (e.g. the + // implementation of CharStream.GetText in UnbufferedCharStream panics an + // UnsupportedOperationException). Explicitly setting the token text allows + // Token.GetText to be called at any time regardless of the input stream + // implementation. + // + // The default value is false to avoid the performance and memory overhead of + // copying text for every token unless explicitly requested. + copyText bool +} + +func NewCommonTokenFactory(copyText bool) *CommonTokenFactory { + return &CommonTokenFactory{copyText: copyText} +} + +// CommonTokenFactoryDEFAULT is the default CommonTokenFactory. It does not +// explicitly copy token text when constructing tokens. +var CommonTokenFactoryDEFAULT = NewCommonTokenFactory(false) + +func (c *CommonTokenFactory) Create(source *TokenSourceCharStreamPair, ttype int, text string, channel, start, stop, line, column int) Token { + t := NewCommonToken(source, ttype, channel, start, stop) + + t.line = line + t.column = column + + if text != "" { + t.SetText(text) + } else if c.copyText && source.charStream != nil { + t.SetText(source.charStream.GetTextFromInterval(NewInterval(start, stop))) + } + + return t +} + +func (c *CommonTokenFactory) createThin(ttype int, text string) Token { + t := NewCommonToken(nil, ttype, TokenDefaultChannel, -1, -1) + t.SetText(text) + + return t +} diff --git a/prutalgen/internal/antlr/common_token_stream.go b/prutalgen/internal/antlr/common_token_stream.go new file mode 100644 index 0000000..b75da9d --- /dev/null +++ b/prutalgen/internal/antlr/common_token_stream.go @@ -0,0 +1,450 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "strconv" +) + +// CommonTokenStream is an implementation of TokenStream that loads tokens from +// a TokenSource on-demand and places the tokens in a buffer to provide access +// to any previous token by index. This token stream ignores the value of +// Token.getChannel. If your parser requires the token stream filter tokens to +// only those on a particular channel, such as Token.DEFAULT_CHANNEL or +// Token.HIDDEN_CHANNEL, use a filtering token stream such a CommonTokenStream. +type CommonTokenStream struct { + channel int + + // fetchedEOF indicates whether the Token.EOF token has been fetched from + // tokenSource and added to tokens. This field improves performance for the + // following cases: + // + // consume: The lookahead check in consume to preven consuming the EOF symbol is + // optimized by checking the values of fetchedEOF and p instead of calling LA. + // + // fetch: The check to prevent adding multiple EOF symbols into tokens is + // trivial with bt field. + fetchedEOF bool + + // index into [tokens] of the current token (next token to consume). + // tokens[p] should be LT(1). It is set to -1 when the stream is first + // constructed or when SetTokenSource is called, indicating that the first token + // has not yet been fetched from the token source. For additional information, + // see the documentation of [IntStream] for a description of initializing methods. + index int + + // tokenSource is the [TokenSource] from which tokens for the bt stream are + // fetched. + tokenSource TokenSource + + // tokens contains all tokens fetched from the token source. The list is considered a + // complete view of the input once fetchedEOF is set to true. + tokens []Token +} + +// NewCommonTokenStream creates a new CommonTokenStream instance using the supplied lexer to produce +// tokens and will pull tokens from the given lexer channel. +func NewCommonTokenStream(lexer Lexer, channel int) *CommonTokenStream { + return &CommonTokenStream{ + channel: channel, + index: -1, + tokenSource: lexer, + tokens: make([]Token, 0), + } +} + +// GetAllTokens returns all tokens currently pulled from the token source. +func (c *CommonTokenStream) GetAllTokens() []Token { + return c.tokens +} + +func (c *CommonTokenStream) Mark() int { + return 0 +} + +func (c *CommonTokenStream) Release(_ int) {} + +func (c *CommonTokenStream) Reset() { + c.fetchedEOF = false + c.tokens = make([]Token, 0) + c.Seek(0) +} + +func (c *CommonTokenStream) Seek(index int) { + c.lazyInit() + c.index = c.adjustSeekIndex(index) +} + +func (c *CommonTokenStream) Get(index int) Token { + c.lazyInit() + + return c.tokens[index] +} + +func (c *CommonTokenStream) Consume() { + SkipEOFCheck := false + + if c.index >= 0 { + if c.fetchedEOF { + // The last token in tokens is EOF. Skip the check if p indexes any fetched. + // token except the last. + SkipEOFCheck = c.index < len(c.tokens)-1 + } else { + // No EOF token in tokens. Skip the check if p indexes a fetched token. + SkipEOFCheck = c.index < len(c.tokens) + } + } else { + // Not yet initialized + SkipEOFCheck = false + } + + if !SkipEOFCheck && c.LA(1) == TokenEOF { + panic("cannot consume EOF") + } + + if c.Sync(c.index + 1) { + c.index = c.adjustSeekIndex(c.index + 1) + } +} + +// Sync makes sure index i in tokens has a token and returns true if a token is +// located at index i and otherwise false. +func (c *CommonTokenStream) Sync(i int) bool { + n := i - len(c.tokens) + 1 // How many more elements do we need? + + if n > 0 { + fetched := c.fetch(n) + return fetched >= n + } + + return true +} + +// fetch adds n elements to buffer and returns the actual number of elements +// added to the buffer. +func (c *CommonTokenStream) fetch(n int) int { + if c.fetchedEOF { + return 0 + } + + for i := 0; i < n; i++ { + t := c.tokenSource.NextToken() + + t.SetTokenIndex(len(c.tokens)) + c.tokens = append(c.tokens, t) + + if t.GetTokenType() == TokenEOF { + c.fetchedEOF = true + + return i + 1 + } + } + + return n +} + +// GetTokens gets all tokens from start to stop inclusive. +func (c *CommonTokenStream) GetTokens(start int, stop int, types *IntervalSet) []Token { + if start < 0 || stop < 0 { + return nil + } + + c.lazyInit() + + subset := make([]Token, 0) + + if stop >= len(c.tokens) { + stop = len(c.tokens) - 1 + } + + for i := start; i < stop; i++ { + t := c.tokens[i] + + if t.GetTokenType() == TokenEOF { + break + } + + if types == nil || types.contains(t.GetTokenType()) { + subset = append(subset, t) + } + } + + return subset +} + +func (c *CommonTokenStream) LA(i int) int { + return c.LT(i).GetTokenType() +} + +func (c *CommonTokenStream) lazyInit() { + if c.index == -1 { + c.setup() + } +} + +func (c *CommonTokenStream) setup() { + c.Sync(0) + c.index = c.adjustSeekIndex(0) +} + +func (c *CommonTokenStream) GetTokenSource() TokenSource { + return c.tokenSource +} + +// SetTokenSource resets the c token stream by setting its token source. +func (c *CommonTokenStream) SetTokenSource(tokenSource TokenSource) { + c.tokenSource = tokenSource + c.tokens = make([]Token, 0) + c.index = -1 + c.fetchedEOF = false +} + +// NextTokenOnChannel returns the index of the next token on channel given a +// starting index. Returns i if tokens[i] is on channel. Returns -1 if there are +// no tokens on channel between 'i' and [TokenEOF]. +func (c *CommonTokenStream) NextTokenOnChannel(i, _ int) int { + c.Sync(i) + + if i >= len(c.tokens) { + return -1 + } + + token := c.tokens[i] + + for token.GetChannel() != c.channel { + if token.GetTokenType() == TokenEOF { + return -1 + } + + i++ + c.Sync(i) + token = c.tokens[i] + } + + return i +} + +// previousTokenOnChannel returns the index of the previous token on channel +// given a starting index. Returns i if tokens[i] is on channel. Returns -1 if +// there are no tokens on channel between i and 0. +func (c *CommonTokenStream) previousTokenOnChannel(i, channel int) int { + for i >= 0 && c.tokens[i].GetChannel() != channel { + i-- + } + + return i +} + +// GetHiddenTokensToRight collects all tokens on a specified channel to the +// right of the current token up until we see a token on DEFAULT_TOKEN_CHANNEL +// or EOF. If channel is -1, it finds any non-default channel token. +func (c *CommonTokenStream) GetHiddenTokensToRight(tokenIndex, channel int) []Token { + c.lazyInit() + + if tokenIndex < 0 || tokenIndex >= len(c.tokens) { + panic(strconv.Itoa(tokenIndex) + " not in 0.." + strconv.Itoa(len(c.tokens)-1)) + } + + nextOnChannel := c.NextTokenOnChannel(tokenIndex+1, LexerDefaultTokenChannel) + from := tokenIndex + 1 + + // If no onChannel to the right, then nextOnChannel == -1, so set 'to' to the last token + var to int + + if nextOnChannel == -1 { + to = len(c.tokens) - 1 + } else { + to = nextOnChannel + } + + return c.filterForChannel(from, to, channel) +} + +// GetHiddenTokensToLeft collects all tokens on channel to the left of the +// current token until we see a token on DEFAULT_TOKEN_CHANNEL. If channel is +// -1, it finds any non default channel token. +func (c *CommonTokenStream) GetHiddenTokensToLeft(tokenIndex, channel int) []Token { + c.lazyInit() + + if tokenIndex < 0 || tokenIndex >= len(c.tokens) { + panic(strconv.Itoa(tokenIndex) + " not in 0.." + strconv.Itoa(len(c.tokens)-1)) + } + + prevOnChannel := c.previousTokenOnChannel(tokenIndex-1, LexerDefaultTokenChannel) + + if prevOnChannel == tokenIndex-1 { + return nil + } + + // If there are none on channel to the left and prevOnChannel == -1 then from = 0 + from := prevOnChannel + 1 + to := tokenIndex - 1 + + return c.filterForChannel(from, to, channel) +} + +func (c *CommonTokenStream) filterForChannel(left, right, channel int) []Token { + hidden := make([]Token, 0) + + for i := left; i < right+1; i++ { + t := c.tokens[i] + + if channel == -1 { + if t.GetChannel() != LexerDefaultTokenChannel { + hidden = append(hidden, t) + } + } else if t.GetChannel() == channel { + hidden = append(hidden, t) + } + } + + if len(hidden) == 0 { + return nil + } + + return hidden +} + +func (c *CommonTokenStream) GetSourceName() string { + return c.tokenSource.GetSourceName() +} + +func (c *CommonTokenStream) Size() int { + return len(c.tokens) +} + +func (c *CommonTokenStream) Index() int { + return c.index +} + +func (c *CommonTokenStream) GetAllText() string { + c.Fill() + return c.GetTextFromInterval(NewInterval(0, len(c.tokens)-1)) +} + +func (c *CommonTokenStream) GetTextFromTokens(start, end Token) string { + if start == nil || end == nil { + return "" + } + + return c.GetTextFromInterval(NewInterval(start.GetTokenIndex(), end.GetTokenIndex())) +} + +func (c *CommonTokenStream) GetTextFromRuleContext(interval RuleContext) string { + return c.GetTextFromInterval(interval.GetSourceInterval()) +} + +func (c *CommonTokenStream) GetTextFromInterval(interval Interval) string { + c.lazyInit() + c.Sync(interval.Stop) + + start := interval.Start + stop := interval.Stop + + if start < 0 || stop < 0 { + return "" + } + + if stop >= len(c.tokens) { + stop = len(c.tokens) - 1 + } + + s := "" + + for i := start; i < stop+1; i++ { + t := c.tokens[i] + + if t.GetTokenType() == TokenEOF { + break + } + + s += t.GetText() + } + + return s +} + +// Fill gets all tokens from the lexer until EOF. +func (c *CommonTokenStream) Fill() { + c.lazyInit() + + for c.fetch(1000) == 1000 { + continue + } +} + +func (c *CommonTokenStream) adjustSeekIndex(i int) int { + return c.NextTokenOnChannel(i, c.channel) +} + +func (c *CommonTokenStream) LB(k int) Token { + if k == 0 || c.index-k < 0 { + return nil + } + + i := c.index + n := 1 + + // Find k good tokens looking backward + for n <= k { + // Skip off-channel tokens + i = c.previousTokenOnChannel(i-1, c.channel) + n++ + } + + if i < 0 { + return nil + } + + return c.tokens[i] +} + +func (c *CommonTokenStream) LT(k int) Token { + c.lazyInit() + + if k == 0 { + return nil + } + + if k < 0 { + return c.LB(-k) + } + + i := c.index + n := 1 // We know tokens[n] is valid + + // Find k good tokens + for n < k { + // Skip off-channel tokens, but make sure to not look past EOF + if c.Sync(i + 1) { + i = c.NextTokenOnChannel(i+1, c.channel) + } + + n++ + } + + return c.tokens[i] +} + +// getNumberOfOnChannelTokens counts EOF once. +func (c *CommonTokenStream) getNumberOfOnChannelTokens() int { + var n int + + c.Fill() + + for i := 0; i < len(c.tokens); i++ { + t := c.tokens[i] + + if t.GetChannel() == c.channel { + n++ + } + + if t.GetTokenType() == TokenEOF { + break + } + } + + return n +} diff --git a/prutalgen/internal/antlr/comparators.go b/prutalgen/internal/antlr/comparators.go new file mode 100644 index 0000000..7467e9b --- /dev/null +++ b/prutalgen/internal/antlr/comparators.go @@ -0,0 +1,150 @@ +package antlr + +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +// This file contains all the implementations of custom comparators used for generic collections when the +// Hash() and Equals() funcs supplied by the struct objects themselves need to be overridden. Normally, we would +// put the comparators in the source file for the struct themselves, but given the organization of this code is +// sorta kinda based upon the Java code, I found it confusing trying to find out which comparator was where and used by +// which instantiation of a collection. For instance, an Array2DHashSet in the Java source, when used with ATNConfig +// collections requires three different comparators depending on what the collection is being used for. Collecting - pun intended - +// all the comparators here, makes it much easier to see which implementation of hash and equals is used by which collection. +// It also makes it easy to verify that the Hash() and Equals() functions marry up with the Java implementations. + +// ObjEqComparator is the equivalent of the Java ObjectEqualityComparator, which is the default instance of +// Equality comparator. We do not have inheritance in Go, only interfaces, so we use generics to enforce some +// type safety and avoid having to implement this for every type that we want to perform comparison on. +// +// This comparator works by using the standard Hash() and Equals() methods of the type T that is being compared. Which +// allows us to use it in any collection instance that does not require a special hash or equals implementation. +type ObjEqComparator[T Collectable[T]] struct{} + +var ( + aStateEqInst = &ObjEqComparator[ATNState]{} + aConfEqInst = &ObjEqComparator[*ATNConfig]{} + + // aConfCompInst is the comparator used for the ATNConfigSet for the configLookup cache + aConfCompInst = &ATNConfigComparator[*ATNConfig]{} + atnConfCompInst = &BaseATNConfigComparator[*ATNConfig]{} + dfaStateEqInst = &ObjEqComparator[*DFAState]{} + semctxEqInst = &ObjEqComparator[SemanticContext]{} + atnAltCfgEqInst = &ATNAltConfigComparator[*ATNConfig]{} + pContextEqInst = &ObjEqComparator[*PredictionContext]{} +) + +// Equals2 delegates to the Equals() method of type T +func (c *ObjEqComparator[T]) Equals2(o1, o2 T) bool { + return o1.Equals(o2) +} + +// Hash1 delegates to the Hash() method of type T +func (c *ObjEqComparator[T]) Hash1(o T) int { + + return o.Hash() +} + +type SemCComparator[T Collectable[T]] struct{} + +// ATNConfigComparator is used as the comparator for the configLookup field of an ATNConfigSet +// and has a custom Equals() and Hash() implementation, because equality is not based on the +// standard Hash() and Equals() methods of the ATNConfig type. +type ATNConfigComparator[T Collectable[T]] struct { +} + +// Equals2 is a custom comparator for ATNConfigs specifically for configLookup +func (c *ATNConfigComparator[T]) Equals2(o1, o2 *ATNConfig) bool { + + // Same pointer, must be equal, even if both nil + // + if o1 == o2 { + return true + + } + + // If either are nil, but not both, then the result is false + // + if o1 == nil || o2 == nil { + return false + } + + return o1.GetState().GetStateNumber() == o2.GetState().GetStateNumber() && + o1.GetAlt() == o2.GetAlt() && + o1.GetSemanticContext().Equals(o2.GetSemanticContext()) +} + +// Hash1 is custom hash implementation for ATNConfigs specifically for configLookup +func (c *ATNConfigComparator[T]) Hash1(o *ATNConfig) int { + + hash := 7 + hash = 31*hash + o.GetState().GetStateNumber() + hash = 31*hash + o.GetAlt() + hash = 31*hash + o.GetSemanticContext().Hash() + return hash +} + +// ATNAltConfigComparator is used as the comparator for mapping configs to Alt Bitsets +type ATNAltConfigComparator[T Collectable[T]] struct { +} + +// Equals2 is a custom comparator for ATNConfigs specifically for configLookup +func (c *ATNAltConfigComparator[T]) Equals2(o1, o2 *ATNConfig) bool { + + // Same pointer, must be equal, even if both nil + // + if o1 == o2 { + return true + + } + + // If either are nil, but not both, then the result is false + // + if o1 == nil || o2 == nil { + return false + } + + return o1.GetState().GetStateNumber() == o2.GetState().GetStateNumber() && + o1.GetContext().Equals(o2.GetContext()) +} + +// Hash1 is custom hash implementation for ATNConfigs specifically for configLookup +func (c *ATNAltConfigComparator[T]) Hash1(o *ATNConfig) int { + h := murmurInit(7) + h = murmurUpdate(h, o.GetState().GetStateNumber()) + h = murmurUpdate(h, o.GetContext().Hash()) + return murmurFinish(h, 2) +} + +// BaseATNConfigComparator is used as the comparator for the configLookup field of a ATNConfigSet +// and has a custom Equals() and Hash() implementation, because equality is not based on the +// standard Hash() and Equals() methods of the ATNConfig type. +type BaseATNConfigComparator[T Collectable[T]] struct { +} + +// Equals2 is a custom comparator for ATNConfigs specifically for baseATNConfigSet +func (c *BaseATNConfigComparator[T]) Equals2(o1, o2 *ATNConfig) bool { + + // Same pointer, must be equal, even if both nil + // + if o1 == o2 { + return true + + } + + // If either are nil, but not both, then the result is false + // + if o1 == nil || o2 == nil { + return false + } + + return o1.GetState().GetStateNumber() == o2.GetState().GetStateNumber() && + o1.GetAlt() == o2.GetAlt() && + o1.GetSemanticContext().Equals(o2.GetSemanticContext()) +} + +// Hash1 is custom hash implementation for ATNConfigs specifically for configLookup, but in fact just +// delegates to the standard Hash() method of the ATNConfig type. +func (c *BaseATNConfigComparator[T]) Hash1(o *ATNConfig) int { + return o.Hash() +} diff --git a/prutalgen/internal/antlr/configuration.go b/prutalgen/internal/antlr/configuration.go new file mode 100644 index 0000000..c2b7245 --- /dev/null +++ b/prutalgen/internal/antlr/configuration.go @@ -0,0 +1,214 @@ +package antlr + +type runtimeConfiguration struct { + statsTraceStacks bool + lexerATNSimulatorDebug bool + lexerATNSimulatorDFADebug bool + parserATNSimulatorDebug bool + parserATNSimulatorTraceATNSim bool + parserATNSimulatorDFADebug bool + parserATNSimulatorRetryDebug bool + lRLoopEntryBranchOpt bool + memoryManager bool +} + +// Global runtime configuration +var runtimeConfig = runtimeConfiguration{ + lRLoopEntryBranchOpt: true, +} + +type runtimeOption func(*runtimeConfiguration) error + +// ConfigureRuntime allows the runtime to be configured globally setting things like trace and statistics options. +// It uses the functional options pattern for go. This is a package global function as it operates on the runtime +// configuration regardless of the instantiation of anything higher up such as a parser or lexer. Generally this is +// used for debugging/tracing/statistics options, which are usually used by the runtime maintainers (or rather the +// only maintainer). However, it is possible that you might want to use this to set a global option concerning the +// memory allocation type used by the runtime such as sync.Pool or not. +// +// The options are applied in the order they are passed in, so the last option will override any previous options. +// +// For example, if you want to turn on the collection create point stack flag to true, you can do: +// +// antlr.ConfigureRuntime(antlr.WithStatsTraceStacks(true)) +// +// If you want to turn it off, you can do: +// +// antlr.ConfigureRuntime(antlr.WithStatsTraceStacks(false)) +func ConfigureRuntime(options ...runtimeOption) error { + for _, option := range options { + err := option(&runtimeConfig) + if err != nil { + return err + } + } + return nil +} + +// WithStatsTraceStacks sets the global flag indicating whether to collect stack traces at the create-point of +// certain structs, such as collections, or the use point of certain methods such as Put(). +// Because this can be expensive, it is turned off by default. However, it +// can be useful to track down exactly where memory is being created and used. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithStatsTraceStacks(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithStatsTraceStacks(false)) +func WithStatsTraceStacks(trace bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.statsTraceStacks = trace + return nil + } +} + +// WithLexerATNSimulatorDebug sets the global flag indicating whether to log debug information from the lexer [ATN] +// simulator. This is useful for debugging lexer issues by comparing the output with the Java runtime. Only useful +// to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithLexerATNSimulatorDebug(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithLexerATNSimulatorDebug(false)) +func WithLexerATNSimulatorDebug(debug bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.lexerATNSimulatorDebug = debug + return nil + } +} + +// WithLexerATNSimulatorDFADebug sets the global flag indicating whether to log debug information from the lexer [ATN] [DFA] +// simulator. This is useful for debugging lexer issues by comparing the output with the Java runtime. Only useful +// to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithLexerATNSimulatorDFADebug(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithLexerATNSimulatorDFADebug(false)) +func WithLexerATNSimulatorDFADebug(debug bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.lexerATNSimulatorDFADebug = debug + return nil + } +} + +// WithParserATNSimulatorDebug sets the global flag indicating whether to log debug information from the parser [ATN] +// simulator. This is useful for debugging parser issues by comparing the output with the Java runtime. Only useful +// to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorDebug(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorDebug(false)) +func WithParserATNSimulatorDebug(debug bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.parserATNSimulatorDebug = debug + return nil + } +} + +// WithParserATNSimulatorTraceATNSim sets the global flag indicating whether to log trace information from the parser [ATN] simulator +// [DFA]. This is useful for debugging parser issues by comparing the output with the Java runtime. Only useful +// to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorTraceATNSim(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorTraceATNSim(false)) +func WithParserATNSimulatorTraceATNSim(trace bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.parserATNSimulatorTraceATNSim = trace + return nil + } +} + +// WithParserATNSimulatorDFADebug sets the global flag indicating whether to log debug information from the parser [ATN] [DFA] +// simulator. This is useful for debugging parser issues by comparing the output with the Java runtime. Only useful +// to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorDFADebug(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorDFADebug(false)) +func WithParserATNSimulatorDFADebug(debug bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.parserATNSimulatorDFADebug = debug + return nil + } +} + +// WithParserATNSimulatorRetryDebug sets the global flag indicating whether to log debug information from the parser [ATN] [DFA] +// simulator when retrying a decision. This is useful for debugging parser issues by comparing the output with the Java runtime. +// Only useful to the runtime maintainers. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorRetryDebug(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithParserATNSimulatorRetryDebug(false)) +func WithParserATNSimulatorRetryDebug(debug bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.parserATNSimulatorRetryDebug = debug + return nil + } +} + +// WithLRLoopEntryBranchOpt sets the global flag indicating whether let recursive loop operations should be +// optimized or not. This is useful for debugging parser issues by comparing the output with the Java runtime. +// It turns off the functionality of [canDropLoopEntryEdgeInLeftRecursiveRule] in [ParserATNSimulator]. +// +// Note that default is to use this optimization. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithLRLoopEntryBranchOpt(true)) +// +// You can turn it off at any time using: +// +// antlr.ConfigureRuntime(antlr.WithLRLoopEntryBranchOpt(false)) +func WithLRLoopEntryBranchOpt(off bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.lRLoopEntryBranchOpt = off + return nil + } +} + +// WithMemoryManager sets the global flag indicating whether to use the memory manager or not. This is useful +// for poorly constructed grammars that create a lot of garbage. It turns on the functionality of [memoryManager], which +// will intercept garbage collection and cause available memory to be reused. At the end of the day, this is no substitute +// for fixing your grammar by ridding yourself of extreme ambiguity. BUt if you are just trying to reuse an opensource +// grammar, this may help make it more practical. +// +// Note that default is to use normal Go memory allocation and not pool memory. +// +// Use: +// +// antlr.ConfigureRuntime(antlr.WithMemoryManager(true)) +// +// Note that if you turn this on, you should probably leave it on. You should use only one memory strategy or the other +// and should remember to nil out any references to the parser or lexer when you are done with them. +func WithMemoryManager(use bool) runtimeOption { + return func(config *runtimeConfiguration) error { + config.memoryManager = use + return nil + } +} diff --git a/prutalgen/internal/antlr/dfa.go b/prutalgen/internal/antlr/dfa.go new file mode 100644 index 0000000..6b63eb1 --- /dev/null +++ b/prutalgen/internal/antlr/dfa.go @@ -0,0 +1,175 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// DFA represents the Deterministic Finite Automaton used by the recognizer, including all the states it can +// reach and the transitions between them. +type DFA struct { + // atnStartState is the ATN state in which this was created + atnStartState DecisionState + + decision int + + // states is all the DFA states. Use Map to get the old state back; Set can only + // indicate whether it is there. Go maps implement key hash collisions and so on and are very + // good, but the DFAState is an object and can't be used directly as the key as it can in say Java + // amd C#, whereby if the hashcode is the same for two objects, then Equals() is called against them + // to see if they really are the same object. Hence, we have our own map storage. + // + states *JStore[*DFAState, *ObjEqComparator[*DFAState]] + + numstates int + + s0 *DFAState + + // precedenceDfa is the backing field for isPrecedenceDfa and setPrecedenceDfa. + // True if the DFA is for a precedence decision and false otherwise. + precedenceDfa bool +} + +func NewDFA(atnStartState DecisionState, decision int) *DFA { + dfa := &DFA{ + atnStartState: atnStartState, + decision: decision, + states: nil, // Lazy initialize + } + if s, ok := atnStartState.(*StarLoopEntryState); ok && s.precedenceRuleDecision { + dfa.precedenceDfa = true + dfa.s0 = NewDFAState(-1, NewATNConfigSet(false)) + dfa.s0.isAcceptState = false + dfa.s0.requiresFullContext = false + } + return dfa +} + +// getPrecedenceStartState gets the start state for the current precedence and +// returns the start state corresponding to the specified precedence if a start +// state exists for the specified precedence and nil otherwise. d must be a +// precedence DFA. See also isPrecedenceDfa. +func (d *DFA) getPrecedenceStartState(precedence int) *DFAState { + if !d.getPrecedenceDfa() { + panic("only precedence DFAs may contain a precedence start state") + } + + // s0.edges is never nil for a precedence DFA + if precedence < 0 || precedence >= len(d.getS0().getEdges()) { + return nil + } + + return d.getS0().getIthEdge(precedence) +} + +// setPrecedenceStartState sets the start state for the current precedence. d +// must be a precedence DFA. See also isPrecedenceDfa. +func (d *DFA) setPrecedenceStartState(precedence int, startState *DFAState) { + if !d.getPrecedenceDfa() { + panic("only precedence DFAs may contain a precedence start state") + } + + if precedence < 0 { + return + } + + // Synchronization on s0 here is ok. When the DFA is turned into a + // precedence DFA, s0 will be initialized once and not updated again. s0.edges + // is never nil for a precedence DFA. + s0 := d.getS0() + if precedence >= s0.numEdges() { + edges := append(s0.getEdges(), make([]*DFAState, precedence+1-s0.numEdges())...) + s0.setEdges(edges) + d.setS0(s0) + } + + s0.setIthEdge(precedence, startState) +} + +func (d *DFA) getPrecedenceDfa() bool { + return d.precedenceDfa +} + +// setPrecedenceDfa sets whether d is a precedence DFA. If precedenceDfa differs +// from the current DFA configuration, then d.states is cleared, the initial +// state s0 is set to a new DFAState with an empty outgoing DFAState.edges to +// store the start states for individual precedence values if precedenceDfa is +// true or nil otherwise, and d.precedenceDfa is updated. +func (d *DFA) setPrecedenceDfa(precedenceDfa bool) { + if d.getPrecedenceDfa() != precedenceDfa { + d.states = nil // Lazy initialize + d.numstates = 0 + + if precedenceDfa { + precedenceState := NewDFAState(-1, NewATNConfigSet(false)) + precedenceState.setEdges(make([]*DFAState, 0)) + precedenceState.isAcceptState = false + precedenceState.requiresFullContext = false + d.setS0(precedenceState) + } else { + d.setS0(nil) + } + + d.precedenceDfa = precedenceDfa + } +} + +// Len returns the number of states in d. We use this instead of accessing states directly so that we can implement lazy +// instantiation of the states JMap. +func (d *DFA) Len() int { + if d.states == nil { + return 0 + } + return d.states.Len() +} + +// Get returns a state that matches s if it is present in the DFA state set. We defer to this +// function instead of accessing states directly so that we can implement lazy instantiation of the states JMap. +func (d *DFA) Get(s *DFAState) (*DFAState, bool) { + if d.states == nil { + return nil, false + } + return d.states.Get(s) +} + +func (d *DFA) Put(s *DFAState) (*DFAState, bool) { + if d.states == nil { + d.states = NewJStore[*DFAState, *ObjEqComparator[*DFAState]](dfaStateEqInst, DFAStateCollection, "DFA via DFA.Put") + } + return d.states.Put(s) +} + +func (d *DFA) getS0() *DFAState { + return d.s0 +} + +func (d *DFA) setS0(s *DFAState) { + d.s0 = s +} + +// sortedStates returns the states in d sorted by their state number, or an empty set if d.states is nil. +func (d *DFA) sortedStates() []*DFAState { + if d.states == nil { + return []*DFAState{} + } + vs := d.states.SortedSlice(func(i, j *DFAState) bool { + return i.stateNumber < j.stateNumber + }) + + return vs +} + +func (d *DFA) String(literalNames []string, symbolicNames []string) string { + if d.getS0() == nil { + return "" + } + + return NewDFASerializer(d, literalNames, symbolicNames).String() +} + +func (d *DFA) ToLexerString() string { + if d.getS0() == nil { + return "" + } + + return NewLexerDFASerializer(d).String() +} diff --git a/prutalgen/internal/antlr/dfa_serializer.go b/prutalgen/internal/antlr/dfa_serializer.go new file mode 100644 index 0000000..0e11009 --- /dev/null +++ b/prutalgen/internal/antlr/dfa_serializer.go @@ -0,0 +1,158 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" + "strings" +) + +// DFASerializer is a DFA walker that knows how to dump the DFA states to serialized +// strings. +type DFASerializer struct { + dfa *DFA + literalNames []string + symbolicNames []string +} + +func NewDFASerializer(dfa *DFA, literalNames, symbolicNames []string) *DFASerializer { + if literalNames == nil { + literalNames = make([]string, 0) + } + + if symbolicNames == nil { + symbolicNames = make([]string, 0) + } + + return &DFASerializer{ + dfa: dfa, + literalNames: literalNames, + symbolicNames: symbolicNames, + } +} + +func (d *DFASerializer) String() string { + if d.dfa.getS0() == nil { + return "" + } + + buf := "" + states := d.dfa.sortedStates() + + for _, s := range states { + if s.edges != nil { + n := len(s.edges) + + for j := 0; j < n; j++ { + t := s.edges[j] + + if t != nil && t.stateNumber != 0x7FFFFFFF { + buf += d.GetStateString(s) + buf += "-" + buf += d.getEdgeLabel(j) + buf += "->" + buf += d.GetStateString(t) + buf += "\n" + } + } + } + } + + if len(buf) == 0 { + return "" + } + + return buf +} + +func (d *DFASerializer) getEdgeLabel(i int) string { + if i == 0 { + return "EOF" + } else if d.literalNames != nil && i-1 < len(d.literalNames) { + return d.literalNames[i-1] + } else if d.symbolicNames != nil && i-1 < len(d.symbolicNames) { + return d.symbolicNames[i-1] + } + + return strconv.Itoa(i - 1) +} + +func (d *DFASerializer) GetStateString(s *DFAState) string { + var a, b string + + if s.isAcceptState { + a = ":" + } + + if s.requiresFullContext { + b = "^" + } + + baseStateStr := a + "s" + strconv.Itoa(s.stateNumber) + b + + if s.isAcceptState { + if s.predicates != nil { + return baseStateStr + "=>" + fmt.Sprint(s.predicates) + } + + return baseStateStr + "=>" + fmt.Sprint(s.prediction) + } + + return baseStateStr +} + +type LexerDFASerializer struct { + *DFASerializer +} + +func NewLexerDFASerializer(dfa *DFA) *LexerDFASerializer { + return &LexerDFASerializer{DFASerializer: NewDFASerializer(dfa, nil, nil)} +} + +func (l *LexerDFASerializer) getEdgeLabel(i int) string { + var sb strings.Builder + sb.Grow(6) + sb.WriteByte('\'') + sb.WriteRune(rune(i)) + sb.WriteByte('\'') + return sb.String() +} + +func (l *LexerDFASerializer) String() string { + if l.dfa.getS0() == nil { + return "" + } + + buf := "" + states := l.dfa.sortedStates() + + for i := 0; i < len(states); i++ { + s := states[i] + + if s.edges != nil { + n := len(s.edges) + + for j := 0; j < n; j++ { + t := s.edges[j] + + if t != nil && t.stateNumber != 0x7FFFFFFF { + buf += l.GetStateString(s) + buf += "-" + buf += l.getEdgeLabel(j) + buf += "->" + buf += l.GetStateString(t) + buf += "\n" + } + } + } + } + + if len(buf) == 0 { + return "" + } + + return buf +} diff --git a/prutalgen/internal/antlr/dfa_state.go b/prutalgen/internal/antlr/dfa_state.go new file mode 100644 index 0000000..6541430 --- /dev/null +++ b/prutalgen/internal/antlr/dfa_state.go @@ -0,0 +1,170 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" +) + +// PredPrediction maps a predicate to a predicted alternative. +type PredPrediction struct { + alt int + pred SemanticContext +} + +func NewPredPrediction(pred SemanticContext, alt int) *PredPrediction { + return &PredPrediction{alt: alt, pred: pred} +} + +func (p *PredPrediction) String() string { + return "(" + fmt.Sprint(p.pred) + ", " + fmt.Sprint(p.alt) + ")" +} + +// DFAState represents a set of possible [ATN] configurations. As Aho, Sethi, +// Ullman p. 117 says: "The DFA uses its state to keep track of all possible +// states the ATN can be in after reading each input symbol. That is to say, +// after reading input a1, a2,..an, the DFA is in a state that represents the +// subset T of the states of the ATN that are reachable from the ATN's start +// state along some path labeled a1a2..an." +// +// In conventional NFA-to-DFA conversion, therefore, the subset T would be a bitset representing the set of +// states the [ATN] could be in. We need to track the alt predicted by each state +// as well, however. More importantly, we need to maintain a stack of states, +// tracking the closure operations as they jump from rule to rule, emulating +// rule invocations (method calls). I have to add a stack to simulate the proper +// lookahead sequences for the underlying LL grammar from which the ATN was +// derived. +// +// I use a set of [ATNConfig] objects, not simple states. An [ATNConfig] is both a +// state (ala normal conversion) and a [RuleContext] describing the chain of rules +// (if any) followed to arrive at that state. +// +// A [DFAState] may have multiple references to a particular state, but with +// different [ATN] contexts (with same or different alts) meaning that state was +// reached via a different set of rule invocations. +type DFAState struct { + stateNumber int + configs *ATNConfigSet + + // edges elements point to the target of the symbol. Shift up by 1 so (-1) + // Token.EOF maps to the first element. + edges []*DFAState + + isAcceptState bool + + // prediction is the 'ttype' we match or alt we predict if the state is 'accept'. + // Set to ATN.INVALID_ALT_NUMBER when predicates != nil or + // requiresFullContext. + prediction int + + lexerActionExecutor *LexerActionExecutor + + // requiresFullContext indicates it was created during an SLL prediction that + // discovered a conflict between the configurations in the state. Future + // ParserATNSimulator.execATN invocations immediately jump doing + // full context prediction if true. + requiresFullContext bool + + // predicates is the predicates associated with the ATN configurations of the + // DFA state during SLL parsing. When we have predicates, requiresFullContext + // is false, since full context prediction evaluates predicates on-the-fly. If + // d is + // not nil, then prediction is ATN.INVALID_ALT_NUMBER. + // + // We only use these for non-requiresFullContext but conflicting states. That + // means we know from the context (it's $ or we don't dip into outer context) + // that it's an ambiguity not a conflict. + // + // This list is computed by + // ParserATNSimulator.predicateDFAState. + predicates []*PredPrediction +} + +func NewDFAState(stateNumber int, configs *ATNConfigSet) *DFAState { + if configs == nil { + configs = NewATNConfigSet(false) + } + + return &DFAState{configs: configs, stateNumber: stateNumber} +} + +// GetAltSet gets the set of all alts mentioned by all ATN configurations in d. +func (d *DFAState) GetAltSet() []int { + var alts []int + + if d.configs != nil { + for _, c := range d.configs.configs { + alts = append(alts, c.GetAlt()) + } + } + + if len(alts) == 0 { + return nil + } + + return alts +} + +func (d *DFAState) getEdges() []*DFAState { + return d.edges +} + +func (d *DFAState) numEdges() int { + return len(d.edges) +} + +func (d *DFAState) getIthEdge(i int) *DFAState { + return d.edges[i] +} + +func (d *DFAState) setEdges(newEdges []*DFAState) { + d.edges = newEdges +} + +func (d *DFAState) setIthEdge(i int, edge *DFAState) { + d.edges[i] = edge +} + +func (d *DFAState) setPrediction(v int) { + d.prediction = v +} + +func (d *DFAState) String() string { + var s string + if d.isAcceptState { + if d.predicates != nil { + s = "=>" + fmt.Sprint(d.predicates) + } else { + s = "=>" + fmt.Sprint(d.prediction) + } + } + + return fmt.Sprintf("%d:%s%s", d.stateNumber, fmt.Sprint(d.configs), s) +} + +func (d *DFAState) Hash() int { + h := murmurInit(7) + h = murmurUpdate(h, d.configs.Hash()) + return murmurFinish(h, 1) +} + +// Equals returns whether d equals other. Two DFAStates are equal if their ATN +// configuration sets are the same. This method is used to see if a state +// already exists. +// +// Because the number of alternatives and number of ATN configurations are +// finite, there is a finite number of DFA states that can be processed. This is +// necessary to show that the algorithm terminates. +// +// Cannot test the DFA state numbers here because in +// ParserATNSimulator.addDFAState we need to know if any other state exists that +// has d exact set of ATN configurations. The stateNumber is irrelevant. +func (d *DFAState) Equals(o Collectable[*DFAState]) bool { + if d == o { + return true + } + + return d.configs.Equals(o.(*DFAState).configs) +} diff --git a/prutalgen/internal/antlr/diagnostic_error_listener.go b/prutalgen/internal/antlr/diagnostic_error_listener.go new file mode 100644 index 0000000..bd2cd8b --- /dev/null +++ b/prutalgen/internal/antlr/diagnostic_error_listener.go @@ -0,0 +1,110 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "strconv" +) + +// +// This implementation of {@link ANTLRErrorListener} can be used to identify +// certain potential correctness and performance problems in grammars. "reports" +// are made by calling {@link Parser//NotifyErrorListeners} with the appropriate +// message. +// +// + +type DiagnosticErrorListener struct { + *DefaultErrorListener + + exactOnly bool +} + +//goland:noinspection GoUnusedExportedFunction +func NewDiagnosticErrorListener(exactOnly bool) *DiagnosticErrorListener { + + n := new(DiagnosticErrorListener) + + // whether all ambiguities or only exact ambiguities are Reported. + n.exactOnly = exactOnly + return n +} + +func (d *DiagnosticErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs *ATNConfigSet) { + if d.exactOnly && !exact { + return + } + msg := "reportAmbiguity d=" + + d.getDecisionDescription(recognizer, dfa) + + ": ambigAlts=" + + d.getConflictingAlts(ambigAlts, configs).String() + + ", input='" + + recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'" + recognizer.NotifyErrorListeners(msg, nil, nil) +} + +func (d *DiagnosticErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, _ *BitSet, _ *ATNConfigSet) { + + msg := "reportAttemptingFullContext d=" + + d.getDecisionDescription(recognizer, dfa) + + ", input='" + + recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'" + recognizer.NotifyErrorListeners(msg, nil, nil) +} + +func (d *DiagnosticErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, _ int, _ *ATNConfigSet) { + msg := "reportContextSensitivity d=" + + d.getDecisionDescription(recognizer, dfa) + + ", input='" + + recognizer.GetTokenStream().GetTextFromInterval(NewInterval(startIndex, stopIndex)) + "'" + recognizer.NotifyErrorListeners(msg, nil, nil) +} + +func (d *DiagnosticErrorListener) getDecisionDescription(recognizer Parser, dfa *DFA) string { + decision := dfa.decision + ruleIndex := dfa.atnStartState.GetRuleIndex() + + ruleNames := recognizer.GetRuleNames() + if ruleIndex < 0 || ruleIndex >= len(ruleNames) { + return strconv.Itoa(decision) + } + ruleName := ruleNames[ruleIndex] + if ruleName == "" { + return strconv.Itoa(decision) + } + return strconv.Itoa(decision) + " (" + ruleName + ")" +} + +// Computes the set of conflicting or ambiguous alternatives from a +// configuration set, if that information was not already provided by the +// parser. +// +// @param ReportedAlts The set of conflicting or ambiguous alternatives, as +// Reported by the parser. +// @param configs The conflicting or ambiguous configuration set. +// @return Returns {@code ReportedAlts} if it is not {@code nil}, otherwise +// returns the set of alternatives represented in {@code configs}. +func (d *DiagnosticErrorListener) getConflictingAlts(ReportedAlts *BitSet, set *ATNConfigSet) *BitSet { + if ReportedAlts != nil { + return ReportedAlts + } + result := NewBitSet() + for _, c := range set.configs { + result.add(c.GetAlt()) + } + + return result +} diff --git a/prutalgen/internal/antlr/error_listener.go b/prutalgen/internal/antlr/error_listener.go new file mode 100644 index 0000000..21a0216 --- /dev/null +++ b/prutalgen/internal/antlr/error_listener.go @@ -0,0 +1,100 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "os" + "strconv" +) + +// Provides an empty default implementation of {@link ANTLRErrorListener}. The +// default implementation of each method does nothing, but can be overridden as +// necessary. + +type ErrorListener interface { + SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) + ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs *ATNConfigSet) + ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs *ATNConfigSet) + ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs *ATNConfigSet) +} + +type DefaultErrorListener struct { +} + +//goland:noinspection GoUnusedExportedFunction +func NewDefaultErrorListener() *DefaultErrorListener { + return new(DefaultErrorListener) +} + +func (d *DefaultErrorListener) SyntaxError(_ Recognizer, _ interface{}, _, _ int, _ string, _ RecognitionException) { +} + +func (d *DefaultErrorListener) ReportAmbiguity(_ Parser, _ *DFA, _, _ int, _ bool, _ *BitSet, _ *ATNConfigSet) { +} + +func (d *DefaultErrorListener) ReportAttemptingFullContext(_ Parser, _ *DFA, _, _ int, _ *BitSet, _ *ATNConfigSet) { +} + +func (d *DefaultErrorListener) ReportContextSensitivity(_ Parser, _ *DFA, _, _, _ int, _ *ATNConfigSet) { +} + +type ConsoleErrorListener struct { + *DefaultErrorListener +} + +func NewConsoleErrorListener() *ConsoleErrorListener { + return new(ConsoleErrorListener) +} + +// ConsoleErrorListenerINSTANCE provides a default instance of {@link ConsoleErrorListener}. +var ConsoleErrorListenerINSTANCE = NewConsoleErrorListener() + +// SyntaxError prints messages to System.err containing the +// values of line, charPositionInLine, and msg using +// the following format: +// +// line : +func (c *ConsoleErrorListener) SyntaxError(_ Recognizer, _ interface{}, line, column int, msg string, _ RecognitionException) { + _, _ = fmt.Fprintln(os.Stderr, "line "+strconv.Itoa(line)+":"+strconv.Itoa(column)+" "+msg) +} + +type ProxyErrorListener struct { + *DefaultErrorListener + delegates []ErrorListener +} + +func NewProxyErrorListener(delegates []ErrorListener) *ProxyErrorListener { + if delegates == nil { + panic("delegates is not provided") + } + l := new(ProxyErrorListener) + l.delegates = delegates + return l +} + +func (p *ProxyErrorListener) SyntaxError(recognizer Recognizer, offendingSymbol interface{}, line, column int, msg string, e RecognitionException) { + for _, d := range p.delegates { + d.SyntaxError(recognizer, offendingSymbol, line, column, msg, e) + } +} + +func (p *ProxyErrorListener) ReportAmbiguity(recognizer Parser, dfa *DFA, startIndex, stopIndex int, exact bool, ambigAlts *BitSet, configs *ATNConfigSet) { + for _, d := range p.delegates { + d.ReportAmbiguity(recognizer, dfa, startIndex, stopIndex, exact, ambigAlts, configs) + } +} + +func (p *ProxyErrorListener) ReportAttemptingFullContext(recognizer Parser, dfa *DFA, startIndex, stopIndex int, conflictingAlts *BitSet, configs *ATNConfigSet) { + for _, d := range p.delegates { + d.ReportAttemptingFullContext(recognizer, dfa, startIndex, stopIndex, conflictingAlts, configs) + } +} + +func (p *ProxyErrorListener) ReportContextSensitivity(recognizer Parser, dfa *DFA, startIndex, stopIndex, prediction int, configs *ATNConfigSet) { + for _, d := range p.delegates { + d.ReportContextSensitivity(recognizer, dfa, startIndex, stopIndex, prediction, configs) + } +} diff --git a/prutalgen/internal/antlr/error_strategy.go b/prutalgen/internal/antlr/error_strategy.go new file mode 100644 index 0000000..9db2be1 --- /dev/null +++ b/prutalgen/internal/antlr/error_strategy.go @@ -0,0 +1,702 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +type ErrorStrategy interface { + reset(Parser) + RecoverInline(Parser) Token + Recover(Parser, RecognitionException) + Sync(Parser) + InErrorRecoveryMode(Parser) bool + ReportError(Parser, RecognitionException) + ReportMatch(Parser) +} + +// DefaultErrorStrategy is the default implementation of ANTLRErrorStrategy used for +// error reporting and recovery in ANTLR parsers. +type DefaultErrorStrategy struct { + errorRecoveryMode bool + lastErrorIndex int + lastErrorStates *IntervalSet +} + +var _ ErrorStrategy = &DefaultErrorStrategy{} + +func NewDefaultErrorStrategy() *DefaultErrorStrategy { + + d := new(DefaultErrorStrategy) + + // Indicates whether the error strategy is currently "recovering from an + // error". This is used to suppress Reporting multiple error messages while + // attempting to recover from a detected syntax error. + // + // @see //InErrorRecoveryMode + // + d.errorRecoveryMode = false + + // The index into the input stream where the last error occurred. + // This is used to prevent infinite loops where an error is found + // but no token is consumed during recovery...another error is found, + // ad nauseam. This is a failsafe mechanism to guarantee that at least + // one token/tree node is consumed for two errors. + // + d.lastErrorIndex = -1 + d.lastErrorStates = nil + return d +} + +//

The default implementation simply calls {@link //endErrorCondition} to +// ensure that the handler is not in error recovery mode.

+func (d *DefaultErrorStrategy) reset(recognizer Parser) { + d.endErrorCondition(recognizer) +} + +// This method is called to enter error recovery mode when a recognition +// exception is Reported. +func (d *DefaultErrorStrategy) beginErrorCondition(_ Parser) { + d.errorRecoveryMode = true +} + +func (d *DefaultErrorStrategy) InErrorRecoveryMode(_ Parser) bool { + return d.errorRecoveryMode +} + +// This method is called to leave error recovery mode after recovering from +// a recognition exception. +func (d *DefaultErrorStrategy) endErrorCondition(_ Parser) { + d.errorRecoveryMode = false + d.lastErrorStates = nil + d.lastErrorIndex = -1 +} + +// ReportMatch is the default implementation of error matching and simply calls endErrorCondition. +func (d *DefaultErrorStrategy) ReportMatch(recognizer Parser) { + d.endErrorCondition(recognizer) +} + +// ReportError is the default implementation of error reporting. +// It returns immediately if the handler is already +// in error recovery mode. Otherwise, it calls [beginErrorCondition] +// and dispatches the Reporting task based on the runtime type of e +// according to the following table. +// +// [NoViableAltException] : Dispatches the call to [ReportNoViableAlternative] +// [InputMisMatchException] : Dispatches the call to [ReportInputMisMatch] +// [FailedPredicateException] : Dispatches the call to [ReportFailedPredicate] +// All other types : Calls [NotifyErrorListeners] to Report the exception +func (d *DefaultErrorStrategy) ReportError(recognizer Parser, e RecognitionException) { + // if we've already Reported an error and have not Matched a token + // yet successfully, don't Report any errors. + if d.InErrorRecoveryMode(recognizer) { + return // don't Report spurious errors + } + d.beginErrorCondition(recognizer) + + switch t := e.(type) { + default: + fmt.Println("unknown recognition error type: " + reflect.TypeOf(e).Name()) + // fmt.Println(e.stack) + recognizer.NotifyErrorListeners(e.GetMessage(), e.GetOffendingToken(), e) + case *NoViableAltException: + d.ReportNoViableAlternative(recognizer, t) + case *InputMisMatchException: + d.ReportInputMisMatch(recognizer, t) + case *FailedPredicateException: + d.ReportFailedPredicate(recognizer, t) + } +} + +// Recover is the default recovery implementation. +// It reSynchronizes the parser by consuming tokens until we find one in the reSynchronization set - +// loosely the set of tokens that can follow the current rule. +func (d *DefaultErrorStrategy) Recover(recognizer Parser, _ RecognitionException) { + + if d.lastErrorIndex == recognizer.GetInputStream().Index() && + d.lastErrorStates != nil && d.lastErrorStates.contains(recognizer.GetState()) { + // uh oh, another error at same token index and previously-Visited + // state in ATN must be a case where LT(1) is in the recovery + // token set so nothing got consumed. Consume a single token + // at least to prevent an infinite loop d is a failsafe. + recognizer.Consume() + } + d.lastErrorIndex = recognizer.GetInputStream().Index() + if d.lastErrorStates == nil { + d.lastErrorStates = NewIntervalSet() + } + d.lastErrorStates.addOne(recognizer.GetState()) + followSet := d.GetErrorRecoverySet(recognizer) + d.consumeUntil(recognizer, followSet) +} + +// Sync is the default implementation of error strategy synchronization. +// +// This Sync makes sure that the current lookahead symbol is consistent with what were expecting +// at this point in the [ATN]. You can call this anytime but ANTLR only +// generates code to check before sub-rules/loops and each iteration. +// +// Implements [Jim Idle]'s magic Sync mechanism in closures and optional +// sub-rules. E.g.: +// +// a : Sync ( stuff Sync )* +// Sync : {consume to what can follow Sync} +// +// At the start of a sub-rule upon error, Sync performs single +// token deletion, if possible. If it can't do that, it bails on the current +// rule and uses the default error recovery, which consumes until the +// reSynchronization set of the current rule. +// +// If the sub-rule is optional +// +// ({@code (...)?}, {@code (...)*}, +// +// or a block with an empty alternative), then the expected set includes what follows +// the sub-rule. +// +// During loop iteration, it consumes until it sees a token that can start a +// sub-rule or what follows loop. Yes, that is pretty aggressive. We opt to +// stay in the loop as long as possible. +// +// # Origins +// +// Previous versions of ANTLR did a poor job of their recovery within loops. +// A single mismatch token or missing token would force the parser to bail +// out of the entire rules surrounding the loop. So, for rule: +// +// classfunc : 'class' ID '{' member* '}' +// +// input with an extra token between members would force the parser to +// consume until it found the next class definition rather than the next +// member definition of the current class. +// +// This functionality cost a bit of effort because the parser has to +// compare the token set at the start of the loop and at each iteration. If for +// some reason speed is suffering for you, you can turn off this +// functionality by simply overriding this method as empty: +// +// { } +// +// [Jim Idle]: https://github.com/jimidle +func (d *DefaultErrorStrategy) Sync(recognizer Parser) { + // If already recovering, don't try to Sync + if d.InErrorRecoveryMode(recognizer) { + return + } + + s := recognizer.GetInterpreter().atn.states[recognizer.GetState()] + la := recognizer.GetTokenStream().LA(1) + + // try cheaper subset first might get lucky. seems to shave a wee bit off + nextTokens := recognizer.GetATN().NextTokens(s, nil) + if nextTokens.contains(TokenEpsilon) || nextTokens.contains(la) { + return + } + + switch s.GetStateType() { + case ATNStateBlockStart, ATNStateStarBlockStart, ATNStatePlusBlockStart, ATNStateStarLoopEntry: + // Report error and recover if possible + if d.SingleTokenDeletion(recognizer) != nil { + return + } + recognizer.SetError(NewInputMisMatchException(recognizer)) + case ATNStatePlusLoopBack, ATNStateStarLoopBack: + d.ReportUnwantedToken(recognizer) + expecting := NewIntervalSet() + expecting.addSet(recognizer.GetExpectedTokens()) + whatFollowsLoopIterationOrRule := expecting.addSet(d.GetErrorRecoverySet(recognizer)) + d.consumeUntil(recognizer, whatFollowsLoopIterationOrRule) + default: + // do nothing if we can't identify the exact kind of ATN state + } +} + +// ReportNoViableAlternative is called by [ReportError] when the exception is a [NoViableAltException]. +// +// See also [ReportError] +func (d *DefaultErrorStrategy) ReportNoViableAlternative(recognizer Parser, e *NoViableAltException) { + tokens := recognizer.GetTokenStream() + var input string + if tokens != nil { + if e.startToken.GetTokenType() == TokenEOF { + input = "" + } else { + input = tokens.GetTextFromTokens(e.startToken, e.offendingToken) + } + } else { + input = "" + } + msg := "no viable alternative at input " + d.escapeWSAndQuote(input) + recognizer.NotifyErrorListeners(msg, e.offendingToken, e) +} + +// ReportInputMisMatch is called by [ReportError] when the exception is an [InputMisMatchException] +// +// See also: [ReportError] +func (d *DefaultErrorStrategy) ReportInputMisMatch(recognizer Parser, e *InputMisMatchException) { + msg := "mismatched input " + d.GetTokenErrorDisplay(e.offendingToken) + + " expecting " + e.getExpectedTokens().StringVerbose(recognizer.GetLiteralNames(), recognizer.GetSymbolicNames(), false) + recognizer.NotifyErrorListeners(msg, e.offendingToken, e) +} + +// ReportFailedPredicate is called by [ReportError] when the exception is a [FailedPredicateException]. +// +// See also: [ReportError] +func (d *DefaultErrorStrategy) ReportFailedPredicate(recognizer Parser, e *FailedPredicateException) { + ruleName := recognizer.GetRuleNames()[recognizer.GetParserRuleContext().GetRuleIndex()] + msg := "rule " + ruleName + " " + e.message + recognizer.NotifyErrorListeners(msg, e.offendingToken, e) +} + +// ReportUnwantedToken is called to report a syntax error that requires the removal +// of a token from the input stream. At the time d method is called, the +// erroneous symbol is the current LT(1) symbol and has not yet been +// removed from the input stream. When this method returns, +// recognizer is in error recovery mode. +// +// This method is called when singleTokenDeletion identifies +// single-token deletion as a viable recovery strategy for a mismatched +// input error. +// +// The default implementation simply returns if the handler is already in +// error recovery mode. Otherwise, it calls beginErrorCondition to +// enter error recovery mode, followed by calling +// [NotifyErrorListeners] +func (d *DefaultErrorStrategy) ReportUnwantedToken(recognizer Parser) { + if d.InErrorRecoveryMode(recognizer) { + return + } + d.beginErrorCondition(recognizer) + t := recognizer.GetCurrentToken() + tokenName := d.GetTokenErrorDisplay(t) + expecting := d.GetExpectedTokens(recognizer) + msg := "extraneous input " + tokenName + " expecting " + + expecting.StringVerbose(recognizer.GetLiteralNames(), recognizer.GetSymbolicNames(), false) + recognizer.NotifyErrorListeners(msg, t, nil) +} + +// ReportMissingToken is called to report a syntax error which requires the +// insertion of a missing token into the input stream. At the time this +// method is called, the missing token has not yet been inserted. When this +// method returns, recognizer is in error recovery mode. +// +// This method is called when singleTokenInsertion identifies +// single-token insertion as a viable recovery strategy for a mismatched +// input error. +// +// The default implementation simply returns if the handler is already in +// error recovery mode. Otherwise, it calls beginErrorCondition to +// enter error recovery mode, followed by calling [NotifyErrorListeners] +func (d *DefaultErrorStrategy) ReportMissingToken(recognizer Parser) { + if d.InErrorRecoveryMode(recognizer) { + return + } + d.beginErrorCondition(recognizer) + t := recognizer.GetCurrentToken() + expecting := d.GetExpectedTokens(recognizer) + msg := "missing " + expecting.StringVerbose(recognizer.GetLiteralNames(), recognizer.GetSymbolicNames(), false) + + " at " + d.GetTokenErrorDisplay(t) + recognizer.NotifyErrorListeners(msg, t, nil) +} + +// The RecoverInline default implementation attempts to recover from the mismatched input +// by using single token insertion and deletion as described below. If the +// recovery attempt fails, this method panics with [InputMisMatchException}. +// TODO: Not sure that panic() is the right thing to do here - JI +// +// # EXTRA TOKEN (single token deletion) +// +// LA(1) is not what we are looking for. If LA(2) has the +// right token, however, then assume LA(1) is some extra spurious +// token and delete it. Then consume and return the next token (which was +// the LA(2) token) as the successful result of the Match operation. +// +// # This recovery strategy is implemented by singleTokenDeletion +// +// # MISSING TOKEN (single token insertion) +// +// If current token -at LA(1) - is consistent with what could come +// after the expected LA(1) token, then assume the token is missing +// and use the parser's [TokenFactory] to create it on the fly. The +// “insertion” is performed by returning the created token as the successful +// result of the Match operation. +// +// This recovery strategy is implemented by [SingleTokenInsertion]. +// +// # Example +// +// For example, Input i=(3 is clearly missing the ')'. When +// the parser returns from the nested call to expr, it will have +// call the chain: +// +// stat → expr → atom +// +// and it will be trying to Match the ')' at this point in the +// derivation: +// +// : ID '=' '(' INT ')' ('+' atom)* ';' +// ^ +// +// The attempt to [Match] ')' will fail when it sees ';' and +// call [RecoverInline]. To recover, it sees that LA(1)==';' +// is in the set of tokens that can follow the ')' token reference +// in rule atom. It can assume that you forgot the ')'. +func (d *DefaultErrorStrategy) RecoverInline(recognizer Parser) Token { + // SINGLE TOKEN DELETION + MatchedSymbol := d.SingleTokenDeletion(recognizer) + if MatchedSymbol != nil { + // we have deleted the extra token. + // now, move past ttype token as if all were ok + recognizer.Consume() + return MatchedSymbol + } + // SINGLE TOKEN INSERTION + if d.SingleTokenInsertion(recognizer) { + return d.GetMissingSymbol(recognizer) + } + // even that didn't work must panic the exception + recognizer.SetError(NewInputMisMatchException(recognizer)) + return nil +} + +// SingleTokenInsertion implements the single-token insertion inline error recovery +// strategy. It is called by [RecoverInline] if the single-token +// deletion strategy fails to recover from the mismatched input. If this +// method returns {@code true}, {@code recognizer} will be in error recovery +// mode. +// +// This method determines whether single-token insertion is viable by +// checking if the LA(1) input symbol could be successfully Matched +// if it were instead the LA(2) symbol. If this method returns +// {@code true}, the caller is responsible for creating and inserting a +// token with the correct type to produce this behavior.

+// +// This func returns true if single-token insertion is a viable recovery +// strategy for the current mismatched input. +func (d *DefaultErrorStrategy) SingleTokenInsertion(recognizer Parser) bool { + currentSymbolType := recognizer.GetTokenStream().LA(1) + // if current token is consistent with what could come after current + // ATN state, then we know we're missing a token error recovery + // is free to conjure up and insert the missing token + atn := recognizer.GetInterpreter().atn + currentState := atn.states[recognizer.GetState()] + next := currentState.GetTransitions()[0].getTarget() + expectingAtLL2 := atn.NextTokens(next, recognizer.GetParserRuleContext()) + if expectingAtLL2.contains(currentSymbolType) { + d.ReportMissingToken(recognizer) + return true + } + + return false +} + +// SingleTokenDeletion implements the single-token deletion inline error recovery +// strategy. It is called by [RecoverInline] to attempt to recover +// from mismatched input. If this method returns nil, the parser and error +// handler state will not have changed. If this method returns non-nil, +// recognizer will not be in error recovery mode since the +// returned token was a successful Match. +// +// If the single-token deletion is successful, this method calls +// [ReportUnwantedToken] to Report the error, followed by +// [Consume] to actually “delete” the extraneous token. Then, +// before returning, [ReportMatch] is called to signal a successful +// Match. +// +// The func returns the successfully Matched [Token] instance if single-token +// deletion successfully recovers from the mismatched input, otherwise nil. +func (d *DefaultErrorStrategy) SingleTokenDeletion(recognizer Parser) Token { + NextTokenType := recognizer.GetTokenStream().LA(2) + expecting := d.GetExpectedTokens(recognizer) + if expecting.contains(NextTokenType) { + d.ReportUnwantedToken(recognizer) + // print("recoverFromMisMatchedToken deleting " \ + // + str(recognizer.GetTokenStream().LT(1)) \ + // + " since " + str(recognizer.GetTokenStream().LT(2)) \ + // + " is what we want", file=sys.stderr) + recognizer.Consume() // simply delete extra token + // we want to return the token we're actually Matching + MatchedSymbol := recognizer.GetCurrentToken() + d.ReportMatch(recognizer) // we know current token is correct + return MatchedSymbol + } + + return nil +} + +// GetMissingSymbol conjures up a missing token during error recovery. +// +// The recognizer attempts to recover from single missing +// symbols. But, actions might refer to that missing symbol. +// For example: +// +// x=ID {f($x)}. +// +// The action clearly assumes +// that there has been an identifier Matched previously and that +// $x points at that token. If that token is missing, but +// the next token in the stream is what we want we assume that +// this token is missing, and we keep going. Because we +// have to return some token to replace the missing token, +// we have to conjure one up. This method gives the user control +// over the tokens returned for missing tokens. Mostly, +// you will want to create something special for identifier +// tokens. For literals such as '{' and ',', the default +// action in the parser or tree parser works. It simply creates +// a [CommonToken] of the appropriate type. The text will be the token name. +// If you need to change which tokens must be created by the lexer, +// override this method to create the appropriate tokens. +func (d *DefaultErrorStrategy) GetMissingSymbol(recognizer Parser) Token { + currentSymbol := recognizer.GetCurrentToken() + expecting := d.GetExpectedTokens(recognizer) + expectedTokenType := expecting.first() + var tokenText string + + if expectedTokenType == TokenEOF { + tokenText = "" + } else { + ln := recognizer.GetLiteralNames() + if expectedTokenType > 0 && expectedTokenType < len(ln) { + tokenText = "" + } else { + tokenText = "" // TODO: matches the JS impl + } + } + current := currentSymbol + lookback := recognizer.GetTokenStream().LT(-1) + if current.GetTokenType() == TokenEOF && lookback != nil { + current = lookback + } + + tf := recognizer.GetTokenFactory() + + return tf.Create(current.GetSource(), expectedTokenType, tokenText, TokenDefaultChannel, -1, -1, current.GetLine(), current.GetColumn()) +} + +func (d *DefaultErrorStrategy) GetExpectedTokens(recognizer Parser) *IntervalSet { + return recognizer.GetExpectedTokens() +} + +// GetTokenErrorDisplay determines how a token should be displayed in an error message. +// The default is to display just the text, but during development you might +// want to have a lot of information spit out. Override this func in that case +// to use t.String() (which, for [CommonToken], dumps everything about +// the token). This is better than forcing you to override a method in +// your token objects because you don't have to go modify your lexer +// so that it creates a new type. +func (d *DefaultErrorStrategy) GetTokenErrorDisplay(t Token) string { + if t == nil { + return "" + } + s := t.GetText() + if s == "" { + if t.GetTokenType() == TokenEOF { + s = "" + } else { + s = "<" + strconv.Itoa(t.GetTokenType()) + ">" + } + } + return d.escapeWSAndQuote(s) +} + +func (d *DefaultErrorStrategy) escapeWSAndQuote(s string) string { + s = strings.Replace(s, "\t", "\\t", -1) + s = strings.Replace(s, "\n", "\\n", -1) + s = strings.Replace(s, "\r", "\\r", -1) + return "'" + s + "'" +} + +// GetErrorRecoverySet computes the error recovery set for the current rule. During +// rule invocation, the parser pushes the set of tokens that can +// follow that rule reference on the stack. This amounts to +// computing FIRST of what follows the rule reference in the +// enclosing rule. See LinearApproximator.FIRST(). +// +// This local follow set only includes tokens +// from within the rule i.e., the FIRST computation done by +// ANTLR stops at the end of a rule. +// +// # Example +// +// When you find a "no viable alt exception", the input is not +// consistent with any of the alternatives for rule r. The best +// thing to do is to consume tokens until you see something that +// can legally follow a call to r or any rule that called r. +// You don't want the exact set of viable next tokens because the +// input might just be missing a token--you might consume the +// rest of the input looking for one of the missing tokens. +// +// Consider the grammar: +// +// a : '[' b ']' +// | '(' b ')' +// ; +// +// b : c '^' INT +// ; +// +// c : ID +// | INT +// ; +// +// At each rule invocation, the set of tokens that could follow +// that rule is pushed on a stack. Here are the various +// context-sensitive follow sets: +// +// FOLLOW(b1_in_a) = FIRST(']') = ']' +// FOLLOW(b2_in_a) = FIRST(')') = ')' +// FOLLOW(c_in_b) = FIRST('^') = '^' +// +// Upon erroneous input “[]”, the call chain is +// +// a → b → c +// +// and, hence, the follow context stack is: +// +// Depth Follow set Start of rule execution +// 0 a (from main()) +// 1 ']' b +// 2 '^' c +// +// Notice that ')' is not included, because b would have to have +// been called from a different context in rule a for ')' to be +// included. +// +// For error recovery, we cannot consider FOLLOW(c) +// (context-sensitive or otherwise). We need the combined set of +// all context-sensitive FOLLOW sets - the set of all tokens that +// could follow any reference in the call chain. We need to +// reSync to one of those tokens. Note that FOLLOW(c)='^' and if +// we reSync'd to that token, we'd consume until EOF. We need to +// Sync to context-sensitive FOLLOWs for a, b, and c: +// +// {']','^'} +// +// In this case, for input "[]", LA(1) is ']' and in the set, so we would +// not consume anything. After printing an error, rule c would +// return normally. Rule b would not find the required '^' though. +// At this point, it gets a mismatched token error and panics an +// exception (since LA(1) is not in the viable following token +// set). The rule exception handler tries to recover, but finds +// the same recovery set and doesn't consume anything. Rule b +// exits normally returning to rule a. Now it finds the ']' (and +// with the successful Match exits errorRecovery mode). +// +// So, you can see that the parser walks up the call chain looking +// for the token that was a member of the recovery set. +// +// Errors are not generated in errorRecovery mode. +// +// ANTLR's error recovery mechanism is based upon original ideas: +// +// [Algorithms + Data Structures = Programs] by Niklaus Wirth and +// [A note on error recovery in recursive descent parsers]. +// +// Later, Josef Grosch had some good ideas in [Efficient and Comfortable Error Recovery in Recursive Descent +// Parsers] +// +// Like Grosch I implement context-sensitive FOLLOW sets that are combined at run-time upon error to avoid overhead +// during parsing. Later, the runtime Sync was improved for loops/sub-rules see [Sync] docs +// +// [A note on error recovery in recursive descent parsers]: http://portal.acm.org/citation.cfm?id=947902.947905 +// [Algorithms + Data Structures = Programs]: https://t.ly/5QzgE +// [Efficient and Comfortable Error Recovery in Recursive Descent Parsers]: ftp://www.cocolab.com/products/cocktail/doca4.ps/ell.ps.zip +func (d *DefaultErrorStrategy) GetErrorRecoverySet(recognizer Parser) *IntervalSet { + atn := recognizer.GetInterpreter().atn + ctx := recognizer.GetParserRuleContext() + recoverSet := NewIntervalSet() + for ctx != nil && ctx.GetInvokingState() >= 0 { + // compute what follows who invoked us + invokingState := atn.states[ctx.GetInvokingState()] + rt := invokingState.GetTransitions()[0] + follow := atn.NextTokens(rt.(*RuleTransition).followState, nil) + recoverSet.addSet(follow) + ctx = ctx.GetParent().(ParserRuleContext) + } + recoverSet.removeOne(TokenEpsilon) + return recoverSet +} + +// Consume tokens until one Matches the given token set.// +func (d *DefaultErrorStrategy) consumeUntil(recognizer Parser, set *IntervalSet) { + ttype := recognizer.GetTokenStream().LA(1) + for ttype != TokenEOF && !set.contains(ttype) { + recognizer.Consume() + ttype = recognizer.GetTokenStream().LA(1) + } +} + +// The BailErrorStrategy implementation of ANTLRErrorStrategy responds to syntax errors +// by immediately canceling the parse operation with a +// [ParseCancellationException]. The implementation ensures that the +// [ParserRuleContext//exception] field is set for all parse tree nodes +// that were not completed prior to encountering the error. +// +// This error strategy is useful in the following scenarios. +// +// - Two-stage parsing: This error strategy allows the first +// stage of two-stage parsing to immediately terminate if an error is +// encountered, and immediately fall back to the second stage. In addition to +// avoiding wasted work by attempting to recover from errors here, the empty +// implementation of [BailErrorStrategy.Sync] improves the performance of +// the first stage. +// +// - Silent validation: When syntax errors are not being +// Reported or logged, and the parse result is simply ignored if errors occur, +// the [BailErrorStrategy] avoids wasting work on recovering from errors +// when the result will be ignored either way. +// +// myparser.SetErrorHandler(NewBailErrorStrategy()) +// +// See also: [Parser.SetErrorHandler(ANTLRErrorStrategy)] +type BailErrorStrategy struct { + *DefaultErrorStrategy +} + +var _ ErrorStrategy = &BailErrorStrategy{} + +//goland:noinspection GoUnusedExportedFunction +func NewBailErrorStrategy() *BailErrorStrategy { + + b := new(BailErrorStrategy) + + b.DefaultErrorStrategy = NewDefaultErrorStrategy() + + return b +} + +// Recover Instead of recovering from exception e, re-panic it wrapped +// in a [ParseCancellationException] so it is not caught by the +// rule func catches. Use Exception.GetCause() to get the +// original [RecognitionException]. +func (b *BailErrorStrategy) Recover(recognizer Parser, e RecognitionException) { + context := recognizer.GetParserRuleContext() + for context != nil { + context.SetException(e) + if parent, ok := context.GetParent().(ParserRuleContext); ok { + context = parent + } else { + context = nil + } + } + recognizer.SetError(NewParseCancellationException()) // TODO: we don't emit e properly +} + +// RecoverInline makes sure we don't attempt to recover inline if the parser +// successfully recovers, it won't panic an exception. +func (b *BailErrorStrategy) RecoverInline(recognizer Parser) Token { + b.Recover(recognizer, NewInputMisMatchException(recognizer)) + + return nil +} + +// Sync makes sure we don't attempt to recover from problems in sub-rules. +func (b *BailErrorStrategy) Sync(_ Parser) { +} diff --git a/prutalgen/internal/antlr/errors.go b/prutalgen/internal/antlr/errors.go new file mode 100644 index 0000000..8f0f2f6 --- /dev/null +++ b/prutalgen/internal/antlr/errors.go @@ -0,0 +1,259 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// The root of the ANTLR exception hierarchy. In general, ANTLR tracks just +// 3 kinds of errors: prediction errors, failed predicate errors, and +// mismatched input errors. In each case, the parser knows where it is +// in the input, where it is in the ATN, the rule invocation stack, +// and what kind of problem occurred. + +type RecognitionException interface { + GetOffendingToken() Token + GetMessage() string + GetInputStream() IntStream +} + +type BaseRecognitionException struct { + message string + recognizer Recognizer + offendingToken Token + offendingState int + ctx RuleContext + input IntStream +} + +func NewBaseRecognitionException(message string, recognizer Recognizer, input IntStream, ctx RuleContext) *BaseRecognitionException { + + // todo + // Error.call(this) + // + // if (!!Error.captureStackTrace) { + // Error.captureStackTrace(this, RecognitionException) + // } else { + // stack := NewError().stack + // } + // TODO: may be able to use - "runtime" func Stack(buf []byte, all bool) int + + t := new(BaseRecognitionException) + + t.message = message + t.recognizer = recognizer + t.input = input + t.ctx = ctx + + // The current Token when an error occurred. Since not all streams + // support accessing symbols by index, we have to track the {@link Token} + // instance itself. + // + t.offendingToken = nil + + // Get the ATN state number the parser was in at the time the error + // occurred. For NoViableAltException and LexerNoViableAltException exceptions, this is the + // DecisionState number. For others, it is the state whose outgoing edge we couldn't Match. + // + t.offendingState = -1 + if t.recognizer != nil { + t.offendingState = t.recognizer.GetState() + } + + return t +} + +func (b *BaseRecognitionException) GetMessage() string { + return b.message +} + +func (b *BaseRecognitionException) GetOffendingToken() Token { + return b.offendingToken +} + +func (b *BaseRecognitionException) GetInputStream() IntStream { + return b.input +} + +//

If the state number is not known, b method returns -1.

+ +// getExpectedTokens gets the set of input symbols which could potentially follow the +// previously Matched symbol at the time this exception was raised. +// +// If the set of expected tokens is not known and could not be computed, +// this method returns nil. +// +// The func returns the set of token types that could potentially follow the current +// state in the {ATN}, or nil if the information is not available. + +func (b *BaseRecognitionException) getExpectedTokens() *IntervalSet { + if b.recognizer != nil { + return b.recognizer.GetATN().getExpectedTokens(b.offendingState, b.ctx) + } + + return nil +} + +func (b *BaseRecognitionException) String() string { + return b.message +} + +type LexerNoViableAltException struct { + *BaseRecognitionException + + startIndex int + deadEndConfigs *ATNConfigSet +} + +func NewLexerNoViableAltException(lexer Lexer, input CharStream, startIndex int, deadEndConfigs *ATNConfigSet) *LexerNoViableAltException { + + l := new(LexerNoViableAltException) + + l.BaseRecognitionException = NewBaseRecognitionException("", lexer, input, nil) + + l.startIndex = startIndex + l.deadEndConfigs = deadEndConfigs + + return l +} + +func (l *LexerNoViableAltException) String() string { + symbol := "" + if l.startIndex >= 0 && l.startIndex < l.input.Size() { + symbol = l.input.(CharStream).GetTextFromInterval(NewInterval(l.startIndex, l.startIndex)) + } + return "LexerNoViableAltException" + symbol +} + +type NoViableAltException struct { + *BaseRecognitionException + + startToken Token + offendingToken Token + ctx ParserRuleContext + deadEndConfigs *ATNConfigSet +} + +// NewNoViableAltException creates an exception indicating that the parser could not decide which of two or more paths +// to take based upon the remaining input. It tracks the starting token +// of the offending input and also knows where the parser was +// in the various paths when the error. +// +// Reported by [ReportNoViableAlternative] +func NewNoViableAltException(recognizer Parser, input TokenStream, startToken Token, offendingToken Token, deadEndConfigs *ATNConfigSet, ctx ParserRuleContext) *NoViableAltException { + + if ctx == nil { + ctx = recognizer.GetParserRuleContext() + } + + if offendingToken == nil { + offendingToken = recognizer.GetCurrentToken() + } + + if startToken == nil { + startToken = recognizer.GetCurrentToken() + } + + if input == nil { + input = recognizer.GetInputStream().(TokenStream) + } + + n := new(NoViableAltException) + n.BaseRecognitionException = NewBaseRecognitionException("", recognizer, input, ctx) + + // Which configurations did we try at input.Index() that couldn't Match + // input.LT(1) + n.deadEndConfigs = deadEndConfigs + + // The token object at the start index the input stream might + // not be buffering tokens so get a reference to it. + // + // At the time the error occurred, of course the stream needs to keep a + // buffer of all the tokens, but later we might not have access to those. + n.startToken = startToken + n.offendingToken = offendingToken + + return n +} + +type InputMisMatchException struct { + *BaseRecognitionException +} + +// NewInputMisMatchException creates an exception that signifies any kind of mismatched input exceptions such as +// when the current input does not Match the expected token. +func NewInputMisMatchException(recognizer Parser) *InputMisMatchException { + + i := new(InputMisMatchException) + i.BaseRecognitionException = NewBaseRecognitionException("", recognizer, recognizer.GetInputStream(), recognizer.GetParserRuleContext()) + + i.offendingToken = recognizer.GetCurrentToken() + + return i + +} + +// FailedPredicateException indicates that a semantic predicate failed during validation. Validation of predicates +// occurs when normally parsing the alternative just like Matching a token. +// Disambiguating predicate evaluation occurs when we test a predicate during +// prediction. +type FailedPredicateException struct { + *BaseRecognitionException + + ruleIndex int + predicateIndex int + predicate string +} + +//goland:noinspection GoUnusedExportedFunction +func NewFailedPredicateException(recognizer Parser, predicate string, message string) *FailedPredicateException { + + f := new(FailedPredicateException) + + f.BaseRecognitionException = NewBaseRecognitionException(f.formatMessage(predicate, message), recognizer, recognizer.GetInputStream(), recognizer.GetParserRuleContext()) + + s := recognizer.GetInterpreter().atn.states[recognizer.GetState()] + trans := s.GetTransitions()[0] + if trans2, ok := trans.(*PredicateTransition); ok { + f.ruleIndex = trans2.ruleIndex + f.predicateIndex = trans2.predIndex + } else { + f.ruleIndex = 0 + f.predicateIndex = 0 + } + f.predicate = predicate + f.offendingToken = recognizer.GetCurrentToken() + + return f +} + +func (f *FailedPredicateException) formatMessage(predicate, message string) string { + if message != "" { + return message + } + + return "failed predicate: {" + predicate + "}?" +} + +type ParseCancellationException struct { +} + +func (p ParseCancellationException) GetOffendingToken() Token { + //TODO implement me + panic("implement me") +} + +func (p ParseCancellationException) GetMessage() string { + //TODO implement me + panic("implement me") +} + +func (p ParseCancellationException) GetInputStream() IntStream { + //TODO implement me + panic("implement me") +} + +func NewParseCancellationException() *ParseCancellationException { + // Error.call(this) + // Error.captureStackTrace(this, ParseCancellationException) + return new(ParseCancellationException) +} diff --git a/prutalgen/internal/antlr/file_stream.go b/prutalgen/internal/antlr/file_stream.go new file mode 100644 index 0000000..5f65f80 --- /dev/null +++ b/prutalgen/internal/antlr/file_stream.go @@ -0,0 +1,67 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "bufio" + "os" +) + +// This is an InputStream that is loaded from a file all at once +// when you construct the object. + +type FileStream struct { + InputStream + filename string +} + +//goland:noinspection GoUnusedExportedFunction +func NewFileStream(fileName string) (*FileStream, error) { + + f, err := os.Open(fileName) + if err != nil { + return nil, err + } + + defer func(f *os.File) { + errF := f.Close() + if errF != nil { + } + }(f) + + reader := bufio.NewReader(f) + fInfo, err := f.Stat() + if err != nil { + return nil, err + } + + fs := &FileStream{ + InputStream: InputStream{ + index: 0, + name: fileName, + }, + filename: fileName, + } + + // Pre-build the buffer and read runes efficiently + // + fs.data = make([]rune, 0, fInfo.Size()) + for { + r, _, err := reader.ReadRune() + if err != nil { + break + } + fs.data = append(fs.data, r) + } + fs.size = len(fs.data) // Size in runes + + // All done. + // + return fs, nil +} + +func (f *FileStream) GetSourceName() string { + return f.filename +} diff --git a/prutalgen/internal/antlr/input_stream.go b/prutalgen/internal/antlr/input_stream.go new file mode 100644 index 0000000..ab4e96b --- /dev/null +++ b/prutalgen/internal/antlr/input_stream.go @@ -0,0 +1,157 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "bufio" + "io" +) + +type InputStream struct { + name string + index int + data []rune + size int +} + +// NewIoStream creates a new input stream from the given io.Reader reader. +// Note that the reader is read completely into memory and so it must actually +// have a stopping point - you cannot pass in a reader on an open-ended source such +// as a socket for instance. +func NewIoStream(reader io.Reader) *InputStream { + + rReader := bufio.NewReader(reader) + + is := &InputStream{ + name: "", + index: 0, + } + + // Pre-build the buffer and read runes reasonably efficiently given that + // we don't exactly know how big the input is. + // + is.data = make([]rune, 0, 512) + for { + r, _, err := rReader.ReadRune() + if err != nil { + break + } + is.data = append(is.data, r) + } + is.size = len(is.data) // number of runes + return is +} + +// NewInputStream creates a new input stream from the given string +func NewInputStream(data string) *InputStream { + + is := &InputStream{ + name: "", + index: 0, + data: []rune(data), // This is actually the most efficient way + } + is.size = len(is.data) // number of runes, but we could also use len(data), which is efficient too + return is +} + +func (is *InputStream) reset() { + is.index = 0 +} + +// Consume moves the input pointer to the next character in the input stream +func (is *InputStream) Consume() { + if is.index >= is.size { + // assert is.LA(1) == TokenEOF + panic("cannot consume EOF") + } + is.index++ +} + +// LA returns the character at the given offset from the start of the input stream +func (is *InputStream) LA(offset int) int { + + if offset == 0 { + return 0 // nil + } + if offset < 0 { + offset++ // e.g., translate LA(-1) to use offset=0 + } + pos := is.index + offset - 1 + + if pos < 0 || pos >= is.size { // invalid + return TokenEOF + } + + return int(is.data[pos]) +} + +// LT returns the character at the given offset from the start of the input stream +func (is *InputStream) LT(offset int) int { + return is.LA(offset) +} + +// Index returns the current offset in to the input stream +func (is *InputStream) Index() int { + return is.index +} + +// Size returns the total number of characters in the input stream +func (is *InputStream) Size() int { + return is.size +} + +// Mark does nothing here as we have entire buffer +func (is *InputStream) Mark() int { + return -1 +} + +// Release does nothing here as we have entire buffer +func (is *InputStream) Release(_ int) { +} + +// Seek the input point to the provided index offset +func (is *InputStream) Seek(index int) { + if index <= is.index { + is.index = index // just jump don't update stream state (line,...) + return + } + // seek forward + is.index = intMin(index, is.size) +} + +// GetText returns the text from the input stream from the start to the stop index +func (is *InputStream) GetText(start int, stop int) string { + if stop >= is.size { + stop = is.size - 1 + } + if start >= is.size { + return "" + } + + return string(is.data[start : stop+1]) +} + +// GetTextFromTokens returns the text from the input stream from the first character of the start token to the last +// character of the stop token +func (is *InputStream) GetTextFromTokens(start, stop Token) string { + if start != nil && stop != nil { + return is.GetTextFromInterval(NewInterval(start.GetTokenIndex(), stop.GetTokenIndex())) + } + + return "" +} + +func (is *InputStream) GetTextFromInterval(i Interval) string { + return is.GetText(i.Start, i.Stop) +} + +func (*InputStream) GetSourceName() string { + return "Obtained from string" +} + +// String returns the entire input stream as a string +func (is *InputStream) String() string { + return string(is.data) +} diff --git a/prutalgen/internal/antlr/int_stream.go b/prutalgen/internal/antlr/int_stream.go new file mode 100644 index 0000000..4778878 --- /dev/null +++ b/prutalgen/internal/antlr/int_stream.go @@ -0,0 +1,16 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +type IntStream interface { + Consume() + LA(int) int + Mark() int + Release(marker int) + Index() int + Seek(index int) + Size() int + GetSourceName() string +} diff --git a/prutalgen/internal/antlr/interval_set.go b/prutalgen/internal/antlr/interval_set.go new file mode 100644 index 0000000..cc50660 --- /dev/null +++ b/prutalgen/internal/antlr/interval_set.go @@ -0,0 +1,330 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "strconv" + "strings" +) + +type Interval struct { + Start int + Stop int +} + +// NewInterval creates a new interval with the given start and stop values. +func NewInterval(start, stop int) Interval { + return Interval{ + Start: start, + Stop: stop, + } +} + +// Contains returns true if the given item is contained within the interval. +func (i Interval) Contains(item int) bool { + return item >= i.Start && item < i.Stop +} + +// String generates a string representation of the interval. +func (i Interval) String() string { + if i.Start == i.Stop-1 { + return strconv.Itoa(i.Start) + } + + return strconv.Itoa(i.Start) + ".." + strconv.Itoa(i.Stop-1) +} + +// Length returns the length of the interval. +func (i Interval) Length() int { + return i.Stop - i.Start +} + +// IntervalSet represents a collection of [Intervals], which may be read-only. +type IntervalSet struct { + intervals []Interval + readOnly bool +} + +// NewIntervalSet creates a new empty, writable, interval set. +func NewIntervalSet() *IntervalSet { + + i := new(IntervalSet) + + i.intervals = nil + i.readOnly = false + + return i +} + +func (i *IntervalSet) Equals(other *IntervalSet) bool { + if len(i.intervals) != len(other.intervals) { + return false + } + + for k, v := range i.intervals { + if v.Start != other.intervals[k].Start || v.Stop != other.intervals[k].Stop { + return false + } + } + + return true +} + +func (i *IntervalSet) first() int { + if len(i.intervals) == 0 { + return TokenInvalidType + } + + return i.intervals[0].Start +} + +func (i *IntervalSet) addOne(v int) { + i.addInterval(NewInterval(v, v+1)) +} + +func (i *IntervalSet) addRange(l, h int) { + i.addInterval(NewInterval(l, h+1)) +} + +func (i *IntervalSet) addInterval(v Interval) { + if i.intervals == nil { + i.intervals = make([]Interval, 0) + i.intervals = append(i.intervals, v) + } else { + // find insert pos + for k, interval := range i.intervals { + // distinct range -> insert + if v.Stop < interval.Start { + i.intervals = append(i.intervals[0:k], append([]Interval{v}, i.intervals[k:]...)...) + return + } else if v.Stop == interval.Start { + i.intervals[k].Start = v.Start + return + } else if v.Start <= interval.Stop { + i.intervals[k] = NewInterval(intMin(interval.Start, v.Start), intMax(interval.Stop, v.Stop)) + + // if not applying to end, merge potential overlaps + if k < len(i.intervals)-1 { + l := i.intervals[k] + r := i.intervals[k+1] + // if r contained in l + if l.Stop >= r.Stop { + i.intervals = append(i.intervals[0:k+1], i.intervals[k+2:]...) + } else if l.Stop >= r.Start { // partial overlap + i.intervals[k] = NewInterval(l.Start, r.Stop) + i.intervals = append(i.intervals[0:k+1], i.intervals[k+2:]...) + } + } + return + } + } + // greater than any exiting + i.intervals = append(i.intervals, v) + } +} + +func (i *IntervalSet) addSet(other *IntervalSet) *IntervalSet { + if other.intervals != nil { + for k := 0; k < len(other.intervals); k++ { + i2 := other.intervals[k] + i.addInterval(NewInterval(i2.Start, i2.Stop)) + } + } + return i +} + +func (i *IntervalSet) complement(start int, stop int) *IntervalSet { + result := NewIntervalSet() + result.addInterval(NewInterval(start, stop+1)) + for j := 0; j < len(i.intervals); j++ { + result.removeRange(i.intervals[j]) + } + return result +} + +func (i *IntervalSet) contains(item int) bool { + if i.intervals == nil { + return false + } + for k := 0; k < len(i.intervals); k++ { + if i.intervals[k].Contains(item) { + return true + } + } + return false +} + +func (i *IntervalSet) length() int { + iLen := 0 + + for _, v := range i.intervals { + iLen += v.Length() + } + + return iLen +} + +func (i *IntervalSet) removeRange(v Interval) { + if v.Start == v.Stop-1 { + i.removeOne(v.Start) + } else if i.intervals != nil { + k := 0 + for n := 0; n < len(i.intervals); n++ { + ni := i.intervals[k] + // intervals are ordered + if v.Stop <= ni.Start { + return + } else if v.Start > ni.Start && v.Stop < ni.Stop { + i.intervals[k] = NewInterval(ni.Start, v.Start) + x := NewInterval(v.Stop, ni.Stop) + // i.intervals.splice(k, 0, x) + i.intervals = append(i.intervals[0:k], append([]Interval{x}, i.intervals[k:]...)...) + return + } else if v.Start <= ni.Start && v.Stop >= ni.Stop { + // i.intervals.splice(k, 1) + i.intervals = append(i.intervals[0:k], i.intervals[k+1:]...) + k = k - 1 // need another pass + } else if v.Start < ni.Stop { + i.intervals[k] = NewInterval(ni.Start, v.Start) + } else if v.Stop < ni.Stop { + i.intervals[k] = NewInterval(v.Stop, ni.Stop) + } + k++ + } + } +} + +func (i *IntervalSet) removeOne(v int) { + if i.intervals != nil { + for k := 0; k < len(i.intervals); k++ { + ki := i.intervals[k] + // intervals i ordered + if v < ki.Start { + return + } else if v == ki.Start && v == ki.Stop-1 { + // i.intervals.splice(k, 1) + i.intervals = append(i.intervals[0:k], i.intervals[k+1:]...) + return + } else if v == ki.Start { + i.intervals[k] = NewInterval(ki.Start+1, ki.Stop) + return + } else if v == ki.Stop-1 { + i.intervals[k] = NewInterval(ki.Start, ki.Stop-1) + return + } else if v < ki.Stop-1 { + x := NewInterval(ki.Start, v) + ki.Start = v + 1 + // i.intervals.splice(k, 0, x) + i.intervals = append(i.intervals[0:k], append([]Interval{x}, i.intervals[k:]...)...) + return + } + } + } +} + +func (i *IntervalSet) String() string { + return i.StringVerbose(nil, nil, false) +} + +func (i *IntervalSet) StringVerbose(literalNames []string, symbolicNames []string, elemsAreChar bool) string { + + if i.intervals == nil { + return "{}" + } else if literalNames != nil || symbolicNames != nil { + return i.toTokenString(literalNames, symbolicNames) + } else if elemsAreChar { + return i.toCharString() + } + + return i.toIndexString() +} + +func (i *IntervalSet) GetIntervals() []Interval { + return i.intervals +} + +func (i *IntervalSet) toCharString() string { + names := make([]string, len(i.intervals)) + + var sb strings.Builder + + for j := 0; j < len(i.intervals); j++ { + v := i.intervals[j] + if v.Stop == v.Start+1 { + if v.Start == TokenEOF { + names = append(names, "") + } else { + sb.WriteByte('\'') + sb.WriteRune(rune(v.Start)) + sb.WriteByte('\'') + names = append(names, sb.String()) + sb.Reset() + } + } else { + sb.WriteByte('\'') + sb.WriteRune(rune(v.Start)) + sb.WriteString("'..'") + sb.WriteRune(rune(v.Stop - 1)) + sb.WriteByte('\'') + names = append(names, sb.String()) + sb.Reset() + } + } + if len(names) > 1 { + return "{" + strings.Join(names, ", ") + "}" + } + + return names[0] +} + +func (i *IntervalSet) toIndexString() string { + + names := make([]string, 0) + for j := 0; j < len(i.intervals); j++ { + v := i.intervals[j] + if v.Stop == v.Start+1 { + if v.Start == TokenEOF { + names = append(names, "") + } else { + names = append(names, strconv.Itoa(v.Start)) + } + } else { + names = append(names, strconv.Itoa(v.Start)+".."+strconv.Itoa(v.Stop-1)) + } + } + if len(names) > 1 { + return "{" + strings.Join(names, ", ") + "}" + } + + return names[0] +} + +func (i *IntervalSet) toTokenString(literalNames []string, symbolicNames []string) string { + names := make([]string, 0) + for _, v := range i.intervals { + for j := v.Start; j < v.Stop; j++ { + names = append(names, i.elementName(literalNames, symbolicNames, j)) + } + } + if len(names) > 1 { + return "{" + strings.Join(names, ", ") + "}" + } + + return names[0] +} + +func (i *IntervalSet) elementName(literalNames []string, symbolicNames []string, a int) string { + if a == TokenEOF { + return "" + } else if a == TokenEpsilon { + return "" + } else { + if a < len(literalNames) && literalNames[a] != "" { + return literalNames[a] + } + + return symbolicNames[a] + } +} diff --git a/prutalgen/internal/antlr/jcollect.go b/prutalgen/internal/antlr/jcollect.go new file mode 100644 index 0000000..6d668f7 --- /dev/null +++ b/prutalgen/internal/antlr/jcollect.go @@ -0,0 +1,684 @@ +package antlr + +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +import ( + "container/list" + "runtime/debug" + "sort" +) + +// Collectable is an interface that a struct should implement if it is to be +// usable as a key in these collections. +type Collectable[T any] interface { + Hash() int + Equals(other Collectable[T]) bool +} + +type Comparator[T any] interface { + Hash1(o T) int + Equals2(T, T) bool +} + +type CollectionSource int +type CollectionDescriptor struct { + SybolicName string + Description string +} + +const ( + UnknownCollection CollectionSource = iota + ATNConfigLookupCollection + ATNStateCollection + DFAStateCollection + ATNConfigCollection + PredictionContextCollection + SemanticContextCollection + ClosureBusyCollection + PredictionVisitedCollection + MergeCacheCollection + PredictionContextCacheCollection + AltSetCollection + ReachSetCollection +) + +var CollectionDescriptors = map[CollectionSource]CollectionDescriptor{ + UnknownCollection: { + SybolicName: "UnknownCollection", + Description: "Unknown collection type. Only used if the target author thought it was an unimportant collection.", + }, + ATNConfigCollection: { + SybolicName: "ATNConfigCollection", + Description: "ATNConfig collection. Used to store the ATNConfigs for a particular state in the ATN." + + "For instance, it is used to store the results of the closure() operation in the ATN.", + }, + ATNConfigLookupCollection: { + SybolicName: "ATNConfigLookupCollection", + Description: "ATNConfigLookup collection. Used to store the ATNConfigs for a particular state in the ATN." + + "This is used to prevent duplicating equivalent states in an ATNConfigurationSet.", + }, + ATNStateCollection: { + SybolicName: "ATNStateCollection", + Description: "ATNState collection. This is used to store the states of the ATN.", + }, + DFAStateCollection: { + SybolicName: "DFAStateCollection", + Description: "DFAState collection. This is used to store the states of the DFA.", + }, + PredictionContextCollection: { + SybolicName: "PredictionContextCollection", + Description: "PredictionContext collection. This is used to store the prediction contexts of the ATN and cache computes.", + }, + SemanticContextCollection: { + SybolicName: "SemanticContextCollection", + Description: "SemanticContext collection. This is used to store the semantic contexts of the ATN.", + }, + ClosureBusyCollection: { + SybolicName: "ClosureBusyCollection", + Description: "ClosureBusy collection. This is used to check and prevent infinite recursion right recursive rules." + + "It stores ATNConfigs that are currently being processed in the closure() operation.", + }, + PredictionVisitedCollection: { + SybolicName: "PredictionVisitedCollection", + Description: "A map that records whether we have visited a particular context when searching through cached entries.", + }, + MergeCacheCollection: { + SybolicName: "MergeCacheCollection", + Description: "A map that records whether we have already merged two particular contexts and can save effort by not repeating it.", + }, + PredictionContextCacheCollection: { + SybolicName: "PredictionContextCacheCollection", + Description: "A map that records whether we have already created a particular context and can save effort by not computing it again.", + }, + AltSetCollection: { + SybolicName: "AltSetCollection", + Description: "Used to eliminate duplicate alternatives in an ATN config set.", + }, + ReachSetCollection: { + SybolicName: "ReachSetCollection", + Description: "Used as merge cache to prevent us needing to compute the merge of two states if we have already done it.", + }, +} + +// JStore implements a container that allows the use of a struct to calculate the key +// for a collection of values akin to map. This is not meant to be a full-blown HashMap but just +// serve the needs of the ANTLR Go runtime. +// +// For ease of porting the logic of the runtime from the master target (Java), this collection +// operates in a similar way to Java, in that it can use any struct that supplies a Hash() and Equals() +// function as the key. The values are stored in a standard go map which internally is a form of hashmap +// itself, the key for the go map is the hash supplied by the key object. The collection is able to deal with +// hash conflicts by using a simple slice of values associated with the hash code indexed bucket. That isn't +// particularly efficient, but it is simple, and it works. As this is specifically for the ANTLR runtime, and +// we understand the requirements, then this is fine - this is not a general purpose collection. +type JStore[T any, C Comparator[T]] struct { + store map[int][]T + len int + comparator Comparator[T] + stats *JStatRec +} + +func NewJStore[T any, C Comparator[T]](comparator Comparator[T], cType CollectionSource, desc string) *JStore[T, C] { + + if comparator == nil { + panic("comparator cannot be nil") + } + + s := &JStore[T, C]{ + store: make(map[int][]T, 1), + comparator: comparator, + } + if collectStats { + s.stats = &JStatRec{ + Source: cType, + Description: desc, + } + + // Track where we created it from if we are being asked to do so + if runtimeConfig.statsTraceStacks { + s.stats.CreateStack = debug.Stack() + } + Statistics.AddJStatRec(s.stats) + } + return s +} + +// Put will store given value in the collection. Note that the key for storage is generated from +// the value itself - this is specifically because that is what ANTLR needs - this would not be useful +// as any kind of general collection. +// +// If the key has a hash conflict, then the value will be added to the slice of values associated with the +// hash, unless the value is already in the slice, in which case the existing value is returned. Value equivalence is +// tested by calling the equals() method on the key. +// +// # If the given value is already present in the store, then the existing value is returned as v and exists is set to true +// +// If the given value is not present in the store, then the value is added to the store and returned as v and exists is set to false. +func (s *JStore[T, C]) Put(value T) (v T, exists bool) { + + if collectStats { + s.stats.Puts++ + } + kh := s.comparator.Hash1(value) + + var hClash bool + for _, v1 := range s.store[kh] { + hClash = true + if s.comparator.Equals2(value, v1) { + if collectStats { + s.stats.PutHits++ + s.stats.PutHashConflicts++ + } + return v1, true + } + if collectStats { + s.stats.PutMisses++ + } + } + if collectStats && hClash { + s.stats.PutHashConflicts++ + } + s.store[kh] = append(s.store[kh], value) + + if collectStats { + if len(s.store[kh]) > s.stats.MaxSlotSize { + s.stats.MaxSlotSize = len(s.store[kh]) + } + } + s.len++ + if collectStats { + s.stats.CurSize = s.len + if s.len > s.stats.MaxSize { + s.stats.MaxSize = s.len + } + } + return value, false +} + +// Get will return the value associated with the key - the type of the key is the same type as the value +// which would not generally be useful, but this is a specific thing for ANTLR where the key is +// generated using the object we are going to store. +func (s *JStore[T, C]) Get(key T) (T, bool) { + if collectStats { + s.stats.Gets++ + } + kh := s.comparator.Hash1(key) + var hClash bool + for _, v := range s.store[kh] { + hClash = true + if s.comparator.Equals2(key, v) { + if collectStats { + s.stats.GetHits++ + s.stats.GetHashConflicts++ + } + return v, true + } + if collectStats { + s.stats.GetMisses++ + } + } + if collectStats { + if hClash { + s.stats.GetHashConflicts++ + } + s.stats.GetNoEnt++ + } + return key, false +} + +// Contains returns true if the given key is present in the store +func (s *JStore[T, C]) Contains(key T) bool { + _, present := s.Get(key) + return present +} + +func (s *JStore[T, C]) SortedSlice(less func(i, j T) bool) []T { + vs := make([]T, 0, len(s.store)) + for _, v := range s.store { + vs = append(vs, v...) + } + sort.Slice(vs, func(i, j int) bool { + return less(vs[i], vs[j]) + }) + + return vs +} + +func (s *JStore[T, C]) Each(f func(T) bool) { + for _, e := range s.store { + for _, v := range e { + f(v) + } + } +} + +func (s *JStore[T, C]) Len() int { + return s.len +} + +func (s *JStore[T, C]) Values() []T { + vs := make([]T, 0, len(s.store)) + for _, e := range s.store { + vs = append(vs, e...) + } + return vs +} + +type entry[K, V any] struct { + key K + val V +} + +type JMap[K, V any, C Comparator[K]] struct { + store map[int][]*entry[K, V] + len int + comparator Comparator[K] + stats *JStatRec +} + +func NewJMap[K, V any, C Comparator[K]](comparator Comparator[K], cType CollectionSource, desc string) *JMap[K, V, C] { + m := &JMap[K, V, C]{ + store: make(map[int][]*entry[K, V], 1), + comparator: comparator, + } + if collectStats { + m.stats = &JStatRec{ + Source: cType, + Description: desc, + } + // Track where we created it from if we are being asked to do so + if runtimeConfig.statsTraceStacks { + m.stats.CreateStack = debug.Stack() + } + Statistics.AddJStatRec(m.stats) + } + return m +} + +func (m *JMap[K, V, C]) Put(key K, val V) (V, bool) { + if collectStats { + m.stats.Puts++ + } + kh := m.comparator.Hash1(key) + + var hClash bool + for _, e := range m.store[kh] { + hClash = true + if m.comparator.Equals2(e.key, key) { + if collectStats { + m.stats.PutHits++ + m.stats.PutHashConflicts++ + } + return e.val, true + } + if collectStats { + m.stats.PutMisses++ + } + } + if collectStats { + if hClash { + m.stats.PutHashConflicts++ + } + } + m.store[kh] = append(m.store[kh], &entry[K, V]{key, val}) + if collectStats { + if len(m.store[kh]) > m.stats.MaxSlotSize { + m.stats.MaxSlotSize = len(m.store[kh]) + } + } + m.len++ + if collectStats { + m.stats.CurSize = m.len + if m.len > m.stats.MaxSize { + m.stats.MaxSize = m.len + } + } + return val, false +} + +func (m *JMap[K, V, C]) Values() []V { + vs := make([]V, 0, len(m.store)) + for _, e := range m.store { + for _, v := range e { + vs = append(vs, v.val) + } + } + return vs +} + +func (m *JMap[K, V, C]) Get(key K) (V, bool) { + if collectStats { + m.stats.Gets++ + } + var none V + kh := m.comparator.Hash1(key) + var hClash bool + for _, e := range m.store[kh] { + hClash = true + if m.comparator.Equals2(e.key, key) { + if collectStats { + m.stats.GetHits++ + m.stats.GetHashConflicts++ + } + return e.val, true + } + if collectStats { + m.stats.GetMisses++ + } + } + if collectStats { + if hClash { + m.stats.GetHashConflicts++ + } + m.stats.GetNoEnt++ + } + return none, false +} + +func (m *JMap[K, V, C]) Len() int { + return m.len +} + +func (m *JMap[K, V, C]) Delete(key K) { + kh := m.comparator.Hash1(key) + for i, e := range m.store[kh] { + if m.comparator.Equals2(e.key, key) { + m.store[kh] = append(m.store[kh][:i], m.store[kh][i+1:]...) + m.len-- + return + } + } +} + +func (m *JMap[K, V, C]) Clear() { + m.store = make(map[int][]*entry[K, V]) +} + +type JPCMap struct { + store *JMap[*PredictionContext, *JMap[*PredictionContext, *PredictionContext, *ObjEqComparator[*PredictionContext]], *ObjEqComparator[*PredictionContext]] + size int + stats *JStatRec +} + +func NewJPCMap(cType CollectionSource, desc string) *JPCMap { + m := &JPCMap{ + store: NewJMap[*PredictionContext, *JMap[*PredictionContext, *PredictionContext, *ObjEqComparator[*PredictionContext]], *ObjEqComparator[*PredictionContext]](pContextEqInst, cType, desc), + } + if collectStats { + m.stats = &JStatRec{ + Source: cType, + Description: desc, + } + // Track where we created it from if we are being asked to do so + if runtimeConfig.statsTraceStacks { + m.stats.CreateStack = debug.Stack() + } + Statistics.AddJStatRec(m.stats) + } + return m +} + +func (pcm *JPCMap) Get(k1, k2 *PredictionContext) (*PredictionContext, bool) { + if collectStats { + pcm.stats.Gets++ + } + // Do we have a map stored by k1? + // + m2, present := pcm.store.Get(k1) + if present { + if collectStats { + pcm.stats.GetHits++ + } + // We found a map of values corresponding to k1, so now we need to look up k2 in that map + // + return m2.Get(k2) + } + if collectStats { + pcm.stats.GetMisses++ + } + return nil, false +} + +func (pcm *JPCMap) Put(k1, k2, v *PredictionContext) { + + if collectStats { + pcm.stats.Puts++ + } + // First does a map already exist for k1? + // + if m2, present := pcm.store.Get(k1); present { + if collectStats { + pcm.stats.PutHits++ + } + _, present = m2.Put(k2, v) + if !present { + pcm.size++ + if collectStats { + pcm.stats.CurSize = pcm.size + if pcm.size > pcm.stats.MaxSize { + pcm.stats.MaxSize = pcm.size + } + } + } + } else { + // No map found for k1, so we create it, add in our value, then store is + // + if collectStats { + pcm.stats.PutMisses++ + m2 = NewJMap[*PredictionContext, *PredictionContext, *ObjEqComparator[*PredictionContext]](pContextEqInst, pcm.stats.Source, pcm.stats.Description+" map entry") + } else { + m2 = NewJMap[*PredictionContext, *PredictionContext, *ObjEqComparator[*PredictionContext]](pContextEqInst, PredictionContextCacheCollection, "map entry") + } + + m2.Put(k2, v) + pcm.store.Put(k1, m2) + pcm.size++ + } +} + +type JPCMap2 struct { + store map[int][]JPCEntry + size int + stats *JStatRec +} + +type JPCEntry struct { + k1, k2, v *PredictionContext +} + +func NewJPCMap2(cType CollectionSource, desc string) *JPCMap2 { + m := &JPCMap2{ + store: make(map[int][]JPCEntry, 1000), + } + if collectStats { + m.stats = &JStatRec{ + Source: cType, + Description: desc, + } + // Track where we created it from if we are being asked to do so + if runtimeConfig.statsTraceStacks { + m.stats.CreateStack = debug.Stack() + } + Statistics.AddJStatRec(m.stats) + } + return m +} + +func dHash(k1, k2 *PredictionContext) int { + return k1.cachedHash*31 + k2.cachedHash +} + +func (pcm *JPCMap2) Get(k1, k2 *PredictionContext) (*PredictionContext, bool) { + if collectStats { + pcm.stats.Gets++ + } + + h := dHash(k1, k2) + var hClash bool + for _, e := range pcm.store[h] { + hClash = true + if e.k1.Equals(k1) && e.k2.Equals(k2) { + if collectStats { + pcm.stats.GetHits++ + pcm.stats.GetHashConflicts++ + } + return e.v, true + } + if collectStats { + pcm.stats.GetMisses++ + } + } + if collectStats { + if hClash { + pcm.stats.GetHashConflicts++ + } + pcm.stats.GetNoEnt++ + } + return nil, false +} + +func (pcm *JPCMap2) Put(k1, k2, v *PredictionContext) (*PredictionContext, bool) { + if collectStats { + pcm.stats.Puts++ + } + h := dHash(k1, k2) + var hClash bool + for _, e := range pcm.store[h] { + hClash = true + if e.k1.Equals(k1) && e.k2.Equals(k2) { + if collectStats { + pcm.stats.PutHits++ + pcm.stats.PutHashConflicts++ + } + return e.v, true + } + if collectStats { + pcm.stats.PutMisses++ + } + } + if collectStats { + if hClash { + pcm.stats.PutHashConflicts++ + } + } + pcm.store[h] = append(pcm.store[h], JPCEntry{k1, k2, v}) + pcm.size++ + if collectStats { + pcm.stats.CurSize = pcm.size + if pcm.size > pcm.stats.MaxSize { + pcm.stats.MaxSize = pcm.size + } + } + return nil, false +} + +type VisitEntry struct { + k *PredictionContext + v *PredictionContext +} +type VisitRecord struct { + store map[*PredictionContext]*PredictionContext + len int + stats *JStatRec +} + +type VisitList struct { + cache *list.List + lock RWMutex +} + +var visitListPool = VisitList{ + cache: list.New(), + lock: RWMutex{}, +} + +// NewVisitRecord returns a new VisitRecord instance from the pool if available. +// Note that this "map" uses a pointer as a key because we are emulating the behavior of +// IdentityHashMap in Java, which uses the `==` operator to compare whether the keys are equal, +// which means is the key the same reference to an object rather than is it .equals() to another +// object. +func NewVisitRecord() *VisitRecord { + visitListPool.lock.Lock() + el := visitListPool.cache.Front() + defer visitListPool.lock.Unlock() + var vr *VisitRecord + if el == nil { + vr = &VisitRecord{ + store: make(map[*PredictionContext]*PredictionContext), + } + if collectStats { + vr.stats = &JStatRec{ + Source: PredictionContextCacheCollection, + Description: "VisitRecord", + } + // Track where we created it from if we are being asked to do so + if runtimeConfig.statsTraceStacks { + vr.stats.CreateStack = debug.Stack() + } + } + } else { + vr = el.Value.(*VisitRecord) + visitListPool.cache.Remove(el) + vr.store = make(map[*PredictionContext]*PredictionContext) + } + if collectStats { + Statistics.AddJStatRec(vr.stats) + } + return vr +} + +func (vr *VisitRecord) Release() { + vr.len = 0 + vr.store = nil + if collectStats { + vr.stats.MaxSize = 0 + vr.stats.CurSize = 0 + vr.stats.Gets = 0 + vr.stats.GetHits = 0 + vr.stats.GetMisses = 0 + vr.stats.GetHashConflicts = 0 + vr.stats.GetNoEnt = 0 + vr.stats.Puts = 0 + vr.stats.PutHits = 0 + vr.stats.PutMisses = 0 + vr.stats.PutHashConflicts = 0 + vr.stats.MaxSlotSize = 0 + } + visitListPool.lock.Lock() + visitListPool.cache.PushBack(vr) + visitListPool.lock.Unlock() +} + +func (vr *VisitRecord) Get(k *PredictionContext) (*PredictionContext, bool) { + if collectStats { + vr.stats.Gets++ + } + v := vr.store[k] + if v != nil { + if collectStats { + vr.stats.GetHits++ + } + return v, true + } + if collectStats { + vr.stats.GetNoEnt++ + } + return nil, false +} + +func (vr *VisitRecord) Put(k, v *PredictionContext) (*PredictionContext, bool) { + if collectStats { + vr.stats.Puts++ + } + vr.store[k] = v + vr.len++ + if collectStats { + vr.stats.CurSize = vr.len + if vr.len > vr.stats.MaxSize { + vr.stats.MaxSize = vr.len + } + } + return v, false +} diff --git a/prutalgen/internal/antlr/lexer.go b/prutalgen/internal/antlr/lexer.go new file mode 100644 index 0000000..e5594b2 --- /dev/null +++ b/prutalgen/internal/antlr/lexer.go @@ -0,0 +1,426 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" +) + +// A lexer is recognizer that draws input symbols from a character stream. +// lexer grammars result in a subclass of this object. A Lexer object +// uses simplified Match() and error recovery mechanisms in the interest +// of speed. +/// + +type Lexer interface { + TokenSource + Recognizer + + Emit() Token + + SetChannel(int) + PushMode(int) + PopMode() int + SetType(int) + SetMode(int) +} + +type BaseLexer struct { + *BaseRecognizer + + Interpreter ILexerATNSimulator + TokenStartCharIndex int + TokenStartLine int + TokenStartColumn int + ActionType int + Virt Lexer // The most derived lexer implementation. Allows virtual method calls. + + input CharStream + factory TokenFactory + tokenFactorySourcePair *TokenSourceCharStreamPair + token Token + hitEOF bool + channel int + thetype int + modeStack IntStack + mode int + text string +} + +func NewBaseLexer(input CharStream) *BaseLexer { + + lexer := new(BaseLexer) + + lexer.BaseRecognizer = NewBaseRecognizer() + + lexer.input = input + lexer.factory = CommonTokenFactoryDEFAULT + lexer.tokenFactorySourcePair = &TokenSourceCharStreamPair{lexer, input} + + lexer.Virt = lexer + + lexer.Interpreter = nil // child classes must populate it + + // The goal of all lexer rules/methods is to create a token object. + // l is an instance variable as multiple rules may collaborate to + // create a single token. NextToken will return l object after + // Matching lexer rule(s). If you subclass to allow multiple token + // emissions, then set l to the last token to be Matched or + // something non nil so that the auto token emit mechanism will not + // emit another token. + lexer.token = nil + + // What character index in the stream did the current token start at? + // Needed, for example, to get the text for current token. Set at + // the start of NextToken. + lexer.TokenStartCharIndex = -1 + + // The line on which the first character of the token resides/// + lexer.TokenStartLine = -1 + + // The character position of first character within the line/// + lexer.TokenStartColumn = -1 + + // Once we see EOF on char stream, next token will be EOF. + // If you have DONE : EOF then you see DONE EOF. + lexer.hitEOF = false + + // The channel number for the current token/// + lexer.channel = TokenDefaultChannel + + // The token type for the current token/// + lexer.thetype = TokenInvalidType + + lexer.modeStack = make([]int, 0) + lexer.mode = LexerDefaultMode + + // You can set the text for the current token to override what is in + // the input char buffer. Use setText() or can set l instance var. + // / + lexer.text = "" + + return lexer +} + +const ( + LexerDefaultMode = 0 + LexerMore = -2 + LexerSkip = -3 +) + +//goland:noinspection GoUnusedConst +const ( + LexerDefaultTokenChannel = TokenDefaultChannel + LexerHidden = TokenHiddenChannel + LexerMinCharValue = 0x0000 + LexerMaxCharValue = 0x10FFFF +) + +func (b *BaseLexer) Reset() { + // wack Lexer state variables + if b.input != nil { + b.input.Seek(0) // rewind the input + } + b.token = nil + b.thetype = TokenInvalidType + b.channel = TokenDefaultChannel + b.TokenStartCharIndex = -1 + b.TokenStartColumn = -1 + b.TokenStartLine = -1 + b.text = "" + + b.hitEOF = false + b.mode = LexerDefaultMode + b.modeStack = make([]int, 0) + + b.Interpreter.reset() +} + +func (b *BaseLexer) GetInterpreter() ILexerATNSimulator { + return b.Interpreter +} + +func (b *BaseLexer) GetInputStream() CharStream { + return b.input +} + +func (b *BaseLexer) GetSourceName() string { + return b.GrammarFileName +} + +func (b *BaseLexer) SetChannel(v int) { + b.channel = v +} + +func (b *BaseLexer) GetTokenFactory() TokenFactory { + return b.factory +} + +func (b *BaseLexer) setTokenFactory(f TokenFactory) { + b.factory = f +} + +func (b *BaseLexer) safeMatch() (ret int) { + defer func() { + if e := recover(); e != nil { + if re, ok := e.(RecognitionException); ok { + b.notifyListeners(re) // Report error + b.Recover(re) + ret = LexerSkip // default + } + } + }() + + return b.Interpreter.Match(b.input, b.mode) +} + +// NextToken returns a token from the lexer input source i.e., Match a token on the source char stream. +func (b *BaseLexer) NextToken() Token { + if b.input == nil { + panic("NextToken requires a non-nil input stream.") + } + + tokenStartMarker := b.input.Mark() + + // previously in finally block + defer func() { + // make sure we release marker after Match or + // unbuffered char stream will keep buffering + b.input.Release(tokenStartMarker) + }() + + for { + if b.hitEOF { + b.EmitEOF() + return b.token + } + b.token = nil + b.channel = TokenDefaultChannel + b.TokenStartCharIndex = b.input.Index() + b.TokenStartColumn = b.Interpreter.GetCharPositionInLine() + b.TokenStartLine = b.Interpreter.GetLine() + b.text = "" + continueOuter := false + for { + b.thetype = TokenInvalidType + + ttype := b.safeMatch() // Defaults to LexerSkip + + if b.input.LA(1) == TokenEOF { + b.hitEOF = true + } + if b.thetype == TokenInvalidType { + b.thetype = ttype + } + if b.thetype == LexerSkip { + continueOuter = true + break + } + if b.thetype != LexerMore { + break + } + } + + if continueOuter { + continue + } + if b.token == nil { + b.Virt.Emit() + } + return b.token + } +} + +// Skip instructs the lexer to Skip creating a token for current lexer rule +// and look for another token. [NextToken] knows to keep looking when +// a lexer rule finishes with token set to [SKIPTOKEN]. Recall that +// if token==nil at end of any token rule, it creates one for you +// and emits it. +func (b *BaseLexer) Skip() { + b.thetype = LexerSkip +} + +func (b *BaseLexer) More() { + b.thetype = LexerMore +} + +// SetMode changes the lexer to a new mode. The lexer will use this mode from hereon in and the rules for that mode +// will be in force. +func (b *BaseLexer) SetMode(m int) { + b.mode = m +} + +// PushMode saves the current lexer mode so that it can be restored later. See [PopMode], then sets the +// current lexer mode to the supplied mode m. +func (b *BaseLexer) PushMode(m int) { + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("pushMode " + strconv.Itoa(m)) + } + b.modeStack.Push(b.mode) + b.mode = m +} + +// PopMode restores the lexer mode saved by a call to [PushMode]. It is a panic error if there is no saved mode to +// return to. +func (b *BaseLexer) PopMode() int { + if len(b.modeStack) == 0 { + panic("Empty Stack") + } + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("popMode back to " + fmt.Sprint(b.modeStack[0:len(b.modeStack)-1])) + } + i, _ := b.modeStack.Pop() + b.mode = i + return b.mode +} + +func (b *BaseLexer) inputStream() CharStream { + return b.input +} + +// SetInputStream resets the lexer input stream and associated lexer state. +func (b *BaseLexer) SetInputStream(input CharStream) { + b.input = nil + b.tokenFactorySourcePair = &TokenSourceCharStreamPair{b, b.input} + b.Reset() + b.input = input + b.tokenFactorySourcePair = &TokenSourceCharStreamPair{b, b.input} +} + +func (b *BaseLexer) GetTokenSourceCharStreamPair() *TokenSourceCharStreamPair { + return b.tokenFactorySourcePair +} + +// EmitToken by default does not support multiple emits per [NextToken] invocation +// for efficiency reasons. Subclass and override this func, [NextToken], +// and [GetToken] (to push tokens into a list and pull from that list +// rather than a single variable as this implementation does). +func (b *BaseLexer) EmitToken(token Token) { + b.token = token +} + +// Emit is the standard method called to automatically emit a token at the +// outermost lexical rule. The token object should point into the +// char buffer start..stop. If there is a text override in 'text', +// use that to set the token's text. Override this method to emit +// custom [Token] objects or provide a new factory. +// / +func (b *BaseLexer) Emit() Token { + t := b.factory.Create(b.tokenFactorySourcePair, b.thetype, b.text, b.channel, b.TokenStartCharIndex, b.GetCharIndex()-1, b.TokenStartLine, b.TokenStartColumn) + b.EmitToken(t) + return t +} + +// EmitEOF emits an EOF token. By default, this is the last token emitted +func (b *BaseLexer) EmitEOF() Token { + cpos := b.GetCharPositionInLine() + lpos := b.GetLine() + eof := b.factory.Create(b.tokenFactorySourcePair, TokenEOF, "", TokenDefaultChannel, b.input.Index(), b.input.Index()-1, lpos, cpos) + b.EmitToken(eof) + return eof +} + +// GetCharPositionInLine returns the current position in the current line as far as the lexer is concerned. +func (b *BaseLexer) GetCharPositionInLine() int { + return b.Interpreter.GetCharPositionInLine() +} + +func (b *BaseLexer) GetLine() int { + return b.Interpreter.GetLine() +} + +func (b *BaseLexer) GetType() int { + return b.thetype +} + +func (b *BaseLexer) SetType(t int) { + b.thetype = t +} + +// GetCharIndex returns the index of the current character of lookahead +func (b *BaseLexer) GetCharIndex() int { + return b.input.Index() +} + +// GetText returns the text Matched so far for the current token or any text override. +func (b *BaseLexer) GetText() string { + if b.text != "" { + return b.text + } + + return b.Interpreter.GetText(b.input) +} + +// SetText sets the complete text of this token; it wipes any previous changes to the text. +func (b *BaseLexer) SetText(text string) { + b.text = text +} + +// GetATN returns the ATN used by the lexer. +func (b *BaseLexer) GetATN() *ATN { + return b.Interpreter.ATN() +} + +// GetAllTokens returns a list of all [Token] objects in input char stream. +// Forces a load of all tokens that can be made from the input char stream. +// +// Does not include EOF token. +func (b *BaseLexer) GetAllTokens() []Token { + vl := b.Virt + tokens := make([]Token, 0) + t := vl.NextToken() + for t.GetTokenType() != TokenEOF { + tokens = append(tokens, t) + t = vl.NextToken() + } + return tokens +} + +func (b *BaseLexer) notifyListeners(e RecognitionException) { + start := b.TokenStartCharIndex + stop := b.input.Index() + text := b.input.GetTextFromInterval(NewInterval(start, stop)) + msg := "token recognition error at: '" + text + "'" + listener := b.GetErrorListenerDispatch() + listener.SyntaxError(b, nil, b.TokenStartLine, b.TokenStartColumn, msg, e) +} + +func (b *BaseLexer) getErrorDisplayForChar(c rune) string { + if c == TokenEOF { + return "" + } else if c == '\n' { + return "\\n" + } else if c == '\t' { + return "\\t" + } else if c == '\r' { + return "\\r" + } else { + return string(c) + } +} + +func (b *BaseLexer) getCharErrorDisplay(c rune) string { + return "'" + b.getErrorDisplayForChar(c) + "'" +} + +// Recover can normally Match any char in its vocabulary after Matching +// a token, so here we do the easy thing and just kill a character and hope +// it all works out. You can instead use the rule invocation stack +// to do sophisticated error recovery if you are in a fragment rule. +// +// In general, lexers should not need to recover and should have rules that cover any eventuality, such as +// a character that makes no sense to the recognizer. +func (b *BaseLexer) Recover(re RecognitionException) { + if b.input.LA(1) != TokenEOF { + if _, ok := re.(*LexerNoViableAltException); ok { + // Skip a char and try again + b.Interpreter.Consume(b.input) + } else { + // TODO: Do we lose character or line position information? + b.input.Consume() + } + } +} diff --git a/prutalgen/internal/antlr/lexer_action.go b/prutalgen/internal/antlr/lexer_action.go new file mode 100644 index 0000000..eaa7393 --- /dev/null +++ b/prutalgen/internal/antlr/lexer_action.go @@ -0,0 +1,452 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import "strconv" + +const ( + // LexerActionTypeChannel represents a [LexerChannelAction] action. + LexerActionTypeChannel = 0 + + // LexerActionTypeCustom represents a [LexerCustomAction] action. + LexerActionTypeCustom = 1 + + // LexerActionTypeMode represents a [LexerModeAction] action. + LexerActionTypeMode = 2 + + // LexerActionTypeMore represents a [LexerMoreAction] action. + LexerActionTypeMore = 3 + + // LexerActionTypePopMode represents a [LexerPopModeAction] action. + LexerActionTypePopMode = 4 + + // LexerActionTypePushMode represents a [LexerPushModeAction] action. + LexerActionTypePushMode = 5 + + // LexerActionTypeSkip represents a [LexerSkipAction] action. + LexerActionTypeSkip = 6 + + // LexerActionTypeType represents a [LexerTypeAction] action. + LexerActionTypeType = 7 +) + +type LexerAction interface { + getActionType() int + getIsPositionDependent() bool + execute(lexer Lexer) + Hash() int + Equals(other LexerAction) bool +} + +type BaseLexerAction struct { + actionType int + isPositionDependent bool +} + +func NewBaseLexerAction(action int) *BaseLexerAction { + la := new(BaseLexerAction) + + la.actionType = action + la.isPositionDependent = false + + return la +} + +func (b *BaseLexerAction) execute(_ Lexer) { + panic("Not implemented") +} + +func (b *BaseLexerAction) getActionType() int { + return b.actionType +} + +func (b *BaseLexerAction) getIsPositionDependent() bool { + return b.isPositionDependent +} + +func (b *BaseLexerAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, b.actionType) + return murmurFinish(h, 1) +} + +func (b *BaseLexerAction) Equals(other LexerAction) bool { + return b.actionType == other.getActionType() +} + +// LexerSkipAction implements the [BaseLexerAction.Skip] lexer action by calling [Lexer.Skip]. +// +// The Skip command does not have any parameters, so this action is +// implemented as a singleton instance exposed by the [LexerSkipActionINSTANCE]. +type LexerSkipAction struct { + *BaseLexerAction +} + +func NewLexerSkipAction() *LexerSkipAction { + la := new(LexerSkipAction) + la.BaseLexerAction = NewBaseLexerAction(LexerActionTypeSkip) + return la +} + +// LexerSkipActionINSTANCE provides a singleton instance of this parameterless lexer action. +var LexerSkipActionINSTANCE = NewLexerSkipAction() + +func (l *LexerSkipAction) execute(lexer Lexer) { + lexer.Skip() +} + +// String returns a string representation of the current [LexerSkipAction]. +func (l *LexerSkipAction) String() string { + return "skip" +} + +func (b *LexerSkipAction) Equals(other LexerAction) bool { + return other.getActionType() == LexerActionTypeSkip +} + +// Implements the {@code type} lexer action by calling {@link Lexer//setType} +// +// with the assigned type. +type LexerTypeAction struct { + *BaseLexerAction + + thetype int +} + +func NewLexerTypeAction(thetype int) *LexerTypeAction { + l := new(LexerTypeAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypeType) + l.thetype = thetype + return l +} + +func (l *LexerTypeAction) execute(lexer Lexer) { + lexer.SetType(l.thetype) +} + +func (l *LexerTypeAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.actionType) + h = murmurUpdate(h, l.thetype) + return murmurFinish(h, 2) +} + +func (l *LexerTypeAction) Equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerTypeAction); !ok { + return false + } else { + return l.thetype == other.(*LexerTypeAction).thetype + } +} + +func (l *LexerTypeAction) String() string { + return "actionType(" + strconv.Itoa(l.thetype) + ")" +} + +// LexerPushModeAction implements the pushMode lexer action by calling +// [Lexer.pushMode] with the assigned mode. +type LexerPushModeAction struct { + *BaseLexerAction + mode int +} + +func NewLexerPushModeAction(mode int) *LexerPushModeAction { + + l := new(LexerPushModeAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypePushMode) + + l.mode = mode + return l +} + +//

This action is implemented by calling {@link Lexer//pushMode} with the +// value provided by {@link //getMode}.

+func (l *LexerPushModeAction) execute(lexer Lexer) { + lexer.PushMode(l.mode) +} + +func (l *LexerPushModeAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.actionType) + h = murmurUpdate(h, l.mode) + return murmurFinish(h, 2) +} + +func (l *LexerPushModeAction) Equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerPushModeAction); !ok { + return false + } else { + return l.mode == other.(*LexerPushModeAction).mode + } +} + +func (l *LexerPushModeAction) String() string { + return "pushMode(" + strconv.Itoa(l.mode) + ")" +} + +// LexerPopModeAction implements the popMode lexer action by calling [Lexer.popMode]. +// +// The popMode command does not have any parameters, so this action is +// implemented as a singleton instance exposed by [LexerPopModeActionINSTANCE] +type LexerPopModeAction struct { + *BaseLexerAction +} + +func NewLexerPopModeAction() *LexerPopModeAction { + + l := new(LexerPopModeAction) + + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypePopMode) + + return l +} + +var LexerPopModeActionINSTANCE = NewLexerPopModeAction() + +//

This action is implemented by calling {@link Lexer//popMode}.

+func (l *LexerPopModeAction) execute(lexer Lexer) { + lexer.PopMode() +} + +func (l *LexerPopModeAction) String() string { + return "popMode" +} + +// Implements the {@code more} lexer action by calling {@link Lexer//more}. +// +//

The {@code more} command does not have any parameters, so l action is +// implemented as a singleton instance exposed by {@link //INSTANCE}.

+ +type LexerMoreAction struct { + *BaseLexerAction +} + +func NewLexerMoreAction() *LexerMoreAction { + l := new(LexerMoreAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypeMore) + + return l +} + +var LexerMoreActionINSTANCE = NewLexerMoreAction() + +//

This action is implemented by calling {@link Lexer//popMode}.

+func (l *LexerMoreAction) execute(lexer Lexer) { + lexer.More() +} + +func (l *LexerMoreAction) String() string { + return "more" +} + +// LexerModeAction implements the mode lexer action by calling [Lexer.mode] with +// the assigned mode. +type LexerModeAction struct { + *BaseLexerAction + mode int +} + +func NewLexerModeAction(mode int) *LexerModeAction { + l := new(LexerModeAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypeMode) + l.mode = mode + return l +} + +//

This action is implemented by calling {@link Lexer//mode} with the +// value provided by {@link //getMode}.

+func (l *LexerModeAction) execute(lexer Lexer) { + lexer.SetMode(l.mode) +} + +func (l *LexerModeAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.actionType) + h = murmurUpdate(h, l.mode) + return murmurFinish(h, 2) +} + +func (l *LexerModeAction) Equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerModeAction); !ok { + return false + } else { + return l.mode == other.(*LexerModeAction).mode + } +} + +func (l *LexerModeAction) String() string { + return "mode(" + strconv.Itoa(l.mode) + ")" +} + +// Executes a custom lexer action by calling {@link Recognizer//action} with the +// rule and action indexes assigned to the custom action. The implementation of +// a custom action is added to the generated code for the lexer in an override +// of {@link Recognizer//action} when the grammar is compiled. +// +//

This class may represent embedded actions created with the {...} +// syntax in ANTLR 4, as well as actions created for lexer commands where the +// command argument could not be evaluated when the grammar was compiled.

+ +// Constructs a custom lexer action with the specified rule and action +// indexes. +// +// @param ruleIndex The rule index to use for calls to +// {@link Recognizer//action}. +// @param actionIndex The action index to use for calls to +// {@link Recognizer//action}. + +type LexerCustomAction struct { + *BaseLexerAction + ruleIndex, actionIndex int +} + +func NewLexerCustomAction(ruleIndex, actionIndex int) *LexerCustomAction { + l := new(LexerCustomAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypeCustom) + l.ruleIndex = ruleIndex + l.actionIndex = actionIndex + l.isPositionDependent = true + return l +} + +//

Custom actions are implemented by calling {@link Lexer//action} with the +// appropriate rule and action indexes.

+func (l *LexerCustomAction) execute(lexer Lexer) { + lexer.Action(nil, l.ruleIndex, l.actionIndex) +} + +func (l *LexerCustomAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.actionType) + h = murmurUpdate(h, l.ruleIndex) + h = murmurUpdate(h, l.actionIndex) + return murmurFinish(h, 3) +} + +func (l *LexerCustomAction) Equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerCustomAction); !ok { + return false + } else { + return l.ruleIndex == other.(*LexerCustomAction).ruleIndex && + l.actionIndex == other.(*LexerCustomAction).actionIndex + } +} + +// LexerChannelAction implements the channel lexer action by calling +// [Lexer.setChannel] with the assigned channel. +// +// Constructs a new channel action with the specified channel value. +type LexerChannelAction struct { + *BaseLexerAction + channel int +} + +// NewLexerChannelAction creates a channel lexer action by calling +// [Lexer.setChannel] with the assigned channel. +// +// Constructs a new channel action with the specified channel value. +func NewLexerChannelAction(channel int) *LexerChannelAction { + l := new(LexerChannelAction) + l.BaseLexerAction = NewBaseLexerAction(LexerActionTypeChannel) + l.channel = channel + return l +} + +//

This action is implemented by calling {@link Lexer//setChannel} with the +// value provided by {@link //getChannel}.

+func (l *LexerChannelAction) execute(lexer Lexer) { + lexer.SetChannel(l.channel) +} + +func (l *LexerChannelAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.actionType) + h = murmurUpdate(h, l.channel) + return murmurFinish(h, 2) +} + +func (l *LexerChannelAction) Equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerChannelAction); !ok { + return false + } else { + return l.channel == other.(*LexerChannelAction).channel + } +} + +func (l *LexerChannelAction) String() string { + return "channel(" + strconv.Itoa(l.channel) + ")" +} + +// This implementation of {@link LexerAction} is used for tracking input offsets +// for position-dependent actions within a {@link LexerActionExecutor}. +// +//

This action is not serialized as part of the ATN, and is only required for +// position-dependent lexer actions which appear at a location other than the +// end of a rule. For more information about DFA optimizations employed for +// lexer actions, see {@link LexerActionExecutor//append} and +// {@link LexerActionExecutor//fixOffsetBeforeMatch}.

+ +type LexerIndexedCustomAction struct { + *BaseLexerAction + offset int + lexerAction LexerAction + isPositionDependent bool +} + +// NewLexerIndexedCustomAction constructs a new indexed custom action by associating a character offset +// with a [LexerAction]. +// +// Note: This class is only required for lexer actions for which +// [LexerAction.isPositionDependent] returns true. +// +// The offset points into the input [CharStream], relative to +// the token start index, at which the specified lexerAction should be +// executed. +func NewLexerIndexedCustomAction(offset int, lexerAction LexerAction) *LexerIndexedCustomAction { + + l := new(LexerIndexedCustomAction) + l.BaseLexerAction = NewBaseLexerAction(lexerAction.getActionType()) + + l.offset = offset + l.lexerAction = lexerAction + l.isPositionDependent = true + + return l +} + +//

This method calls {@link //execute} on the result of {@link //getAction} +// using the provided {@code lexer}.

+func (l *LexerIndexedCustomAction) execute(lexer Lexer) { + // assume the input stream position was properly set by the calling code + l.lexerAction.execute(lexer) +} + +func (l *LexerIndexedCustomAction) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, l.offset) + h = murmurUpdate(h, l.lexerAction.Hash()) + return murmurFinish(h, 2) +} + +func (l *LexerIndexedCustomAction) equals(other LexerAction) bool { + if l == other { + return true + } else if _, ok := other.(*LexerIndexedCustomAction); !ok { + return false + } else { + return l.offset == other.(*LexerIndexedCustomAction).offset && + l.lexerAction.Equals(other.(*LexerIndexedCustomAction).lexerAction) + } +} diff --git a/prutalgen/internal/antlr/lexer_action_executor.go b/prutalgen/internal/antlr/lexer_action_executor.go new file mode 100644 index 0000000..76f943f --- /dev/null +++ b/prutalgen/internal/antlr/lexer_action_executor.go @@ -0,0 +1,174 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// Represents an executor for a sequence of lexer actions which traversed during +// the Matching operation of a lexer rule (token). +// +//

The executor tracks position information for position-dependent lexer actions +// efficiently, ensuring that actions appearing only at the end of the rule do +// not cause bloating of the {@link DFA} created for the lexer.

+ +type LexerActionExecutor struct { + lexerActions []LexerAction + cachedHash int +} + +func NewLexerActionExecutor(lexerActions []LexerAction) *LexerActionExecutor { + + if lexerActions == nil { + lexerActions = make([]LexerAction, 0) + } + + l := new(LexerActionExecutor) + + l.lexerActions = lexerActions + + // Caches the result of {@link //hashCode} since the hash code is an element + // of the performance-critical {@link ATNConfig//hashCode} operation. + l.cachedHash = murmurInit(0) + for _, a := range lexerActions { + l.cachedHash = murmurUpdate(l.cachedHash, a.Hash()) + } + l.cachedHash = murmurFinish(l.cachedHash, len(lexerActions)) + + return l +} + +// LexerActionExecutorappend creates a [LexerActionExecutor] which executes the actions for +// the input [LexerActionExecutor] followed by a specified +// [LexerAction]. +// TODO: This does not match the Java code +func LexerActionExecutorappend(lexerActionExecutor *LexerActionExecutor, lexerAction LexerAction) *LexerActionExecutor { + if lexerActionExecutor == nil { + return NewLexerActionExecutor([]LexerAction{lexerAction}) + } + + return NewLexerActionExecutor(append(lexerActionExecutor.lexerActions, lexerAction)) +} + +// fixOffsetBeforeMatch creates a [LexerActionExecutor] which encodes the current offset +// for position-dependent lexer actions. +// +// Normally, when the executor encounters lexer actions where +// [LexerAction.isPositionDependent] returns true, it calls +// [IntStream.Seek] on the input [CharStream] to set the input +// position to the end of the current token. This behavior provides +// for efficient [DFA] representation of lexer actions which appear at the end +// of a lexer rule, even when the lexer rule Matches a variable number of +// characters. +// +// Prior to traversing a Match transition in the [ATN], the current offset +// from the token start index is assigned to all position-dependent lexer +// actions which have not already been assigned a fixed offset. By storing +// the offsets relative to the token start index, the [DFA] representation of +// lexer actions which appear in the middle of tokens remains efficient due +// to sharing among tokens of the same Length, regardless of their absolute +// position in the input stream. +// +// If the current executor already has offsets assigned to all +// position-dependent lexer actions, the method returns this instance. +// +// The offset is assigned to all position-dependent +// lexer actions which do not already have offsets assigned. +// +// The func returns a [LexerActionExecutor] that stores input stream offsets +// for all position-dependent lexer actions. +func (l *LexerActionExecutor) fixOffsetBeforeMatch(offset int) *LexerActionExecutor { + var updatedLexerActions []LexerAction + for i := 0; i < len(l.lexerActions); i++ { + _, ok := l.lexerActions[i].(*LexerIndexedCustomAction) + if l.lexerActions[i].getIsPositionDependent() && !ok { + if updatedLexerActions == nil { + updatedLexerActions = make([]LexerAction, 0, len(l.lexerActions)) + updatedLexerActions = append(updatedLexerActions, l.lexerActions...) + } + updatedLexerActions[i] = NewLexerIndexedCustomAction(offset, l.lexerActions[i]) + } + } + if updatedLexerActions == nil { + return l + } + + return NewLexerActionExecutor(updatedLexerActions) +} + +// Execute the actions encapsulated by l executor within the context of a +// particular {@link Lexer}. +// +//

This method calls {@link IntStream//seek} to set the position of the +// {@code input} {@link CharStream} prior to calling +// {@link LexerAction//execute} on a position-dependent action. Before the +// method returns, the input position will be restored to the same position +// it was in when the method was invoked.

+// +// @param lexer The lexer instance. +// @param input The input stream which is the source for the current token. +// When l method is called, the current {@link IntStream//index} for +// {@code input} should be the start of the following token, i.e. 1 +// character past the end of the current token. +// @param startIndex The token start index. This value may be passed to +// {@link IntStream//seek} to set the {@code input} position to the beginning +// of the token. +// / +func (l *LexerActionExecutor) execute(lexer Lexer, input CharStream, startIndex int) { + requiresSeek := false + stopIndex := input.Index() + + defer func() { + if requiresSeek { + input.Seek(stopIndex) + } + }() + + for i := 0; i < len(l.lexerActions); i++ { + lexerAction := l.lexerActions[i] + if la, ok := lexerAction.(*LexerIndexedCustomAction); ok { + offset := la.offset + input.Seek(startIndex + offset) + lexerAction = la.lexerAction + requiresSeek = (startIndex + offset) != stopIndex + } else if lexerAction.getIsPositionDependent() { + input.Seek(stopIndex) + requiresSeek = false + } + lexerAction.execute(lexer) + } +} + +func (l *LexerActionExecutor) Hash() int { + if l == nil { + // TODO: Why is this here? l should not be nil + return 61 + } + + // TODO: This is created from the action itself when the struct is created - will this be an issue at some point? Java uses the runtime assign hashcode + return l.cachedHash +} + +func (l *LexerActionExecutor) Equals(other interface{}) bool { + if l == other { + return true + } + othert, ok := other.(*LexerActionExecutor) + if !ok { + return false + } + if othert == nil { + return false + } + if l.cachedHash != othert.cachedHash { + return false + } + if len(l.lexerActions) != len(othert.lexerActions) { + return false + } + for i, v := range l.lexerActions { + if !v.Equals(othert.lexerActions[i]) { + return false + } + } + return true +} diff --git a/prutalgen/internal/antlr/lexer_atn_simulator.go b/prutalgen/internal/antlr/lexer_atn_simulator.go new file mode 100644 index 0000000..fe938b0 --- /dev/null +++ b/prutalgen/internal/antlr/lexer_atn_simulator.go @@ -0,0 +1,677 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" + "strings" +) + +//goland:noinspection GoUnusedGlobalVariable +var ( + LexerATNSimulatorMinDFAEdge = 0 + LexerATNSimulatorMaxDFAEdge = 127 // forces unicode to stay in ATN + + LexerATNSimulatorMatchCalls = 0 +) + +type ILexerATNSimulator interface { + IATNSimulator + + reset() + Match(input CharStream, mode int) int + GetCharPositionInLine() int + GetLine() int + GetText(input CharStream) string + Consume(input CharStream) +} + +type LexerATNSimulator struct { + BaseATNSimulator + + recog Lexer + predictionMode int + mergeCache *JPCMap2 + startIndex int + Line int + CharPositionInLine int + mode int + prevAccept *SimState + MatchCalls int +} + +func NewLexerATNSimulator(recog Lexer, atn *ATN, decisionToDFA []*DFA, sharedContextCache *PredictionContextCache) *LexerATNSimulator { + l := &LexerATNSimulator{ + BaseATNSimulator: BaseATNSimulator{ + atn: atn, + sharedContextCache: sharedContextCache, + }, + } + + l.decisionToDFA = decisionToDFA + l.recog = recog + + // The current token's starting index into the character stream. + // Shared across DFA to ATN simulation in case the ATN fails and the + // DFA did not have a previous accept state. In l case, we use the + // ATN-generated exception object. + l.startIndex = -1 + + // line number 1..n within the input + l.Line = 1 + + // The index of the character relative to the beginning of the line + // 0..n-1 + l.CharPositionInLine = 0 + + l.mode = LexerDefaultMode + + // Used during DFA/ATN exec to record the most recent accept configuration + // info + l.prevAccept = NewSimState() + + return l +} + +func (l *LexerATNSimulator) copyState(simulator *LexerATNSimulator) { + l.CharPositionInLine = simulator.CharPositionInLine + l.Line = simulator.Line + l.mode = simulator.mode + l.startIndex = simulator.startIndex +} + +func (l *LexerATNSimulator) Match(input CharStream, mode int) int { + l.MatchCalls++ + l.mode = mode + mark := input.Mark() + + defer func() { + input.Release(mark) + }() + + l.startIndex = input.Index() + l.prevAccept.reset() + + dfa := l.decisionToDFA[mode] + + var s0 *DFAState + l.atn.stateMu.RLock() + s0 = dfa.getS0() + l.atn.stateMu.RUnlock() + + if s0 == nil { + return l.MatchATN(input) + } + + return l.execATN(input, s0) +} + +func (l *LexerATNSimulator) reset() { + l.prevAccept.reset() + l.startIndex = -1 + l.Line = 1 + l.CharPositionInLine = 0 + l.mode = LexerDefaultMode +} + +func (l *LexerATNSimulator) MatchATN(input CharStream) int { + startState := l.atn.modeToStartState[l.mode] + + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("MatchATN mode " + strconv.Itoa(l.mode) + " start: " + startState.String()) + } + oldMode := l.mode + s0Closure := l.computeStartState(input, startState) + suppressEdge := s0Closure.hasSemanticContext + s0Closure.hasSemanticContext = false + + next := l.addDFAState(s0Closure, suppressEdge) + + predict := l.execATN(input, next) + + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("DFA after MatchATN: " + l.decisionToDFA[oldMode].ToLexerString()) + } + return predict +} + +func (l *LexerATNSimulator) execATN(input CharStream, ds0 *DFAState) int { + + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("start state closure=" + ds0.configs.String()) + } + if ds0.isAcceptState { + // allow zero-Length tokens + l.captureSimState(l.prevAccept, input, ds0) + } + t := input.LA(1) + s := ds0 // s is current/from DFA state + + for { // while more work + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("execATN loop starting closure: " + s.configs.String()) + } + + // As we move src->trg, src->trg, we keep track of the previous trg to + // avoid looking up the DFA state again, which is expensive. + // If the previous target was already part of the DFA, we might + // be able to avoid doing a reach operation upon t. If s!=nil, + // it means that semantic predicates didn't prevent us from + // creating a DFA state. Once we know s!=nil, we check to see if + // the DFA state has an edge already for t. If so, we can just reuse + // it's configuration set there's no point in re-computing it. + // This is kind of like doing DFA simulation within the ATN + // simulation because DFA simulation is really just a way to avoid + // computing reach/closure sets. Technically, once we know that + // we have a previously added DFA state, we could jump over to + // the DFA simulator. But, that would mean popping back and forth + // a lot and making things more complicated algorithmically. + // This optimization makes a lot of sense for loops within DFA. + // A character will take us back to an existing DFA state + // that already has lots of edges out of it. e.g., .* in comments. + target := l.getExistingTargetState(s, t) + if target == nil { + target = l.computeTargetState(input, s, t) + // print("Computed:" + str(target)) + } + if target == ATNSimulatorError { + break + } + // If l is a consumable input element, make sure to consume before + // capturing the accept state so the input index, line, and char + // position accurately reflect the state of the interpreter at the + // end of the token. + if t != TokenEOF { + l.Consume(input) + } + if target.isAcceptState { + l.captureSimState(l.prevAccept, input, target) + if t == TokenEOF { + break + } + } + t = input.LA(1) + s = target // flip current DFA target becomes new src/from state + } + + return l.failOrAccept(l.prevAccept, input, s.configs, t) +} + +// Get an existing target state for an edge in the DFA. If the target state +// for the edge has not yet been computed or is otherwise not available, +// l method returns {@code nil}. +// +// @param s The current DFA state +// @param t The next input symbol +// @return The existing target DFA state for the given input symbol +// {@code t}, or {@code nil} if the target state for l edge is not +// already cached +func (l *LexerATNSimulator) getExistingTargetState(s *DFAState, t int) *DFAState { + if t < LexerATNSimulatorMinDFAEdge || t > LexerATNSimulatorMaxDFAEdge { + return nil + } + + l.atn.edgeMu.RLock() + defer l.atn.edgeMu.RUnlock() + if s.getEdges() == nil { + return nil + } + target := s.getIthEdge(t - LexerATNSimulatorMinDFAEdge) + if runtimeConfig.lexerATNSimulatorDebug && target != nil { + fmt.Println("reuse state " + strconv.Itoa(s.stateNumber) + " edge to " + strconv.Itoa(target.stateNumber)) + } + return target +} + +// computeTargetState computes a target state for an edge in the [DFA], and attempt to add the +// computed state and corresponding edge to the [DFA]. +// +// The func returns the computed target [DFA] state for the given input symbol t. +// If this does not lead to a valid [DFA] state, this method +// returns ATNSimulatorError. +func (l *LexerATNSimulator) computeTargetState(input CharStream, s *DFAState, t int) *DFAState { + reach := NewOrderedATNConfigSet() + + // if we don't find an existing DFA state + // Fill reach starting from closure, following t transitions + l.getReachableConfigSet(input, s.configs, reach, t) + + if len(reach.configs) == 0 { // we got nowhere on t from s + if !reach.hasSemanticContext { + // we got nowhere on t, don't panic out l knowledge it'd + // cause a fail-over from DFA later. + l.addDFAEdge(s, t, ATNSimulatorError, nil) + } + // stop when we can't Match any more char + return ATNSimulatorError + } + // Add an edge from s to target DFA found/created for reach + return l.addDFAEdge(s, t, nil, reach) +} + +func (l *LexerATNSimulator) failOrAccept(prevAccept *SimState, input CharStream, reach *ATNConfigSet, t int) int { + if l.prevAccept.dfaState != nil { + lexerActionExecutor := prevAccept.dfaState.lexerActionExecutor + l.accept(input, lexerActionExecutor, l.startIndex, prevAccept.index, prevAccept.line, prevAccept.column) + return prevAccept.dfaState.prediction + } + + // if no accept and EOF is first char, return EOF + if t == TokenEOF && input.Index() == l.startIndex { + return TokenEOF + } + + panic(NewLexerNoViableAltException(l.recog, input, l.startIndex, reach)) +} + +// getReachableConfigSet when given a starting configuration set, figures out all [ATN] configurations +// we can reach upon input t. +// +// Parameter reach is a return parameter. +func (l *LexerATNSimulator) getReachableConfigSet(input CharStream, closure *ATNConfigSet, reach *ATNConfigSet, t int) { + // l is used to Skip processing for configs which have a lower priority + // than a runtimeConfig that already reached an accept state for the same rule + SkipAlt := ATNInvalidAltNumber + + for _, cfg := range closure.configs { + currentAltReachedAcceptState := cfg.GetAlt() == SkipAlt + if currentAltReachedAcceptState && cfg.passedThroughNonGreedyDecision { + continue + } + + if runtimeConfig.lexerATNSimulatorDebug { + + fmt.Printf("testing %s at %s\n", l.GetTokenName(t), cfg.String()) + } + + for _, trans := range cfg.GetState().GetTransitions() { + target := l.getReachableTarget(trans, t) + if target != nil { + lexerActionExecutor := cfg.lexerActionExecutor + if lexerActionExecutor != nil { + lexerActionExecutor = lexerActionExecutor.fixOffsetBeforeMatch(input.Index() - l.startIndex) + } + treatEOFAsEpsilon := t == TokenEOF + config := NewLexerATNConfig3(cfg, target, lexerActionExecutor) + if l.closure(input, config, reach, + currentAltReachedAcceptState, true, treatEOFAsEpsilon) { + // any remaining configs for l alt have a lower priority + // than the one that just reached an accept state. + SkipAlt = cfg.GetAlt() + } + } + } + } +} + +func (l *LexerATNSimulator) accept(input CharStream, lexerActionExecutor *LexerActionExecutor, startIndex, index, line, charPos int) { + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Printf("ACTION %v\n", lexerActionExecutor) + } + // seek to after last char in token + input.Seek(index) + l.Line = line + l.CharPositionInLine = charPos + if lexerActionExecutor != nil && l.recog != nil { + lexerActionExecutor.execute(l.recog, input, startIndex) + } +} + +func (l *LexerATNSimulator) getReachableTarget(trans Transition, t int) ATNState { + if trans.Matches(t, 0, LexerMaxCharValue) { + return trans.getTarget() + } + + return nil +} + +func (l *LexerATNSimulator) computeStartState(input CharStream, p ATNState) *ATNConfigSet { + configs := NewOrderedATNConfigSet() + for i := 0; i < len(p.GetTransitions()); i++ { + target := p.GetTransitions()[i].getTarget() + cfg := NewLexerATNConfig6(target, i+1, BasePredictionContextEMPTY) + l.closure(input, cfg, configs, false, false, false) + } + + return configs +} + +// closure since the alternatives within any lexer decision are ordered by +// preference, this method stops pursuing the closure as soon as an accept +// state is reached. After the first accept state is reached by depth-first +// search from runtimeConfig, all other (potentially reachable) states for +// this rule would have a lower priority. +// +// The func returns true if an accept state is reached. +func (l *LexerATNSimulator) closure(input CharStream, config *ATNConfig, configs *ATNConfigSet, + currentAltReachedAcceptState, speculative, treatEOFAsEpsilon bool) bool { + + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("closure(" + config.String() + ")") + } + + _, ok := config.state.(*RuleStopState) + if ok { + + if runtimeConfig.lexerATNSimulatorDebug { + if l.recog != nil { + fmt.Printf("closure at %s rule stop %s\n", l.recog.GetRuleNames()[config.state.GetRuleIndex()], config) + } else { + fmt.Printf("closure at rule stop %s\n", config) + } + } + + if config.context == nil || config.context.hasEmptyPath() { + if config.context == nil || config.context.isEmpty() { + configs.Add(config, nil) + return true + } + + configs.Add(NewLexerATNConfig2(config, config.state, BasePredictionContextEMPTY), nil) + currentAltReachedAcceptState = true + } + if config.context != nil && !config.context.isEmpty() { + for i := 0; i < config.context.length(); i++ { + if config.context.getReturnState(i) != BasePredictionContextEmptyReturnState { + newContext := config.context.GetParent(i) // "pop" return state + returnState := l.atn.states[config.context.getReturnState(i)] + cfg := NewLexerATNConfig2(config, returnState, newContext) + currentAltReachedAcceptState = l.closure(input, cfg, configs, currentAltReachedAcceptState, speculative, treatEOFAsEpsilon) + } + } + } + return currentAltReachedAcceptState + } + // optimization + if !config.state.GetEpsilonOnlyTransitions() { + if !currentAltReachedAcceptState || !config.passedThroughNonGreedyDecision { + configs.Add(config, nil) + } + } + for j := 0; j < len(config.state.GetTransitions()); j++ { + trans := config.state.GetTransitions()[j] + cfg := l.getEpsilonTarget(input, config, trans, configs, speculative, treatEOFAsEpsilon) + if cfg != nil { + currentAltReachedAcceptState = l.closure(input, cfg, configs, + currentAltReachedAcceptState, speculative, treatEOFAsEpsilon) + } + } + return currentAltReachedAcceptState +} + +// side-effect: can alter configs.hasSemanticContext +func (l *LexerATNSimulator) getEpsilonTarget(input CharStream, config *ATNConfig, trans Transition, + configs *ATNConfigSet, speculative, treatEOFAsEpsilon bool) *ATNConfig { + + var cfg *ATNConfig + + if trans.getSerializationType() == TransitionRULE { + + rt := trans.(*RuleTransition) + newContext := SingletonBasePredictionContextCreate(config.context, rt.followState.GetStateNumber()) + cfg = NewLexerATNConfig2(config, trans.getTarget(), newContext) + + } else if trans.getSerializationType() == TransitionPRECEDENCE { + panic("Precedence predicates are not supported in lexers.") + } else if trans.getSerializationType() == TransitionPREDICATE { + // Track traversing semantic predicates. If we traverse, + // we cannot add a DFA state for l "reach" computation + // because the DFA would not test the predicate again in the + // future. Rather than creating collections of semantic predicates + // like v3 and testing them on prediction, v4 will test them on the + // fly all the time using the ATN not the DFA. This is slower but + // semantically it's not used that often. One of the key elements to + // l predicate mechanism is not adding DFA states that see + // predicates immediately afterwards in the ATN. For example, + + // a : ID {p1}? | ID {p2}? + + // should create the start state for rule 'a' (to save start state + // competition), but should not create target of ID state. The + // collection of ATN states the following ID references includes + // states reached by traversing predicates. Since l is when we + // test them, we cannot cash the DFA state target of ID. + + pt := trans.(*PredicateTransition) + + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("EVAL rule " + strconv.Itoa(trans.(*PredicateTransition).ruleIndex) + ":" + strconv.Itoa(pt.predIndex)) + } + configs.hasSemanticContext = true + if l.evaluatePredicate(input, pt.ruleIndex, pt.predIndex, speculative) { + cfg = NewLexerATNConfig4(config, trans.getTarget()) + } + } else if trans.getSerializationType() == TransitionACTION { + if config.context == nil || config.context.hasEmptyPath() { + // execute actions anywhere in the start rule for a token. + // + // TODO: if the entry rule is invoked recursively, some + // actions may be executed during the recursive call. The + // problem can appear when hasEmptyPath() is true but + // isEmpty() is false. In this case, the config needs to be + // split into two contexts - one with just the empty path + // and another with everything but the empty path. + // Unfortunately, the current algorithm does not allow + // getEpsilonTarget to return two configurations, so + // additional modifications are needed before we can support + // the split operation. + lexerActionExecutor := LexerActionExecutorappend(config.lexerActionExecutor, l.atn.lexerActions[trans.(*ActionTransition).actionIndex]) + cfg = NewLexerATNConfig3(config, trans.getTarget(), lexerActionExecutor) + } else { + // ignore actions in referenced rules + cfg = NewLexerATNConfig4(config, trans.getTarget()) + } + } else if trans.getSerializationType() == TransitionEPSILON { + cfg = NewLexerATNConfig4(config, trans.getTarget()) + } else if trans.getSerializationType() == TransitionATOM || + trans.getSerializationType() == TransitionRANGE || + trans.getSerializationType() == TransitionSET { + if treatEOFAsEpsilon { + if trans.Matches(TokenEOF, 0, LexerMaxCharValue) { + cfg = NewLexerATNConfig4(config, trans.getTarget()) + } + } + } + return cfg +} + +// evaluatePredicate eEvaluates a predicate specified in the lexer. +// +// If speculative is true, this method was called before +// [consume] for the Matched character. This method should call +// [consume] before evaluating the predicate to ensure position +// sensitive values, including [GetText], [GetLine], +// and [GetColumn], properly reflect the current +// lexer state. This method should restore input and the simulator +// to the original state before returning, i.e. undo the actions made by the +// call to [Consume]. +// +// The func returns true if the specified predicate evaluates to true. +func (l *LexerATNSimulator) evaluatePredicate(input CharStream, ruleIndex, predIndex int, speculative bool) bool { + // assume true if no recognizer was provided + if l.recog == nil { + return true + } + if !speculative { + return l.recog.Sempred(nil, ruleIndex, predIndex) + } + savedcolumn := l.CharPositionInLine + savedLine := l.Line + index := input.Index() + marker := input.Mark() + + defer func() { + l.CharPositionInLine = savedcolumn + l.Line = savedLine + input.Seek(index) + input.Release(marker) + }() + + l.Consume(input) + return l.recog.Sempred(nil, ruleIndex, predIndex) +} + +func (l *LexerATNSimulator) captureSimState(settings *SimState, input CharStream, dfaState *DFAState) { + settings.index = input.Index() + settings.line = l.Line + settings.column = l.CharPositionInLine + settings.dfaState = dfaState +} + +func (l *LexerATNSimulator) addDFAEdge(from *DFAState, tk int, to *DFAState, cfgs *ATNConfigSet) *DFAState { + if to == nil && cfgs != nil { + // leading to l call, ATNConfigSet.hasSemanticContext is used as a + // marker indicating dynamic predicate evaluation makes l edge + // dependent on the specific input sequence, so the static edge in the + // DFA should be omitted. The target DFAState is still created since + // execATN has the ability to reSynchronize with the DFA state cache + // following the predicate evaluation step. + // + // TJP notes: next time through the DFA, we see a pred again and eval. + // If that gets us to a previously created (but dangling) DFA + // state, we can continue in pure DFA mode from there. + // + suppressEdge := cfgs.hasSemanticContext + cfgs.hasSemanticContext = false + to = l.addDFAState(cfgs, true) + + if suppressEdge { + return to + } + } + // add the edge + if tk < LexerATNSimulatorMinDFAEdge || tk > LexerATNSimulatorMaxDFAEdge { + // Only track edges within the DFA bounds + return to + } + if runtimeConfig.lexerATNSimulatorDebug { + fmt.Println("EDGE " + from.String() + " -> " + to.String() + " upon " + strconv.Itoa(tk)) + } + l.atn.edgeMu.Lock() + defer l.atn.edgeMu.Unlock() + if from.getEdges() == nil { + // make room for tokens 1..n and -1 masquerading as index 0 + from.setEdges(make([]*DFAState, LexerATNSimulatorMaxDFAEdge-LexerATNSimulatorMinDFAEdge+1)) + } + from.setIthEdge(tk-LexerATNSimulatorMinDFAEdge, to) // connect + + return to +} + +// Add a NewDFA state if there isn't one with l set of +// configurations already. This method also detects the first +// configuration containing an ATN rule stop state. Later, when +// traversing the DFA, we will know which rule to accept. +func (l *LexerATNSimulator) addDFAState(configs *ATNConfigSet, suppressEdge bool) *DFAState { + + proposed := NewDFAState(-1, configs) + var firstConfigWithRuleStopState *ATNConfig + + for _, cfg := range configs.configs { + _, ok := cfg.GetState().(*RuleStopState) + + if ok { + firstConfigWithRuleStopState = cfg + break + } + } + if firstConfigWithRuleStopState != nil { + proposed.isAcceptState = true + proposed.lexerActionExecutor = firstConfigWithRuleStopState.lexerActionExecutor + proposed.setPrediction(l.atn.ruleToTokenType[firstConfigWithRuleStopState.GetState().GetRuleIndex()]) + } + dfa := l.decisionToDFA[l.mode] + + l.atn.stateMu.Lock() + defer l.atn.stateMu.Unlock() + existing, present := dfa.Get(proposed) + if present { + + // This state was already present, so just return it. + // + proposed = existing + } else { + + // We need to add the new state + // + proposed.stateNumber = dfa.Len() + configs.readOnly = true + configs.configLookup = nil // Not needed now + proposed.configs = configs + dfa.Put(proposed) + } + if !suppressEdge { + dfa.setS0(proposed) + } + return proposed +} + +func (l *LexerATNSimulator) getDFA(mode int) *DFA { + return l.decisionToDFA[mode] +} + +// GetText returns the text [Match]ed so far for the current token. +func (l *LexerATNSimulator) GetText(input CharStream) string { + // index is first lookahead char, don't include. + return input.GetTextFromInterval(NewInterval(l.startIndex, input.Index()-1)) +} + +func (l *LexerATNSimulator) Consume(input CharStream) { + curChar := input.LA(1) + if curChar == int('\n') { + l.Line++ + l.CharPositionInLine = 0 + } else { + l.CharPositionInLine++ + } + input.Consume() +} + +func (l *LexerATNSimulator) GetCharPositionInLine() int { + return l.CharPositionInLine +} + +func (l *LexerATNSimulator) GetLine() int { + return l.Line +} + +func (l *LexerATNSimulator) GetTokenName(tt int) string { + if tt == -1 { + return "EOF" + } + + var sb strings.Builder + sb.Grow(6) + sb.WriteByte('\'') + sb.WriteRune(rune(tt)) + sb.WriteByte('\'') + + return sb.String() +} + +func resetSimState(sim *SimState) { + sim.index = -1 + sim.line = 0 + sim.column = -1 + sim.dfaState = nil +} + +type SimState struct { + index int + line int + column int + dfaState *DFAState +} + +func NewSimState() *SimState { + s := new(SimState) + resetSimState(s) + return s +} + +func (s *SimState) reset() { + resetSimState(s) +} diff --git a/prutalgen/internal/antlr/ll1_analyzer.go b/prutalgen/internal/antlr/ll1_analyzer.go new file mode 100644 index 0000000..dfdff00 --- /dev/null +++ b/prutalgen/internal/antlr/ll1_analyzer.go @@ -0,0 +1,219 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +type LL1Analyzer struct { + atn *ATN +} + +func NewLL1Analyzer(atn *ATN) *LL1Analyzer { + la := new(LL1Analyzer) + la.atn = atn + return la +} + +const ( + // LL1AnalyzerHitPred is a special value added to the lookahead sets to indicate that we hit + // a predicate during analysis if + // + // seeThruPreds==false + LL1AnalyzerHitPred = TokenInvalidType +) + +// * +// Calculates the SLL(1) expected lookahead set for each outgoing transition +// of an {@link ATNState}. The returned array has one element for each +// outgoing transition in {@code s}. If the closure from transition +// i leads to a semantic predicate before Matching a symbol, the +// element at index i of the result will be {@code nil}. +// +// @param s the ATN state +// @return the expected symbols for each outgoing transition of {@code s}. +func (la *LL1Analyzer) getDecisionLookahead(s ATNState) []*IntervalSet { + if s == nil { + return nil + } + count := len(s.GetTransitions()) + look := make([]*IntervalSet, count) + for alt := 0; alt < count; alt++ { + + look[alt] = NewIntervalSet() + // TODO: This is one of the reasons that ATNConfigs are allocated and freed all the time - fix this tomorrow jim! + lookBusy := NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, "LL1Analyzer.getDecisionLookahead for lookBusy") + la.look1(s.GetTransitions()[alt].getTarget(), nil, BasePredictionContextEMPTY, look[alt], lookBusy, NewBitSet(), false, false) + + // Wipe out lookahead for la alternative if we found nothing, + // or we had a predicate when we !seeThruPreds + if look[alt].length() == 0 || look[alt].contains(LL1AnalyzerHitPred) { + look[alt] = nil + } + } + return look +} + +// Look computes the set of tokens that can follow s in the [ATN] in the +// specified ctx. +// +// If ctx is nil and the end of the rule containing +// s is reached, [EPSILON] is added to the result set. +// +// If ctx is not nil and the end of the outermost rule is +// reached, [EOF] is added to the result set. +// +// Parameter s the ATN state, and stopState is the ATN state to stop at. This can be a +// [BlockEndState] to detect epsilon paths through a closure. +// +// Parameter ctx is the complete parser context, or nil if the context +// should be ignored +// +// The func returns the set of tokens that can follow s in the [ATN] in the +// specified ctx. +func (la *LL1Analyzer) Look(s, stopState ATNState, ctx RuleContext) *IntervalSet { + r := NewIntervalSet() + var lookContext *PredictionContext + if ctx != nil { + lookContext = predictionContextFromRuleContext(s.GetATN(), ctx) + } + la.look1(s, stopState, lookContext, r, NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, "LL1Analyzer.Look for la.look1()"), + NewBitSet(), true, true) + return r +} + +//* +// Compute set of tokens that can follow {@code s} in the ATN in the +// specified {@code ctx}. +// +//

If {@code ctx} is {@code nil} and {@code stopState} or the end of the +// rule containing {@code s} is reached, {@link Token//EPSILON} is added to +// the result set. If {@code ctx} is not {@code nil} and {@code addEOF} is +// {@code true} and {@code stopState} or the end of the outermost rule is +// reached, {@link Token//EOF} is added to the result set.

+// +// @param s the ATN state. +// @param stopState the ATN state to stop at. This can be a +// {@link BlockEndState} to detect epsilon paths through a closure. +// @param ctx The outer context, or {@code nil} if the outer context should +// not be used. +// @param look The result lookahead set. +// @param lookBusy A set used for preventing epsilon closures in the ATN +// from causing a stack overflow. Outside code should pass +// {@code NewSet} for la argument. +// @param calledRuleStack A set used for preventing left recursion in the +// ATN from causing a stack overflow. Outside code should pass +// {@code NewBitSet()} for la argument. +// @param seeThruPreds {@code true} to true semantic predicates as +// implicitly {@code true} and "see through them", otherwise {@code false} +// to treat semantic predicates as opaque and add {@link //HitPred} to the +// result if one is encountered. +// @param addEOF Add {@link Token//EOF} to the result if the end of the +// outermost context is reached. This parameter has no effect if {@code ctx} +// is {@code nil}. + +func (la *LL1Analyzer) look2(_, stopState ATNState, ctx *PredictionContext, look *IntervalSet, lookBusy *JStore[*ATNConfig, Comparator[*ATNConfig]], + calledRuleStack *BitSet, seeThruPreds, addEOF bool, i int) { + + returnState := la.atn.states[ctx.getReturnState(i)] + la.look1(returnState, stopState, ctx.GetParent(i), look, lookBusy, calledRuleStack, seeThruPreds, addEOF) + +} + +func (la *LL1Analyzer) look1(s, stopState ATNState, ctx *PredictionContext, look *IntervalSet, lookBusy *JStore[*ATNConfig, Comparator[*ATNConfig]], calledRuleStack *BitSet, seeThruPreds, addEOF bool) { + + c := NewATNConfig6(s, 0, ctx) + + if lookBusy.Contains(c) { + return + } + + _, present := lookBusy.Put(c) + if present { + return + + } + if s == stopState { + if ctx == nil { + look.addOne(TokenEpsilon) + return + } else if ctx.isEmpty() && addEOF { + look.addOne(TokenEOF) + return + } + } + + _, ok := s.(*RuleStopState) + + if ok { + if ctx == nil { + look.addOne(TokenEpsilon) + return + } else if ctx.isEmpty() && addEOF { + look.addOne(TokenEOF) + return + } + + if ctx.pcType != PredictionContextEmpty { + removed := calledRuleStack.contains(s.GetRuleIndex()) + defer func() { + if removed { + calledRuleStack.add(s.GetRuleIndex()) + } + }() + calledRuleStack.remove(s.GetRuleIndex()) + // run thru all possible stack tops in ctx + for i := 0; i < ctx.length(); i++ { + returnState := la.atn.states[ctx.getReturnState(i)] + la.look2(returnState, stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF, i) + } + return + } + } + + n := len(s.GetTransitions()) + + for i := 0; i < n; i++ { + t := s.GetTransitions()[i] + + if t1, ok := t.(*RuleTransition); ok { + if calledRuleStack.contains(t1.getTarget().GetRuleIndex()) { + continue + } + + newContext := SingletonBasePredictionContextCreate(ctx, t1.followState.GetStateNumber()) + la.look3(stopState, newContext, look, lookBusy, calledRuleStack, seeThruPreds, addEOF, t1) + } else if t2, ok := t.(AbstractPredicateTransition); ok { + if seeThruPreds { + la.look1(t2.getTarget(), stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF) + } else { + look.addOne(LL1AnalyzerHitPred) + } + } else if t.getIsEpsilon() { + la.look1(t.getTarget(), stopState, ctx, look, lookBusy, calledRuleStack, seeThruPreds, addEOF) + } else if _, ok := t.(*WildcardTransition); ok { + look.addRange(TokenMinUserTokenType, la.atn.maxTokenType) + } else { + set := t.getLabel() + if set != nil { + if _, ok := t.(*NotSetTransition); ok { + set = set.complement(TokenMinUserTokenType, la.atn.maxTokenType) + } + look.addSet(set) + } + } + } +} + +func (la *LL1Analyzer) look3(stopState ATNState, ctx *PredictionContext, look *IntervalSet, lookBusy *JStore[*ATNConfig, Comparator[*ATNConfig]], + calledRuleStack *BitSet, seeThruPreds, addEOF bool, t1 *RuleTransition) { + + newContext := SingletonBasePredictionContextCreate(ctx, t1.followState.GetStateNumber()) + + defer func() { + calledRuleStack.remove(t1.getTarget().GetRuleIndex()) + }() + + calledRuleStack.add(t1.getTarget().GetRuleIndex()) + la.look1(t1.getTarget(), stopState, newContext, look, lookBusy, calledRuleStack, seeThruPreds, addEOF) + +} diff --git a/prutalgen/internal/antlr/mutex.go b/prutalgen/internal/antlr/mutex.go new file mode 100644 index 0000000..2b0cda4 --- /dev/null +++ b/prutalgen/internal/antlr/mutex.go @@ -0,0 +1,41 @@ +//go:build !antlr.nomutex +// +build !antlr.nomutex + +package antlr + +import "sync" + +// Mutex is a simple mutex implementation which just delegates to sync.Mutex, it +// is used to provide a mutex implementation for the antlr package, which users +// can turn off with the build tag -tags antlr.nomutex +type Mutex struct { + mu sync.Mutex +} + +func (m *Mutex) Lock() { + m.mu.Lock() +} + +func (m *Mutex) Unlock() { + m.mu.Unlock() +} + +type RWMutex struct { + mu sync.RWMutex +} + +func (m *RWMutex) Lock() { + m.mu.Lock() +} + +func (m *RWMutex) Unlock() { + m.mu.Unlock() +} + +func (m *RWMutex) RLock() { + m.mu.RLock() +} + +func (m *RWMutex) RUnlock() { + m.mu.RUnlock() +} diff --git a/prutalgen/internal/antlr/mutex_nomutex.go b/prutalgen/internal/antlr/mutex_nomutex.go new file mode 100644 index 0000000..6b9cf4d --- /dev/null +++ b/prutalgen/internal/antlr/mutex_nomutex.go @@ -0,0 +1,31 @@ +// +build antlr.nomutex + +package antlr + +type Mutex struct{} + +func (m *Mutex) Lock() { + // No-op +} + +func (m *Mutex) Unlock() { + // No-op +} + +type RWMutex struct{} + +func (m *RWMutex) Lock() { + // No-op +} + +func (m *RWMutex) Unlock() { + // No-op +} + +func (m *RWMutex) RLock() { + // No-op +} + +func (m *RWMutex) RUnlock() { + // No-op +} \ No newline at end of file diff --git a/prutalgen/internal/antlr/nostatistics.go b/prutalgen/internal/antlr/nostatistics.go new file mode 100644 index 0000000..923c7b5 --- /dev/null +++ b/prutalgen/internal/antlr/nostatistics.go @@ -0,0 +1,47 @@ +//go:build !antlr.stats + +package antlr + +// This file is compiled when the build configuration antlr.stats is not enabled. +// which then allows the compiler to optimize out all the code that is not used. +const collectStats = false + +// goRunStats is a dummy struct used when build configuration antlr.stats is not enabled. +type goRunStats struct { +} + +var Statistics = &goRunStats{} + +func (s *goRunStats) AddJStatRec(_ *JStatRec) { + // Do nothing - compiler will optimize this out (hopefully) +} + +func (s *goRunStats) CollectionAnomalies() { + // Do nothing - compiler will optimize this out (hopefully) +} + +func (s *goRunStats) Reset() { + // Do nothing - compiler will optimize this out (hopefully) +} + +func (s *goRunStats) Report(dir string, prefix string) error { + // Do nothing - compiler will optimize this out (hopefully) + return nil +} + +func (s *goRunStats) Analyze() { + // Do nothing - compiler will optimize this out (hopefully) +} + +type statsOption func(*goRunStats) error + +func (s *goRunStats) Configure(options ...statsOption) error { + // Do nothing - compiler will optimize this out (hopefully) + return nil +} + +func WithTopN(topN int) statsOption { + return func(s *goRunStats) error { + return nil + } +} diff --git a/prutalgen/internal/antlr/parser.go b/prutalgen/internal/antlr/parser.go new file mode 100644 index 0000000..fb57ac1 --- /dev/null +++ b/prutalgen/internal/antlr/parser.go @@ -0,0 +1,700 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" +) + +type Parser interface { + Recognizer + + GetInterpreter() *ParserATNSimulator + + GetTokenStream() TokenStream + GetTokenFactory() TokenFactory + GetParserRuleContext() ParserRuleContext + SetParserRuleContext(ParserRuleContext) + Consume() Token + GetParseListeners() []ParseTreeListener + + GetErrorHandler() ErrorStrategy + SetErrorHandler(ErrorStrategy) + GetInputStream() IntStream + GetCurrentToken() Token + GetExpectedTokens() *IntervalSet + NotifyErrorListeners(string, Token, RecognitionException) + IsExpectedToken(int) bool + GetPrecedence() int + GetRuleInvocationStack(ParserRuleContext) []string +} + +type BaseParser struct { + *BaseRecognizer + + Interpreter *ParserATNSimulator + BuildParseTrees bool + + input TokenStream + errHandler ErrorStrategy + precedenceStack IntStack + ctx ParserRuleContext + + tracer *TraceListener + parseListeners []ParseTreeListener + _SyntaxErrors int +} + +// NewBaseParser contains all the parsing support code to embed in parsers. Essentially most of it is error +// recovery stuff. +// +//goland:noinspection GoUnusedExportedFunction +func NewBaseParser(input TokenStream) *BaseParser { + + p := new(BaseParser) + + p.BaseRecognizer = NewBaseRecognizer() + + // The input stream. + p.input = nil + + // The error handling strategy for the parser. The default value is a new + // instance of {@link DefaultErrorStrategy}. + p.errHandler = NewDefaultErrorStrategy() + p.precedenceStack = make([]int, 0) + p.precedenceStack.Push(0) + + // The ParserRuleContext object for the currently executing rule. + // p.is always non-nil during the parsing process. + p.ctx = nil + + // Specifies whether the parser should construct a parse tree during + // the parsing process. The default value is {@code true}. + p.BuildParseTrees = true + + // When setTrace(true) is called, a reference to the + // TraceListener is stored here, so it can be easily removed in a + // later call to setTrace(false). The listener itself is + // implemented as a parser listener so p.field is not directly used by + // other parser methods. + p.tracer = nil + + // The list of ParseTreeListener listeners registered to receive + // events during the parse. + p.parseListeners = nil + + // The number of syntax errors Reported during parsing. p.value is + // incremented each time NotifyErrorListeners is called. + p._SyntaxErrors = 0 + p.SetInputStream(input) + + return p +} + +// This field maps from the serialized ATN string to the deserialized [ATN] with +// bypass alternatives. +// +// [ATNDeserializationOptions.isGenerateRuleBypassTransitions] +// +//goland:noinspection GoUnusedGlobalVariable +var bypassAltsAtnCache = make(map[string]int) + +// reset the parser's state// +func (p *BaseParser) reset() { + if p.input != nil { + p.input.Seek(0) + } + p.errHandler.reset(p) + p.ctx = nil + p._SyntaxErrors = 0 + p.SetTrace(nil) + p.precedenceStack = make([]int, 0) + p.precedenceStack.Push(0) + if p.Interpreter != nil { + p.Interpreter.reset() + } +} + +func (p *BaseParser) GetErrorHandler() ErrorStrategy { + return p.errHandler +} + +func (p *BaseParser) SetErrorHandler(e ErrorStrategy) { + p.errHandler = e +} + +// Match current input symbol against {@code ttype}. If the symbol type +// Matches, {@link ANTLRErrorStrategy//ReportMatch} and {@link //consume} are +// called to complete the Match process. +// +//

If the symbol type does not Match, +// {@link ANTLRErrorStrategy//recoverInline} is called on the current error +// strategy to attempt recovery. If {@link //getBuildParseTree} is +// {@code true} and the token index of the symbol returned by +// {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to +// the parse tree by calling {@link ParserRuleContext//addErrorNode}.

+// +// @param ttype the token type to Match +// @return the Matched symbol +// @panics RecognitionException if the current input symbol did not Match +// {@code ttype} and the error strategy could not recover from the +// mismatched symbol + +func (p *BaseParser) Match(ttype int) Token { + + t := p.GetCurrentToken() + + if t.GetTokenType() == ttype { + p.errHandler.ReportMatch(p) + p.Consume() + } else { + t = p.errHandler.RecoverInline(p) + if p.HasError() { + return nil + } + if p.BuildParseTrees && t.GetTokenIndex() == -1 { + + // we must have conjured up a new token during single token + // insertion if it's not the current symbol + p.ctx.AddErrorNode(t) + } + } + + return t +} + +// Match current input symbol as a wildcard. If the symbol type Matches +// (i.e. has a value greater than 0), {@link ANTLRErrorStrategy//ReportMatch} +// and {@link //consume} are called to complete the Match process. +// +//

If the symbol type does not Match, +// {@link ANTLRErrorStrategy//recoverInline} is called on the current error +// strategy to attempt recovery. If {@link //getBuildParseTree} is +// {@code true} and the token index of the symbol returned by +// {@link ANTLRErrorStrategy//recoverInline} is -1, the symbol is added to +// the parse tree by calling {@link ParserRuleContext//addErrorNode}.

+// +// @return the Matched symbol +// @panics RecognitionException if the current input symbol did not Match +// a wildcard and the error strategy could not recover from the mismatched +// symbol + +func (p *BaseParser) MatchWildcard() Token { + t := p.GetCurrentToken() + if t.GetTokenType() > 0 { + p.errHandler.ReportMatch(p) + p.Consume() + } else { + t = p.errHandler.RecoverInline(p) + if p.BuildParseTrees && t.GetTokenIndex() == -1 { + // we must have conjured up a new token during single token + // insertion if it's not the current symbol + p.ctx.AddErrorNode(t) + } + } + return t +} + +func (p *BaseParser) GetParserRuleContext() ParserRuleContext { + return p.ctx +} + +func (p *BaseParser) SetParserRuleContext(v ParserRuleContext) { + p.ctx = v +} + +func (p *BaseParser) GetParseListeners() []ParseTreeListener { + if p.parseListeners == nil { + return make([]ParseTreeListener, 0) + } + return p.parseListeners +} + +// AddParseListener registers listener to receive events during the parsing process. +// +// To support output-preserving grammar transformations (including but not +// limited to left-recursion removal, automated left-factoring, and +// optimized code generation), calls to listener methods during the parse +// may differ substantially from calls made by +// [ParseTreeWalker.DEFAULT] used after the parse is complete. In +// particular, rule entry and exit events may occur in a different order +// during the parse than after the parser. In addition, calls to certain +// rule entry methods may be omitted. +// +// With the following specific exceptions, calls to listener events are +// deterministic, i.e. for identical input the calls to listener +// methods will be the same. +// +// - Alterations to the grammar used to generate code may change the +// behavior of the listener calls. +// - Alterations to the command line options passed to ANTLR 4 when +// generating the parser may change the behavior of the listener calls. +// - Changing the version of the ANTLR Tool used to generate the parser +// may change the behavior of the listener calls. +func (p *BaseParser) AddParseListener(listener ParseTreeListener) { + if listener == nil { + panic("listener") + } + if p.parseListeners == nil { + p.parseListeners = make([]ParseTreeListener, 0) + } + p.parseListeners = append(p.parseListeners, listener) +} + +// RemoveParseListener removes listener from the list of parse listeners. +// +// If listener is nil or has not been added as a parse +// listener, this func does nothing. +func (p *BaseParser) RemoveParseListener(listener ParseTreeListener) { + + if p.parseListeners != nil { + + idx := -1 + for i, v := range p.parseListeners { + if v == listener { + idx = i + break + } + } + + if idx == -1 { + return + } + + // remove the listener from the slice + p.parseListeners = append(p.parseListeners[0:idx], p.parseListeners[idx+1:]...) + + if len(p.parseListeners) == 0 { + p.parseListeners = nil + } + } +} + +// Remove all parse listeners. +func (p *BaseParser) removeParseListeners() { + p.parseListeners = nil +} + +// TriggerEnterRuleEvent notifies all parse listeners of an enter rule event. +func (p *BaseParser) TriggerEnterRuleEvent() { + if p.parseListeners != nil { + ctx := p.ctx + for _, listener := range p.parseListeners { + listener.EnterEveryRule(ctx) + ctx.EnterRule(listener) + } + } +} + +// TriggerExitRuleEvent notifies any parse listeners of an exit rule event. +func (p *BaseParser) TriggerExitRuleEvent() { + if p.parseListeners != nil { + // reverse order walk of listeners + ctx := p.ctx + l := len(p.parseListeners) - 1 + + for i := range p.parseListeners { + listener := p.parseListeners[l-i] + ctx.ExitRule(listener) + listener.ExitEveryRule(ctx) + } + } +} + +func (p *BaseParser) GetInterpreter() *ParserATNSimulator { + return p.Interpreter +} + +func (p *BaseParser) GetATN() *ATN { + return p.Interpreter.atn +} + +func (p *BaseParser) GetTokenFactory() TokenFactory { + return p.input.GetTokenSource().GetTokenFactory() +} + +// setTokenFactory is used to tell our token source and error strategy about a new way to create tokens. +func (p *BaseParser) setTokenFactory(factory TokenFactory) { + p.input.GetTokenSource().setTokenFactory(factory) +} + +// GetATNWithBypassAlts - the ATN with bypass alternatives is expensive to create, so we create it +// lazily. +func (p *BaseParser) GetATNWithBypassAlts() { + + // TODO - Implement this? + panic("Not implemented!") + + // serializedAtn := p.getSerializedATN() + // if (serializedAtn == nil) { + // panic("The current parser does not support an ATN with bypass alternatives.") + // } + // result := p.bypassAltsAtnCache[serializedAtn] + // if (result == nil) { + // deserializationOptions := NewATNDeserializationOptions(nil) + // deserializationOptions.generateRuleBypassTransitions = true + // result = NewATNDeserializer(deserializationOptions).deserialize(serializedAtn) + // p.bypassAltsAtnCache[serializedAtn] = result + // } + // return result +} + +// The preferred method of getting a tree pattern. For example, here's a +// sample use: +// +//
+// ParseTree t = parser.expr()
+// ParseTreePattern p = parser.compileParseTreePattern("<ID>+0",
+// MyParser.RULE_expr)
+// ParseTreeMatch m = p.Match(t)
+// String id = m.Get("ID")
+// 
+ +//goland:noinspection GoUnusedParameter +func (p *BaseParser) compileParseTreePattern(pattern, patternRuleIndex, lexer Lexer) { + + panic("NewParseTreePatternMatcher not implemented!") + // + // if (lexer == nil) { + // if (p.GetTokenStream() != nil) { + // tokenSource := p.GetTokenStream().GetTokenSource() + // if _, ok := tokenSource.(ILexer); ok { + // lexer = tokenSource + // } + // } + // } + // if (lexer == nil) { + // panic("Parser can't discover a lexer to use") + // } + + // m := NewParseTreePatternMatcher(lexer, p) + // return m.compile(pattern, patternRuleIndex) +} + +func (p *BaseParser) GetInputStream() IntStream { + return p.GetTokenStream() +} + +func (p *BaseParser) SetInputStream(input TokenStream) { + p.SetTokenStream(input) +} + +func (p *BaseParser) GetTokenStream() TokenStream { + return p.input +} + +// SetTokenStream installs input as the token stream and resets the parser. +func (p *BaseParser) SetTokenStream(input TokenStream) { + p.input = nil + p.reset() + p.input = input +} + +// GetCurrentToken returns the current token at LT(1). +// +// [Match] needs to return the current input symbol, which gets put +// into the label for the associated token ref e.g., x=ID. +func (p *BaseParser) GetCurrentToken() Token { + return p.input.LT(1) +} + +func (p *BaseParser) NotifyErrorListeners(msg string, offendingToken Token, err RecognitionException) { + if offendingToken == nil { + offendingToken = p.GetCurrentToken() + } + p._SyntaxErrors++ + line := offendingToken.GetLine() + column := offendingToken.GetColumn() + listener := p.GetErrorListenerDispatch() + listener.SyntaxError(p, offendingToken, line, column, msg, err) +} + +func (p *BaseParser) Consume() Token { + o := p.GetCurrentToken() + if o.GetTokenType() != TokenEOF { + p.GetInputStream().Consume() + } + hasListener := p.parseListeners != nil && len(p.parseListeners) > 0 + if p.BuildParseTrees || hasListener { + if p.errHandler.InErrorRecoveryMode(p) { + node := p.ctx.AddErrorNode(o) + if p.parseListeners != nil { + for _, l := range p.parseListeners { + l.VisitErrorNode(node) + } + } + + } else { + node := p.ctx.AddTokenNode(o) + if p.parseListeners != nil { + for _, l := range p.parseListeners { + l.VisitTerminal(node) + } + } + } + // node.invokingState = p.state + } + + return o +} + +func (p *BaseParser) addContextToParseTree() { + // add current context to parent if we have a parent + if p.ctx.GetParent() != nil { + p.ctx.GetParent().(ParserRuleContext).AddChild(p.ctx) + } +} + +func (p *BaseParser) EnterRule(localctx ParserRuleContext, state, _ int) { + p.SetState(state) + p.ctx = localctx + p.ctx.SetStart(p.input.LT(1)) + if p.BuildParseTrees { + p.addContextToParseTree() + } + if p.parseListeners != nil { + p.TriggerEnterRuleEvent() + } +} + +func (p *BaseParser) ExitRule() { + p.ctx.SetStop(p.input.LT(-1)) + // trigger event on ctx, before it reverts to parent + if p.parseListeners != nil { + p.TriggerExitRuleEvent() + } + p.SetState(p.ctx.GetInvokingState()) + if p.ctx.GetParent() != nil { + p.ctx = p.ctx.GetParent().(ParserRuleContext) + } else { + p.ctx = nil + } +} + +func (p *BaseParser) EnterOuterAlt(localctx ParserRuleContext, altNum int) { + localctx.SetAltNumber(altNum) + // if we have a new localctx, make sure we replace existing ctx + // that is previous child of parse tree + if p.BuildParseTrees && p.ctx != localctx { + if p.ctx.GetParent() != nil { + p.ctx.GetParent().(ParserRuleContext).RemoveLastChild() + p.ctx.GetParent().(ParserRuleContext).AddChild(localctx) + } + } + p.ctx = localctx +} + +// Get the precedence level for the top-most precedence rule. +// +// @return The precedence level for the top-most precedence rule, or -1 if +// the parser context is not nested within a precedence rule. + +func (p *BaseParser) GetPrecedence() int { + if len(p.precedenceStack) == 0 { + return -1 + } + + return p.precedenceStack[len(p.precedenceStack)-1] +} + +func (p *BaseParser) EnterRecursionRule(localctx ParserRuleContext, state, _, precedence int) { + p.SetState(state) + p.precedenceStack.Push(precedence) + p.ctx = localctx + p.ctx.SetStart(p.input.LT(1)) + if p.parseListeners != nil { + p.TriggerEnterRuleEvent() // simulates rule entry for + // left-recursive rules + } +} + +// +// Like {@link //EnterRule} but for recursive rules. + +func (p *BaseParser) PushNewRecursionContext(localctx ParserRuleContext, state, _ int) { + previous := p.ctx + previous.SetParent(localctx) + previous.SetInvokingState(state) + previous.SetStop(p.input.LT(-1)) + + p.ctx = localctx + p.ctx.SetStart(previous.GetStart()) + if p.BuildParseTrees { + p.ctx.AddChild(previous) + } + if p.parseListeners != nil { + p.TriggerEnterRuleEvent() // simulates rule entry for + // left-recursive rules + } +} + +func (p *BaseParser) UnrollRecursionContexts(parentCtx ParserRuleContext) { + _, _ = p.precedenceStack.Pop() + p.ctx.SetStop(p.input.LT(-1)) + retCtx := p.ctx // save current ctx (return value) + // unroll so ctx is as it was before call to recursive method + if p.parseListeners != nil { + for p.ctx != parentCtx { + p.TriggerExitRuleEvent() + p.ctx = p.ctx.GetParent().(ParserRuleContext) + } + } else { + p.ctx = parentCtx + } + // hook into tree + retCtx.SetParent(parentCtx) + if p.BuildParseTrees && parentCtx != nil { + // add return ctx into invoking rule's tree + parentCtx.AddChild(retCtx) + } +} + +func (p *BaseParser) GetInvokingContext(ruleIndex int) ParserRuleContext { + ctx := p.ctx + for ctx != nil { + if ctx.GetRuleIndex() == ruleIndex { + return ctx + } + ctx = ctx.GetParent().(ParserRuleContext) + } + return nil +} + +func (p *BaseParser) Precpred(_ RuleContext, precedence int) bool { + return precedence >= p.precedenceStack[len(p.precedenceStack)-1] +} + +//goland:noinspection GoUnusedParameter +func (p *BaseParser) inContext(context ParserRuleContext) bool { + // TODO: useful in parser? + return false +} + +// IsExpectedToken checks whether symbol can follow the current state in the +// {ATN}. The behavior of p.method is equivalent to the following, but is +// implemented such that the complete context-sensitive follow set does not +// need to be explicitly constructed. +// +// return getExpectedTokens().contains(symbol) +func (p *BaseParser) IsExpectedToken(symbol int) bool { + atn := p.Interpreter.atn + ctx := p.ctx + s := atn.states[p.state] + following := atn.NextTokens(s, nil) + if following.contains(symbol) { + return true + } + if !following.contains(TokenEpsilon) { + return false + } + for ctx != nil && ctx.GetInvokingState() >= 0 && following.contains(TokenEpsilon) { + invokingState := atn.states[ctx.GetInvokingState()] + rt := invokingState.GetTransitions()[0] + following = atn.NextTokens(rt.(*RuleTransition).followState, nil) + if following.contains(symbol) { + return true + } + ctx = ctx.GetParent().(ParserRuleContext) + } + if following.contains(TokenEpsilon) && symbol == TokenEOF { + return true + } + + return false +} + +// GetExpectedTokens and returns the set of input symbols which could follow the current parser +// state and context, as given by [GetState] and [GetContext], +// respectively. +func (p *BaseParser) GetExpectedTokens() *IntervalSet { + return p.Interpreter.atn.getExpectedTokens(p.state, p.ctx) +} + +func (p *BaseParser) GetExpectedTokensWithinCurrentRule() *IntervalSet { + atn := p.Interpreter.atn + s := atn.states[p.state] + return atn.NextTokens(s, nil) +} + +// GetRuleIndex get a rule's index (i.e., RULE_ruleName field) or -1 if not found. +func (p *BaseParser) GetRuleIndex(ruleName string) int { + var ruleIndex, ok = p.GetRuleIndexMap()[ruleName] + if ok { + return ruleIndex + } + + return -1 +} + +// GetRuleInvocationStack returns a list of the rule names in your parser instance +// leading up to a call to the current rule. You could override if +// you want more details such as the file/line info of where +// in the ATN a rule is invoked. +func (p *BaseParser) GetRuleInvocationStack(c ParserRuleContext) []string { + if c == nil { + c = p.ctx + } + stack := make([]string, 0) + for c != nil { + // compute what follows who invoked us + ruleIndex := c.GetRuleIndex() + if ruleIndex < 0 { + stack = append(stack, "n/a") + } else { + stack = append(stack, p.GetRuleNames()[ruleIndex]) + } + + vp := c.GetParent() + + if vp == nil { + break + } + + c = vp.(ParserRuleContext) + } + return stack +} + +// GetDFAStrings returns a list of all DFA states used for debugging purposes +func (p *BaseParser) GetDFAStrings() string { + return fmt.Sprint(p.Interpreter.decisionToDFA) +} + +// DumpDFA prints the whole of the DFA for debugging +func (p *BaseParser) DumpDFA() { + seenOne := false + for _, dfa := range p.Interpreter.decisionToDFA { + if dfa.Len() > 0 { + if seenOne { + fmt.Println() + } + fmt.Println("Decision " + strconv.Itoa(dfa.decision) + ":") + fmt.Print(dfa.String(p.LiteralNames, p.SymbolicNames)) + seenOne = true + } + } +} + +func (p *BaseParser) GetSourceName() string { + return p.GrammarFileName +} + +// SetTrace installs a trace listener for the parse. +// +// During a parse it is sometimes useful to listen in on the rule entry and exit +// events as well as token Matches. This is for quick and dirty debugging. +func (p *BaseParser) SetTrace(trace *TraceListener) { + if trace == nil { + p.RemoveParseListener(p.tracer) + p.tracer = nil + } else { + if p.tracer != nil { + p.RemoveParseListener(p.tracer) + } + p.tracer = NewTraceListener(p) + p.AddParseListener(p.tracer) + } +} diff --git a/prutalgen/internal/antlr/parser_atn_simulator.go b/prutalgen/internal/antlr/parser_atn_simulator.go new file mode 100644 index 0000000..724fa17 --- /dev/null +++ b/prutalgen/internal/antlr/parser_atn_simulator.go @@ -0,0 +1,1666 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" + "strings" +) + +// ClosureBusy is a store of ATNConfigs and is a tiny abstraction layer over +// a standard JStore so that we can use Lazy instantiation of the JStore, mostly +// to avoid polluting the stats module with a ton of JStore instances with nothing in them. +type ClosureBusy struct { + bMap *JStore[*ATNConfig, Comparator[*ATNConfig]] + desc string +} + +// NewClosureBusy creates a new ClosureBusy instance used to avoid infinite recursion for right-recursive rules +func NewClosureBusy(desc string) *ClosureBusy { + return &ClosureBusy{ + desc: desc, + } +} + +func (c *ClosureBusy) Put(config *ATNConfig) (*ATNConfig, bool) { + if c.bMap == nil { + c.bMap = NewJStore[*ATNConfig, Comparator[*ATNConfig]](aConfEqInst, ClosureBusyCollection, c.desc) + } + return c.bMap.Put(config) +} + +type ParserATNSimulator struct { + BaseATNSimulator + + parser Parser + predictionMode int + input TokenStream + startIndex int + dfa *DFA + mergeCache *JPCMap + outerContext ParserRuleContext +} + +//goland:noinspection GoUnusedExportedFunction +func NewParserATNSimulator(parser Parser, atn *ATN, decisionToDFA []*DFA, sharedContextCache *PredictionContextCache) *ParserATNSimulator { + + p := &ParserATNSimulator{ + BaseATNSimulator: BaseATNSimulator{ + atn: atn, + sharedContextCache: sharedContextCache, + }, + } + + p.parser = parser + p.decisionToDFA = decisionToDFA + // SLL, LL, or LL + exact ambig detection?// + p.predictionMode = PredictionModeLL + // LAME globals to avoid parameters!!!!! I need these down deep in predTransition + p.input = nil + p.startIndex = 0 + p.outerContext = nil + p.dfa = nil + // Each prediction operation uses a cache for merge of prediction contexts. + // Don't keep around as it wastes huge amounts of memory. [JPCMap] + // isn't Synchronized, but we're ok since two threads shouldn't reuse same + // parser/atn-simulator object because it can only handle one input at a time. + // This maps graphs a and b to merged result c. (a,b) -> c. We can avoid + // the merge if we ever see a and b again. Note that (b,a) -> c should + // also be examined during cache lookup. + // + p.mergeCache = nil + + return p +} + +func (p *ParserATNSimulator) GetPredictionMode() int { + return p.predictionMode +} + +func (p *ParserATNSimulator) SetPredictionMode(v int) { + p.predictionMode = v +} + +func (p *ParserATNSimulator) reset() { +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) AdaptivePredict(parser *BaseParser, input TokenStream, decision int, outerContext ParserRuleContext) int { + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("adaptivePredict decision " + strconv.Itoa(decision) + + " exec LA(1)==" + p.getLookaheadName(input) + + " line " + strconv.Itoa(input.LT(1).GetLine()) + ":" + + strconv.Itoa(input.LT(1).GetColumn())) + } + p.input = input + p.startIndex = input.Index() + p.outerContext = outerContext + + dfa := p.decisionToDFA[decision] + p.dfa = dfa + m := input.Mark() + index := input.Index() + + defer func() { + p.dfa = nil + p.mergeCache = nil // whack cache after each prediction + // Do not attempt to run a GC now that we're done with the cache as makes the + // GC overhead terrible for badly formed grammars and has little effect on well formed + // grammars. + // I have made some extra effort to try and reduce memory pressure by reusing allocations when + // possible. However, it can only have a limited effect. The real solution is to encourage grammar + // authors to think more carefully about their grammar and to use the new antlr.stats tag to inspect + // what is happening at runtime, along with using the error listener to report ambiguities. + + input.Seek(index) + input.Release(m) + }() + + // Now we are certain to have a specific decision's DFA + // But, do we still need an initial state? + var s0 *DFAState + p.atn.stateMu.RLock() + if dfa.getPrecedenceDfa() { + p.atn.edgeMu.RLock() + // the start state for a precedence DFA depends on the current + // parser precedence, and is provided by a DFA method. + s0 = dfa.getPrecedenceStartState(p.parser.GetPrecedence()) + p.atn.edgeMu.RUnlock() + } else { + // the start state for a "regular" DFA is just s0 + s0 = dfa.getS0() + } + p.atn.stateMu.RUnlock() + + if s0 == nil { + if outerContext == nil { + outerContext = ParserRuleContextEmpty + } + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("predictATN decision " + strconv.Itoa(dfa.decision) + + " exec LA(1)==" + p.getLookaheadName(input) + + ", outerContext=" + outerContext.String(p.parser.GetRuleNames(), nil)) + } + fullCtx := false + s0Closure := p.computeStartState(dfa.atnStartState, ParserRuleContextEmpty, fullCtx) + + p.atn.stateMu.Lock() + if dfa.getPrecedenceDfa() { + // If p is a precedence DFA, we use applyPrecedenceFilter + // to convert the computed start state to a precedence start + // state. We then use DFA.setPrecedenceStartState to set the + // appropriate start state for the precedence level rather + // than simply setting DFA.s0. + // + dfa.s0.configs = s0Closure + s0Closure = p.applyPrecedenceFilter(s0Closure) + s0 = p.addDFAState(dfa, NewDFAState(-1, s0Closure)) + p.atn.edgeMu.Lock() + dfa.setPrecedenceStartState(p.parser.GetPrecedence(), s0) + p.atn.edgeMu.Unlock() + } else { + s0 = p.addDFAState(dfa, NewDFAState(-1, s0Closure)) + dfa.setS0(s0) + } + p.atn.stateMu.Unlock() + } + + alt, re := p.execATN(dfa, s0, input, index, outerContext) + parser.SetError(re) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("DFA after predictATN: " + dfa.String(p.parser.GetLiteralNames(), nil)) + } + return alt + +} + +// execATN performs ATN simulation to compute a predicted alternative based +// upon the remaining input, but also updates the DFA cache to avoid +// having to traverse the ATN again for the same input sequence. +// +// There are some key conditions we're looking for after computing a new +// set of ATN configs (proposed DFA state): +// +// - If the set is empty, there is no viable alternative for current symbol +// - Does the state uniquely predict an alternative? +// - Does the state have a conflict that would prevent us from +// putting it on the work list? +// +// We also have some key operations to do: +// +// - Add an edge from previous DFA state to potentially NewDFA state, D, +// - Upon current symbol but only if adding to work list, which means in all +// cases except no viable alternative (and possibly non-greedy decisions?) +// - Collecting predicates and adding semantic context to DFA accept states +// - adding rule context to context-sensitive DFA accept states +// - Consuming an input symbol +// - Reporting a conflict +// - Reporting an ambiguity +// - Reporting a context sensitivity +// - Reporting insufficient predicates +// +// Cover these cases: +// +// - dead end +// - single alt +// - single alt + predicates +// - conflict +// - conflict + predicates +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) execATN(dfa *DFA, s0 *DFAState, input TokenStream, startIndex int, outerContext ParserRuleContext) (int, RecognitionException) { + + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("execATN decision " + strconv.Itoa(dfa.decision) + + ", DFA state " + s0.String() + + ", LA(1)==" + p.getLookaheadName(input) + + " line " + strconv.Itoa(input.LT(1).GetLine()) + ":" + strconv.Itoa(input.LT(1).GetColumn())) + } + + previousD := s0 + + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("s0 = " + s0.String()) + } + t := input.LA(1) + for { // for more work + D := p.getExistingTargetState(previousD, t) + if D == nil { + D = p.computeTargetState(dfa, previousD, t) + } + if D == ATNSimulatorError { + // if any configs in previous dipped into outer context, that + // means that input up to t actually finished entry rule + // at least for SLL decision. Full LL doesn't dip into outer + // so don't need special case. + // We will get an error no matter what so delay until after + // decision better error message. Also, no reachable target + // ATN states in SLL implies LL will also get nowhere. + // If conflict in states that dip out, choose min since we + // will get error no matter what. + e := p.noViableAlt(input, outerContext, previousD.configs, startIndex) + input.Seek(startIndex) + alt := p.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previousD.configs, outerContext) + if alt != ATNInvalidAltNumber { + return alt, nil + } + p.parser.SetError(e) + return ATNInvalidAltNumber, e + } + if D.requiresFullContext && p.predictionMode != PredictionModeSLL { + // IF PREDS, MIGHT RESOLVE TO SINGLE ALT => SLL (or syntax error) + conflictingAlts := D.configs.conflictingAlts + if D.predicates != nil { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("DFA state has preds in DFA sim LL fail-over") + } + conflictIndex := input.Index() + if conflictIndex != startIndex { + input.Seek(startIndex) + } + conflictingAlts = p.evalSemanticContext(D.predicates, outerContext, true) + if conflictingAlts.length() == 1 { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("Full LL avoided") + } + return conflictingAlts.minValue(), nil + } + if conflictIndex != startIndex { + // restore the index so Reporting the fallback to full + // context occurs with the index at the correct spot + input.Seek(conflictIndex) + } + } + if runtimeConfig.parserATNSimulatorDFADebug { + fmt.Println("ctx sensitive state " + outerContext.String(nil, nil) + " in " + D.String()) + } + fullCtx := true + s0Closure := p.computeStartState(dfa.atnStartState, outerContext, fullCtx) + p.ReportAttemptingFullContext(dfa, conflictingAlts, D.configs, startIndex, input.Index()) + alt, re := p.execATNWithFullContext(dfa, D, s0Closure, input, startIndex, outerContext) + return alt, re + } + if D.isAcceptState { + if D.predicates == nil { + return D.prediction, nil + } + stopIndex := input.Index() + input.Seek(startIndex) + alts := p.evalSemanticContext(D.predicates, outerContext, true) + + switch alts.length() { + case 0: + return ATNInvalidAltNumber, p.noViableAlt(input, outerContext, D.configs, startIndex) + case 1: + return alts.minValue(), nil + default: + // Report ambiguity after predicate evaluation to make sure the correct set of ambig alts is Reported. + p.ReportAmbiguity(dfa, D, startIndex, stopIndex, false, alts, D.configs) + return alts.minValue(), nil + } + } + previousD = D + + if t != TokenEOF { + input.Consume() + t = input.LA(1) + } + } +} + +// Get an existing target state for an edge in the DFA. If the target state +// for the edge has not yet been computed or is otherwise not available, +// p method returns {@code nil}. +// +// @param previousD The current DFA state +// @param t The next input symbol +// @return The existing target DFA state for the given input symbol +// {@code t}, or {@code nil} if the target state for p edge is not +// already cached + +func (p *ParserATNSimulator) getExistingTargetState(previousD *DFAState, t int) *DFAState { + if t+1 < 0 { + return nil + } + + p.atn.edgeMu.RLock() + defer p.atn.edgeMu.RUnlock() + edges := previousD.getEdges() + if edges == nil || t+1 >= len(edges) { + return nil + } + return previousD.getIthEdge(t + 1) +} + +// Compute a target state for an edge in the DFA, and attempt to add the +// computed state and corresponding edge to the DFA. +// +// @param dfa The DFA +// @param previousD The current DFA state +// @param t The next input symbol +// +// @return The computed target DFA state for the given input symbol +// {@code t}. If {@code t} does not lead to a valid DFA state, p method +// returns {@link //ERROR}. +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) computeTargetState(dfa *DFA, previousD *DFAState, t int) *DFAState { + reach := p.computeReachSet(previousD.configs, t, false) + + if reach == nil { + p.addDFAEdge(dfa, previousD, t, ATNSimulatorError) + return ATNSimulatorError + } + // create new target state we'll add to DFA after it's complete + D := NewDFAState(-1, reach) + + predictedAlt := p.getUniqueAlt(reach) + + if runtimeConfig.parserATNSimulatorDebug { + altSubSets := PredictionModegetConflictingAltSubsets(reach) + fmt.Println("SLL altSubSets=" + fmt.Sprint(altSubSets) + + ", previous=" + previousD.configs.String() + + ", configs=" + reach.String() + + ", predict=" + strconv.Itoa(predictedAlt) + + ", allSubsetsConflict=" + + fmt.Sprint(PredictionModeallSubsetsConflict(altSubSets)) + + ", conflictingAlts=" + p.getConflictingAlts(reach).String()) + } + if predictedAlt != ATNInvalidAltNumber { + // NO CONFLICT, UNIQUELY PREDICTED ALT + D.isAcceptState = true + D.configs.uniqueAlt = predictedAlt + D.setPrediction(predictedAlt) + } else if PredictionModehasSLLConflictTerminatingPrediction(p.predictionMode, reach) { + // MORE THAN ONE VIABLE ALTERNATIVE + D.configs.conflictingAlts = p.getConflictingAlts(reach) + D.requiresFullContext = true + // in SLL-only mode, we will stop at p state and return the minimum alt + D.isAcceptState = true + D.setPrediction(D.configs.conflictingAlts.minValue()) + } + if D.isAcceptState && D.configs.hasSemanticContext { + p.predicateDFAState(D, p.atn.getDecisionState(dfa.decision)) + if D.predicates != nil { + D.setPrediction(ATNInvalidAltNumber) + } + } + // all adds to dfa are done after we've created full D state + D = p.addDFAEdge(dfa, previousD, t, D) + return D +} + +func (p *ParserATNSimulator) predicateDFAState(dfaState *DFAState, decisionState DecisionState) { + // We need to test all predicates, even in DFA states that + // uniquely predict alternative. + nalts := len(decisionState.GetTransitions()) + // Update DFA so reach becomes accept state with (predicate,alt) + // pairs if preds found for conflicting alts + altsToCollectPredsFrom := p.getConflictingAltsOrUniqueAlt(dfaState.configs) + altToPred := p.getPredsForAmbigAlts(altsToCollectPredsFrom, dfaState.configs, nalts) + if altToPred != nil { + dfaState.predicates = p.getPredicatePredictions(altsToCollectPredsFrom, altToPred) + dfaState.setPrediction(ATNInvalidAltNumber) // make sure we use preds + } else { + // There are preds in configs but they might go away + // when OR'd together like {p}? || NONE == NONE. If neither + // alt has preds, resolve to min alt + dfaState.setPrediction(altsToCollectPredsFrom.minValue()) + } +} + +// comes back with reach.uniqueAlt set to a valid alt +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) execATNWithFullContext(dfa *DFA, D *DFAState, s0 *ATNConfigSet, input TokenStream, startIndex int, outerContext ParserRuleContext) (int, RecognitionException) { + + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("execATNWithFullContext " + s0.String()) + } + + fullCtx := true + foundExactAmbig := false + var reach *ATNConfigSet + previous := s0 + input.Seek(startIndex) + t := input.LA(1) + predictedAlt := -1 + + for { // for more work + reach = p.computeReachSet(previous, t, fullCtx) + if reach == nil { + // if any configs in previous dipped into outer context, that + // means that input up to t actually finished entry rule + // at least for LL decision. Full LL doesn't dip into outer + // so don't need special case. + // We will get an error no matter what so delay until after + // decision better error message. Also, no reachable target + // ATN states in SLL implies LL will also get nowhere. + // If conflict in states that dip out, choose min since we + // will get error no matter what. + input.Seek(startIndex) + alt := p.getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(previous, outerContext) + if alt != ATNInvalidAltNumber { + return alt, nil + } + return alt, p.noViableAlt(input, outerContext, previous, startIndex) + } + altSubSets := PredictionModegetConflictingAltSubsets(reach) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("LL altSubSets=" + fmt.Sprint(altSubSets) + ", predict=" + + strconv.Itoa(PredictionModegetUniqueAlt(altSubSets)) + ", resolvesToJustOneViableAlt=" + + fmt.Sprint(PredictionModeresolvesToJustOneViableAlt(altSubSets))) + } + reach.uniqueAlt = p.getUniqueAlt(reach) + // unique prediction? + if reach.uniqueAlt != ATNInvalidAltNumber { + predictedAlt = reach.uniqueAlt + break + } + if p.predictionMode != PredictionModeLLExactAmbigDetection { + predictedAlt = PredictionModeresolvesToJustOneViableAlt(altSubSets) + if predictedAlt != ATNInvalidAltNumber { + break + } + } else { + // In exact ambiguity mode, we never try to terminate early. + // Just keeps scarfing until we know what the conflict is + if PredictionModeallSubsetsConflict(altSubSets) && PredictionModeallSubsetsEqual(altSubSets) { + foundExactAmbig = true + predictedAlt = PredictionModegetSingleViableAlt(altSubSets) + break + } + // else there are multiple non-conflicting subsets or + // we're not sure what the ambiguity is yet. + // So, keep going. + } + previous = reach + if t != TokenEOF { + input.Consume() + t = input.LA(1) + } + } + // If the configuration set uniquely predicts an alternative, + // without conflict, then we know that it's a full LL decision + // not SLL. + if reach.uniqueAlt != ATNInvalidAltNumber { + p.ReportContextSensitivity(dfa, predictedAlt, reach, startIndex, input.Index()) + return predictedAlt, nil + } + // We do not check predicates here because we have checked them + // on-the-fly when doing full context prediction. + + // + // In non-exact ambiguity detection mode, we might actually be able to + // detect an exact ambiguity, but I'm not going to spend the cycles + // needed to check. We only emit ambiguity warnings in exact ambiguity + // mode. + // + // For example, we might know that we have conflicting configurations. + // But, that does not mean that there is no way forward without a + // conflict. It's possible to have non-conflicting alt subsets as in: + // + // altSubSets=[{1, 2}, {1, 2}, {1}, {1, 2}] + // + // from + // + // [(17,1,[5 $]), (13,1,[5 10 $]), (21,1,[5 10 $]), (11,1,[$]), + // (13,2,[5 10 $]), (21,2,[5 10 $]), (11,2,[$])] + // + // In p case, (17,1,[5 $]) indicates there is some next sequence that + // would resolve p without conflict to alternative 1. Any other viable + // next sequence, however, is associated with a conflict. We stop + // looking for input because no amount of further lookahead will alter + // the fact that we should predict alternative 1. We just can't say for + // sure that there is an ambiguity without looking further. + + p.ReportAmbiguity(dfa, D, startIndex, input.Index(), foundExactAmbig, reach.Alts(), reach) + + return predictedAlt, nil +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) computeReachSet(closure *ATNConfigSet, t int, fullCtx bool) *ATNConfigSet { + if p.mergeCache == nil { + p.mergeCache = NewJPCMap(ReachSetCollection, "Merge cache for computeReachSet()") + } + intermediate := NewATNConfigSet(fullCtx) + + // Configurations already in a rule stop state indicate reaching the end + // of the decision rule (local context) or end of the start rule (full + // context). Once reached, these configurations are never updated by a + // closure operation, so they are handled separately for the performance + // advantage of having a smaller intermediate set when calling closure. + // + // For full-context reach operations, separate handling is required to + // ensure that the alternative Matching the longest overall sequence is + // chosen when multiple such configurations can Match the input. + + var skippedStopStates []*ATNConfig + + // First figure out where we can reach on input t + for _, c := range closure.configs { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("testing " + p.GetTokenName(t) + " at " + c.String()) + } + + if _, ok := c.GetState().(*RuleStopState); ok { + if fullCtx || t == TokenEOF { + skippedStopStates = append(skippedStopStates, c) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("added " + c.String() + " to SkippedStopStates") + } + } + continue + } + + for _, trans := range c.GetState().GetTransitions() { + target := p.getReachableTarget(trans, t) + if target != nil { + cfg := NewATNConfig4(c, target) + intermediate.Add(cfg, p.mergeCache) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("added " + cfg.String() + " to intermediate") + } + } + } + } + + // Now figure out where the reach operation can take us... + var reach *ATNConfigSet + + // This block optimizes the reach operation for intermediate sets which + // trivially indicate a termination state for the overall + // AdaptivePredict operation. + // + // The conditions assume that intermediate + // contains all configurations relevant to the reach set, but p + // condition is not true when one or more configurations have been + // withheld in SkippedStopStates, or when the current symbol is EOF. + // + if skippedStopStates == nil && t != TokenEOF { + if len(intermediate.configs) == 1 { + // Don't pursue the closure if there is just one state. + // It can only have one alternative just add to result + // Also don't pursue the closure if there is unique alternative + // among the configurations. + reach = intermediate + } else if p.getUniqueAlt(intermediate) != ATNInvalidAltNumber { + // Also don't pursue the closure if there is unique alternative + // among the configurations. + reach = intermediate + } + } + // If the reach set could not be trivially determined, perform a closure + // operation on the intermediate set to compute its initial value. + // + if reach == nil { + reach = NewATNConfigSet(fullCtx) + closureBusy := NewClosureBusy("ParserATNSimulator.computeReachSet() make a closureBusy") + treatEOFAsEpsilon := t == TokenEOF + amount := len(intermediate.configs) + for k := 0; k < amount; k++ { + p.closure(intermediate.configs[k], reach, closureBusy, false, fullCtx, treatEOFAsEpsilon) + } + } + if t == TokenEOF { + // After consuming EOF no additional input is possible, so we are + // only interested in configurations which reached the end of the + // decision rule (local context) or end of the start rule (full + // context). Update reach to contain only these configurations. This + // handles both explicit EOF transitions in the grammar and implicit + // EOF transitions following the end of the decision or start rule. + // + // When reach==intermediate, no closure operation was performed. In + // p case, removeAllConfigsNotInRuleStopState needs to check for + // reachable rule stop states as well as configurations already in + // a rule stop state. + // + // This is handled before the configurations in SkippedStopStates, + // because any configurations potentially added from that list are + // already guaranteed to meet this condition whether it's + // required. + // + reach = p.removeAllConfigsNotInRuleStopState(reach, reach.Equals(intermediate)) + } + // If SkippedStopStates!=nil, then it contains at least one + // configuration. For full-context reach operations, these + // configurations reached the end of the start rule, in which case we + // only add them back to reach if no configuration during the current + // closure operation reached such a state. This ensures AdaptivePredict + // chooses an alternative Matching the longest overall sequence when + // multiple alternatives are viable. + // + if skippedStopStates != nil && ((!fullCtx) || (!PredictionModehasConfigInRuleStopState(reach))) { + for l := 0; l < len(skippedStopStates); l++ { + reach.Add(skippedStopStates[l], p.mergeCache) + } + } + + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("computeReachSet " + closure.String() + " -> " + reach.String()) + } + + if len(reach.configs) == 0 { + return nil + } + + return reach +} + +// removeAllConfigsNotInRuleStopState returns a configuration set containing only the configurations from +// configs which are in a [RuleStopState]. If all +// configurations in configs are already in a rule stop state, this +// method simply returns configs. +// +// When lookToEndOfRule is true, this method uses +// [ATN].[NextTokens] for each configuration in configs which is +// not already in a rule stop state to see if a rule stop state is reachable +// from the configuration via epsilon-only transitions. +// +// When lookToEndOfRule is true, this method checks for rule stop states +// reachable by epsilon-only transitions from each configuration in +// configs. +// +// The func returns configs if all configurations in configs are in a +// rule stop state, otherwise it returns a new configuration set containing only +// the configurations from configs which are in a rule stop state +func (p *ParserATNSimulator) removeAllConfigsNotInRuleStopState(configs *ATNConfigSet, lookToEndOfRule bool) *ATNConfigSet { + if PredictionModeallConfigsInRuleStopStates(configs) { + return configs + } + result := NewATNConfigSet(configs.fullCtx) + for _, config := range configs.configs { + if _, ok := config.GetState().(*RuleStopState); ok { + result.Add(config, p.mergeCache) + continue + } + if lookToEndOfRule && config.GetState().GetEpsilonOnlyTransitions() { + NextTokens := p.atn.NextTokens(config.GetState(), nil) + if NextTokens.contains(TokenEpsilon) { + endOfRuleState := p.atn.ruleToStopState[config.GetState().GetRuleIndex()] + result.Add(NewATNConfig4(config, endOfRuleState), p.mergeCache) + } + } + } + return result +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) computeStartState(a ATNState, ctx RuleContext, fullCtx bool) *ATNConfigSet { + // always at least the implicit call to start rule + initialContext := predictionContextFromRuleContext(p.atn, ctx) + configs := NewATNConfigSet(fullCtx) + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("computeStartState from ATN state " + a.String() + + " initialContext=" + initialContext.String()) + } + + for i := 0; i < len(a.GetTransitions()); i++ { + target := a.GetTransitions()[i].getTarget() + c := NewATNConfig6(target, i+1, initialContext) + closureBusy := NewClosureBusy("ParserATNSimulator.computeStartState() make a closureBusy") + p.closure(c, configs, closureBusy, true, fullCtx, false) + } + return configs +} + +// applyPrecedenceFilter transforms the start state computed by +// [computeStartState] to the special start state used by a +// precedence [DFA] for a particular precedence value. The transformation +// process applies the following changes to the start state's configuration +// set. +// +// 1. Evaluate the precedence predicates for each configuration using +// [SemanticContext].evalPrecedence. +// 2. Remove all configurations which predict an alternative greater than +// 1, for which another configuration that predicts alternative 1 is in the +// same ATN state with the same prediction context. +// +// Transformation 2 is valid for the following reasons: +// +// - The closure block cannot contain any epsilon transitions which bypass +// the body of the closure, so all states reachable via alternative 1 are +// part of the precedence alternatives of the transformed left-recursive +// rule. +// - The "primary" portion of a left recursive rule cannot contain an +// epsilon transition, so the only way an alternative other than 1 can exist +// in a state that is also reachable via alternative 1 is by nesting calls +// to the left-recursive rule, with the outer calls not being at the +// preferred precedence level. +// +// The prediction context must be considered by this filter to address +// situations like the following: +// +// grammar TA +// prog: statement* EOF +// statement: letterA | statement letterA 'b' +// letterA: 'a' +// +// In the above grammar, the [ATN] state immediately before the token +// reference 'a' in letterA is reachable from the left edge +// of both the primary and closure blocks of the left-recursive rule +// statement. The prediction context associated with each of these +// configurations distinguishes between them, and prevents the alternative +// which stepped out to prog, and then back in to statement +// from being eliminated by the filter. +// +// The func returns the transformed configuration set representing the start state +// for a precedence [DFA] at a particular precedence level (determined by +// calling [Parser].getPrecedence). +func (p *ParserATNSimulator) applyPrecedenceFilter(configs *ATNConfigSet) *ATNConfigSet { + + statesFromAlt1 := make(map[int]*PredictionContext) + configSet := NewATNConfigSet(configs.fullCtx) + + for _, config := range configs.configs { + // handle alt 1 first + if config.GetAlt() != 1 { + continue + } + updatedContext := config.GetSemanticContext().evalPrecedence(p.parser, p.outerContext) + if updatedContext == nil { + // the configuration was eliminated + continue + } + statesFromAlt1[config.GetState().GetStateNumber()] = config.GetContext() + if updatedContext != config.GetSemanticContext() { + configSet.Add(NewATNConfig2(config, updatedContext), p.mergeCache) + } else { + configSet.Add(config, p.mergeCache) + } + } + for _, config := range configs.configs { + + if config.GetAlt() == 1 { + // already handled + continue + } + // In the future, p elimination step could be updated to also + // filter the prediction context for alternatives predicting alt>1 + // (basically a graph subtraction algorithm). + if !config.getPrecedenceFilterSuppressed() { + context := statesFromAlt1[config.GetState().GetStateNumber()] + if context != nil && context.Equals(config.GetContext()) { + // eliminated + continue + } + } + configSet.Add(config, p.mergeCache) + } + return configSet +} + +func (p *ParserATNSimulator) getReachableTarget(trans Transition, ttype int) ATNState { + if trans.Matches(ttype, 0, p.atn.maxTokenType) { + return trans.getTarget() + } + + return nil +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) getPredsForAmbigAlts(ambigAlts *BitSet, configs *ATNConfigSet, nalts int) []SemanticContext { + + altToPred := make([]SemanticContext, nalts+1) + for _, c := range configs.configs { + if ambigAlts.contains(c.GetAlt()) { + altToPred[c.GetAlt()] = SemanticContextorContext(altToPred[c.GetAlt()], c.GetSemanticContext()) + } + } + nPredAlts := 0 + for i := 1; i <= nalts; i++ { + pred := altToPred[i] + if pred == nil { + altToPred[i] = SemanticContextNone + } else if pred != SemanticContextNone { + nPredAlts++ + } + } + // unambiguous alts are nil in altToPred + if nPredAlts == 0 { + altToPred = nil + } + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("getPredsForAmbigAlts result " + fmt.Sprint(altToPred)) + } + return altToPred +} + +func (p *ParserATNSimulator) getPredicatePredictions(ambigAlts *BitSet, altToPred []SemanticContext) []*PredPrediction { + pairs := make([]*PredPrediction, 0) + containsPredicate := false + for i := 1; i < len(altToPred); i++ { + pred := altToPred[i] + // un-predicated is indicated by SemanticContextNONE + if ambigAlts != nil && ambigAlts.contains(i) { + pairs = append(pairs, NewPredPrediction(pred, i)) + } + if pred != SemanticContextNone { + containsPredicate = true + } + } + if !containsPredicate { + return nil + } + return pairs +} + +// getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule is used to improve the localization of error messages by +// choosing an alternative rather than panic a NoViableAltException in particular prediction scenarios where the +// Error state was reached during [ATN] simulation. +// +// The default implementation of this method uses the following +// algorithm to identify an [ATN] configuration which successfully parsed the +// decision entry rule. Choosing such an alternative ensures that the +// [ParserRuleContext] returned by the calling rule will be complete +// and valid, and the syntax error will be Reported later at a more +// localized location. +// +// - If a syntactically valid path or paths reach the end of the decision rule, and +// they are semantically valid if predicated, return the min associated alt. +// - Else, if a semantically invalid but syntactically valid path exist +// or paths exist, return the minimum associated alt. +// - Otherwise, return [ATNInvalidAltNumber]. +// +// In some scenarios, the algorithm described above could predict an +// alternative which will result in a [FailedPredicateException] in +// the parser. Specifically, this could occur if the only configuration +// capable of successfully parsing to the end of the decision rule is +// blocked by a semantic predicate. By choosing this alternative within +// [AdaptivePredict] instead of panic a [NoViableAltException], the resulting +// [FailedPredicateException] in the parser will identify the specific +// predicate which is preventing the parser from successfully parsing the +// decision rule, which helps developers identify and correct logic errors +// in semantic predicates. +// +// pass in the configs holding ATN configurations which were valid immediately before +// the ERROR state was reached, outerContext as the initial parser context from the paper +// or the parser stack at the instant before prediction commences. +// +// The func returns the value to return from [AdaptivePredict], or +// [ATNInvalidAltNumber] if a suitable alternative was not +// identified and [AdaptivePredict] should report an error instead. +func (p *ParserATNSimulator) getSynValidOrSemInvalidAltThatFinishedDecisionEntryRule(configs *ATNConfigSet, outerContext ParserRuleContext) int { + cfgs := p.splitAccordingToSemanticValidity(configs, outerContext) + semValidConfigs := cfgs[0] + semInvalidConfigs := cfgs[1] + alt := p.GetAltThatFinishedDecisionEntryRule(semValidConfigs) + if alt != ATNInvalidAltNumber { // semantically/syntactically viable path exists + return alt + } + // Is there a syntactically valid path with a failed pred? + if len(semInvalidConfigs.configs) > 0 { + alt = p.GetAltThatFinishedDecisionEntryRule(semInvalidConfigs) + if alt != ATNInvalidAltNumber { // syntactically viable path exists + return alt + } + } + return ATNInvalidAltNumber +} + +func (p *ParserATNSimulator) GetAltThatFinishedDecisionEntryRule(configs *ATNConfigSet) int { + alts := NewIntervalSet() + + for _, c := range configs.configs { + _, ok := c.GetState().(*RuleStopState) + + if c.GetReachesIntoOuterContext() > 0 || (ok && c.GetContext().hasEmptyPath()) { + alts.addOne(c.GetAlt()) + } + } + if alts.length() == 0 { + return ATNInvalidAltNumber + } + + return alts.first() +} + +// Walk the list of configurations and split them according to +// those that have preds evaluating to true/false. If no pred, assume +// true pred and include in succeeded set. Returns Pair of sets. +// +// Create a NewSet so as not to alter the incoming parameter. +// +// Assumption: the input stream has been restored to the starting point +// prediction, which is where predicates need to evaluate. + +type ATNConfigSetPair struct { + item0, item1 *ATNConfigSet +} + +func (p *ParserATNSimulator) splitAccordingToSemanticValidity(configs *ATNConfigSet, outerContext ParserRuleContext) []*ATNConfigSet { + succeeded := NewATNConfigSet(configs.fullCtx) + failed := NewATNConfigSet(configs.fullCtx) + + for _, c := range configs.configs { + if c.GetSemanticContext() != SemanticContextNone { + predicateEvaluationResult := c.GetSemanticContext().evaluate(p.parser, outerContext) + if predicateEvaluationResult { + succeeded.Add(c, nil) + } else { + failed.Add(c, nil) + } + } else { + succeeded.Add(c, nil) + } + } + return []*ATNConfigSet{succeeded, failed} +} + +// evalSemanticContext looks through a list of predicate/alt pairs, returning alts for the +// pairs that win. A [SemanticContextNone] predicate indicates an alt containing an +// un-predicated runtimeConfig which behaves as "always true." If !complete +// then we stop at the first predicate that evaluates to true. This +// includes pairs with nil predicates. +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) evalSemanticContext(predPredictions []*PredPrediction, outerContext ParserRuleContext, complete bool) *BitSet { + predictions := NewBitSet() + for i := 0; i < len(predPredictions); i++ { + pair := predPredictions[i] + if pair.pred == SemanticContextNone { + predictions.add(pair.alt) + if !complete { + break + } + continue + } + + predicateEvaluationResult := pair.pred.evaluate(p.parser, outerContext) + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorDFADebug { + fmt.Println("eval pred " + pair.String() + "=" + fmt.Sprint(predicateEvaluationResult)) + } + if predicateEvaluationResult { + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorDFADebug { + fmt.Println("PREDICT " + fmt.Sprint(pair.alt)) + } + predictions.add(pair.alt) + if !complete { + break + } + } + } + return predictions +} + +func (p *ParserATNSimulator) closure(config *ATNConfig, configs *ATNConfigSet, closureBusy *ClosureBusy, collectPredicates, fullCtx, treatEOFAsEpsilon bool) { + initialDepth := 0 + p.closureCheckingStopState(config, configs, closureBusy, collectPredicates, + fullCtx, initialDepth, treatEOFAsEpsilon) +} + +func (p *ParserATNSimulator) closureCheckingStopState(config *ATNConfig, configs *ATNConfigSet, closureBusy *ClosureBusy, collectPredicates, fullCtx bool, depth int, treatEOFAsEpsilon bool) { + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("closure(" + config.String() + ")") + } + + var stack []*ATNConfig + visited := make(map[*ATNConfig]bool) + + stack = append(stack, config) + + for len(stack) > 0 { + currConfig := stack[len(stack)-1] + stack = stack[:len(stack)-1] + + if _, ok := visited[currConfig]; ok { + continue + } + visited[currConfig] = true + + if _, ok := currConfig.GetState().(*RuleStopState); ok { + // We hit rule end. If we have context info, use it + // run thru all possible stack tops in ctx + if !currConfig.GetContext().isEmpty() { + for i := 0; i < currConfig.GetContext().length(); i++ { + if currConfig.GetContext().getReturnState(i) == BasePredictionContextEmptyReturnState { + if fullCtx { + nb := NewATNConfig1(currConfig, currConfig.GetState(), BasePredictionContextEMPTY) + configs.Add(nb, p.mergeCache) + continue + } else { + // we have no context info, just chase follow links (if greedy) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("FALLING off rule " + p.getRuleName(currConfig.GetState().GetRuleIndex())) + } + p.closureWork(currConfig, configs, closureBusy, collectPredicates, fullCtx, depth, treatEOFAsEpsilon) + } + continue + } + returnState := p.atn.states[currConfig.GetContext().getReturnState(i)] + newContext := currConfig.GetContext().GetParent(i) // "pop" return state + + c := NewATNConfig5(returnState, currConfig.GetAlt(), newContext, currConfig.GetSemanticContext()) + // While we have context to pop back from, we may have + // gotten that context AFTER having falling off a rule. + // Make sure we track that we are now out of context. + c.SetReachesIntoOuterContext(currConfig.GetReachesIntoOuterContext()) + + stack = append(stack, c) + } + continue + } else if fullCtx { + // reached end of start rule + configs.Add(currConfig, p.mergeCache) + continue + } else { + // else if we have no context info, just chase follow links (if greedy) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("FALLING off rule " + p.getRuleName(currConfig.GetState().GetRuleIndex())) + } + } + } + + p.closureWork(currConfig, configs, closureBusy, collectPredicates, fullCtx, depth, treatEOFAsEpsilon) + } +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) closureCheckingStopStateRecursive(config *ATNConfig, configs *ATNConfigSet, closureBusy *ClosureBusy, collectPredicates, fullCtx bool, depth int, treatEOFAsEpsilon bool) { + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("closure(" + config.String() + ")") + } + + if _, ok := config.GetState().(*RuleStopState); ok { + // We hit rule end. If we have context info, use it + // run thru all possible stack tops in ctx + if !config.GetContext().isEmpty() { + for i := 0; i < config.GetContext().length(); i++ { + if config.GetContext().getReturnState(i) == BasePredictionContextEmptyReturnState { + if fullCtx { + nb := NewATNConfig1(config, config.GetState(), BasePredictionContextEMPTY) + configs.Add(nb, p.mergeCache) + continue + } else { + // we have no context info, just chase follow links (if greedy) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("FALLING off rule " + p.getRuleName(config.GetState().GetRuleIndex())) + } + p.closureWork(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEOFAsEpsilon) + } + continue + } + returnState := p.atn.states[config.GetContext().getReturnState(i)] + newContext := config.GetContext().GetParent(i) // "pop" return state + + c := NewATNConfig5(returnState, config.GetAlt(), newContext, config.GetSemanticContext()) + // While we have context to pop back from, we may have + // gotten that context AFTER having falling off a rule. + // Make sure we track that we are now out of context. + c.SetReachesIntoOuterContext(config.GetReachesIntoOuterContext()) + p.closureCheckingStopState(c, configs, closureBusy, collectPredicates, fullCtx, depth-1, treatEOFAsEpsilon) + } + return + } else if fullCtx { + // reached end of start rule + configs.Add(config, p.mergeCache) + return + } else { + // else if we have no context info, just chase follow links (if greedy) + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("FALLING off rule " + p.getRuleName(config.GetState().GetRuleIndex())) + } + } + } + p.closureWork(config, configs, closureBusy, collectPredicates, fullCtx, depth, treatEOFAsEpsilon) +} + +// Do the actual work of walking epsilon edges +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) closureWork(config *ATNConfig, configs *ATNConfigSet, closureBusy *ClosureBusy, collectPredicates, fullCtx bool, depth int, treatEOFAsEpsilon bool) { + state := config.GetState() + // optimization + if !state.GetEpsilonOnlyTransitions() { + configs.Add(config, p.mergeCache) + // make sure to not return here, because EOF transitions can act as + // both epsilon transitions and non-epsilon transitions. + } + for i := 0; i < len(state.GetTransitions()); i++ { + if i == 0 && p.canDropLoopEntryEdgeInLeftRecursiveRule(config) { + continue + } + + t := state.GetTransitions()[i] + _, ok := t.(*ActionTransition) + continueCollecting := collectPredicates && !ok + c := p.getEpsilonTarget(config, t, continueCollecting, depth == 0, fullCtx, treatEOFAsEpsilon) + if c != nil { + newDepth := depth + + if _, ok := config.GetState().(*RuleStopState); ok { + // target fell off end of rule mark resulting c as having dipped into outer context + // We can't get here if incoming config was rule stop and we had context + // track how far we dip into outer context. Might + // come in handy and we avoid evaluating context dependent + // preds if this is > 0. + + if p.dfa != nil && p.dfa.getPrecedenceDfa() { + if t.(*EpsilonTransition).outermostPrecedenceReturn == p.dfa.atnStartState.GetRuleIndex() { + c.setPrecedenceFilterSuppressed(true) + } + } + + c.SetReachesIntoOuterContext(c.GetReachesIntoOuterContext() + 1) + + _, present := closureBusy.Put(c) + if present { + // avoid infinite recursion for right-recursive rules + continue + } + + configs.dipsIntoOuterContext = true // TODO: can remove? only care when we add to set per middle of this method + newDepth-- + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("dips into outer ctx: " + c.String()) + } + } else { + + if !t.getIsEpsilon() { + _, present := closureBusy.Put(c) + if present { + // avoid infinite recursion for EOF* and EOF+ + continue + } + } + if _, ok := t.(*RuleTransition); ok { + // latch when newDepth goes negative - once we step out of the entry context we can't return + if newDepth >= 0 { + newDepth++ + } + } + } + p.closureCheckingStopState(c, configs, closureBusy, continueCollecting, fullCtx, newDepth, treatEOFAsEpsilon) + } + } +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) canDropLoopEntryEdgeInLeftRecursiveRule(config *ATNConfig) bool { + if !runtimeConfig.lRLoopEntryBranchOpt { + return false + } + + _p := config.GetState() + + // First check to see if we are in StarLoopEntryState generated during + // left-recursion elimination. For efficiency, also check if + // the context has an empty stack case. If so, it would mean + // global FOLLOW so we can't perform optimization + if _p.GetStateType() != ATNStateStarLoopEntry { + return false + } + startLoop, ok := _p.(*StarLoopEntryState) + if !ok { + return false + } + if !startLoop.precedenceRuleDecision || + config.GetContext().isEmpty() || + config.GetContext().hasEmptyPath() { + return false + } + + // Require all return states to return back to the same rule + // that p is in. + numCtxs := config.GetContext().length() + for i := 0; i < numCtxs; i++ { + returnState := p.atn.states[config.GetContext().getReturnState(i)] + if returnState.GetRuleIndex() != _p.GetRuleIndex() { + return false + } + } + x := _p.GetTransitions()[0].getTarget() + decisionStartState := x.(BlockStartState) + blockEndStateNum := decisionStartState.getEndState().stateNumber + blockEndState := p.atn.states[blockEndStateNum].(*BlockEndState) + + // Verify that the top of each stack context leads to loop entry/exit + // state through epsilon edges and w/o leaving rule. + + for i := 0; i < numCtxs; i++ { // for each stack context + returnStateNumber := config.GetContext().getReturnState(i) + returnState := p.atn.states[returnStateNumber] + + // all states must have single outgoing epsilon edge + if len(returnState.GetTransitions()) != 1 || !returnState.GetTransitions()[0].getIsEpsilon() { + return false + } + + // Look for prefix op case like 'not expr', (' type ')' expr + returnStateTarget := returnState.GetTransitions()[0].getTarget() + if returnState.GetStateType() == ATNStateBlockEnd && returnStateTarget == _p { + continue + } + + // Look for 'expr op expr' or case where expr's return state is block end + // of (...)* internal block; the block end points to loop back + // which points to p but we don't need to check that + if returnState == blockEndState { + continue + } + + // Look for ternary expr ? expr : expr. The return state points at block end, + // which points at loop entry state + if returnStateTarget == blockEndState { + continue + } + + // Look for complex prefix 'between expr and expr' case where 2nd expr's + // return state points at block end state of (...)* internal block + if returnStateTarget.GetStateType() == ATNStateBlockEnd && + len(returnStateTarget.GetTransitions()) == 1 && + returnStateTarget.GetTransitions()[0].getIsEpsilon() && + returnStateTarget.GetTransitions()[0].getTarget() == _p { + continue + } + + // anything else ain't conforming + return false + } + + return true +} + +func (p *ParserATNSimulator) getRuleName(index int) string { + if p.parser != nil && index >= 0 { + return p.parser.GetRuleNames()[index] + } + var sb strings.Builder + sb.Grow(32) + + sb.WriteString("') + return sb.String() +} + +func (p *ParserATNSimulator) getEpsilonTarget(config *ATNConfig, t Transition, collectPredicates, inContext, fullCtx, treatEOFAsEpsilon bool) *ATNConfig { + + switch t.getSerializationType() { + case TransitionRULE: + return p.ruleTransition(config, t.(*RuleTransition)) + case TransitionPRECEDENCE: + return p.precedenceTransition(config, t.(*PrecedencePredicateTransition), collectPredicates, inContext, fullCtx) + case TransitionPREDICATE: + return p.predTransition(config, t.(*PredicateTransition), collectPredicates, inContext, fullCtx) + case TransitionACTION: + return p.actionTransition(config, t.(*ActionTransition)) + case TransitionEPSILON: + return NewATNConfig4(config, t.getTarget()) + case TransitionATOM, TransitionRANGE, TransitionSET: + // EOF transitions act like epsilon transitions after the first EOF + // transition is traversed + if treatEOFAsEpsilon { + if t.Matches(TokenEOF, 0, 1) { + return NewATNConfig4(config, t.getTarget()) + } + } + return nil + default: + return nil + } +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) actionTransition(config *ATNConfig, t *ActionTransition) *ATNConfig { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("ACTION edge " + strconv.Itoa(t.ruleIndex) + ":" + strconv.Itoa(t.actionIndex)) + } + return NewATNConfig4(config, t.getTarget()) +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) precedenceTransition(config *ATNConfig, + pt *PrecedencePredicateTransition, collectPredicates, inContext, fullCtx bool) *ATNConfig { + + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("PRED (collectPredicates=" + fmt.Sprint(collectPredicates) + ") " + + strconv.Itoa(pt.precedence) + ">=_p, ctx dependent=true") + if p.parser != nil { + fmt.Println("context surrounding pred is " + fmt.Sprint(p.parser.GetRuleInvocationStack(nil))) + } + } + var c *ATNConfig + if collectPredicates && inContext { + if fullCtx { + // In full context mode, we can evaluate predicates on-the-fly + // during closure, which dramatically reduces the size of + // the runtimeConfig sets. It also obviates the need to test predicates + // later during conflict resolution. + currentPosition := p.input.Index() + p.input.Seek(p.startIndex) + predSucceeds := pt.getPredicate().evaluate(p.parser, p.outerContext) + p.input.Seek(currentPosition) + if predSucceeds { + c = NewATNConfig4(config, pt.getTarget()) // no pred context + } + } else { + newSemCtx := SemanticContextandContext(config.GetSemanticContext(), pt.getPredicate()) + c = NewATNConfig3(config, pt.getTarget(), newSemCtx) + } + } else { + c = NewATNConfig4(config, pt.getTarget()) + } + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("runtimeConfig from pred transition=" + c.String()) + } + return c +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) predTransition(config *ATNConfig, pt *PredicateTransition, collectPredicates, inContext, fullCtx bool) *ATNConfig { + + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("PRED (collectPredicates=" + fmt.Sprint(collectPredicates) + ") " + strconv.Itoa(pt.ruleIndex) + + ":" + strconv.Itoa(pt.predIndex) + ", ctx dependent=" + fmt.Sprint(pt.isCtxDependent)) + if p.parser != nil { + fmt.Println("context surrounding pred is " + fmt.Sprint(p.parser.GetRuleInvocationStack(nil))) + } + } + var c *ATNConfig + if collectPredicates && (!pt.isCtxDependent || inContext) { + if fullCtx { + // In full context mode, we can evaluate predicates on-the-fly + // during closure, which dramatically reduces the size of + // the config sets. It also obviates the need to test predicates + // later during conflict resolution. + currentPosition := p.input.Index() + p.input.Seek(p.startIndex) + predSucceeds := pt.getPredicate().evaluate(p.parser, p.outerContext) + p.input.Seek(currentPosition) + if predSucceeds { + c = NewATNConfig4(config, pt.getTarget()) // no pred context + } + } else { + newSemCtx := SemanticContextandContext(config.GetSemanticContext(), pt.getPredicate()) + c = NewATNConfig3(config, pt.getTarget(), newSemCtx) + } + } else { + c = NewATNConfig4(config, pt.getTarget()) + } + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("config from pred transition=" + c.String()) + } + return c +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) ruleTransition(config *ATNConfig, t *RuleTransition) *ATNConfig { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("CALL rule " + p.getRuleName(t.getTarget().GetRuleIndex()) + ", ctx=" + config.GetContext().String()) + } + returnState := t.followState + newContext := SingletonBasePredictionContextCreate(config.GetContext(), returnState.GetStateNumber()) + return NewATNConfig1(config, t.getTarget(), newContext) +} + +func (p *ParserATNSimulator) getConflictingAlts(configs *ATNConfigSet) *BitSet { + altsets := PredictionModegetConflictingAltSubsets(configs) + return PredictionModeGetAlts(altsets) +} + +// getConflictingAltsOrUniqueAlt Sam pointed out a problem with the previous definition, v3, of +// ambiguous states. If we have another state associated with conflicting +// alternatives, we should keep going. For example, the following grammar +// +// s : (ID | ID ID?) ; +// +// When the [ATN] simulation reaches the state before ;, it has a [DFA] +// state that looks like: +// +// [12|1|[], 6|2|[], 12|2|[]]. +// +// Naturally +// +// 12|1|[] and 12|2|[] +// +// conflict, but we cannot stop processing this node +// because alternative to has another way to continue, via +// +// [6|2|[]]. +// +// The key is that we have a single state that has config's only associated +// with a single alternative, 2, and crucially the state transitions +// among the configurations are all non-epsilon transitions. That means +// we don't consider any conflicts that include alternative 2. So, we +// ignore the conflict between alts 1 and 2. We ignore a set of +// conflicting alts when there is an intersection with an alternative +// associated with a single alt state in the state config-list map. +// +// It's also the case that we might have two conflicting configurations but +// also a 3rd non-conflicting configuration for a different alternative: +// +// [1|1|[], 1|2|[], 8|3|[]]. +// +// This can come about from grammar: +// +// a : A | A | A B +// +// After Matching input A, we reach the stop state for rule A, state 1. +// State 8 is the state right before B. Clearly alternatives 1 and 2 +// conflict and no amount of further lookahead will separate the two. +// However, alternative 3 will be able to continue, so we do not +// stop working on this state. +// +// In the previous example, we're concerned +// with states associated with the conflicting alternatives. Here alt +// 3 is not associated with the conflicting configs, but since we can continue +// looking for input reasonably, I don't declare the state done. We +// ignore a set of conflicting alts when we have an alternative +// that we still need to pursue. +func (p *ParserATNSimulator) getConflictingAltsOrUniqueAlt(configs *ATNConfigSet) *BitSet { + var conflictingAlts *BitSet + if configs.uniqueAlt != ATNInvalidAltNumber { + conflictingAlts = NewBitSet() + conflictingAlts.add(configs.uniqueAlt) + } else { + conflictingAlts = configs.conflictingAlts + } + return conflictingAlts +} + +func (p *ParserATNSimulator) GetTokenName(t int) string { + if t == TokenEOF { + return "EOF" + } + + if p.parser != nil && p.parser.GetLiteralNames() != nil && t < len(p.parser.GetLiteralNames()) { + return p.parser.GetLiteralNames()[t] + "<" + strconv.Itoa(t) + ">" + } + + if p.parser != nil && p.parser.GetLiteralNames() != nil && t < len(p.parser.GetSymbolicNames()) { + return p.parser.GetSymbolicNames()[t] + "<" + strconv.Itoa(t) + ">" + } + + return strconv.Itoa(t) +} + +func (p *ParserATNSimulator) getLookaheadName(input TokenStream) string { + return p.GetTokenName(input.LA(1)) +} + +// Used for debugging in [AdaptivePredict] around [execATN], but I cut +// it out for clarity now that alg. works well. We can leave this +// "dead" code for a bit. +func (p *ParserATNSimulator) dumpDeadEndConfigs(_ *NoViableAltException) { + + panic("Not implemented") + + // fmt.Println("dead end configs: ") + // var decs = nvae.deadEndConfigs + // + // for i:=0; i0) { + // var t = c.state.GetTransitions()[0] + // if t2, ok := t.(*AtomTransition); ok { + // trans = "Atom "+ p.GetTokenName(t2.label) + // } else if t3, ok := t.(SetTransition); ok { + // _, ok := t.(*NotSetTransition) + // + // var s string + // if (ok){ + // s = "~" + // } + // + // trans = s + "Set " + t3.set + // } + // } + // fmt.Errorf(c.String(p.parser, true) + ":" + trans) + // } +} + +func (p *ParserATNSimulator) noViableAlt(input TokenStream, outerContext ParserRuleContext, configs *ATNConfigSet, startIndex int) *NoViableAltException { + return NewNoViableAltException(p.parser, input, input.Get(startIndex), input.LT(1), configs, outerContext) +} + +func (p *ParserATNSimulator) getUniqueAlt(configs *ATNConfigSet) int { + alt := ATNInvalidAltNumber + for _, c := range configs.configs { + if alt == ATNInvalidAltNumber { + alt = c.GetAlt() // found first alt + } else if c.GetAlt() != alt { + return ATNInvalidAltNumber + } + } + return alt +} + +// Add an edge to the DFA, if possible. This method calls +// {@link //addDFAState} to ensure the {@code to} state is present in the +// DFA. If {@code from} is {@code nil}, or if {@code t} is outside the +// range of edges that can be represented in the DFA tables, p method +// returns without adding the edge to the DFA. +// +//

If {@code to} is {@code nil}, p method returns {@code nil}. +// Otherwise, p method returns the {@link DFAState} returned by calling +// {@link //addDFAState} for the {@code to} state.

+// +// @param dfa The DFA +// @param from The source state for the edge +// @param t The input symbol +// @param to The target state for the edge +// +// @return If {@code to} is {@code nil}, p method returns {@code nil} +// otherwise p method returns the result of calling {@link //addDFAState} +// on {@code to} +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) addDFAEdge(dfa *DFA, from *DFAState, t int, to *DFAState) *DFAState { + if runtimeConfig.parserATNSimulatorDebug { + fmt.Println("EDGE " + from.String() + " -> " + to.String() + " upon " + p.GetTokenName(t)) + } + if to == nil { + return nil + } + p.atn.stateMu.Lock() + to = p.addDFAState(dfa, to) // used existing if possible not incoming + p.atn.stateMu.Unlock() + if from == nil || t < -1 || t > p.atn.maxTokenType { + return to + } + p.atn.edgeMu.Lock() + if from.getEdges() == nil { + from.setEdges(make([]*DFAState, p.atn.maxTokenType+1+1)) + } + from.setIthEdge(t+1, to) // connect + p.atn.edgeMu.Unlock() + + if runtimeConfig.parserATNSimulatorDebug { + var names []string + if p.parser != nil { + names = p.parser.GetLiteralNames() + } + + fmt.Println("DFA=\n" + dfa.String(names, nil)) + } + return to +} + +// addDFAState adds state D to the [DFA] if it is not already present, and returns +// the actual instance stored in the [DFA]. If a state equivalent to D +// is already in the [DFA], the existing state is returned. Otherwise, this +// method returns D after adding it to the [DFA]. +// +// If D is [ATNSimulatorError], this method returns [ATNSimulatorError] and +// does not change the DFA. +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) addDFAState(dfa *DFA, d *DFAState) *DFAState { + if d == ATNSimulatorError { + return d + } + + existing, present := dfa.Get(d) + if present { + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Print("addDFAState " + d.String() + " exists") + } + return existing + } + + // The state will be added if not already there or we will be given back the existing state struct + // if it is present. + // + d.stateNumber = dfa.Len() + if !d.configs.readOnly { + d.configs.OptimizeConfigs(&p.BaseATNSimulator) + d.configs.readOnly = true + d.configs.configLookup = nil + } + dfa.Put(d) + + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("addDFAState new " + d.String()) + } + + return d +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) ReportAttemptingFullContext(dfa *DFA, conflictingAlts *BitSet, configs *ATNConfigSet, startIndex, stopIndex int) { + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorRetryDebug { + interval := NewInterval(startIndex, stopIndex+1) + fmt.Println("ReportAttemptingFullContext decision=" + strconv.Itoa(dfa.decision) + ":" + configs.String() + + ", input=" + p.parser.GetTokenStream().GetTextFromInterval(interval)) + } + if p.parser != nil { + p.parser.GetErrorListenerDispatch().ReportAttemptingFullContext(p.parser, dfa, startIndex, stopIndex, conflictingAlts, configs) + } +} + +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) ReportContextSensitivity(dfa *DFA, prediction int, configs *ATNConfigSet, startIndex, stopIndex int) { + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorRetryDebug { + interval := NewInterval(startIndex, stopIndex+1) + fmt.Println("ReportContextSensitivity decision=" + strconv.Itoa(dfa.decision) + ":" + configs.String() + + ", input=" + p.parser.GetTokenStream().GetTextFromInterval(interval)) + } + if p.parser != nil { + p.parser.GetErrorListenerDispatch().ReportContextSensitivity(p.parser, dfa, startIndex, stopIndex, prediction, configs) + } +} + +// ReportAmbiguity reports and ambiguity in the parse, which shows that the parser will explore a different route. +// +// If context-sensitive parsing, we know it's an ambiguity not a conflict or error, but we can report it to the developer +// so that they can see that this is happening and can take action if they want to. +// +//goland:noinspection GoBoolExpressions +func (p *ParserATNSimulator) ReportAmbiguity(dfa *DFA, _ *DFAState, startIndex, stopIndex int, + exact bool, ambigAlts *BitSet, configs *ATNConfigSet) { + if runtimeConfig.parserATNSimulatorDebug || runtimeConfig.parserATNSimulatorRetryDebug { + interval := NewInterval(startIndex, stopIndex+1) + fmt.Println("ReportAmbiguity " + ambigAlts.String() + ":" + configs.String() + + ", input=" + p.parser.GetTokenStream().GetTextFromInterval(interval)) + } + if p.parser != nil { + p.parser.GetErrorListenerDispatch().ReportAmbiguity(p.parser, dfa, startIndex, stopIndex, exact, ambigAlts, configs) + } +} diff --git a/prutalgen/internal/antlr/parser_rule_context.go b/prutalgen/internal/antlr/parser_rule_context.go new file mode 100644 index 0000000..c249bc1 --- /dev/null +++ b/prutalgen/internal/antlr/parser_rule_context.go @@ -0,0 +1,421 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "reflect" + "strconv" +) + +type ParserRuleContext interface { + RuleContext + + SetException(RecognitionException) + + AddTokenNode(token Token) *TerminalNodeImpl + AddErrorNode(badToken Token) *ErrorNodeImpl + + EnterRule(listener ParseTreeListener) + ExitRule(listener ParseTreeListener) + + SetStart(Token) + GetStart() Token + + SetStop(Token) + GetStop() Token + + AddChild(child RuleContext) RuleContext + RemoveLastChild() +} + +type BaseParserRuleContext struct { + parentCtx RuleContext + invokingState int + RuleIndex int + + start, stop Token + exception RecognitionException + children []Tree +} + +func NewBaseParserRuleContext(parent ParserRuleContext, invokingStateNumber int) *BaseParserRuleContext { + prc := new(BaseParserRuleContext) + InitBaseParserRuleContext(prc, parent, invokingStateNumber) + return prc +} + +func InitBaseParserRuleContext(prc *BaseParserRuleContext, parent ParserRuleContext, invokingStateNumber int) { + // What context invoked b rule? + prc.parentCtx = parent + + // What state invoked the rule associated with b context? + // The "return address" is the followState of invokingState + // If parent is nil, b should be -1. + if parent == nil { + prc.invokingState = -1 + } else { + prc.invokingState = invokingStateNumber + } + + prc.RuleIndex = -1 + // * If we are debugging or building a parse tree for a Visitor, + // we need to track all of the tokens and rule invocations associated + // with prc rule's context. This is empty for parsing w/o tree constr. + // operation because we don't the need to track the details about + // how we parse prc rule. + // / + prc.children = nil + prc.start = nil + prc.stop = nil + // The exception that forced prc rule to return. If the rule successfully + // completed, prc is {@code nil}. + prc.exception = nil +} + +func (prc *BaseParserRuleContext) SetException(e RecognitionException) { + prc.exception = e +} + +func (prc *BaseParserRuleContext) GetChildren() []Tree { + return prc.children +} + +func (prc *BaseParserRuleContext) CopyFrom(ctx *BaseParserRuleContext) { + // from RuleContext + prc.parentCtx = ctx.parentCtx + prc.invokingState = ctx.invokingState + prc.children = nil + prc.start = ctx.start + prc.stop = ctx.stop +} + +func (prc *BaseParserRuleContext) GetText() string { + if prc.GetChildCount() == 0 { + return "" + } + + var s string + for _, child := range prc.children { + s += child.(ParseTree).GetText() + } + + return s +} + +// EnterRule is called when any rule is entered. +func (prc *BaseParserRuleContext) EnterRule(_ ParseTreeListener) { +} + +// ExitRule is called when any rule is exited. +func (prc *BaseParserRuleContext) ExitRule(_ ParseTreeListener) { +} + +// * Does not set parent link other add methods do that +func (prc *BaseParserRuleContext) addTerminalNodeChild(child TerminalNode) TerminalNode { + if prc.children == nil { + prc.children = make([]Tree, 0) + } + if child == nil { + panic("Child may not be null") + } + prc.children = append(prc.children, child) + return child +} + +func (prc *BaseParserRuleContext) AddChild(child RuleContext) RuleContext { + if prc.children == nil { + prc.children = make([]Tree, 0) + } + if child == nil { + panic("Child may not be null") + } + prc.children = append(prc.children, child) + return child +} + +// RemoveLastChild is used by [EnterOuterAlt] to toss out a [RuleContext] previously added as +// we entered a rule. If we have a label, we will need to remove +// the generic ruleContext object. +func (prc *BaseParserRuleContext) RemoveLastChild() { + if prc.children != nil && len(prc.children) > 0 { + prc.children = prc.children[0 : len(prc.children)-1] + } +} + +func (prc *BaseParserRuleContext) AddTokenNode(token Token) *TerminalNodeImpl { + + node := NewTerminalNodeImpl(token) + prc.addTerminalNodeChild(node) + node.parentCtx = prc + return node + +} + +func (prc *BaseParserRuleContext) AddErrorNode(badToken Token) *ErrorNodeImpl { + node := NewErrorNodeImpl(badToken) + prc.addTerminalNodeChild(node) + node.parentCtx = prc + return node +} + +func (prc *BaseParserRuleContext) GetChild(i int) Tree { + if prc.children != nil && len(prc.children) >= i { + return prc.children[i] + } + + return nil +} + +func (prc *BaseParserRuleContext) GetChildOfType(i int, childType reflect.Type) RuleContext { + if childType == nil { + return prc.GetChild(i).(RuleContext) + } + + for j := 0; j < len(prc.children); j++ { + child := prc.children[j] + if reflect.TypeOf(child) == childType { + if i == 0 { + return child.(RuleContext) + } + + i-- + } + } + + return nil +} + +func (prc *BaseParserRuleContext) ToStringTree(ruleNames []string, recog Recognizer) string { + return TreesStringTree(prc, ruleNames, recog) +} + +func (prc *BaseParserRuleContext) GetRuleContext() RuleContext { + return prc +} + +func (prc *BaseParserRuleContext) Accept(visitor ParseTreeVisitor) interface{} { + return visitor.VisitChildren(prc) +} + +func (prc *BaseParserRuleContext) SetStart(t Token) { + prc.start = t +} + +func (prc *BaseParserRuleContext) GetStart() Token { + return prc.start +} + +func (prc *BaseParserRuleContext) SetStop(t Token) { + prc.stop = t +} + +func (prc *BaseParserRuleContext) GetStop() Token { + return prc.stop +} + +func (prc *BaseParserRuleContext) GetToken(ttype int, i int) TerminalNode { + + for j := 0; j < len(prc.children); j++ { + child := prc.children[j] + if c2, ok := child.(TerminalNode); ok { + if c2.GetSymbol().GetTokenType() == ttype { + if i == 0 { + return c2 + } + + i-- + } + } + } + return nil +} + +func (prc *BaseParserRuleContext) GetTokens(ttype int) []TerminalNode { + if prc.children == nil { + return make([]TerminalNode, 0) + } + + tokens := make([]TerminalNode, 0) + + for j := 0; j < len(prc.children); j++ { + child := prc.children[j] + if tchild, ok := child.(TerminalNode); ok { + if tchild.GetSymbol().GetTokenType() == ttype { + tokens = append(tokens, tchild) + } + } + } + + return tokens +} + +func (prc *BaseParserRuleContext) GetPayload() interface{} { + return prc +} + +func (prc *BaseParserRuleContext) getChild(ctxType reflect.Type, i int) RuleContext { + if prc.children == nil || i < 0 || i >= len(prc.children) { + return nil + } + + j := -1 // what element have we found with ctxType? + for _, o := range prc.children { + + childType := reflect.TypeOf(o) + + if childType.Implements(ctxType) { + j++ + if j == i { + return o.(RuleContext) + } + } + } + return nil +} + +// Go lacks generics, so it's not possible for us to return the child with the correct type, but we do +// check for convertibility + +func (prc *BaseParserRuleContext) GetTypedRuleContext(ctxType reflect.Type, i int) RuleContext { + return prc.getChild(ctxType, i) +} + +func (prc *BaseParserRuleContext) GetTypedRuleContexts(ctxType reflect.Type) []RuleContext { + if prc.children == nil { + return make([]RuleContext, 0) + } + + contexts := make([]RuleContext, 0) + + for _, child := range prc.children { + childType := reflect.TypeOf(child) + + if childType.ConvertibleTo(ctxType) { + contexts = append(contexts, child.(RuleContext)) + } + } + return contexts +} + +func (prc *BaseParserRuleContext) GetChildCount() int { + if prc.children == nil { + return 0 + } + + return len(prc.children) +} + +func (prc *BaseParserRuleContext) GetSourceInterval() Interval { + if prc.start == nil || prc.stop == nil { + return TreeInvalidInterval + } + + return NewInterval(prc.start.GetTokenIndex(), prc.stop.GetTokenIndex()) +} + +//need to manage circular dependencies, so export now + +// Print out a whole tree, not just a node, in LISP format +// (root child1 .. childN). Print just a node if b is a leaf. +// + +func (prc *BaseParserRuleContext) String(ruleNames []string, stop RuleContext) string { + + var p ParserRuleContext = prc + s := "[" + for p != nil && p != stop { + if ruleNames == nil { + if !p.IsEmpty() { + s += strconv.Itoa(p.GetInvokingState()) + } + } else { + ri := p.GetRuleIndex() + var ruleName string + if ri >= 0 && ri < len(ruleNames) { + ruleName = ruleNames[ri] + } else { + ruleName = strconv.Itoa(ri) + } + s += ruleName + } + if p.GetParent() != nil && (ruleNames != nil || !p.GetParent().(ParserRuleContext).IsEmpty()) { + s += " " + } + pi := p.GetParent() + if pi != nil { + p = pi.(ParserRuleContext) + } else { + p = nil + } + } + s += "]" + return s +} + +func (prc *BaseParserRuleContext) SetParent(v Tree) { + if v == nil { + prc.parentCtx = nil + } else { + prc.parentCtx = v.(RuleContext) + } +} + +func (prc *BaseParserRuleContext) GetInvokingState() int { + return prc.invokingState +} + +func (prc *BaseParserRuleContext) SetInvokingState(t int) { + prc.invokingState = t +} + +func (prc *BaseParserRuleContext) GetRuleIndex() int { + return prc.RuleIndex +} + +func (prc *BaseParserRuleContext) GetAltNumber() int { + return ATNInvalidAltNumber +} + +func (prc *BaseParserRuleContext) SetAltNumber(_ int) {} + +// IsEmpty returns true if the context of b is empty. +// +// A context is empty if there is no invoking state, meaning nobody calls +// current context. +func (prc *BaseParserRuleContext) IsEmpty() bool { + return prc.invokingState == -1 +} + +// GetParent returns the combined text of all child nodes. This method only considers +// tokens which have been added to the parse tree. +// +// Since tokens on hidden channels (e.g. whitespace or comments) are not +// added to the parse trees, they will not appear in the output of this +// method. +func (prc *BaseParserRuleContext) GetParent() Tree { + return prc.parentCtx +} + +var ParserRuleContextEmpty = NewBaseParserRuleContext(nil, -1) + +type InterpreterRuleContext interface { + ParserRuleContext +} + +type BaseInterpreterRuleContext struct { + *BaseParserRuleContext +} + +//goland:noinspection GoUnusedExportedFunction +func NewBaseInterpreterRuleContext(parent BaseInterpreterRuleContext, invokingStateNumber, ruleIndex int) *BaseInterpreterRuleContext { + + prc := new(BaseInterpreterRuleContext) + + prc.BaseParserRuleContext = NewBaseParserRuleContext(parent, invokingStateNumber) + + prc.RuleIndex = ruleIndex + + return prc +} diff --git a/prutalgen/internal/antlr/prediction_context.go b/prutalgen/internal/antlr/prediction_context.go new file mode 100644 index 0000000..a1d5186 --- /dev/null +++ b/prutalgen/internal/antlr/prediction_context.go @@ -0,0 +1,727 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" +) + +var _emptyPredictionContextHash int + +func init() { + _emptyPredictionContextHash = murmurInit(1) + _emptyPredictionContextHash = murmurFinish(_emptyPredictionContextHash, 0) +} + +func calculateEmptyHash() int { + return _emptyPredictionContextHash +} + +const ( + // BasePredictionContextEmptyReturnState represents {@code $} in an array in full context mode, $ + // doesn't mean wildcard: + // + // $ + x = [$,x] + // + // Here, + // + // $ = EmptyReturnState + BasePredictionContextEmptyReturnState = 0x7FFFFFFF +) + +// TODO: JI These are meant to be atomics - this does not seem to match the Java runtime here +// +//goland:noinspection GoUnusedGlobalVariable +var ( + BasePredictionContextglobalNodeCount = 1 + BasePredictionContextid = BasePredictionContextglobalNodeCount +) + +const ( + PredictionContextEmpty = iota + PredictionContextSingleton + PredictionContextArray +) + +// PredictionContext is a go idiomatic implementation of PredictionContext that does not rty to +// emulate inheritance from Java, and can be used without an interface definition. An interface +// is not required because no user code will ever need to implement this interface. +type PredictionContext struct { + cachedHash int + pcType int + parentCtx *PredictionContext + returnState int + parents []*PredictionContext + returnStates []int +} + +func NewEmptyPredictionContext() *PredictionContext { + nep := &PredictionContext{} + nep.cachedHash = calculateEmptyHash() + nep.pcType = PredictionContextEmpty + nep.returnState = BasePredictionContextEmptyReturnState + return nep +} + +func NewBaseSingletonPredictionContext(parent *PredictionContext, returnState int) *PredictionContext { + pc := &PredictionContext{} + pc.pcType = PredictionContextSingleton + pc.returnState = returnState + pc.parentCtx = parent + if parent != nil { + pc.cachedHash = calculateHash(parent, returnState) + } else { + pc.cachedHash = calculateEmptyHash() + } + return pc +} + +func SingletonBasePredictionContextCreate(parent *PredictionContext, returnState int) *PredictionContext { + if returnState == BasePredictionContextEmptyReturnState && parent == nil { + // someone can pass in the bits of an array ctx that mean $ + return BasePredictionContextEMPTY + } + return NewBaseSingletonPredictionContext(parent, returnState) +} + +func NewArrayPredictionContext(parents []*PredictionContext, returnStates []int) *PredictionContext { + // Parent can be nil only if full ctx mode and we make an array + // from {@link //EMPTY} and non-empty. We merge {@link //EMPTY} by using + // nil parent and + // returnState == {@link //EmptyReturnState}. + hash := murmurInit(1) + for _, parent := range parents { + hash = murmurUpdate(hash, parent.Hash()) + } + for _, returnState := range returnStates { + hash = murmurUpdate(hash, returnState) + } + hash = murmurFinish(hash, len(parents)<<1) + + nec := &PredictionContext{} + nec.cachedHash = hash + nec.pcType = PredictionContextArray + nec.parents = parents + nec.returnStates = returnStates + return nec +} + +func (p *PredictionContext) Hash() int { + return p.cachedHash +} + +func (p *PredictionContext) Equals(other Collectable[*PredictionContext]) bool { + if p == other { + return true + } + switch p.pcType { + case PredictionContextEmpty: + otherP := other.(*PredictionContext) + return other == nil || otherP == nil || otherP.isEmpty() + case PredictionContextSingleton: + return p.SingletonEquals(other) + case PredictionContextArray: + return p.ArrayEquals(other) + } + return false +} + +func (p *PredictionContext) ArrayEquals(o Collectable[*PredictionContext]) bool { + if o == nil { + return false + } + other := o.(*PredictionContext) + if other == nil || other.pcType != PredictionContextArray { + return false + } + if p.cachedHash != other.Hash() { + return false // can't be same if hash is different + } + + // Must compare the actual array elements and not just the array address + // + return intSlicesEqual(p.returnStates, other.returnStates) && + pcSliceEqual(p.parents, other.parents) +} + +func (p *PredictionContext) SingletonEquals(other Collectable[*PredictionContext]) bool { + if other == nil { + return false + } + otherP := other.(*PredictionContext) + if otherP == nil || otherP.pcType != PredictionContextSingleton { + return false + } + + if p.cachedHash != otherP.Hash() { + return false // Can't be same if hash is different + } + + if p.returnState != otherP.getReturnState(0) { + return false + } + + // Both parents must be nil if one is + if p.parentCtx == nil { + return otherP.parentCtx == nil + } + + return p.parentCtx.Equals(otherP.parentCtx) +} + +func (p *PredictionContext) GetParent(i int) *PredictionContext { + switch p.pcType { + case PredictionContextEmpty: + return nil + case PredictionContextSingleton: + return p.parentCtx + case PredictionContextArray: + return p.parents[i] + } + return nil +} + +func (p *PredictionContext) getReturnState(i int) int { + switch p.pcType { + case PredictionContextArray: + return p.returnStates[i] + default: + return p.returnState + } +} + +func (p *PredictionContext) GetReturnStates() []int { + switch p.pcType { + case PredictionContextArray: + return p.returnStates + default: + return []int{p.returnState} + } +} + +func (p *PredictionContext) length() int { + switch p.pcType { + case PredictionContextArray: + return len(p.returnStates) + default: + return 1 + } +} + +func (p *PredictionContext) hasEmptyPath() bool { + switch p.pcType { + case PredictionContextSingleton: + return p.returnState == BasePredictionContextEmptyReturnState + } + return p.getReturnState(p.length()-1) == BasePredictionContextEmptyReturnState +} + +func (p *PredictionContext) String() string { + switch p.pcType { + case PredictionContextEmpty: + return "$" + case PredictionContextSingleton: + var up string + + if p.parentCtx == nil { + up = "" + } else { + up = p.parentCtx.String() + } + + if len(up) == 0 { + if p.returnState == BasePredictionContextEmptyReturnState { + return "$" + } + + return strconv.Itoa(p.returnState) + } + + return strconv.Itoa(p.returnState) + " " + up + case PredictionContextArray: + if p.isEmpty() { + return "[]" + } + + s := "[" + for i := 0; i < len(p.returnStates); i++ { + if i > 0 { + s = s + ", " + } + if p.returnStates[i] == BasePredictionContextEmptyReturnState { + s = s + "$" + continue + } + s = s + strconv.Itoa(p.returnStates[i]) + if !p.parents[i].isEmpty() { + s = s + " " + p.parents[i].String() + } else { + s = s + "nil" + } + } + return s + "]" + + default: + return "unknown" + } +} + +func (p *PredictionContext) isEmpty() bool { + switch p.pcType { + case PredictionContextEmpty: + return true + case PredictionContextArray: + // since EmptyReturnState can only appear in the last position, we + // don't need to verify that size==1 + return p.returnStates[0] == BasePredictionContextEmptyReturnState + default: + return false + } +} + +func (p *PredictionContext) Type() int { + return p.pcType +} + +func calculateHash(parent *PredictionContext, returnState int) int { + h := murmurInit(1) + h = murmurUpdate(h, parent.Hash()) + h = murmurUpdate(h, returnState) + return murmurFinish(h, 2) +} + +// Convert a {@link RuleContext} tree to a {@link BasePredictionContext} graph. +// Return {@link //EMPTY} if {@code outerContext} is empty or nil. +// / +func predictionContextFromRuleContext(a *ATN, outerContext RuleContext) *PredictionContext { + if outerContext == nil { + outerContext = ParserRuleContextEmpty + } + // if we are in RuleContext of start rule, s, then BasePredictionContext + // is EMPTY. Nobody called us. (if we are empty, return empty) + if outerContext.GetParent() == nil || outerContext == ParserRuleContextEmpty { + return BasePredictionContextEMPTY + } + // If we have a parent, convert it to a BasePredictionContext graph + parent := predictionContextFromRuleContext(a, outerContext.GetParent().(RuleContext)) + state := a.states[outerContext.GetInvokingState()] + transition := state.GetTransitions()[0] + + return SingletonBasePredictionContextCreate(parent, transition.(*RuleTransition).followState.GetStateNumber()) +} + +func merge(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { + + // Share same graph if both same + // + if a == b || a.Equals(b) { + return a + } + + if a.pcType == PredictionContextSingleton && b.pcType == PredictionContextSingleton { + return mergeSingletons(a, b, rootIsWildcard, mergeCache) + } + // At least one of a or b is array + // If one is $ and rootIsWildcard, return $ as wildcard + if rootIsWildcard { + if a.isEmpty() { + return a + } + if b.isEmpty() { + return b + } + } + + // Convert either Singleton or Empty to arrays, so that we can merge them + // + ara := convertToArray(a) + arb := convertToArray(b) + return mergeArrays(ara, arb, rootIsWildcard, mergeCache) +} + +func convertToArray(pc *PredictionContext) *PredictionContext { + switch pc.Type() { + case PredictionContextEmpty: + return NewArrayPredictionContext([]*PredictionContext{}, []int{}) + case PredictionContextSingleton: + return NewArrayPredictionContext([]*PredictionContext{pc.GetParent(0)}, []int{pc.getReturnState(0)}) + default: + // Already an array + } + return pc +} + +// mergeSingletons merges two Singleton [PredictionContext] instances. +// +// Stack tops equal, parents merge is same return left graph. +//

+// +//

Same stack top, parents differ merge parents giving array node, then +// remainders of those graphs. A new root node is created to point to the +// merged parents.
+//

+// +//

Different stack tops pointing to same parent. Make array node for the +// root where both element in the root point to the same (original) +// parent.
+//

+// +//

Different stack tops pointing to different parents. Make array node for +// the root where each element points to the corresponding original +// parent.
+//

+// +// @param a the first {@link SingletonBasePredictionContext} +// @param b the second {@link SingletonBasePredictionContext} +// @param rootIsWildcard {@code true} if this is a local-context merge, +// otherwise false to indicate a full-context merge +// @param mergeCache +// / +func mergeSingletons(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { + if mergeCache != nil { + previous, present := mergeCache.Get(a, b) + if present { + return previous + } + previous, present = mergeCache.Get(b, a) + if present { + return previous + } + } + + rootMerge := mergeRoot(a, b, rootIsWildcard) + if rootMerge != nil { + if mergeCache != nil { + mergeCache.Put(a, b, rootMerge) + } + return rootMerge + } + if a.returnState == b.returnState { + parent := merge(a.parentCtx, b.parentCtx, rootIsWildcard, mergeCache) + // if parent is same as existing a or b parent or reduced to a parent, + // return it + if parent.Equals(a.parentCtx) { + return a // ax + bx = ax, if a=b + } + if parent.Equals(b.parentCtx) { + return b // ax + bx = bx, if a=b + } + // else: ax + ay = a'[x,y] + // merge parents x and y, giving array node with x,y then remainders + // of those graphs. dup a, a' points at merged array. + // New joined parent so create a new singleton pointing to it, a' + spc := SingletonBasePredictionContextCreate(parent, a.returnState) + if mergeCache != nil { + mergeCache.Put(a, b, spc) + } + return spc + } + // a != b payloads differ + // see if we can collapse parents due to $+x parents if local ctx + var singleParent *PredictionContext + if a.Equals(b) || (a.parentCtx != nil && a.parentCtx.Equals(b.parentCtx)) { // ax + + // bx = + // [a,b]x + singleParent = a.parentCtx + } + if singleParent != nil { // parents are same + // sort payloads and use same parent + payloads := []int{a.returnState, b.returnState} + if a.returnState > b.returnState { + payloads[0] = b.returnState + payloads[1] = a.returnState + } + parents := []*PredictionContext{singleParent, singleParent} + apc := NewArrayPredictionContext(parents, payloads) + if mergeCache != nil { + mergeCache.Put(a, b, apc) + } + return apc + } + // parents differ and can't merge them. Just pack together + // into array can't merge. + // ax + by = [ax,by] + payloads := []int{a.returnState, b.returnState} + parents := []*PredictionContext{a.parentCtx, b.parentCtx} + if a.returnState > b.returnState { // sort by payload + payloads[0] = b.returnState + payloads[1] = a.returnState + parents = []*PredictionContext{b.parentCtx, a.parentCtx} + } + apc := NewArrayPredictionContext(parents, payloads) + if mergeCache != nil { + mergeCache.Put(a, b, apc) + } + return apc +} + +// Handle case where at least one of {@code a} or {@code b} is +// {@link //EMPTY}. In the following diagrams, the symbol {@code $} is used +// to represent {@link //EMPTY}. +// +//

Local-Context Merges

+// +//

These local-context merge operations are used when {@code rootIsWildcard} +// is true.

+// +//

{@link //EMPTY} is superset of any graph return {@link //EMPTY}.
+//

+// +//

{@link //EMPTY} and anything is {@code //EMPTY}, so merged parent is +// {@code //EMPTY} return left graph.
+//

+// +//

Special case of last merge if local context.
+//

+// +//

Full-Context Merges

+// +//

These full-context merge operations are used when {@code rootIsWildcard} +// is false.

+// +//

+// +//

Must keep all contexts {@link //EMPTY} in array is a special value (and +// nil parent).
+//

+// +//

+// +// @param a the first {@link SingletonBasePredictionContext} +// @param b the second {@link SingletonBasePredictionContext} +// @param rootIsWildcard {@code true} if this is a local-context merge, +// otherwise false to indicate a full-context merge +// / +func mergeRoot(a, b *PredictionContext, rootIsWildcard bool) *PredictionContext { + if rootIsWildcard { + if a.pcType == PredictionContextEmpty { + return BasePredictionContextEMPTY // // + b =// + } + if b.pcType == PredictionContextEmpty { + return BasePredictionContextEMPTY // a +// =// + } + } else { + if a.isEmpty() && b.isEmpty() { + return BasePredictionContextEMPTY // $ + $ = $ + } else if a.isEmpty() { // $ + x = [$,x] + payloads := []int{b.getReturnState(-1), BasePredictionContextEmptyReturnState} + parents := []*PredictionContext{b.GetParent(-1), nil} + return NewArrayPredictionContext(parents, payloads) + } else if b.isEmpty() { // x + $ = [$,x] ($ is always first if present) + payloads := []int{a.getReturnState(-1), BasePredictionContextEmptyReturnState} + parents := []*PredictionContext{a.GetParent(-1), nil} + return NewArrayPredictionContext(parents, payloads) + } + } + return nil +} + +// Merge two {@link ArrayBasePredictionContext} instances. +// +//

Different tops, different parents.
+//

+// +//

Shared top, same parents.
+//

+// +//

Shared top, different parents.
+//

+// +//

Shared top, all shared parents.
+//

+// +//

Equal tops, merge parents and reduce top to +// {@link SingletonBasePredictionContext}.
+//

+// +//goland:noinspection GoBoolExpressions +func mergeArrays(a, b *PredictionContext, rootIsWildcard bool, mergeCache *JPCMap) *PredictionContext { + if mergeCache != nil { + previous, present := mergeCache.Get(a, b) + if present { + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> previous") + } + return previous + } + previous, present = mergeCache.Get(b, a) + if present { + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> previous") + } + return previous + } + } + // merge sorted payloads a + b => M + i := 0 // walks a + j := 0 // walks b + k := 0 // walks target M array + + mergedReturnStates := make([]int, len(a.returnStates)+len(b.returnStates)) + mergedParents := make([]*PredictionContext, len(a.returnStates)+len(b.returnStates)) + // walk and merge to yield mergedParents, mergedReturnStates + for i < len(a.returnStates) && j < len(b.returnStates) { + aParent := a.parents[i] + bParent := b.parents[j] + if a.returnStates[i] == b.returnStates[j] { + // same payload (stack tops are equal), must yield merged singleton + payload := a.returnStates[i] + // $+$ = $ + bothDollars := payload == BasePredictionContextEmptyReturnState && aParent == nil && bParent == nil + axAX := aParent != nil && bParent != nil && aParent.Equals(bParent) // ax+ax + // -> + // ax + if bothDollars || axAX { + mergedParents[k] = aParent // choose left + mergedReturnStates[k] = payload + } else { // ax+ay -> a'[x,y] + mergedParent := merge(aParent, bParent, rootIsWildcard, mergeCache) + mergedParents[k] = mergedParent + mergedReturnStates[k] = payload + } + i++ // hop over left one as usual + j++ // but also Skip one in right side since we merge + } else if a.returnStates[i] < b.returnStates[j] { // copy a[i] to M + mergedParents[k] = aParent + mergedReturnStates[k] = a.returnStates[i] + i++ + } else { // b > a, copy b[j] to M + mergedParents[k] = bParent + mergedReturnStates[k] = b.returnStates[j] + j++ + } + k++ + } + // copy over any payloads remaining in either array + if i < len(a.returnStates) { + for p := i; p < len(a.returnStates); p++ { + mergedParents[k] = a.parents[p] + mergedReturnStates[k] = a.returnStates[p] + k++ + } + } else { + for p := j; p < len(b.returnStates); p++ { + mergedParents[k] = b.parents[p] + mergedReturnStates[k] = b.returnStates[p] + k++ + } + } + // trim merged if we combined a few that had same stack tops + if k < len(mergedParents) { // write index < last position trim + if k == 1 { // for just one merged element, return singleton top + pc := SingletonBasePredictionContextCreate(mergedParents[0], mergedReturnStates[0]) + if mergeCache != nil { + mergeCache.Put(a, b, pc) + } + return pc + } + mergedParents = mergedParents[0:k] + mergedReturnStates = mergedReturnStates[0:k] + } + + M := NewArrayPredictionContext(mergedParents, mergedReturnStates) + + // if we created same array as a or b, return that instead + // TODO: JI track whether this is possible above during merge sort for speed and possibly avoid an allocation + if M.Equals(a) { + if mergeCache != nil { + mergeCache.Put(a, b, a) + } + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> a") + } + return a + } + if M.Equals(b) { + if mergeCache != nil { + mergeCache.Put(a, b, b) + } + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> b") + } + return b + } + combineCommonParents(&mergedParents) + + if mergeCache != nil { + mergeCache.Put(a, b, M) + } + if runtimeConfig.parserATNSimulatorTraceATNSim { + fmt.Println("mergeArrays a=" + a.String() + ",b=" + b.String() + " -> " + M.String()) + } + return M +} + +// Make pass over all M parents and merge any Equals() ones. +// Note that we pass a pointer to the slice as we want to modify it in place. +// +//goland:noinspection GoUnusedFunction +func combineCommonParents(parents *[]*PredictionContext) { + uniqueParents := NewJStore[*PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCollection, "combineCommonParents for PredictionContext") + + for p := 0; p < len(*parents); p++ { + parent := (*parents)[p] + _, _ = uniqueParents.Put(parent) + } + for q := 0; q < len(*parents); q++ { + pc, _ := uniqueParents.Get((*parents)[q]) + (*parents)[q] = pc + } +} + +func getCachedBasePredictionContext(context *PredictionContext, contextCache *PredictionContextCache, visited *VisitRecord) *PredictionContext { + if context.isEmpty() { + return context + } + existing, present := visited.Get(context) + if present { + return existing + } + + existing, present = contextCache.Get(context) + if present { + visited.Put(context, existing) + return existing + } + changed := false + parents := make([]*PredictionContext, context.length()) + for i := 0; i < len(parents); i++ { + parent := getCachedBasePredictionContext(context.GetParent(i), contextCache, visited) + if changed || !parent.Equals(context.GetParent(i)) { + if !changed { + parents = make([]*PredictionContext, context.length()) + for j := 0; j < context.length(); j++ { + parents[j] = context.GetParent(j) + } + changed = true + } + parents[i] = parent + } + } + if !changed { + contextCache.add(context) + visited.Put(context, context) + return context + } + var updated *PredictionContext + if len(parents) == 0 { + updated = BasePredictionContextEMPTY + } else if len(parents) == 1 { + updated = SingletonBasePredictionContextCreate(parents[0], context.getReturnState(0)) + } else { + updated = NewArrayPredictionContext(parents, context.GetReturnStates()) + } + contextCache.add(updated) + visited.Put(updated, updated) + visited.Put(context, updated) + + return updated +} diff --git a/prutalgen/internal/antlr/prediction_context_cache.go b/prutalgen/internal/antlr/prediction_context_cache.go new file mode 100644 index 0000000..25dfb11 --- /dev/null +++ b/prutalgen/internal/antlr/prediction_context_cache.go @@ -0,0 +1,48 @@ +package antlr + +var BasePredictionContextEMPTY = &PredictionContext{ + cachedHash: calculateEmptyHash(), + pcType: PredictionContextEmpty, + returnState: BasePredictionContextEmptyReturnState, +} + +// PredictionContextCache is Used to cache [PredictionContext] objects. It is used for the shared +// context cash associated with contexts in DFA states. This cache +// can be used for both lexers and parsers. +type PredictionContextCache struct { + cache *JMap[*PredictionContext, *PredictionContext, Comparator[*PredictionContext]] +} + +func NewPredictionContextCache() *PredictionContextCache { + return &PredictionContextCache{ + cache: NewJMap[*PredictionContext, *PredictionContext, Comparator[*PredictionContext]](pContextEqInst, PredictionContextCacheCollection, "NewPredictionContextCache()"), + } +} + +// Add a context to the cache and return it. If the context already exists, +// return that one instead and do not add a new context to the cache. +// Protect shared cache from unsafe thread access. +func (p *PredictionContextCache) add(ctx *PredictionContext) *PredictionContext { + if ctx.isEmpty() { + return BasePredictionContextEMPTY + } + + // Put will return the existing entry if it is present (note this is done via Equals, not whether it is + // the same pointer), otherwise it will add the new entry and return that. + // + existing, present := p.cache.Get(ctx) + if present { + return existing + } + p.cache.Put(ctx, ctx) + return ctx +} + +func (p *PredictionContextCache) Get(ctx *PredictionContext) (*PredictionContext, bool) { + pc, exists := p.cache.Get(ctx) + return pc, exists +} + +func (p *PredictionContextCache) length() int { + return p.cache.Len() +} diff --git a/prutalgen/internal/antlr/prediction_mode.go b/prutalgen/internal/antlr/prediction_mode.go new file mode 100644 index 0000000..3f85a6a --- /dev/null +++ b/prutalgen/internal/antlr/prediction_mode.go @@ -0,0 +1,536 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// This enumeration defines the prediction modes available in ANTLR 4 along with +// utility methods for analyzing configuration sets for conflicts and/or +// ambiguities. + +const ( + // PredictionModeSLL represents the SLL(*) prediction mode. + // This prediction mode ignores the current + // parser context when making predictions. This is the fastest prediction + // mode, and provides correct results for many grammars. This prediction + // mode is more powerful than the prediction mode provided by ANTLR 3, but + // may result in syntax errors for grammar and input combinations which are + // not SLL. + // + // When using this prediction mode, the parser will either return a correct + // parse tree (i.e. the same parse tree that would be returned with the + // [PredictionModeLL] prediction mode), or it will Report a syntax error. If a + // syntax error is encountered when using the SLL prediction mode, + // it may be due to either an actual syntax error in the input or indicate + // that the particular combination of grammar and input requires the more + // powerful LL prediction abilities to complete successfully. + // + // This prediction mode does not provide any guarantees for prediction + // behavior for syntactically-incorrect inputs. + // + PredictionModeSLL = 0 + + // PredictionModeLL represents the LL(*) prediction mode. + // This prediction mode allows the current parser + // context to be used for resolving SLL conflicts that occur during + // prediction. This is the fastest prediction mode that guarantees correct + // parse results for all combinations of grammars with syntactically correct + // inputs. + // + // When using this prediction mode, the parser will make correct decisions + // for all syntactically-correct grammar and input combinations. However, in + // cases where the grammar is truly ambiguous this prediction mode might not + // report a precise answer for exactly which alternatives are + // ambiguous. + // + // This prediction mode does not provide any guarantees for prediction + // behavior for syntactically-incorrect inputs. + // + PredictionModeLL = 1 + + // PredictionModeLLExactAmbigDetection represents the LL(*) prediction mode + // with exact ambiguity detection. + // + // In addition to the correctness guarantees provided by the [PredictionModeLL] prediction mode, + // this prediction mode instructs the prediction algorithm to determine the + // complete and exact set of ambiguous alternatives for every ambiguous + // decision encountered while parsing. + // + // This prediction mode may be used for diagnosing ambiguities during + // grammar development. Due to the performance overhead of calculating sets + // of ambiguous alternatives, this prediction mode should be avoided when + // the exact results are not necessary. + // + // This prediction mode does not provide any guarantees for prediction + // behavior for syntactically-incorrect inputs. + // + PredictionModeLLExactAmbigDetection = 2 +) + +// PredictionModehasSLLConflictTerminatingPrediction computes the SLL prediction termination condition. +// +// This method computes the SLL prediction termination condition for both of +// the following cases: +// +// - The usual SLL+LL fallback upon SLL conflict +// - Pure SLL without LL fallback +// +// # Combined SLL+LL Parsing +// +// When LL-fallback is enabled upon SLL conflict, correct predictions are +// ensured regardless of how the termination condition is computed by this +// method. Due to the substantially higher cost of LL prediction, the +// prediction should only fall back to LL when the additional lookahead +// cannot lead to a unique SLL prediction. +// +// Assuming combined SLL+LL parsing, an SLL configuration set with only +// conflicting subsets should fall back to full LL, even if the +// configuration sets don't resolve to the same alternative, e.g. +// +// {1,2} and {3,4} +// +// If there is at least one non-conflicting +// configuration, SLL could continue with the hopes that more lookahead will +// resolve via one of those non-conflicting configurations. +// +// Here's the prediction termination rule them: SLL (for SLL+LL parsing) +// stops when it sees only conflicting configuration subsets. In contrast, +// full LL keeps going when there is uncertainty. +// +// # Heuristic +// +// As a heuristic, we stop prediction when we see any conflicting subset +// unless we see a state that only has one alternative associated with it. +// The single-alt-state thing lets prediction continue upon rules like +// (otherwise, it would admit defeat too soon): +// +// [12|1|[], 6|2|[], 12|2|[]]. s : (ID | ID ID?) ; +// +// When the [ATN] simulation reaches the state before ';', it has a +// [DFA] state that looks like: +// +// [12|1|[], 6|2|[], 12|2|[]] +// +// Naturally +// +// 12|1|[] and 12|2|[] +// +// conflict, but we cannot stop processing this node because alternative to has another way to continue, +// via +// +// [6|2|[]] +// +// It also let's us continue for this rule: +// +// [1|1|[], 1|2|[], 8|3|[]] a : A | A | A B ; +// +// After Matching input A, we reach the stop state for rule A, state 1. +// State 8 is the state immediately before B. Clearly alternatives 1 and 2 +// conflict and no amount of further lookahead will separate the two. +// However, alternative 3 will be able to continue, and so we do not stop +// working on this state. In the previous example, we're concerned with +// states associated with the conflicting alternatives. Here alt 3 is not +// associated with the conflicting configs, but since we can continue +// looking for input reasonably, don't declare the state done. +// +// # Pure SLL Parsing +// +// To handle pure SLL parsing, all we have to do is make sure that we +// combine stack contexts for configurations that differ only by semantic +// predicate. From there, we can do the usual SLL termination heuristic. +// +// # Predicates in SLL+LL Parsing +// +// SLL decisions don't evaluate predicates until after they reach [DFA] stop +// states because they need to create the [DFA] cache that works in all +// semantic situations. In contrast, full LL evaluates predicates collected +// during start state computation, so it can ignore predicates thereafter. +// This means that SLL termination detection can totally ignore semantic +// predicates. +// +// Implementation-wise, [ATNConfigSet] combines stack contexts but not +// semantic predicate contexts, so we might see two configurations like the +// following: +// +// (s, 1, x, {}), (s, 1, x', {p}) +// +// Before testing these configurations against others, we have to merge +// x and x' (without modifying the existing configurations). +// For example, we test (x+x')==x” when looking for conflicts in +// the following configurations: +// +// (s, 1, x, {}), (s, 1, x', {p}), (s, 2, x”, {}) +// +// If the configuration set has predicates (as indicated by +// [ATNConfigSet.hasSemanticContext]), this algorithm makes a copy of +// the configurations to strip out all the predicates so that a standard +// [ATNConfigSet] will merge everything ignoring predicates. +func PredictionModehasSLLConflictTerminatingPrediction(mode int, configs *ATNConfigSet) bool { + + // Configs in rule stop states indicate reaching the end of the decision + // rule (local context) or end of start rule (full context). If all + // configs meet this condition, then none of the configurations is able + // to Match additional input, so we terminate prediction. + // + if PredictionModeallConfigsInRuleStopStates(configs) { + return true + } + + // pure SLL mode parsing + if mode == PredictionModeSLL { + // Don't bother with combining configs from different semantic + // contexts if we can fail over to full LL costs more time + // since we'll often fail over anyway. + if configs.hasSemanticContext { + // dup configs, tossing out semantic predicates + dup := NewATNConfigSet(false) + for _, c := range configs.configs { + + // NewATNConfig({semanticContext:}, c) + c = NewATNConfig2(c, SemanticContextNone) + dup.Add(c, nil) + } + configs = dup + } + // now we have combined contexts for configs with dissimilar predicates + } + // pure SLL or combined SLL+LL mode parsing + altsets := PredictionModegetConflictingAltSubsets(configs) + return PredictionModehasConflictingAltSet(altsets) && !PredictionModehasStateAssociatedWithOneAlt(configs) +} + +// PredictionModehasConfigInRuleStopState checks if any configuration in the given configs is in a +// [RuleStopState]. Configurations meeting this condition have reached +// the end of the decision rule (local context) or end of start rule (full +// context). +// +// The func returns true if any configuration in the supplied configs is in a [RuleStopState] +func PredictionModehasConfigInRuleStopState(configs *ATNConfigSet) bool { + for _, c := range configs.configs { + if _, ok := c.GetState().(*RuleStopState); ok { + return true + } + } + return false +} + +// PredictionModeallConfigsInRuleStopStates checks if all configurations in configs are in a +// [RuleStopState]. Configurations meeting this condition have reached +// the end of the decision rule (local context) or end of start rule (full +// context). +// +// the func returns true if all configurations in configs are in a +// [RuleStopState] +func PredictionModeallConfigsInRuleStopStates(configs *ATNConfigSet) bool { + + for _, c := range configs.configs { + if _, ok := c.GetState().(*RuleStopState); !ok { + return false + } + } + return true +} + +// PredictionModeresolvesToJustOneViableAlt checks full LL prediction termination. +// +// Can we stop looking ahead during [ATN] simulation or is there some +// uncertainty as to which alternative we will ultimately pick, after +// consuming more input? Even if there are partial conflicts, we might know +// that everything is going to resolve to the same minimum alternative. That +// means we can stop since no more lookahead will change that fact. On the +// other hand, there might be multiple conflicts that resolve to different +// minimums. That means we need more look ahead to decide which of those +// alternatives we should predict. +// +// The basic idea is to split the set of configurations 'C', into +// conflicting subsets (s, _, ctx, _) and singleton subsets with +// non-conflicting configurations. Two configurations conflict if they have +// identical [ATNConfig].state and [ATNConfig].context values +// but a different [ATNConfig].alt value, e.g. +// +// (s, i, ctx, _) +// +// and +// +// (s, j, ctx, _) ; for i != j +// +// Reduce these configuration subsets to the set of possible alternatives. +// You can compute the alternative subsets in one pass as follows: +// +// A_s,ctx = {i | (s, i, ctx, _)} +// +// for each configuration in C holding s and ctx fixed. +// +// Or in pseudo-code: +// +// for each configuration c in C: +// map[c] U = c.ATNConfig.alt alt // map hash/equals uses s and x, not alt and not pred +// +// The values in map are the set of +// +// A_s,ctx +// +// sets. +// +// If +// +// |A_s,ctx| = 1 +// +// then there is no conflict associated with s and ctx. +// +// Reduce the subsets to singletons by choosing a minimum of each subset. If +// the union of these alternative subsets is a singleton, then no amount of +// further lookahead will help us. We will always pick that alternative. If, +// however, there is more than one alternative, then we are uncertain which +// alternative to predict and must continue looking for resolution. We may +// or may not discover an ambiguity in the future, even if there are no +// conflicting subsets this round. +// +// The biggest sin is to terminate early because it means we've made a +// decision but were uncertain as to the eventual outcome. We haven't used +// enough lookahead. On the other hand, announcing a conflict too late is no +// big deal; you will still have the conflict. It's just inefficient. It +// might even look until the end of file. +// +// No special consideration for semantic predicates is required because +// predicates are evaluated on-the-fly for full LL prediction, ensuring that +// no configuration contains a semantic context during the termination +// check. +// +// # Conflicting Configs +// +// Two configurations: +// +// (s, i, x) and (s, j, x') +// +// conflict when i != j but x = x'. Because we merge all +// (s, i, _) configurations together, that means that there are at +// most n configurations associated with state s for +// n possible alternatives in the decision. The merged stacks +// complicate the comparison of configuration contexts x and x'. +// +// Sam checks to see if one is a subset of the other by calling +// merge and checking to see if the merged result is either x or x'. +// If the x associated with lowest alternative i +// is the superset, then i is the only possible prediction since the +// others resolve to min(i) as well. However, if x is +// associated with j > i then at least one stack configuration for +// j is not in conflict with alternative i. The algorithm +// should keep going, looking for more lookahead due to the uncertainty. +// +// For simplicity, I'm doing an equality check between x and +// x', which lets the algorithm continue to consume lookahead longer +// than necessary. The reason I like the equality is of course the +// simplicity but also because that is the test you need to detect the +// alternatives that are actually in conflict. +// +// # Continue/Stop Rule +// +// Continue if the union of resolved alternative sets from non-conflicting and +// conflicting alternative subsets has more than one alternative. We are +// uncertain about which alternative to predict. +// +// The complete set of alternatives, +// +// [i for (_, i, _)] +// +// tells us which alternatives are still in the running for the amount of input we've +// consumed at this point. The conflicting sets let us to strip away +// configurations that won't lead to more states because we resolve +// conflicts to the configuration with a minimum alternate for the +// conflicting set. +// +// Cases +// +// - no conflicts and more than 1 alternative in set => continue +// - (s, 1, x), (s, 2, x), (s, 3, z), (s', 1, y), (s', 2, y) yields non-conflicting set +// {3} ∪ conflicting sets min({1,2}) ∪ min({1,2}) = {1,3} => continue +// - (s, 1, x), (s, 2, x), (s', 1, y), (s', 2, y), (s”, 1, z) yields non-conflicting set +// {1} ∪ conflicting sets min({1,2}) ∪ min({1,2}) = {1} => stop and predict 1 +// - (s, 1, x), (s, 2, x), (s', 1, y), (s', 2, y) yields conflicting, reduced sets +// {1} ∪ {1} = {1} => stop and predict 1, can announce ambiguity {1,2} +// - (s, 1, x), (s, 2, x), (s', 2, y), (s', 3, y) yields conflicting, reduced sets +// {1} ∪ {2} = {1,2} => continue +// - (s, 1, x), (s, 2, x), (s', 2, y), (s', 3, y) yields conflicting, reduced sets +// {1} ∪ {2} = {1,2} => continue +// - (s, 1, x), (s, 2, x), (s', 3, y), (s', 4, y) yields conflicting, reduced sets +// {1} ∪ {3} = {1,3} => continue +// +// # Exact Ambiguity Detection +// +// If all states report the same conflicting set of alternatives, then we +// know we have the exact ambiguity set: +// +// |A_i| > 1 +// +// and +// +// A_i = A_j ; for all i, j +// +// In other words, we continue examining lookahead until all A_i +// have more than one alternative and all A_i are the same. If +// +// A={{1,2}, {1,3}} +// +// then regular LL prediction would terminate because the resolved set is {1}. +// To determine what the real ambiguity is, we have to know whether the ambiguity is between one and +// two or one and three so we keep going. We can only stop prediction when +// we need exact ambiguity detection when the sets look like: +// +// A={{1,2}} +// +// or +// +// {{1,2},{1,2}}, etc... +func PredictionModeresolvesToJustOneViableAlt(altsets []*BitSet) int { + return PredictionModegetSingleViableAlt(altsets) +} + +// PredictionModeallSubsetsConflict determines if every alternative subset in altsets contains more +// than one alternative. +// +// The func returns true if every [BitSet] in altsets has +// [BitSet].cardinality cardinality > 1 +func PredictionModeallSubsetsConflict(altsets []*BitSet) bool { + return !PredictionModehasNonConflictingAltSet(altsets) +} + +// PredictionModehasNonConflictingAltSet determines if any single alternative subset in altsets contains +// exactly one alternative. +// +// The func returns true if altsets contains at least one [BitSet] with +// [BitSet].cardinality cardinality 1 +func PredictionModehasNonConflictingAltSet(altsets []*BitSet) bool { + for i := 0; i < len(altsets); i++ { + alts := altsets[i] + if alts.length() == 1 { + return true + } + } + return false +} + +// PredictionModehasConflictingAltSet determines if any single alternative subset in altsets contains +// more than one alternative. +// +// The func returns true if altsets contains a [BitSet] with +// [BitSet].cardinality cardinality > 1, otherwise false +func PredictionModehasConflictingAltSet(altsets []*BitSet) bool { + for i := 0; i < len(altsets); i++ { + alts := altsets[i] + if alts.length() > 1 { + return true + } + } + return false +} + +// PredictionModeallSubsetsEqual determines if every alternative subset in altsets is equivalent. +// +// The func returns true if every member of altsets is equal to the others. +func PredictionModeallSubsetsEqual(altsets []*BitSet) bool { + var first *BitSet + + for i := 0; i < len(altsets); i++ { + alts := altsets[i] + if first == nil { + first = alts + } else if alts != first { + return false + } + } + + return true +} + +// PredictionModegetUniqueAlt returns the unique alternative predicted by all alternative subsets in +// altsets. If no such alternative exists, this method returns +// [ATNInvalidAltNumber]. +// +// @param altsets a collection of alternative subsets +func PredictionModegetUniqueAlt(altsets []*BitSet) int { + all := PredictionModeGetAlts(altsets) + if all.length() == 1 { + return all.minValue() + } + + return ATNInvalidAltNumber +} + +// PredictionModeGetAlts returns the complete set of represented alternatives for a collection of +// alternative subsets. This method returns the union of each [BitSet] +// in altsets, being the set of represented alternatives in altsets. +func PredictionModeGetAlts(altsets []*BitSet) *BitSet { + all := NewBitSet() + for _, alts := range altsets { + all.or(alts) + } + return all +} + +// PredictionModegetConflictingAltSubsets gets the conflicting alt subsets from a configuration set. +// +// for each configuration c in configs: +// map[c] U= c.ATNConfig.alt // map hash/equals uses s and x, not alt and not pred +func PredictionModegetConflictingAltSubsets(configs *ATNConfigSet) []*BitSet { + configToAlts := NewJMap[*ATNConfig, *BitSet, *ATNAltConfigComparator[*ATNConfig]](atnAltCfgEqInst, AltSetCollection, "PredictionModegetConflictingAltSubsets()") + + for _, c := range configs.configs { + + alts, ok := configToAlts.Get(c) + if !ok { + alts = NewBitSet() + configToAlts.Put(c, alts) + } + alts.add(c.GetAlt()) + } + + return configToAlts.Values() +} + +// PredictionModeGetStateToAltMap gets a map from state to alt subset from a configuration set. +// +// for each configuration c in configs: +// map[c.ATNConfig.state] U= c.ATNConfig.alt} +func PredictionModeGetStateToAltMap(configs *ATNConfigSet) *AltDict { + m := NewAltDict() + + for _, c := range configs.configs { + alts := m.Get(c.GetState().String()) + if alts == nil { + alts = NewBitSet() + m.put(c.GetState().String(), alts) + } + alts.(*BitSet).add(c.GetAlt()) + } + return m +} + +func PredictionModehasStateAssociatedWithOneAlt(configs *ATNConfigSet) bool { + values := PredictionModeGetStateToAltMap(configs).values() + for i := 0; i < len(values); i++ { + if values[i].(*BitSet).length() == 1 { + return true + } + } + return false +} + +// PredictionModegetSingleViableAlt gets the single alternative predicted by all alternative subsets in altsets +// if there is one. +// +// TODO: JI - Review this code - it does not seem to do the same thing as the Java code - maybe because [BitSet] is not like the Java utils BitSet +func PredictionModegetSingleViableAlt(altsets []*BitSet) int { + result := ATNInvalidAltNumber + + for i := 0; i < len(altsets); i++ { + alts := altsets[i] + minAlt := alts.minValue() + if result == ATNInvalidAltNumber { + result = minAlt + } else if result != minAlt { // more than 1 viable alt + return ATNInvalidAltNumber + } + } + return result +} diff --git a/prutalgen/internal/antlr/recognizer.go b/prutalgen/internal/antlr/recognizer.go new file mode 100644 index 0000000..9d57d14 --- /dev/null +++ b/prutalgen/internal/antlr/recognizer.go @@ -0,0 +1,241 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strings" + + "strconv" +) + +type Recognizer interface { + GetLiteralNames() []string + GetSymbolicNames() []string + GetRuleNames() []string + + Sempred(RuleContext, int, int) bool + Precpred(RuleContext, int) bool + + GetState() int + SetState(int) + Action(RuleContext, int, int) + AddErrorListener(ErrorListener) + RemoveErrorListeners() + GetATN() *ATN + GetErrorListenerDispatch() ErrorListener + HasError() bool + GetError() RecognitionException + SetError(RecognitionException) +} + +type BaseRecognizer struct { + listeners []ErrorListener + state int + + RuleNames []string + LiteralNames []string + SymbolicNames []string + GrammarFileName string + SynErr RecognitionException +} + +func NewBaseRecognizer() *BaseRecognizer { + rec := new(BaseRecognizer) + rec.listeners = []ErrorListener{ConsoleErrorListenerINSTANCE} + rec.state = -1 + return rec +} + +//goland:noinspection GoUnusedGlobalVariable +var tokenTypeMapCache = make(map[string]int) + +//goland:noinspection GoUnusedGlobalVariable +var ruleIndexMapCache = make(map[string]int) + +func (b *BaseRecognizer) checkVersion(toolVersion string) { + runtimeVersion := "4.13.2" + if runtimeVersion != toolVersion { + fmt.Println("ANTLR runtime and generated code versions disagree: " + runtimeVersion + "!=" + toolVersion) + } +} + +func (b *BaseRecognizer) SetError(err RecognitionException) { + b.SynErr = err +} + +func (b *BaseRecognizer) HasError() bool { + return b.SynErr != nil +} + +func (b *BaseRecognizer) GetError() RecognitionException { + return b.SynErr +} + +func (b *BaseRecognizer) Action(_ RuleContext, _, _ int) { + panic("action not implemented on Recognizer!") +} + +func (b *BaseRecognizer) AddErrorListener(listener ErrorListener) { + b.listeners = append(b.listeners, listener) +} + +func (b *BaseRecognizer) RemoveErrorListeners() { + b.listeners = make([]ErrorListener, 0) +} + +func (b *BaseRecognizer) GetRuleNames() []string { + return b.RuleNames +} + +func (b *BaseRecognizer) GetTokenNames() []string { + return b.LiteralNames +} + +func (b *BaseRecognizer) GetSymbolicNames() []string { + return b.SymbolicNames +} + +func (b *BaseRecognizer) GetLiteralNames() []string { + return b.LiteralNames +} + +func (b *BaseRecognizer) GetState() int { + return b.state +} + +func (b *BaseRecognizer) SetState(v int) { + b.state = v +} + +//func (b *Recognizer) GetTokenTypeMap() { +// var tokenNames = b.GetTokenNames() +// if (tokenNames==nil) { +// panic("The current recognizer does not provide a list of token names.") +// } +// var result = tokenTypeMapCache[tokenNames] +// if(result==nil) { +// result = tokenNames.reduce(function(o, k, i) { o[k] = i }) +// result.EOF = TokenEOF +// tokenTypeMapCache[tokenNames] = result +// } +// return result +//} + +// GetRuleIndexMap Get a map from rule names to rule indexes. +// +// Used for XPath and tree pattern compilation. +// +// TODO: JI This is not yet implemented in the Go runtime. Maybe not needed. +func (b *BaseRecognizer) GetRuleIndexMap() map[string]int { + + panic("Method not defined!") + // var ruleNames = b.GetRuleNames() + // if (ruleNames==nil) { + // panic("The current recognizer does not provide a list of rule names.") + // } + // + // var result = ruleIndexMapCache[ruleNames] + // if(result==nil) { + // result = ruleNames.reduce(function(o, k, i) { o[k] = i }) + // ruleIndexMapCache[ruleNames] = result + // } + // return result +} + +// GetTokenType get the token type based upon its name +func (b *BaseRecognizer) GetTokenType(_ string) int { + panic("Method not defined!") + // var ttype = b.GetTokenTypeMap()[tokenName] + // if (ttype !=nil) { + // return ttype + // } else { + // return TokenInvalidType + // } +} + +//func (b *Recognizer) GetTokenTypeMap() map[string]int { +// Vocabulary vocabulary = getVocabulary() +// +// Synchronized (tokenTypeMapCache) { +// Map result = tokenTypeMapCache.Get(vocabulary) +// if (result == null) { +// result = new HashMap() +// for (int i = 0; i < GetATN().maxTokenType; i++) { +// String literalName = vocabulary.getLiteralName(i) +// if (literalName != null) { +// result.put(literalName, i) +// } +// +// String symbolicName = vocabulary.GetSymbolicName(i) +// if (symbolicName != null) { +// result.put(symbolicName, i) +// } +// } +// +// result.put("EOF", Token.EOF) +// result = Collections.unmodifiableMap(result) +// tokenTypeMapCache.put(vocabulary, result) +// } +// +// return result +// } +//} + +// GetErrorHeader returns the error header, normally line/character position information. +// +// Can be overridden in sub structs embedding BaseRecognizer. +func (b *BaseRecognizer) GetErrorHeader(e RecognitionException) string { + line := e.GetOffendingToken().GetLine() + column := e.GetOffendingToken().GetColumn() + return "line " + strconv.Itoa(line) + ":" + strconv.Itoa(column) +} + +// GetTokenErrorDisplay shows how a token should be displayed in an error message. +// +// The default is to display just the text, but during development you might +// want to have a lot of information spit out. Override in that case +// to use t.String() (which, for CommonToken, dumps everything about +// the token). This is better than forcing you to override a method in +// your token objects because you don't have to go modify your lexer +// so that it creates a NewJava type. +// +// Deprecated: This method is not called by the ANTLR 4 Runtime. Specific +// implementations of [ANTLRErrorStrategy] may provide a similar +// feature when necessary. For example, see [DefaultErrorStrategy].GetTokenErrorDisplay() +func (b *BaseRecognizer) GetTokenErrorDisplay(t Token) string { + if t == nil { + return "" + } + s := t.GetText() + if s == "" { + if t.GetTokenType() == TokenEOF { + s = "" + } else { + s = "<" + strconv.Itoa(t.GetTokenType()) + ">" + } + } + s = strings.Replace(s, "\t", "\\t", -1) + s = strings.Replace(s, "\n", "\\n", -1) + s = strings.Replace(s, "\r", "\\r", -1) + + return "'" + s + "'" +} + +func (b *BaseRecognizer) GetErrorListenerDispatch() ErrorListener { + return NewProxyErrorListener(b.listeners) +} + +// Sempred embedding structs need to override this if there are sempreds or actions +// that the ATN interpreter needs to execute +func (b *BaseRecognizer) Sempred(_ RuleContext, _ int, _ int) bool { + return true +} + +// Precpred embedding structs need to override this if there are preceding predicates +// that the ATN interpreter needs to execute +func (b *BaseRecognizer) Precpred(_ RuleContext, _ int) bool { + return true +} diff --git a/prutalgen/internal/antlr/rule_context.go b/prutalgen/internal/antlr/rule_context.go new file mode 100644 index 0000000..f2ad047 --- /dev/null +++ b/prutalgen/internal/antlr/rule_context.go @@ -0,0 +1,40 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// RuleContext is a record of a single rule invocation. It knows +// which context invoked it, if any. If there is no parent context, then +// naturally the invoking state is not valid. The parent link +// provides a chain upwards from the current rule invocation to the root +// of the invocation tree, forming a stack. +// +// We actually carry no information about the rule associated with this context (except +// when parsing). We keep only the state number of the invoking state from +// the [ATN] submachine that invoked this. Contrast this with the s +// pointer inside [ParserRuleContext] that tracks the current state +// being "executed" for the current rule. +// +// The parent contexts are useful for computing lookahead sets and +// getting error information. +// +// These objects are used during parsing and prediction. +// For the special case of parsers, we use the struct +// [ParserRuleContext], which embeds a RuleContext. +// +// @see ParserRuleContext +type RuleContext interface { + RuleNode + + GetInvokingState() int + SetInvokingState(int) + + GetRuleIndex() int + IsEmpty() bool + + GetAltNumber() int + SetAltNumber(altNumber int) + + String([]string, RuleContext) string +} diff --git a/prutalgen/internal/antlr/semantic_context.go b/prutalgen/internal/antlr/semantic_context.go new file mode 100644 index 0000000..68cb906 --- /dev/null +++ b/prutalgen/internal/antlr/semantic_context.go @@ -0,0 +1,464 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" +) + +// SemanticContext is a tree structure used to record the semantic context in which +// +// an ATN configuration is valid. It's either a single predicate, +// a conjunction p1 && p2, or a sum of products p1 || p2. +// +// I have scoped the AND, OR, and Predicate subclasses of +// [SemanticContext] within the scope of this outer ``class'' +type SemanticContext interface { + Equals(other Collectable[SemanticContext]) bool + Hash() int + + evaluate(parser Recognizer, outerContext RuleContext) bool + evalPrecedence(parser Recognizer, outerContext RuleContext) SemanticContext + + String() string +} + +func SemanticContextandContext(a, b SemanticContext) SemanticContext { + if a == nil || a == SemanticContextNone { + return b + } + if b == nil || b == SemanticContextNone { + return a + } + result := NewAND(a, b) + if len(result.opnds) == 1 { + return result.opnds[0] + } + + return result +} + +func SemanticContextorContext(a, b SemanticContext) SemanticContext { + if a == nil { + return b + } + if b == nil { + return a + } + if a == SemanticContextNone || b == SemanticContextNone { + return SemanticContextNone + } + result := NewOR(a, b) + if len(result.opnds) == 1 { + return result.opnds[0] + } + + return result +} + +type Predicate struct { + ruleIndex int + predIndex int + isCtxDependent bool +} + +func NewPredicate(ruleIndex, predIndex int, isCtxDependent bool) *Predicate { + p := new(Predicate) + + p.ruleIndex = ruleIndex + p.predIndex = predIndex + p.isCtxDependent = isCtxDependent // e.g., $i ref in pred + return p +} + +//The default {@link SemanticContext}, which is semantically equivalent to +//a predicate of the form {@code {true}?}. + +var SemanticContextNone = NewPredicate(-1, -1, false) + +func (p *Predicate) evalPrecedence(_ Recognizer, _ RuleContext) SemanticContext { + return p +} + +func (p *Predicate) evaluate(parser Recognizer, outerContext RuleContext) bool { + + var localctx RuleContext + + if p.isCtxDependent { + localctx = outerContext + } + + return parser.Sempred(localctx, p.ruleIndex, p.predIndex) +} + +func (p *Predicate) Equals(other Collectable[SemanticContext]) bool { + if p == other { + return true + } else if _, ok := other.(*Predicate); !ok { + return false + } else { + return p.ruleIndex == other.(*Predicate).ruleIndex && + p.predIndex == other.(*Predicate).predIndex && + p.isCtxDependent == other.(*Predicate).isCtxDependent + } +} + +func (p *Predicate) Hash() int { + h := murmurInit(0) + h = murmurUpdate(h, p.ruleIndex) + h = murmurUpdate(h, p.predIndex) + if p.isCtxDependent { + h = murmurUpdate(h, 1) + } else { + h = murmurUpdate(h, 0) + } + return murmurFinish(h, 3) +} + +func (p *Predicate) String() string { + return "{" + strconv.Itoa(p.ruleIndex) + ":" + strconv.Itoa(p.predIndex) + "}?" +} + +type PrecedencePredicate struct { + precedence int +} + +func NewPrecedencePredicate(precedence int) *PrecedencePredicate { + + p := new(PrecedencePredicate) + p.precedence = precedence + + return p +} + +func (p *PrecedencePredicate) evaluate(parser Recognizer, outerContext RuleContext) bool { + return parser.Precpred(outerContext, p.precedence) +} + +func (p *PrecedencePredicate) evalPrecedence(parser Recognizer, outerContext RuleContext) SemanticContext { + if parser.Precpred(outerContext, p.precedence) { + return SemanticContextNone + } + + return nil +} + +func (p *PrecedencePredicate) compareTo(other *PrecedencePredicate) int { + return p.precedence - other.precedence +} + +func (p *PrecedencePredicate) Equals(other Collectable[SemanticContext]) bool { + + var op *PrecedencePredicate + var ok bool + if op, ok = other.(*PrecedencePredicate); !ok { + return false + } + + if p == op { + return true + } + + return p.precedence == other.(*PrecedencePredicate).precedence +} + +func (p *PrecedencePredicate) Hash() int { + h := uint32(1) + h = 31*h + uint32(p.precedence) + return int(h) +} + +func (p *PrecedencePredicate) String() string { + return "{" + strconv.Itoa(p.precedence) + ">=prec}?" +} + +func PrecedencePredicatefilterPrecedencePredicates(set *JStore[SemanticContext, Comparator[SemanticContext]]) []*PrecedencePredicate { + result := make([]*PrecedencePredicate, 0) + + set.Each(func(v SemanticContext) bool { + if c2, ok := v.(*PrecedencePredicate); ok { + result = append(result, c2) + } + return true + }) + + return result +} + +// A semantic context which is true whenever none of the contained contexts +// is false.` + +type AND struct { + opnds []SemanticContext +} + +func NewAND(a, b SemanticContext) *AND { + + operands := NewJStore[SemanticContext, Comparator[SemanticContext]](semctxEqInst, SemanticContextCollection, "NewAND() operands") + if aa, ok := a.(*AND); ok { + for _, o := range aa.opnds { + operands.Put(o) + } + } else { + operands.Put(a) + } + + if ba, ok := b.(*AND); ok { + for _, o := range ba.opnds { + operands.Put(o) + } + } else { + operands.Put(b) + } + precedencePredicates := PrecedencePredicatefilterPrecedencePredicates(operands) + if len(precedencePredicates) > 0 { + // interested in the transition with the lowest precedence + var reduced *PrecedencePredicate + + for _, p := range precedencePredicates { + if reduced == nil || p.precedence < reduced.precedence { + reduced = p + } + } + + operands.Put(reduced) + } + + vs := operands.Values() + opnds := make([]SemanticContext, len(vs)) + copy(opnds, vs) + + and := new(AND) + and.opnds = opnds + + return and +} + +func (a *AND) Equals(other Collectable[SemanticContext]) bool { + if a == other { + return true + } + if _, ok := other.(*AND); !ok { + return false + } else { + for i, v := range other.(*AND).opnds { + if !a.opnds[i].Equals(v) { + return false + } + } + return true + } +} + +// {@inheritDoc} +// +//

+// The evaluation of predicates by a context is short-circuiting, but +// unordered.

+func (a *AND) evaluate(parser Recognizer, outerContext RuleContext) bool { + for i := 0; i < len(a.opnds); i++ { + if !a.opnds[i].evaluate(parser, outerContext) { + return false + } + } + return true +} + +func (a *AND) evalPrecedence(parser Recognizer, outerContext RuleContext) SemanticContext { + differs := false + operands := make([]SemanticContext, 0) + + for i := 0; i < len(a.opnds); i++ { + context := a.opnds[i] + evaluated := context.evalPrecedence(parser, outerContext) + differs = differs || (evaluated != context) + if evaluated == nil { + // The AND context is false if any element is false + return nil + } else if evaluated != SemanticContextNone { + // Reduce the result by Skipping true elements + operands = append(operands, evaluated) + } + } + if !differs { + return a + } + + if len(operands) == 0 { + // all elements were true, so the AND context is true + return SemanticContextNone + } + + var result SemanticContext + + for _, o := range operands { + if result == nil { + result = o + } else { + result = SemanticContextandContext(result, o) + } + } + + return result +} + +func (a *AND) Hash() int { + h := murmurInit(37) // Init with a value different from OR + for _, op := range a.opnds { + h = murmurUpdate(h, op.Hash()) + } + return murmurFinish(h, len(a.opnds)) +} + +func (o *OR) Hash() int { + h := murmurInit(41) // Init with o value different from AND + for _, op := range o.opnds { + h = murmurUpdate(h, op.Hash()) + } + return murmurFinish(h, len(o.opnds)) +} + +func (a *AND) String() string { + s := "" + + for _, o := range a.opnds { + s += "&& " + fmt.Sprint(o) + } + + if len(s) > 3 { + return s[0:3] + } + + return s +} + +// +// A semantic context which is true whenever at least one of the contained +// contexts is true. +// + +type OR struct { + opnds []SemanticContext +} + +func NewOR(a, b SemanticContext) *OR { + + operands := NewJStore[SemanticContext, Comparator[SemanticContext]](semctxEqInst, SemanticContextCollection, "NewOR() operands") + if aa, ok := a.(*OR); ok { + for _, o := range aa.opnds { + operands.Put(o) + } + } else { + operands.Put(a) + } + + if ba, ok := b.(*OR); ok { + for _, o := range ba.opnds { + operands.Put(o) + } + } else { + operands.Put(b) + } + precedencePredicates := PrecedencePredicatefilterPrecedencePredicates(operands) + if len(precedencePredicates) > 0 { + // interested in the transition with the lowest precedence + var reduced *PrecedencePredicate + + for _, p := range precedencePredicates { + if reduced == nil || p.precedence > reduced.precedence { + reduced = p + } + } + + operands.Put(reduced) + } + + vs := operands.Values() + + opnds := make([]SemanticContext, len(vs)) + copy(opnds, vs) + + o := new(OR) + o.opnds = opnds + + return o +} + +func (o *OR) Equals(other Collectable[SemanticContext]) bool { + if o == other { + return true + } else if _, ok := other.(*OR); !ok { + return false + } else { + for i, v := range other.(*OR).opnds { + if !o.opnds[i].Equals(v) { + return false + } + } + return true + } +} + +//

+// The evaluation of predicates by o context is short-circuiting, but +// unordered.

+func (o *OR) evaluate(parser Recognizer, outerContext RuleContext) bool { + for i := 0; i < len(o.opnds); i++ { + if o.opnds[i].evaluate(parser, outerContext) { + return true + } + } + return false +} + +func (o *OR) evalPrecedence(parser Recognizer, outerContext RuleContext) SemanticContext { + differs := false + operands := make([]SemanticContext, 0) + for i := 0; i < len(o.opnds); i++ { + context := o.opnds[i] + evaluated := context.evalPrecedence(parser, outerContext) + differs = differs || (evaluated != context) + if evaluated == SemanticContextNone { + // The OR context is true if any element is true + return SemanticContextNone + } else if evaluated != nil { + // Reduce the result by Skipping false elements + operands = append(operands, evaluated) + } + } + if !differs { + return o + } + if len(operands) == 0 { + // all elements were false, so the OR context is false + return nil + } + var result SemanticContext + + for _, o := range operands { + if result == nil { + result = o + } else { + result = SemanticContextorContext(result, o) + } + } + + return result +} + +func (o *OR) String() string { + s := "" + + for _, o := range o.opnds { + s += "|| " + fmt.Sprint(o) + } + + if len(s) > 3 { + return s[0:3] + } + + return s +} diff --git a/prutalgen/internal/antlr/statistics.go b/prutalgen/internal/antlr/statistics.go new file mode 100644 index 0000000..8cb5f3e --- /dev/null +++ b/prutalgen/internal/antlr/statistics.go @@ -0,0 +1,280 @@ +//go:build antlr.stats + +package antlr + +import ( + "fmt" + "log" + "os" + "path/filepath" + "sort" + "strconv" +) + +// This file allows the user to collect statistics about the runtime of the ANTLR runtime. It is not enabled by default +// and so incurs no time penalty. To enable it, you must build the runtime with the antlr.stats build tag. +// + +// Tells various components to collect statistics - because it is only true when this file is included, it will +// allow the compiler to completely eliminate all the code that is only used when collecting statistics. +const collectStats = true + +// goRunStats is a collection of all the various data the ANTLR runtime has collected about a particular run. +// It is exported so that it can be used by others to look for things that are not already looked for in the +// runtime statistics. +type goRunStats struct { + + // jStats is a slice of all the [JStatRec] records that have been created, which is one for EVERY collection created + // during a run. It is exported so that it can be used by others to look for things that are not already looked for + // within this package. + // + jStats []*JStatRec + jStatsLock RWMutex + topN int + topNByMax []*JStatRec + topNByUsed []*JStatRec + unusedCollections map[CollectionSource]int + counts map[CollectionSource]int +} + +const ( + collectionsFile = "collections" +) + +var ( + Statistics = &goRunStats{ + topN: 10, + } +) + +type statsOption func(*goRunStats) error + +// Configure allows the statistics system to be configured as the user wants and override the defaults +func (s *goRunStats) Configure(options ...statsOption) error { + for _, option := range options { + err := option(s) + if err != nil { + return err + } + } + return nil +} + +// WithTopN sets the number of things to list in the report when we are concerned with the top N things. +// +// For example, if you want to see the top 20 collections by size, you can do: +// +// antlr.Statistics.Configure(antlr.WithTopN(20)) +func WithTopN(topN int) statsOption { + return func(s *goRunStats) error { + s.topN = topN + return nil + } +} + +// Analyze looks through all the statistical records and computes all the outputs that might be useful to the user. +// +// The function gathers and analyzes a number of statistics about any particular run of +// an ANTLR generated recognizer. In the vast majority of cases, the statistics are only +// useful to maintainers of ANTLR itself, but they can be useful to users as well. They may be +// especially useful in tracking down bugs or performance problems when an ANTLR user could +// supply the output from this package, but cannot supply the grammar file(s) they are using, even +// privately to the maintainers. +// +// The statistics are gathered by the runtime itself, and are not gathered by the parser or lexer, but the user +// must call this function their selves to analyze the statistics. This is because none of the infrastructure is +// extant unless the calling program is built with the antlr.stats tag like so: +// +// go build -tags antlr.stats . +// +// When a program is built with the antlr.stats tag, the Statistics object is created and available outside +// the package. The user can then call the [Statistics.Analyze] function to analyze the statistics and then call the +// [Statistics.Report] function to report the statistics. +// +// Please forward any questions about this package to the ANTLR discussion groups on GitHub or send to them to +// me [Jim Idle] directly at jimi@idle.ws +// +// [Jim Idle]: https:://github.com/jim-idle +func (s *goRunStats) Analyze() { + + // Look for anything that looks strange and record it in our local maps etc for the report to present it + // + s.CollectionAnomalies() + s.TopNCollections() +} + +// TopNCollections looks through all the statistical records and gathers the top ten collections by size. +func (s *goRunStats) TopNCollections() { + + // Let's sort the stat records by MaxSize + // + sort.Slice(s.jStats, func(i, j int) bool { + return s.jStats[i].MaxSize > s.jStats[j].MaxSize + }) + + for i := 0; i < len(s.jStats) && i < s.topN; i++ { + s.topNByMax = append(s.topNByMax, s.jStats[i]) + } + + // Sort by the number of times used + // + sort.Slice(s.jStats, func(i, j int) bool { + return s.jStats[i].Gets+s.jStats[i].Puts > s.jStats[j].Gets+s.jStats[j].Puts + }) + for i := 0; i < len(s.jStats) && i < s.topN; i++ { + s.topNByUsed = append(s.topNByUsed, s.jStats[i]) + } +} + +// Report dumps a markdown formatted report of all the statistics collected during a run to the given dir output +// path, which should represent a directory. Generated files will be prefixed with the given prefix and will be +// given a type name such as `anomalies` and a time stamp such as `2021-09-01T12:34:56` and a .md suffix. +func (s *goRunStats) Report(dir string, prefix string) error { + + isDir, err := isDirectory(dir) + switch { + case err != nil: + return err + case !isDir: + return fmt.Errorf("output directory `%s` is not a directory", dir) + } + s.reportCollections(dir, prefix) + + // Clean out any old data in case the user forgets + // + s.Reset() + return nil +} + +func (s *goRunStats) Reset() { + s.jStats = nil + s.topNByUsed = nil + s.topNByMax = nil +} + +func (s *goRunStats) reportCollections(dir, prefix string) { + cname := filepath.Join(dir, ".asciidoctor") + // If the file doesn't exist, create it, or append to the file + f, err := os.OpenFile(cname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + _, _ = f.WriteString(`// .asciidoctorconfig +++++ + +++++`) + _ = f.Close() + + fname := filepath.Join(dir, prefix+"_"+"_"+collectionsFile+"_"+".adoc") + // If the file doesn't exist, create it, or append to the file + f, err = os.OpenFile(fname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + log.Fatal(err) + } + defer func(f *os.File) { + err := f.Close() + if err != nil { + log.Fatal(err) + } + }(f) + _, _ = f.WriteString("= Collections for " + prefix + "\n\n") + + _, _ = f.WriteString("== Summary\n") + + if s.unusedCollections != nil { + _, _ = f.WriteString("=== Unused Collections\n") + _, _ = f.WriteString("Unused collections incur a penalty for allocation that makes them a candidate for either\n") + _, _ = f.WriteString(" removal or optimization. If you are using a collection that is not used, you should\n") + _, _ = f.WriteString(" consider removing it. If you are using a collection that is used, but not very often,\n") + _, _ = f.WriteString(" you should consider using lazy initialization to defer the allocation until it is\n") + _, _ = f.WriteString(" actually needed.\n\n") + + _, _ = f.WriteString("\n.Unused collections\n") + _, _ = f.WriteString(`[cols="<3,>1"]` + "\n\n") + _, _ = f.WriteString("|===\n") + _, _ = f.WriteString("| Type | Count\n") + + for k, v := range s.unusedCollections { + _, _ = f.WriteString("| " + CollectionDescriptors[k].SybolicName + " | " + strconv.Itoa(v) + "\n") + } + f.WriteString("|===\n\n") + } + + _, _ = f.WriteString("\n.Summary of Collections\n") + _, _ = f.WriteString(`[cols="<3,>1"]` + "\n\n") + _, _ = f.WriteString("|===\n") + _, _ = f.WriteString("| Type | Count\n") + for k, v := range s.counts { + _, _ = f.WriteString("| " + CollectionDescriptors[k].SybolicName + " | " + strconv.Itoa(v) + "\n") + } + _, _ = f.WriteString("| Total | " + strconv.Itoa(len(s.jStats)) + "\n") + _, _ = f.WriteString("|===\n\n") + + _, _ = f.WriteString("\n.Summary of Top " + strconv.Itoa(s.topN) + " Collections by MaxSize\n") + _, _ = f.WriteString(`[cols="<1,<3,>1,>1,>1,>1"]` + "\n\n") + _, _ = f.WriteString("|===\n") + _, _ = f.WriteString("| Source | Description | MaxSize | EndSize | Puts | Gets\n") + for _, c := range s.topNByMax { + _, _ = f.WriteString("| " + CollectionDescriptors[c.Source].SybolicName + "\n") + _, _ = f.WriteString("| " + c.Description + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.MaxSize) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.CurSize) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.Puts) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.Gets) + "\n") + _, _ = f.WriteString("\n") + } + _, _ = f.WriteString("|===\n\n") + + _, _ = f.WriteString("\n.Summary of Top " + strconv.Itoa(s.topN) + " Collections by Access\n") + _, _ = f.WriteString(`[cols="<1,<3,>1,>1,>1,>1,>1"]` + "\n\n") + _, _ = f.WriteString("|===\n") + _, _ = f.WriteString("| Source | Description | MaxSize | EndSize | Puts | Gets | P+G\n") + for _, c := range s.topNByUsed { + _, _ = f.WriteString("| " + CollectionDescriptors[c.Source].SybolicName + "\n") + _, _ = f.WriteString("| " + c.Description + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.MaxSize) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.CurSize) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.Puts) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.Gets) + "\n") + _, _ = f.WriteString("| " + strconv.Itoa(c.Gets+c.Puts) + "\n") + _, _ = f.WriteString("\n") + } + _, _ = f.WriteString("|===\n\n") +} + +// AddJStatRec adds a [JStatRec] record to the [goRunStats] collection when build runtimeConfig antlr.stats is enabled. +func (s *goRunStats) AddJStatRec(rec *JStatRec) { + s.jStatsLock.Lock() + defer s.jStatsLock.Unlock() + s.jStats = append(s.jStats, rec) +} + +// CollectionAnomalies looks through all the statistical records and gathers any anomalies that have been found. +func (s *goRunStats) CollectionAnomalies() { + s.jStatsLock.RLock() + defer s.jStatsLock.RUnlock() + s.counts = make(map[CollectionSource]int, len(s.jStats)) + for _, c := range s.jStats { + + // Accumlate raw counts + // + s.counts[c.Source]++ + + // Look for allocated but unused collections and count them + if c.MaxSize == 0 && c.Puts == 0 { + if s.unusedCollections == nil { + s.unusedCollections = make(map[CollectionSource]int) + } + s.unusedCollections[c.Source]++ + } + if c.MaxSize > 6000 { + fmt.Println("Collection ", c.Description, "accumulated a max size of ", c.MaxSize, " - this is probably too large and indicates a poorly formed grammar") + } + } + +} diff --git a/prutalgen/internal/antlr/stats_data.go b/prutalgen/internal/antlr/stats_data.go new file mode 100644 index 0000000..4d9eb94 --- /dev/null +++ b/prutalgen/internal/antlr/stats_data.go @@ -0,0 +1,23 @@ +package antlr + +// A JStatRec is a record of a particular use of a [JStore], [JMap] or JPCMap] collection. Typically, it will be +// used to look for unused collections that wre allocated anyway, problems with hash bucket clashes, and anomalies +// such as huge numbers of Gets with no entries found GetNoEnt. You can refer to the CollectionAnomalies() function +// for ideas on what can be gleaned from these statistics about collections. +type JStatRec struct { + Source CollectionSource + MaxSize int + CurSize int + Gets int + GetHits int + GetMisses int + GetHashConflicts int + GetNoEnt int + Puts int + PutHits int + PutMisses int + PutHashConflicts int + MaxSlotSize int + Description string + CreateStack []byte +} diff --git a/prutalgen/internal/antlr/token.go b/prutalgen/internal/antlr/token.go new file mode 100644 index 0000000..f5bc342 --- /dev/null +++ b/prutalgen/internal/antlr/token.go @@ -0,0 +1,213 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "strconv" + "strings" +) + +type TokenSourceCharStreamPair struct { + tokenSource TokenSource + charStream CharStream +} + +// A token has properties: text, type, line, character position in the line +// (so we can ignore tabs), token channel, index, and source from which +// we obtained this token. + +type Token interface { + GetSource() *TokenSourceCharStreamPair + GetTokenType() int + GetChannel() int + GetStart() int + GetStop() int + GetLine() int + GetColumn() int + + GetText() string + SetText(s string) + + GetTokenIndex() int + SetTokenIndex(v int) + + GetTokenSource() TokenSource + GetInputStream() CharStream + + String() string +} + +type BaseToken struct { + source *TokenSourceCharStreamPair + tokenType int // token type of the token + channel int // The parser ignores everything not on DEFAULT_CHANNEL + start int // optional return -1 if not implemented. + stop int // optional return -1 if not implemented. + tokenIndex int // from 0..n-1 of the token object in the input stream + line int // line=1..n of the 1st character + column int // beginning of the line at which it occurs, 0..n-1 + text string // text of the token. + readOnly bool +} + +const ( + TokenInvalidType = 0 + + // TokenEpsilon - during lookahead operations, this "token" signifies we hit the rule end [ATN] state + // and did not follow it despite needing to. + TokenEpsilon = -2 + + TokenMinUserTokenType = 1 + + TokenEOF = -1 + + // TokenDefaultChannel is the default channel upon which tokens are sent to the parser. + // + // All tokens go to the parser (unless [Skip] is called in the lexer rule) + // on a particular "channel". The parser tunes to a particular channel + // so that whitespace etc... can go to the parser on a "hidden" channel. + TokenDefaultChannel = 0 + + // TokenHiddenChannel defines the normal hidden channel - the parser wil not see tokens that are not on [TokenDefaultChannel]. + // + // Anything on a different channel than TokenDefaultChannel is not parsed by parser. + TokenHiddenChannel = 1 +) + +func (b *BaseToken) GetChannel() int { + return b.channel +} + +func (b *BaseToken) GetStart() int { + return b.start +} + +func (b *BaseToken) GetStop() int { + return b.stop +} + +func (b *BaseToken) GetLine() int { + return b.line +} + +func (b *BaseToken) GetColumn() int { + return b.column +} + +func (b *BaseToken) GetTokenType() int { + return b.tokenType +} + +func (b *BaseToken) GetSource() *TokenSourceCharStreamPair { + return b.source +} + +func (b *BaseToken) GetText() string { + if b.text != "" { + return b.text + } + input := b.GetInputStream() + if input == nil { + return "" + } + n := input.Size() + if b.GetStart() < n && b.GetStop() < n { + return input.GetTextFromInterval(NewInterval(b.GetStart(), b.GetStop())) + } + return "" +} + +func (b *BaseToken) SetText(text string) { + b.text = text +} + +func (b *BaseToken) GetTokenIndex() int { + return b.tokenIndex +} + +func (b *BaseToken) SetTokenIndex(v int) { + b.tokenIndex = v +} + +func (b *BaseToken) GetTokenSource() TokenSource { + return b.source.tokenSource +} + +func (b *BaseToken) GetInputStream() CharStream { + return b.source.charStream +} + +func (b *BaseToken) String() string { + txt := b.GetText() + if txt != "" { + txt = strings.Replace(txt, "\n", "\\n", -1) + txt = strings.Replace(txt, "\r", "\\r", -1) + txt = strings.Replace(txt, "\t", "\\t", -1) + } else { + txt = "" + } + + var ch string + if b.GetChannel() > 0 { + ch = ",channel=" + strconv.Itoa(b.GetChannel()) + } else { + ch = "" + } + + return "[@" + strconv.Itoa(b.GetTokenIndex()) + "," + strconv.Itoa(b.GetStart()) + ":" + strconv.Itoa(b.GetStop()) + "='" + + txt + "',<" + strconv.Itoa(b.GetTokenType()) + ">" + + ch + "," + strconv.Itoa(b.GetLine()) + ":" + strconv.Itoa(b.GetColumn()) + "]" +} + +type CommonToken struct { + BaseToken +} + +func NewCommonToken(source *TokenSourceCharStreamPair, tokenType, channel, start, stop int) *CommonToken { + + t := &CommonToken{ + BaseToken: BaseToken{ + source: source, + tokenType: tokenType, + channel: channel, + start: start, + stop: stop, + tokenIndex: -1, + }, + } + + if t.source.tokenSource != nil { + t.line = source.tokenSource.GetLine() + t.column = source.tokenSource.GetCharPositionInLine() + } else { + t.column = -1 + } + return t +} + +// An empty {@link Pair} which is used as the default value of +// {@link //source} for tokens that do not have a source. + +//CommonToken.EMPTY_SOURCE = [ nil, nil ] + +// Constructs a New{@link CommonToken} as a copy of another {@link Token}. +// +//

+// If {@code oldToken} is also a {@link CommonToken} instance, the newly +// constructed token will share a reference to the {@link //text} field and +// the {@link Pair} stored in {@link //source}. Otherwise, {@link //text} will +// be assigned the result of calling {@link //GetText}, and {@link //source} +// will be constructed from the result of {@link Token//GetTokenSource} and +// {@link Token//GetInputStream}.

+// +// @param oldToken The token to copy. +func (c *CommonToken) clone() *CommonToken { + t := NewCommonToken(c.source, c.tokenType, c.channel, c.start, c.stop) + t.tokenIndex = c.GetTokenIndex() + t.line = c.GetLine() + t.column = c.GetColumn() + t.text = c.GetText() + return t +} diff --git a/prutalgen/internal/antlr/token_source.go b/prutalgen/internal/antlr/token_source.go new file mode 100644 index 0000000..a3f36ea --- /dev/null +++ b/prutalgen/internal/antlr/token_source.go @@ -0,0 +1,17 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +type TokenSource interface { + NextToken() Token + Skip() + More() + GetLine() int + GetCharPositionInLine() int + GetInputStream() CharStream + GetSourceName() string + setTokenFactory(factory TokenFactory) + GetTokenFactory() TokenFactory +} diff --git a/prutalgen/internal/antlr/token_stream.go b/prutalgen/internal/antlr/token_stream.go new file mode 100644 index 0000000..bf4ff66 --- /dev/null +++ b/prutalgen/internal/antlr/token_stream.go @@ -0,0 +1,21 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +type TokenStream interface { + IntStream + + LT(k int) Token + Reset() + + Get(index int) Token + GetTokenSource() TokenSource + SetTokenSource(TokenSource) + + GetAllText() string + GetTextFromInterval(Interval) string + GetTextFromRuleContext(RuleContext) string + GetTextFromTokens(Token, Token) string +} diff --git a/prutalgen/internal/antlr/tokenstream_rewriter.go b/prutalgen/internal/antlr/tokenstream_rewriter.go new file mode 100644 index 0000000..ccf59b4 --- /dev/null +++ b/prutalgen/internal/antlr/tokenstream_rewriter.go @@ -0,0 +1,662 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "bytes" + "fmt" +) + +// +// Useful for rewriting out a buffered input token stream after doing some +// augmentation or other manipulations on it. + +//

+// You can insert stuff, replace, and delete chunks. Note that the operations +// are done lazily--only if you convert the buffer to a {@link String} with +// {@link TokenStream#getText()}. This is very efficient because you are not +// moving data around all the time. As the buffer of tokens is converted to +// strings, the {@link #getText()} method(s) scan the input token stream and +// check to see if there is an operation at the current index. If so, the +// operation is done and then normal {@link String} rendering continues on the +// buffer. This is like having multiple Turing machine instruction streams +// (programs) operating on a single input tape. :)

+//

+ +// This rewriter makes no modifications to the token stream. It does not ask the +// stream to fill itself up nor does it advance the input cursor. The token +// stream {@link TokenStream#index()} will return the same value before and +// after any {@link #getText()} call.

+ +//

+// The rewriter only works on tokens that you have in the buffer and ignores the +// current input cursor. If you are buffering tokens on-demand, calling +// {@link #getText()} halfway through the input will only do rewrites for those +// tokens in the first half of the file.

+ +//

+// Since the operations are done lazily at {@link #getText}-time, operations do +// not screw up the token index values. That is, an insert operation at token +// index {@code i} does not change the index values for tokens +// {@code i}+1..n-1.

+ +//

+// Because operations never actually alter the buffer, you may always get the +// original token stream back without undoing anything. Since the instructions +// are queued up, you can easily simulate transactions and roll back any changes +// if there is an error just by removing instructions. For example,

+ +//
+// CharStream input = new ANTLRFileStream("input");
+// TLexer lex = new TLexer(input);
+// CommonTokenStream tokens = new CommonTokenStream(lex);
+// T parser = new T(tokens);
+// TokenStreamRewriter rewriter = new TokenStreamRewriter(tokens);
+// parser.startRule();
+// 
+ +//

+// Then in the rules, you can execute (assuming rewriter is visible):

+ +//
+// Token t,u;
+// ...
+// rewriter.insertAfter(t, "text to put after t");}
+// rewriter.insertAfter(u, "text after u");}
+// System.out.println(rewriter.getText());
+// 
+ +//

+// You can also have multiple "instruction streams" and get multiple rewrites +// from a single pass over the input. Just name the instruction streams and use +// that name again when printing the buffer. This could be useful for generating +// a C file and also its header file--all from the same buffer:

+ +//
+// rewriter.insertAfter("pass1", t, "text to put after t");}
+// rewriter.insertAfter("pass2", u, "text after u");}
+// System.out.println(rewriter.getText("pass1"));
+// System.out.println(rewriter.getText("pass2"));
+// 
+ +//

+// If you don't use named rewrite streams, a "default" stream is used as the +// first example shows.

+ +const ( + DefaultProgramName = "default" + ProgramInitSize = 100 + MinTokenIndex = 0 +) + +// Define the rewrite operation hierarchy + +type RewriteOperation interface { + + // Execute the rewrite operation by possibly adding to the buffer. + // Return the index of the next token to operate on. + Execute(buffer *bytes.Buffer) int + String() string + GetInstructionIndex() int + GetIndex() int + GetText() string + GetOpName() string + GetTokens() TokenStream + SetInstructionIndex(val int) + SetIndex(int) + SetText(string) + SetOpName(string) + SetTokens(TokenStream) +} + +type BaseRewriteOperation struct { + //Current index of rewrites list + instructionIndex int + //Token buffer index + index int + //Substitution text + text string + //Actual operation name + opName string + //Pointer to token steam + tokens TokenStream +} + +func (op *BaseRewriteOperation) GetInstructionIndex() int { + return op.instructionIndex +} + +func (op *BaseRewriteOperation) GetIndex() int { + return op.index +} + +func (op *BaseRewriteOperation) GetText() string { + return op.text +} + +func (op *BaseRewriteOperation) GetOpName() string { + return op.opName +} + +func (op *BaseRewriteOperation) GetTokens() TokenStream { + return op.tokens +} + +func (op *BaseRewriteOperation) SetInstructionIndex(val int) { + op.instructionIndex = val +} + +func (op *BaseRewriteOperation) SetIndex(val int) { + op.index = val +} + +func (op *BaseRewriteOperation) SetText(val string) { + op.text = val +} + +func (op *BaseRewriteOperation) SetOpName(val string) { + op.opName = val +} + +func (op *BaseRewriteOperation) SetTokens(val TokenStream) { + op.tokens = val +} + +func (op *BaseRewriteOperation) Execute(_ *bytes.Buffer) int { + return op.index +} + +func (op *BaseRewriteOperation) String() string { + return fmt.Sprintf("<%s@%d:\"%s\">", + op.opName, + op.tokens.Get(op.GetIndex()), + op.text, + ) + +} + +type InsertBeforeOp struct { + BaseRewriteOperation +} + +func NewInsertBeforeOp(index int, text string, stream TokenStream) *InsertBeforeOp { + return &InsertBeforeOp{BaseRewriteOperation: BaseRewriteOperation{ + index: index, + text: text, + opName: "InsertBeforeOp", + tokens: stream, + }} +} + +func (op *InsertBeforeOp) Execute(buffer *bytes.Buffer) int { + buffer.WriteString(op.text) + if op.tokens.Get(op.index).GetTokenType() != TokenEOF { + buffer.WriteString(op.tokens.Get(op.index).GetText()) + } + return op.index + 1 +} + +func (op *InsertBeforeOp) String() string { + return op.BaseRewriteOperation.String() +} + +// InsertAfterOp distinguishes between insert after/before to do the "insert after" instructions +// first and then the "insert before" instructions at same index. Implementation +// of "insert after" is "insert before index+1". +type InsertAfterOp struct { + BaseRewriteOperation +} + +func NewInsertAfterOp(index int, text string, stream TokenStream) *InsertAfterOp { + return &InsertAfterOp{ + BaseRewriteOperation: BaseRewriteOperation{ + index: index + 1, + text: text, + tokens: stream, + }, + } +} + +func (op *InsertAfterOp) Execute(buffer *bytes.Buffer) int { + buffer.WriteString(op.text) + if op.tokens.Get(op.index).GetTokenType() != TokenEOF { + buffer.WriteString(op.tokens.Get(op.index).GetText()) + } + return op.index + 1 +} + +func (op *InsertAfterOp) String() string { + return op.BaseRewriteOperation.String() +} + +// ReplaceOp tries to replace range from x..y with (y-x)+1 ReplaceOp +// instructions. +type ReplaceOp struct { + BaseRewriteOperation + LastIndex int +} + +func NewReplaceOp(from, to int, text string, stream TokenStream) *ReplaceOp { + return &ReplaceOp{ + BaseRewriteOperation: BaseRewriteOperation{ + index: from, + text: text, + opName: "ReplaceOp", + tokens: stream, + }, + LastIndex: to, + } +} + +func (op *ReplaceOp) Execute(buffer *bytes.Buffer) int { + if op.text != "" { + buffer.WriteString(op.text) + } + return op.LastIndex + 1 +} + +func (op *ReplaceOp) String() string { + if op.text == "" { + return fmt.Sprintf("", + op.tokens.Get(op.index), op.tokens.Get(op.LastIndex)) + } + return fmt.Sprintf("", + op.tokens.Get(op.index), op.tokens.Get(op.LastIndex), op.text) +} + +type TokenStreamRewriter struct { + //Our source stream + tokens TokenStream + // You may have multiple, named streams of rewrite operations. + // I'm calling these things "programs." + // Maps String (name) → rewrite (List) + programs map[string][]RewriteOperation + lastRewriteTokenIndexes map[string]int +} + +func NewTokenStreamRewriter(tokens TokenStream) *TokenStreamRewriter { + return &TokenStreamRewriter{ + tokens: tokens, + programs: map[string][]RewriteOperation{ + DefaultProgramName: make([]RewriteOperation, 0, ProgramInitSize), + }, + lastRewriteTokenIndexes: map[string]int{}, + } +} + +func (tsr *TokenStreamRewriter) GetTokenStream() TokenStream { + return tsr.tokens +} + +// Rollback the instruction stream for a program so that +// the indicated instruction (via instructionIndex) is no +// longer in the stream. UNTESTED! +func (tsr *TokenStreamRewriter) Rollback(programName string, instructionIndex int) { + is, ok := tsr.programs[programName] + if ok { + tsr.programs[programName] = is[MinTokenIndex:instructionIndex] + } +} + +func (tsr *TokenStreamRewriter) RollbackDefault(instructionIndex int) { + tsr.Rollback(DefaultProgramName, instructionIndex) +} + +// DeleteProgram Reset the program so that no instructions exist +func (tsr *TokenStreamRewriter) DeleteProgram(programName string) { + tsr.Rollback(programName, MinTokenIndex) //TODO: double test on that cause lower bound is not included +} + +func (tsr *TokenStreamRewriter) DeleteProgramDefault() { + tsr.DeleteProgram(DefaultProgramName) +} + +func (tsr *TokenStreamRewriter) InsertAfter(programName string, index int, text string) { + // to insert after, just insert before next index (even if past end) + var op RewriteOperation = NewInsertAfterOp(index, text, tsr.tokens) + rewrites := tsr.GetProgram(programName) + op.SetInstructionIndex(len(rewrites)) + tsr.AddToProgram(programName, op) +} + +func (tsr *TokenStreamRewriter) InsertAfterDefault(index int, text string) { + tsr.InsertAfter(DefaultProgramName, index, text) +} + +func (tsr *TokenStreamRewriter) InsertAfterToken(programName string, token Token, text string) { + tsr.InsertAfter(programName, token.GetTokenIndex(), text) +} + +func (tsr *TokenStreamRewriter) InsertBefore(programName string, index int, text string) { + var op RewriteOperation = NewInsertBeforeOp(index, text, tsr.tokens) + rewrites := tsr.GetProgram(programName) + op.SetInstructionIndex(len(rewrites)) + tsr.AddToProgram(programName, op) +} + +func (tsr *TokenStreamRewriter) InsertBeforeDefault(index int, text string) { + tsr.InsertBefore(DefaultProgramName, index, text) +} + +func (tsr *TokenStreamRewriter) InsertBeforeToken(programName string, token Token, text string) { + tsr.InsertBefore(programName, token.GetTokenIndex(), text) +} + +func (tsr *TokenStreamRewriter) Replace(programName string, from, to int, text string) { + if from > to || from < 0 || to < 0 || to >= tsr.tokens.Size() { + panic(fmt.Sprintf("replace: range invalid: %d..%d(size=%d)", + from, to, tsr.tokens.Size())) + } + var op RewriteOperation = NewReplaceOp(from, to, text, tsr.tokens) + rewrites := tsr.GetProgram(programName) + op.SetInstructionIndex(len(rewrites)) + tsr.AddToProgram(programName, op) +} + +func (tsr *TokenStreamRewriter) ReplaceDefault(from, to int, text string) { + tsr.Replace(DefaultProgramName, from, to, text) +} + +func (tsr *TokenStreamRewriter) ReplaceDefaultPos(index int, text string) { + tsr.ReplaceDefault(index, index, text) +} + +func (tsr *TokenStreamRewriter) ReplaceToken(programName string, from, to Token, text string) { + tsr.Replace(programName, from.GetTokenIndex(), to.GetTokenIndex(), text) +} + +func (tsr *TokenStreamRewriter) ReplaceTokenDefault(from, to Token, text string) { + tsr.ReplaceToken(DefaultProgramName, from, to, text) +} + +func (tsr *TokenStreamRewriter) ReplaceTokenDefaultPos(index Token, text string) { + tsr.ReplaceTokenDefault(index, index, text) +} + +func (tsr *TokenStreamRewriter) Delete(programName string, from, to int) { + tsr.Replace(programName, from, to, "") +} + +func (tsr *TokenStreamRewriter) DeleteDefault(from, to int) { + tsr.Delete(DefaultProgramName, from, to) +} + +func (tsr *TokenStreamRewriter) DeleteDefaultPos(index int) { + tsr.DeleteDefault(index, index) +} + +func (tsr *TokenStreamRewriter) DeleteToken(programName string, from, to Token) { + tsr.ReplaceToken(programName, from, to, "") +} + +func (tsr *TokenStreamRewriter) DeleteTokenDefault(from, to Token) { + tsr.DeleteToken(DefaultProgramName, from, to) +} + +func (tsr *TokenStreamRewriter) GetLastRewriteTokenIndex(programName string) int { + i, ok := tsr.lastRewriteTokenIndexes[programName] + if !ok { + return -1 + } + return i +} + +func (tsr *TokenStreamRewriter) GetLastRewriteTokenIndexDefault() int { + return tsr.GetLastRewriteTokenIndex(DefaultProgramName) +} + +func (tsr *TokenStreamRewriter) SetLastRewriteTokenIndex(programName string, i int) { + tsr.lastRewriteTokenIndexes[programName] = i +} + +func (tsr *TokenStreamRewriter) InitializeProgram(name string) []RewriteOperation { + is := make([]RewriteOperation, 0, ProgramInitSize) + tsr.programs[name] = is + return is +} + +func (tsr *TokenStreamRewriter) AddToProgram(name string, op RewriteOperation) { + is := tsr.GetProgram(name) + is = append(is, op) + tsr.programs[name] = is +} + +func (tsr *TokenStreamRewriter) GetProgram(name string) []RewriteOperation { + is, ok := tsr.programs[name] + if !ok { + is = tsr.InitializeProgram(name) + } + return is +} + +// GetTextDefault returns the text from the original tokens altered per the +// instructions given to this rewriter. +func (tsr *TokenStreamRewriter) GetTextDefault() string { + return tsr.GetText( + DefaultProgramName, + NewInterval(0, tsr.tokens.Size()-1)) +} + +// GetText returns the text from the original tokens altered per the +// instructions given to this rewriter. +func (tsr *TokenStreamRewriter) GetText(programName string, interval Interval) string { + rewrites := tsr.programs[programName] + start := interval.Start + stop := interval.Stop + // ensure start/end are in range + stop = min(stop, tsr.tokens.Size()-1) + start = max(start, 0) + if len(rewrites) == 0 { + return tsr.tokens.GetTextFromInterval(interval) // no instructions to execute + } + buf := bytes.Buffer{} + // First, optimize instruction stream + indexToOp := reduceToSingleOperationPerIndex(rewrites) + // Walk buffer, executing instructions and emitting tokens + for i := start; i <= stop && i < tsr.tokens.Size(); { + op := indexToOp[i] + delete(indexToOp, i) // remove so any left have index size-1 + t := tsr.tokens.Get(i) + if op == nil { + // no operation at that index, just dump token + if t.GetTokenType() != TokenEOF { + buf.WriteString(t.GetText()) + } + i++ // move to next token + } else { + i = op.Execute(&buf) // execute operation and skip + } + } + // include stuff after end if it's last index in buffer + // So, if they did an insertAfter(lastValidIndex, "foo"), include + // foo if end==lastValidIndex. + if stop == tsr.tokens.Size()-1 { + // Scan any remaining operations after last token + // should be included (they will be inserts). + for _, op := range indexToOp { + if op.GetIndex() >= tsr.tokens.Size()-1 { + buf.WriteString(op.GetText()) + } + } + } + return buf.String() +} + +// reduceToSingleOperationPerIndex combines operations and report invalid operations (like +// overlapping replaces that are not completed nested). Inserts to +// same index need to be combined etc... +// +// Here are the cases: +// +// I.i.u I.j.v leave alone, non-overlapping +// I.i.u I.i.v combine: Iivu +// +// R.i-j.u R.x-y.v | i-j in x-y delete first R +// R.i-j.u R.i-j.v delete first R +// R.i-j.u R.x-y.v | x-y in i-j ERROR +// R.i-j.u R.x-y.v | boundaries overlap ERROR +// +// Delete special case of replace (text==null): +// D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right) +// +// I.i.u R.x-y.v | i in (x+1)-y delete I (since insert before +// we're not deleting i) +// I.i.u R.x-y.v | i not in (x+1)-y leave alone, non-overlapping +// R.x-y.v I.i.u | i in x-y ERROR +// R.x-y.v I.x.u R.x-y.uv (combine, delete I) +// R.x-y.v I.i.u | i not in x-y leave alone, non-overlapping +// +// I.i.u = insert u before op @ index i +// R.x-y.u = replace x-y indexed tokens with u +// +// First we need to examine replaces. For any replace op: +// +// 1. wipe out any insertions before op within that range. +// 2. Drop any replace op before that is contained completely within +// that range. +// 3. Throw exception upon boundary overlap with any previous replace. +// +// Then we can deal with inserts: +// +// 1. for any inserts to same index, combine even if not adjacent. +// 2. for any prior replace with same left boundary, combine this +// insert with replace and delete this 'replace'. +// 3. throw exception if index in same range as previous replace +// +// Don't actually delete; make op null in list. Easier to walk list. +// Later we can throw as we add to index → op map. +// +// Note that I.2 R.2-2 will wipe out I.2 even though, technically, the +// inserted stuff would be before the 'replace' range. But, if you +// add tokens in front of a method body '{' and then delete the method +// body, I think the stuff before the '{' you added should disappear too. +// +// The func returns a map from token index to operation. +func reduceToSingleOperationPerIndex(rewrites []RewriteOperation) map[int]RewriteOperation { + // WALK REPLACES + for i := 0; i < len(rewrites); i++ { + op := rewrites[i] + if op == nil { + continue + } + rop, ok := op.(*ReplaceOp) + if !ok { + continue + } + // Wipe prior inserts within range + for j := 0; j < i && j < len(rewrites); j++ { + if iop, ok := rewrites[j].(*InsertBeforeOp); ok { + if iop.index == rop.index { + // E.g., insert before 2, delete 2..2; update replace + // text to include insert before, kill insert + rewrites[iop.instructionIndex] = nil + if rop.text != "" { + rop.text = iop.text + rop.text + } else { + rop.text = iop.text + } + } else if iop.index > rop.index && iop.index <= rop.LastIndex { + // delete insert as it's a no-op. + rewrites[iop.instructionIndex] = nil + } + } + } + // Drop any prior replaces contained within + for j := 0; j < i && j < len(rewrites); j++ { + if prevop, ok := rewrites[j].(*ReplaceOp); ok { + if prevop.index >= rop.index && prevop.LastIndex <= rop.LastIndex { + // delete replace as it's a no-op. + rewrites[prevop.instructionIndex] = nil + continue + } + // throw exception unless disjoint or identical + disjoint := prevop.LastIndex < rop.index || prevop.index > rop.LastIndex + // Delete special case of replace (text==null): + // D.i-j.u D.x-y.v | boundaries overlap combine to max(min)..max(right) + if prevop.text == "" && rop.text == "" && !disjoint { + rewrites[prevop.instructionIndex] = nil + rop.index = min(prevop.index, rop.index) + rop.LastIndex = max(prevop.LastIndex, rop.LastIndex) + } else if !disjoint { + panic("replace op boundaries of " + rop.String() + " overlap with previous " + prevop.String()) + } + } + } + } + // WALK INSERTS + for i := 0; i < len(rewrites); i++ { + op := rewrites[i] + if op == nil { + continue + } + //hack to replicate inheritance in composition + _, iok := rewrites[i].(*InsertBeforeOp) + _, aok := rewrites[i].(*InsertAfterOp) + if !iok && !aok { + continue + } + iop := rewrites[i] + // combine current insert with prior if any at same index + // deviating a bit from TokenStreamRewriter.java - hard to incorporate inheritance logic + for j := 0; j < i && j < len(rewrites); j++ { + if nextIop, ok := rewrites[j].(*InsertAfterOp); ok { + if nextIop.index == iop.GetIndex() { + iop.SetText(nextIop.text + iop.GetText()) + rewrites[j] = nil + } + } + if prevIop, ok := rewrites[j].(*InsertBeforeOp); ok { + if prevIop.index == iop.GetIndex() { + iop.SetText(iop.GetText() + prevIop.text) + rewrites[prevIop.instructionIndex] = nil + } + } + } + // look for replaces where iop.index is in range; error + for j := 0; j < i && j < len(rewrites); j++ { + if rop, ok := rewrites[j].(*ReplaceOp); ok { + if iop.GetIndex() == rop.index { + rop.text = iop.GetText() + rop.text + rewrites[i] = nil + continue + } + if iop.GetIndex() >= rop.index && iop.GetIndex() <= rop.LastIndex { + panic("insert op " + iop.String() + " within boundaries of previous " + rop.String()) + } + } + } + } + m := map[int]RewriteOperation{} + for i := 0; i < len(rewrites); i++ { + op := rewrites[i] + if op == nil { + continue + } + if _, ok := m[op.GetIndex()]; ok { + panic("should only be one op per index") + } + m[op.GetIndex()] = op + } + return m +} + +/* + Quick fixing Go lack of overloads +*/ + +func max(a, b int) int { + if a > b { + return a + } else { + return b + } +} +func min(a, b int) int { + if a < b { + return a + } else { + return b + } +} diff --git a/prutalgen/internal/antlr/trace_listener.go b/prutalgen/internal/antlr/trace_listener.go new file mode 100644 index 0000000..7b663bf --- /dev/null +++ b/prutalgen/internal/antlr/trace_listener.go @@ -0,0 +1,32 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import "fmt" + +type TraceListener struct { + parser *BaseParser +} + +func NewTraceListener(parser *BaseParser) *TraceListener { + tl := new(TraceListener) + tl.parser = parser + return tl +} + +func (t *TraceListener) VisitErrorNode(_ ErrorNode) { +} + +func (t *TraceListener) EnterEveryRule(ctx ParserRuleContext) { + fmt.Println("enter " + t.parser.GetRuleNames()[ctx.GetRuleIndex()] + ", LT(1)=" + t.parser.input.LT(1).GetText()) +} + +func (t *TraceListener) VisitTerminal(node TerminalNode) { + fmt.Println("consume " + fmt.Sprint(node.GetSymbol()) + " rule " + t.parser.GetRuleNames()[t.parser.ctx.GetRuleIndex()]) +} + +func (t *TraceListener) ExitEveryRule(ctx ParserRuleContext) { + fmt.Println("exit " + t.parser.GetRuleNames()[ctx.GetRuleIndex()] + ", LT(1)=" + t.parser.input.LT(1).GetText()) +} diff --git a/prutalgen/internal/antlr/transition.go b/prutalgen/internal/antlr/transition.go new file mode 100644 index 0000000..313b0fc --- /dev/null +++ b/prutalgen/internal/antlr/transition.go @@ -0,0 +1,439 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "fmt" + "strconv" + "strings" +) + +// atom, set, epsilon, action, predicate, rule transitions. +// +//

This is a one way link. It emanates from a state (usually via a list of +// transitions) and has a target state.

+// +//

Since we never have to change the ATN transitions once we construct it, +// the states. We'll use the term Edge for the DFA to distinguish them from +// ATN transitions.

+ +type Transition interface { + getTarget() ATNState + setTarget(ATNState) + getIsEpsilon() bool + getLabel() *IntervalSet + getSerializationType() int + Matches(int, int, int) bool +} + +type BaseTransition struct { + target ATNState + isEpsilon bool + label int + intervalSet *IntervalSet + serializationType int +} + +func NewBaseTransition(target ATNState) *BaseTransition { + + if target == nil { + panic("target cannot be nil.") + } + + t := new(BaseTransition) + + t.target = target + // Are we epsilon, action, sempred? + t.isEpsilon = false + t.intervalSet = nil + + return t +} + +func (t *BaseTransition) getTarget() ATNState { + return t.target +} + +func (t *BaseTransition) setTarget(s ATNState) { + t.target = s +} + +func (t *BaseTransition) getIsEpsilon() bool { + return t.isEpsilon +} + +func (t *BaseTransition) getLabel() *IntervalSet { + return t.intervalSet +} + +func (t *BaseTransition) getSerializationType() int { + return t.serializationType +} + +func (t *BaseTransition) Matches(_, _, _ int) bool { + panic("Not implemented") +} + +const ( + TransitionEPSILON = 1 + TransitionRANGE = 2 + TransitionRULE = 3 + TransitionPREDICATE = 4 // e.g., {isType(input.LT(1))}? + TransitionATOM = 5 + TransitionACTION = 6 + TransitionSET = 7 // ~(A|B) or ~atom, wildcard, which convert to next 2 + TransitionNOTSET = 8 + TransitionWILDCARD = 9 + TransitionPRECEDENCE = 10 +) + +//goland:noinspection GoUnusedGlobalVariable +var TransitionserializationNames = []string{ + "INVALID", + "EPSILON", + "RANGE", + "RULE", + "PREDICATE", + "ATOM", + "ACTION", + "SET", + "NOT_SET", + "WILDCARD", + "PRECEDENCE", +} + +//var TransitionserializationTypes struct { +// EpsilonTransition int +// RangeTransition int +// RuleTransition int +// PredicateTransition int +// AtomTransition int +// ActionTransition int +// SetTransition int +// NotSetTransition int +// WildcardTransition int +// PrecedencePredicateTransition int +//}{ +// TransitionEPSILON, +// TransitionRANGE, +// TransitionRULE, +// TransitionPREDICATE, +// TransitionATOM, +// TransitionACTION, +// TransitionSET, +// TransitionNOTSET, +// TransitionWILDCARD, +// TransitionPRECEDENCE +//} + +// AtomTransition +// TODO: make all transitions sets? no, should remove set edges +type AtomTransition struct { + BaseTransition +} + +func NewAtomTransition(target ATNState, intervalSet int) *AtomTransition { + t := &AtomTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionATOM, + label: intervalSet, + isEpsilon: false, + }, + } + t.intervalSet = t.makeLabel() + + return t +} + +func (t *AtomTransition) makeLabel() *IntervalSet { + s := NewIntervalSet() + s.addOne(t.label) + return s +} + +func (t *AtomTransition) Matches(symbol, _, _ int) bool { + return t.label == symbol +} + +func (t *AtomTransition) String() string { + return strconv.Itoa(t.label) +} + +type RuleTransition struct { + BaseTransition + followState ATNState + ruleIndex, precedence int +} + +func NewRuleTransition(ruleStart ATNState, ruleIndex, precedence int, followState ATNState) *RuleTransition { + return &RuleTransition{ + BaseTransition: BaseTransition{ + target: ruleStart, + isEpsilon: true, + serializationType: TransitionRULE, + }, + ruleIndex: ruleIndex, + precedence: precedence, + followState: followState, + } +} + +func (t *RuleTransition) Matches(_, _, _ int) bool { + return false +} + +type EpsilonTransition struct { + BaseTransition + outermostPrecedenceReturn int +} + +func NewEpsilonTransition(target ATNState, outermostPrecedenceReturn int) *EpsilonTransition { + return &EpsilonTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionEPSILON, + isEpsilon: true, + }, + outermostPrecedenceReturn: outermostPrecedenceReturn, + } +} + +func (t *EpsilonTransition) Matches(_, _, _ int) bool { + return false +} + +func (t *EpsilonTransition) String() string { + return "epsilon" +} + +type RangeTransition struct { + BaseTransition + start, stop int +} + +func NewRangeTransition(target ATNState, start, stop int) *RangeTransition { + t := &RangeTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionRANGE, + isEpsilon: false, + }, + start: start, + stop: stop, + } + t.intervalSet = t.makeLabel() + return t +} + +func (t *RangeTransition) makeLabel() *IntervalSet { + s := NewIntervalSet() + s.addRange(t.start, t.stop) + return s +} + +func (t *RangeTransition) Matches(symbol, _, _ int) bool { + return symbol >= t.start && symbol <= t.stop +} + +func (t *RangeTransition) String() string { + var sb strings.Builder + sb.WriteByte('\'') + sb.WriteRune(rune(t.start)) + sb.WriteString("'..'") + sb.WriteRune(rune(t.stop)) + sb.WriteByte('\'') + return sb.String() +} + +type AbstractPredicateTransition interface { + Transition + IAbstractPredicateTransitionFoo() +} + +type BaseAbstractPredicateTransition struct { + BaseTransition +} + +func NewBasePredicateTransition(target ATNState) *BaseAbstractPredicateTransition { + return &BaseAbstractPredicateTransition{ + BaseTransition: BaseTransition{ + target: target, + }, + } +} + +func (a *BaseAbstractPredicateTransition) IAbstractPredicateTransitionFoo() {} + +type PredicateTransition struct { + BaseAbstractPredicateTransition + isCtxDependent bool + ruleIndex, predIndex int +} + +func NewPredicateTransition(target ATNState, ruleIndex, predIndex int, isCtxDependent bool) *PredicateTransition { + return &PredicateTransition{ + BaseAbstractPredicateTransition: BaseAbstractPredicateTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionPREDICATE, + isEpsilon: true, + }, + }, + isCtxDependent: isCtxDependent, + ruleIndex: ruleIndex, + predIndex: predIndex, + } +} + +func (t *PredicateTransition) Matches(_, _, _ int) bool { + return false +} + +func (t *PredicateTransition) getPredicate() *Predicate { + return NewPredicate(t.ruleIndex, t.predIndex, t.isCtxDependent) +} + +func (t *PredicateTransition) String() string { + return "pred_" + strconv.Itoa(t.ruleIndex) + ":" + strconv.Itoa(t.predIndex) +} + +type ActionTransition struct { + BaseTransition + isCtxDependent bool + ruleIndex, actionIndex, predIndex int +} + +func NewActionTransition(target ATNState, ruleIndex, actionIndex int, isCtxDependent bool) *ActionTransition { + return &ActionTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionACTION, + isEpsilon: true, + }, + isCtxDependent: isCtxDependent, + ruleIndex: ruleIndex, + actionIndex: actionIndex, + } +} + +func (t *ActionTransition) Matches(_, _, _ int) bool { + return false +} + +func (t *ActionTransition) String() string { + return "action_" + strconv.Itoa(t.ruleIndex) + ":" + strconv.Itoa(t.actionIndex) +} + +type SetTransition struct { + BaseTransition +} + +func NewSetTransition(target ATNState, set *IntervalSet) *SetTransition { + t := &SetTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionSET, + }, + } + + if set != nil { + t.intervalSet = set + } else { + t.intervalSet = NewIntervalSet() + t.intervalSet.addOne(TokenInvalidType) + } + return t +} + +func (t *SetTransition) Matches(symbol, _, _ int) bool { + return t.intervalSet.contains(symbol) +} + +func (t *SetTransition) String() string { + return t.intervalSet.String() +} + +type NotSetTransition struct { + SetTransition +} + +func NewNotSetTransition(target ATNState, set *IntervalSet) *NotSetTransition { + t := &NotSetTransition{ + SetTransition: SetTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionNOTSET, + }, + }, + } + if set != nil { + t.intervalSet = set + } else { + t.intervalSet = NewIntervalSet() + t.intervalSet.addOne(TokenInvalidType) + } + + return t +} + +func (t *NotSetTransition) Matches(symbol, minVocabSymbol, maxVocabSymbol int) bool { + return symbol >= minVocabSymbol && symbol <= maxVocabSymbol && !t.intervalSet.contains(symbol) +} + +func (t *NotSetTransition) String() string { + return "~" + t.intervalSet.String() +} + +type WildcardTransition struct { + BaseTransition +} + +func NewWildcardTransition(target ATNState) *WildcardTransition { + return &WildcardTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionWILDCARD, + }, + } +} + +func (t *WildcardTransition) Matches(symbol, minVocabSymbol, maxVocabSymbol int) bool { + return symbol >= minVocabSymbol && symbol <= maxVocabSymbol +} + +func (t *WildcardTransition) String() string { + return "." +} + +type PrecedencePredicateTransition struct { + BaseAbstractPredicateTransition + precedence int +} + +func NewPrecedencePredicateTransition(target ATNState, precedence int) *PrecedencePredicateTransition { + return &PrecedencePredicateTransition{ + BaseAbstractPredicateTransition: BaseAbstractPredicateTransition{ + BaseTransition: BaseTransition{ + target: target, + serializationType: TransitionPRECEDENCE, + isEpsilon: true, + }, + }, + precedence: precedence, + } +} + +func (t *PrecedencePredicateTransition) Matches(_, _, _ int) bool { + return false +} + +func (t *PrecedencePredicateTransition) getPredicate() *PrecedencePredicate { + return NewPrecedencePredicate(t.precedence) +} + +func (t *PrecedencePredicateTransition) String() string { + return fmt.Sprint(t.precedence) + " >= _p" +} diff --git a/prutalgen/internal/antlr/tree.go b/prutalgen/internal/antlr/tree.go new file mode 100644 index 0000000..c288420 --- /dev/null +++ b/prutalgen/internal/antlr/tree.go @@ -0,0 +1,304 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +// The basic notion of a tree has a parent, a payload, and a list of children. +// It is the most abstract interface for all the trees used by ANTLR. +/// + +var TreeInvalidInterval = NewInterval(-1, -2) + +type Tree interface { + GetParent() Tree + SetParent(Tree) + GetPayload() interface{} + GetChild(i int) Tree + GetChildCount() int + GetChildren() []Tree +} + +type SyntaxTree interface { + Tree + GetSourceInterval() Interval +} + +type ParseTree interface { + SyntaxTree + Accept(Visitor ParseTreeVisitor) interface{} + GetText() string + ToStringTree([]string, Recognizer) string +} + +type RuleNode interface { + ParseTree + GetRuleContext() RuleContext +} + +type TerminalNode interface { + ParseTree + GetSymbol() Token +} + +type ErrorNode interface { + TerminalNode + + errorNode() +} + +type ParseTreeVisitor interface { + Visit(tree ParseTree) interface{} + VisitChildren(node RuleNode) interface{} + VisitTerminal(node TerminalNode) interface{} + VisitErrorNode(node ErrorNode) interface{} +} + +type BaseParseTreeVisitor struct{} + +var _ ParseTreeVisitor = &BaseParseTreeVisitor{} + +func (v *BaseParseTreeVisitor) Visit(tree ParseTree) interface{} { return tree.Accept(v) } +func (v *BaseParseTreeVisitor) VisitChildren(_ RuleNode) interface{} { return nil } +func (v *BaseParseTreeVisitor) VisitTerminal(_ TerminalNode) interface{} { return nil } +func (v *BaseParseTreeVisitor) VisitErrorNode(_ ErrorNode) interface{} { return nil } + +// TODO: Implement this? +//func (this ParseTreeVisitor) Visit(ctx) { +// if (Utils.isArray(ctx)) { +// self := this +// return ctx.map(function(child) { return VisitAtom(self, child)}) +// } else { +// return VisitAtom(this, ctx) +// } +//} +// +//func VisitAtom(Visitor, ctx) { +// if (ctx.parser == nil) { //is terminal +// return +// } +// +// name := ctx.parser.ruleNames[ctx.ruleIndex] +// funcName := "Visit" + Utils.titleCase(name) +// +// return Visitor[funcName](ctx) +//} + +type ParseTreeListener interface { + VisitTerminal(node TerminalNode) + VisitErrorNode(node ErrorNode) + EnterEveryRule(ctx ParserRuleContext) + ExitEveryRule(ctx ParserRuleContext) +} + +type BaseParseTreeListener struct{} + +var _ ParseTreeListener = &BaseParseTreeListener{} + +func (l *BaseParseTreeListener) VisitTerminal(_ TerminalNode) {} +func (l *BaseParseTreeListener) VisitErrorNode(_ ErrorNode) {} +func (l *BaseParseTreeListener) EnterEveryRule(_ ParserRuleContext) {} +func (l *BaseParseTreeListener) ExitEveryRule(_ ParserRuleContext) {} + +type TerminalNodeImpl struct { + parentCtx RuleContext + symbol Token +} + +var _ TerminalNode = &TerminalNodeImpl{} + +func NewTerminalNodeImpl(symbol Token) *TerminalNodeImpl { + tn := new(TerminalNodeImpl) + + tn.parentCtx = nil + tn.symbol = symbol + + return tn +} + +func (t *TerminalNodeImpl) GetChild(_ int) Tree { + return nil +} + +func (t *TerminalNodeImpl) GetChildren() []Tree { + return nil +} + +func (t *TerminalNodeImpl) SetChildren(_ []Tree) { + panic("Cannot set children on terminal node") +} + +func (t *TerminalNodeImpl) GetSymbol() Token { + return t.symbol +} + +func (t *TerminalNodeImpl) GetParent() Tree { + return t.parentCtx +} + +func (t *TerminalNodeImpl) SetParent(tree Tree) { + t.parentCtx = tree.(RuleContext) +} + +func (t *TerminalNodeImpl) GetPayload() interface{} { + return t.symbol +} + +func (t *TerminalNodeImpl) GetSourceInterval() Interval { + if t.symbol == nil { + return TreeInvalidInterval + } + tokenIndex := t.symbol.GetTokenIndex() + return NewInterval(tokenIndex, tokenIndex) +} + +func (t *TerminalNodeImpl) GetChildCount() int { + return 0 +} + +func (t *TerminalNodeImpl) Accept(v ParseTreeVisitor) interface{} { + return v.VisitTerminal(t) +} + +func (t *TerminalNodeImpl) GetText() string { + return t.symbol.GetText() +} + +func (t *TerminalNodeImpl) String() string { + if t.symbol.GetTokenType() == TokenEOF { + return "" + } + + return t.symbol.GetText() +} + +func (t *TerminalNodeImpl) ToStringTree(_ []string, _ Recognizer) string { + return t.String() +} + +// Represents a token that was consumed during reSynchronization +// rather than during a valid Match operation. For example, +// we will create this kind of a node during single token insertion +// and deletion as well as during "consume until error recovery set" +// upon no viable alternative exceptions. + +type ErrorNodeImpl struct { + *TerminalNodeImpl +} + +var _ ErrorNode = &ErrorNodeImpl{} + +func NewErrorNodeImpl(token Token) *ErrorNodeImpl { + en := new(ErrorNodeImpl) + en.TerminalNodeImpl = NewTerminalNodeImpl(token) + return en +} + +func (e *ErrorNodeImpl) errorNode() {} + +func (e *ErrorNodeImpl) Accept(v ParseTreeVisitor) interface{} { + return v.VisitErrorNode(e) +} + +type ParseTreeWalker struct { +} + +func NewParseTreeWalker() *ParseTreeWalker { + return new(ParseTreeWalker) +} + +// Walk performs a walk on the given parse tree starting at the root and going down recursively +// with depth-first search. On each node, [EnterRule] is called before +// recursively walking down into child nodes, then [ExitRule] is called after the recursive call to wind up. +func (p *ParseTreeWalker) Walk(listener ParseTreeListener, t Tree) { + switch tt := t.(type) { + case ErrorNode: + listener.VisitErrorNode(tt) + case TerminalNode: + listener.VisitTerminal(tt) + default: + p.EnterRule(listener, t.(RuleNode)) + for i := 0; i < t.GetChildCount(); i++ { + child := t.GetChild(i) + p.Walk(listener, child) + } + p.ExitRule(listener, t.(RuleNode)) + } +} + +// EnterRule enters a grammar rule by first triggering the generic event [ParseTreeListener].[EnterEveryRule] +// then by triggering the event specific to the given parse tree node +func (p *ParseTreeWalker) EnterRule(listener ParseTreeListener, r RuleNode) { + ctx := r.GetRuleContext().(ParserRuleContext) + listener.EnterEveryRule(ctx) + ctx.EnterRule(listener) +} + +// ExitRule exits a grammar rule by first triggering the event specific to the given parse tree node +// then by triggering the generic event [ParseTreeListener].ExitEveryRule +func (p *ParseTreeWalker) ExitRule(listener ParseTreeListener, r RuleNode) { + ctx := r.GetRuleContext().(ParserRuleContext) + ctx.ExitRule(listener) + listener.ExitEveryRule(ctx) +} + +//goland:noinspection GoUnusedGlobalVariable +var ParseTreeWalkerDefault = NewParseTreeWalker() + +type IterativeParseTreeWalker struct { + *ParseTreeWalker +} + +//goland:noinspection GoUnusedExportedFunction +func NewIterativeParseTreeWalker() *IterativeParseTreeWalker { + return new(IterativeParseTreeWalker) +} + +func (i *IterativeParseTreeWalker) Walk(listener ParseTreeListener, t Tree) { + var stack []Tree + var indexStack []int + currentNode := t + currentIndex := 0 + + for currentNode != nil { + // pre-order visit + switch tt := currentNode.(type) { + case ErrorNode: + listener.VisitErrorNode(tt) + case TerminalNode: + listener.VisitTerminal(tt) + default: + i.EnterRule(listener, currentNode.(RuleNode)) + } + // Move down to first child, if exists + if currentNode.GetChildCount() > 0 { + stack = append(stack, currentNode) + indexStack = append(indexStack, currentIndex) + currentIndex = 0 + currentNode = currentNode.GetChild(0) + continue + } + + for { + // post-order visit + if ruleNode, ok := currentNode.(RuleNode); ok { + i.ExitRule(listener, ruleNode) + } + // No parent, so no siblings + if len(stack) == 0 { + currentNode = nil + currentIndex = 0 + break + } + // Move to next sibling if possible + currentIndex++ + if stack[len(stack)-1].GetChildCount() > currentIndex { + currentNode = stack[len(stack)-1].GetChild(currentIndex) + break + } + // No next, sibling, so move up + currentNode, stack = stack[len(stack)-1], stack[:len(stack)-1] + currentIndex, indexStack = indexStack[len(indexStack)-1], indexStack[:len(indexStack)-1] + } + } +} diff --git a/prutalgen/internal/antlr/trees.go b/prutalgen/internal/antlr/trees.go new file mode 100644 index 0000000..f44c05d --- /dev/null +++ b/prutalgen/internal/antlr/trees.go @@ -0,0 +1,142 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import "fmt" + +/** A set of utility routines useful for all kinds of ANTLR trees. */ + +// TreesStringTree prints out a whole tree in LISP form. [getNodeText] is used on the +// node payloads to get the text for the nodes. Detects parse trees and extracts data appropriately. +func TreesStringTree(tree Tree, ruleNames []string, recog Recognizer) string { + + if recog != nil { + ruleNames = recog.GetRuleNames() + } + + s := TreesGetNodeText(tree, ruleNames, nil) + + s = EscapeWhitespace(s, false) + c := tree.GetChildCount() + if c == 0 { + return s + } + res := "(" + s + " " + if c > 0 { + s = TreesStringTree(tree.GetChild(0), ruleNames, nil) + res += s + } + for i := 1; i < c; i++ { + s = TreesStringTree(tree.GetChild(i), ruleNames, nil) + res += " " + s + } + res += ")" + return res +} + +func TreesGetNodeText(t Tree, ruleNames []string, recog Parser) string { + if recog != nil { + ruleNames = recog.GetRuleNames() + } + + if ruleNames != nil { + switch t2 := t.(type) { + case RuleNode: + t3 := t2.GetRuleContext() + altNumber := t3.GetAltNumber() + + if altNumber != ATNInvalidAltNumber { + return fmt.Sprintf("%s:%d", ruleNames[t3.GetRuleIndex()], altNumber) + } + return ruleNames[t3.GetRuleIndex()] + case ErrorNode: + return fmt.Sprint(t2) + case TerminalNode: + if t2.GetSymbol() != nil { + return t2.GetSymbol().GetText() + } + } + } + + // no recognition for rule names + payload := t.GetPayload() + if p2, ok := payload.(Token); ok { + return p2.GetText() + } + + return fmt.Sprint(t.GetPayload()) +} + +// TreesGetChildren returns am ordered list of all children of this node +// +//goland:noinspection GoUnusedExportedFunction +func TreesGetChildren(t Tree) []Tree { + list := make([]Tree, 0) + for i := 0; i < t.GetChildCount(); i++ { + list = append(list, t.GetChild(i)) + } + return list +} + +// TreesgetAncestors returns a list of all ancestors of this node. The first node of list is the root +// and the last node is the parent of this node. +// +//goland:noinspection GoUnusedExportedFunction +func TreesgetAncestors(t Tree) []Tree { + ancestors := make([]Tree, 0) + t = t.GetParent() + for t != nil { + f := []Tree{t} + ancestors = append(f, ancestors...) + t = t.GetParent() + } + return ancestors +} + +//goland:noinspection GoUnusedExportedFunction +func TreesFindAllTokenNodes(t ParseTree, ttype int) []ParseTree { + return TreesfindAllNodes(t, ttype, true) +} + +//goland:noinspection GoUnusedExportedFunction +func TreesfindAllRuleNodes(t ParseTree, ruleIndex int) []ParseTree { + return TreesfindAllNodes(t, ruleIndex, false) +} + +func TreesfindAllNodes(t ParseTree, index int, findTokens bool) []ParseTree { + nodes := make([]ParseTree, 0) + treesFindAllNodes(t, index, findTokens, &nodes) + return nodes +} + +func treesFindAllNodes(t ParseTree, index int, findTokens bool, nodes *[]ParseTree) { + // check this node (the root) first + + t2, ok := t.(TerminalNode) + t3, ok2 := t.(ParserRuleContext) + + if findTokens && ok { + if t2.GetSymbol().GetTokenType() == index { + *nodes = append(*nodes, t2) + } + } else if !findTokens && ok2 { + if t3.GetRuleIndex() == index { + *nodes = append(*nodes, t3) + } + } + // check children + for i := 0; i < t.GetChildCount(); i++ { + treesFindAllNodes(t.GetChild(i).(ParseTree), index, findTokens, nodes) + } +} + +//goland:noinspection GoUnusedExportedFunction +func TreesDescendants(t ParseTree) []ParseTree { + nodes := []ParseTree{t} + for i := 0; i < t.GetChildCount(); i++ { + nodes = append(nodes, TreesDescendants(t.GetChild(i).(ParseTree))...) + } + return nodes +} diff --git a/prutalgen/internal/antlr/utils.go b/prutalgen/internal/antlr/utils.go new file mode 100644 index 0000000..36a37f2 --- /dev/null +++ b/prutalgen/internal/antlr/utils.go @@ -0,0 +1,381 @@ +// Copyright (c) 2012-2022 The ANTLR Project. All rights reserved. +// Use of this file is governed by the BSD 3-clause license that +// can be found in the LICENSE.txt file in the project root. + +package antlr + +import ( + "bytes" + "errors" + "fmt" + "math/bits" + "os" + "strconv" + "strings" + "syscall" +) + +func intMin(a, b int) int { + if a < b { + return a + } + return b +} + +func intMax(a, b int) int { + if a > b { + return a + } + return b +} + +// A simple integer stack + +type IntStack []int + +var ErrEmptyStack = errors.New("stack is empty") + +func (s *IntStack) Pop() (int, error) { + l := len(*s) - 1 + if l < 0 { + return 0, ErrEmptyStack + } + v := (*s)[l] + *s = (*s)[0:l] + return v, nil +} + +func (s *IntStack) Push(e int) { + *s = append(*s, e) +} + +const bitsPerWord = 64 + +func indexForBit(bit int) int { + return bit / bitsPerWord +} + +//goland:noinspection GoUnusedExportedFunction,GoUnusedFunction +func wordForBit(data []uint64, bit int) uint64 { + idx := indexForBit(bit) + if idx >= len(data) { + return 0 + } + return data[idx] +} + +func maskForBit(bit int) uint64 { + return uint64(1) << (bit % bitsPerWord) +} + +func wordsNeeded(bit int) int { + return indexForBit(bit) + 1 +} + +type BitSet struct { + data []uint64 +} + +// NewBitSet creates a new bitwise set +// TODO: See if we can replace with the standard library's BitSet +func NewBitSet() *BitSet { + return &BitSet{} +} + +func (b *BitSet) add(value int) { + idx := indexForBit(value) + if idx >= len(b.data) { + size := wordsNeeded(value) + data := make([]uint64, size) + copy(data, b.data) + b.data = data + } + b.data[idx] |= maskForBit(value) +} + +func (b *BitSet) clear(index int) { + idx := indexForBit(index) + if idx >= len(b.data) { + return + } + b.data[idx] &= ^maskForBit(index) +} + +func (b *BitSet) or(set *BitSet) { + // Get min size necessary to represent the bits in both sets. + bLen := b.minLen() + setLen := set.minLen() + maxLen := intMax(bLen, setLen) + if maxLen > len(b.data) { + // Increase the size of len(b.data) to represent the bits in both sets. + data := make([]uint64, maxLen) + copy(data, b.data) + b.data = data + } + // len(b.data) is at least setLen. + for i := 0; i < setLen; i++ { + b.data[i] |= set.data[i] + } +} + +func (b *BitSet) remove(value int) { + b.clear(value) +} + +func (b *BitSet) contains(value int) bool { + idx := indexForBit(value) + if idx >= len(b.data) { + return false + } + return (b.data[idx] & maskForBit(value)) != 0 +} + +func (b *BitSet) minValue() int { + for i, v := range b.data { + if v == 0 { + continue + } + return i*bitsPerWord + bits.TrailingZeros64(v) + } + return 2147483647 +} + +func (b *BitSet) equals(other interface{}) bool { + otherBitSet, ok := other.(*BitSet) + if !ok { + return false + } + + if b == otherBitSet { + return true + } + + // We only compare set bits, so we cannot rely on the two slices having the same size. Its + // possible for two BitSets to have different slice lengths but the same set bits. So we only + // compare the relevant words and ignore the trailing zeros. + bLen := b.minLen() + otherLen := otherBitSet.minLen() + + if bLen != otherLen { + return false + } + + for i := 0; i < bLen; i++ { + if b.data[i] != otherBitSet.data[i] { + return false + } + } + + return true +} + +func (b *BitSet) minLen() int { + for i := len(b.data); i > 0; i-- { + if b.data[i-1] != 0 { + return i + } + } + return 0 +} + +func (b *BitSet) length() int { + cnt := 0 + for _, val := range b.data { + cnt += bits.OnesCount64(val) + } + return cnt +} + +func (b *BitSet) String() string { + vals := make([]string, 0, b.length()) + + for i, v := range b.data { + for v != 0 { + n := bits.TrailingZeros64(v) + vals = append(vals, strconv.Itoa(i*bitsPerWord+n)) + v &= ^(uint64(1) << n) + } + } + + return "{" + strings.Join(vals, ", ") + "}" +} + +type AltDict struct { + data map[string]interface{} +} + +func NewAltDict() *AltDict { + d := new(AltDict) + d.data = make(map[string]interface{}) + return d +} + +func (a *AltDict) Get(key string) interface{} { + key = "k-" + key + return a.data[key] +} + +func (a *AltDict) put(key string, value interface{}) { + key = "k-" + key + a.data[key] = value +} + +func (a *AltDict) values() []interface{} { + vs := make([]interface{}, len(a.data)) + i := 0 + for _, v := range a.data { + vs[i] = v + i++ + } + return vs +} + +func EscapeWhitespace(s string, escapeSpaces bool) string { + + s = strings.Replace(s, "\t", "\\t", -1) + s = strings.Replace(s, "\n", "\\n", -1) + s = strings.Replace(s, "\r", "\\r", -1) + if escapeSpaces { + s = strings.Replace(s, " ", "\u00B7", -1) + } + return s +} + +//goland:noinspection GoUnusedExportedFunction +func TerminalNodeToStringArray(sa []TerminalNode) []string { + st := make([]string, len(sa)) + + for i, s := range sa { + st[i] = fmt.Sprintf("%v", s) + } + + return st +} + +//goland:noinspection GoUnusedExportedFunction +func PrintArrayJavaStyle(sa []string) string { + var buffer bytes.Buffer + + buffer.WriteString("[") + + for i, s := range sa { + buffer.WriteString(s) + if i != len(sa)-1 { + buffer.WriteString(", ") + } + } + + buffer.WriteString("]") + + return buffer.String() +} + +// murmur hash +func murmurInit(seed int) int { + return seed +} + +func murmurUpdate(h int, value int) int { + const c1 uint32 = 0xCC9E2D51 + const c2 uint32 = 0x1B873593 + const r1 uint32 = 15 + const r2 uint32 = 13 + const m uint32 = 5 + const n uint32 = 0xE6546B64 + + k := uint32(value) + k *= c1 + k = (k << r1) | (k >> (32 - r1)) + k *= c2 + + hash := uint32(h) ^ k + hash = (hash << r2) | (hash >> (32 - r2)) + hash = hash*m + n + return int(hash) +} + +func murmurFinish(h int, numberOfWords int) int { + var hash = uint32(h) + hash ^= uint32(numberOfWords) << 2 + hash ^= hash >> 16 + hash *= 0x85ebca6b + hash ^= hash >> 13 + hash *= 0xc2b2ae35 + hash ^= hash >> 16 + + return int(hash) +} + +func isDirectory(dir string) (bool, error) { + fileInfo, err := os.Stat(dir) + if err != nil { + switch { + case errors.Is(err, syscall.ENOENT): + // The given directory does not exist, so we will try to create it + // + err = os.MkdirAll(dir, 0755) + if err != nil { + return false, err + } + + return true, nil + case err != nil: + return false, err + default: + } + } + return fileInfo.IsDir(), err +} + +// intSlicesEqual returns true if the two slices of ints are equal, and is a little +// faster than slices.Equal. +func intSlicesEqual(s1, s2 []int) bool { + if s1 == nil && s2 == nil { + return true + } + if s1 == nil || s2 == nil { + return false + } + if len(s1) == 0 && len(s2) == 0 { + return true + } + + if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) { + return false + } + // If the slices are using the same memory, then they are the same slice + if &s1[0] == &s2[0] { + return true + } + for i, v := range s1 { + if v != s2[i] { + return false + } + } + return true +} + +func pcSliceEqual(s1, s2 []*PredictionContext) bool { + if s1 == nil && s2 == nil { + return true + } + if s1 == nil || s2 == nil { + return false + } + if len(s1) == 0 && len(s2) == 0 { + return true + } + if len(s1) == 0 || len(s2) == 0 || len(s1) != len(s2) { + return false + } + // If the slices are using the same memory, then they are the same slice + if &s1[0] == &s2[0] { + return true + } + for i, v := range s1 { + if !v.Equals(s2[i]) { + return false + } + } + return true +} diff --git a/prutalgen/internal/parser/protobuf_base_listener.go b/prutalgen/internal/parser/protobuf_base_listener.go new file mode 100644 index 0000000..a0a13cc --- /dev/null +++ b/prutalgen/internal/parser/protobuf_base_listener.go @@ -0,0 +1,340 @@ +// Code generated from ./Protobuf.g4 by ANTLR 4.13.2. DO NOT EDIT. + +package parser // Protobuf + +import "github.com/cloudwego/prutal/prutalgen/internal/antlr" + +// BaseProtobufListener is a complete listener for a parse tree produced by ProtobufParser. +type BaseProtobufListener struct{} + +var _ ProtobufListener = &BaseProtobufListener{} + +// VisitTerminal is called when a terminal node is visited. +func (s *BaseProtobufListener) VisitTerminal(node antlr.TerminalNode) {} + +// VisitErrorNode is called when an error node is visited. +func (s *BaseProtobufListener) VisitErrorNode(node antlr.ErrorNode) {} + +// EnterEveryRule is called when any rule is entered. +func (s *BaseProtobufListener) EnterEveryRule(ctx antlr.ParserRuleContext) {} + +// ExitEveryRule is called when any rule is exited. +func (s *BaseProtobufListener) ExitEveryRule(ctx antlr.ParserRuleContext) {} + +// EnterProto is called when production proto is entered. +func (s *BaseProtobufListener) EnterProto(ctx *ProtoContext) {} + +// ExitProto is called when production proto is exited. +func (s *BaseProtobufListener) ExitProto(ctx *ProtoContext) {} + +// EnterEdition is called when production edition is entered. +func (s *BaseProtobufListener) EnterEdition(ctx *EditionContext) {} + +// ExitEdition is called when production edition is exited. +func (s *BaseProtobufListener) ExitEdition(ctx *EditionContext) {} + +// EnterImportStatement is called when production importStatement is entered. +func (s *BaseProtobufListener) EnterImportStatement(ctx *ImportStatementContext) {} + +// ExitImportStatement is called when production importStatement is exited. +func (s *BaseProtobufListener) ExitImportStatement(ctx *ImportStatementContext) {} + +// EnterPackageStatement is called when production packageStatement is entered. +func (s *BaseProtobufListener) EnterPackageStatement(ctx *PackageStatementContext) {} + +// ExitPackageStatement is called when production packageStatement is exited. +func (s *BaseProtobufListener) ExitPackageStatement(ctx *PackageStatementContext) {} + +// EnterOptionStatement is called when production optionStatement is entered. +func (s *BaseProtobufListener) EnterOptionStatement(ctx *OptionStatementContext) {} + +// ExitOptionStatement is called when production optionStatement is exited. +func (s *BaseProtobufListener) ExitOptionStatement(ctx *OptionStatementContext) {} + +// EnterOptionName is called when production optionName is entered. +func (s *BaseProtobufListener) EnterOptionName(ctx *OptionNameContext) {} + +// ExitOptionName is called when production optionName is exited. +func (s *BaseProtobufListener) ExitOptionName(ctx *OptionNameContext) {} + +// EnterFieldLabel is called when production fieldLabel is entered. +func (s *BaseProtobufListener) EnterFieldLabel(ctx *FieldLabelContext) {} + +// ExitFieldLabel is called when production fieldLabel is exited. +func (s *BaseProtobufListener) ExitFieldLabel(ctx *FieldLabelContext) {} + +// EnterField is called when production field is entered. +func (s *BaseProtobufListener) EnterField(ctx *FieldContext) {} + +// ExitField is called when production field is exited. +func (s *BaseProtobufListener) ExitField(ctx *FieldContext) {} + +// EnterFieldOptions is called when production fieldOptions is entered. +func (s *BaseProtobufListener) EnterFieldOptions(ctx *FieldOptionsContext) {} + +// ExitFieldOptions is called when production fieldOptions is exited. +func (s *BaseProtobufListener) ExitFieldOptions(ctx *FieldOptionsContext) {} + +// EnterFieldOption is called when production fieldOption is entered. +func (s *BaseProtobufListener) EnterFieldOption(ctx *FieldOptionContext) {} + +// ExitFieldOption is called when production fieldOption is exited. +func (s *BaseProtobufListener) ExitFieldOption(ctx *FieldOptionContext) {} + +// EnterFieldNumber is called when production fieldNumber is entered. +func (s *BaseProtobufListener) EnterFieldNumber(ctx *FieldNumberContext) {} + +// ExitFieldNumber is called when production fieldNumber is exited. +func (s *BaseProtobufListener) ExitFieldNumber(ctx *FieldNumberContext) {} + +// EnterOneof is called when production oneof is entered. +func (s *BaseProtobufListener) EnterOneof(ctx *OneofContext) {} + +// ExitOneof is called when production oneof is exited. +func (s *BaseProtobufListener) ExitOneof(ctx *OneofContext) {} + +// EnterOneofField is called when production oneofField is entered. +func (s *BaseProtobufListener) EnterOneofField(ctx *OneofFieldContext) {} + +// ExitOneofField is called when production oneofField is exited. +func (s *BaseProtobufListener) ExitOneofField(ctx *OneofFieldContext) {} + +// EnterMapField is called when production mapField is entered. +func (s *BaseProtobufListener) EnterMapField(ctx *MapFieldContext) {} + +// ExitMapField is called when production mapField is exited. +func (s *BaseProtobufListener) ExitMapField(ctx *MapFieldContext) {} + +// EnterKeyType is called when production keyType is entered. +func (s *BaseProtobufListener) EnterKeyType(ctx *KeyTypeContext) {} + +// ExitKeyType is called when production keyType is exited. +func (s *BaseProtobufListener) ExitKeyType(ctx *KeyTypeContext) {} + +// EnterFieldType is called when production fieldType is entered. +func (s *BaseProtobufListener) EnterFieldType(ctx *FieldTypeContext) {} + +// ExitFieldType is called when production fieldType is exited. +func (s *BaseProtobufListener) ExitFieldType(ctx *FieldTypeContext) {} + +// EnterReserved is called when production reserved is entered. +func (s *BaseProtobufListener) EnterReserved(ctx *ReservedContext) {} + +// ExitReserved is called when production reserved is exited. +func (s *BaseProtobufListener) ExitReserved(ctx *ReservedContext) {} + +// EnterExtensions is called when production extensions is entered. +func (s *BaseProtobufListener) EnterExtensions(ctx *ExtensionsContext) {} + +// ExitExtensions is called when production extensions is exited. +func (s *BaseProtobufListener) ExitExtensions(ctx *ExtensionsContext) {} + +// EnterRanges is called when production ranges is entered. +func (s *BaseProtobufListener) EnterRanges(ctx *RangesContext) {} + +// ExitRanges is called when production ranges is exited. +func (s *BaseProtobufListener) ExitRanges(ctx *RangesContext) {} + +// EnterOneRange is called when production oneRange is entered. +func (s *BaseProtobufListener) EnterOneRange(ctx *OneRangeContext) {} + +// ExitOneRange is called when production oneRange is exited. +func (s *BaseProtobufListener) ExitOneRange(ctx *OneRangeContext) {} + +// EnterReservedFieldNames is called when production reservedFieldNames is entered. +func (s *BaseProtobufListener) EnterReservedFieldNames(ctx *ReservedFieldNamesContext) {} + +// ExitReservedFieldNames is called when production reservedFieldNames is exited. +func (s *BaseProtobufListener) ExitReservedFieldNames(ctx *ReservedFieldNamesContext) {} + +// EnterTopLevelDef is called when production topLevelDef is entered. +func (s *BaseProtobufListener) EnterTopLevelDef(ctx *TopLevelDefContext) {} + +// ExitTopLevelDef is called when production topLevelDef is exited. +func (s *BaseProtobufListener) ExitTopLevelDef(ctx *TopLevelDefContext) {} + +// EnterEnumDef is called when production enumDef is entered. +func (s *BaseProtobufListener) EnterEnumDef(ctx *EnumDefContext) {} + +// ExitEnumDef is called when production enumDef is exited. +func (s *BaseProtobufListener) ExitEnumDef(ctx *EnumDefContext) {} + +// EnterEnumBody is called when production enumBody is entered. +func (s *BaseProtobufListener) EnterEnumBody(ctx *EnumBodyContext) {} + +// ExitEnumBody is called when production enumBody is exited. +func (s *BaseProtobufListener) ExitEnumBody(ctx *EnumBodyContext) {} + +// EnterEnumElement is called when production enumElement is entered. +func (s *BaseProtobufListener) EnterEnumElement(ctx *EnumElementContext) {} + +// ExitEnumElement is called when production enumElement is exited. +func (s *BaseProtobufListener) ExitEnumElement(ctx *EnumElementContext) {} + +// EnterEnumField is called when production enumField is entered. +func (s *BaseProtobufListener) EnterEnumField(ctx *EnumFieldContext) {} + +// ExitEnumField is called when production enumField is exited. +func (s *BaseProtobufListener) ExitEnumField(ctx *EnumFieldContext) {} + +// EnterEnumValueOptions is called when production enumValueOptions is entered. +func (s *BaseProtobufListener) EnterEnumValueOptions(ctx *EnumValueOptionsContext) {} + +// ExitEnumValueOptions is called when production enumValueOptions is exited. +func (s *BaseProtobufListener) ExitEnumValueOptions(ctx *EnumValueOptionsContext) {} + +// EnterEnumValueOption is called when production enumValueOption is entered. +func (s *BaseProtobufListener) EnterEnumValueOption(ctx *EnumValueOptionContext) {} + +// ExitEnumValueOption is called when production enumValueOption is exited. +func (s *BaseProtobufListener) ExitEnumValueOption(ctx *EnumValueOptionContext) {} + +// EnterMessageDef is called when production messageDef is entered. +func (s *BaseProtobufListener) EnterMessageDef(ctx *MessageDefContext) {} + +// ExitMessageDef is called when production messageDef is exited. +func (s *BaseProtobufListener) ExitMessageDef(ctx *MessageDefContext) {} + +// EnterMessageBody is called when production messageBody is entered. +func (s *BaseProtobufListener) EnterMessageBody(ctx *MessageBodyContext) {} + +// ExitMessageBody is called when production messageBody is exited. +func (s *BaseProtobufListener) ExitMessageBody(ctx *MessageBodyContext) {} + +// EnterMessageElement is called when production messageElement is entered. +func (s *BaseProtobufListener) EnterMessageElement(ctx *MessageElementContext) {} + +// ExitMessageElement is called when production messageElement is exited. +func (s *BaseProtobufListener) ExitMessageElement(ctx *MessageElementContext) {} + +// EnterExtendDef is called when production extendDef is entered. +func (s *BaseProtobufListener) EnterExtendDef(ctx *ExtendDefContext) {} + +// ExitExtendDef is called when production extendDef is exited. +func (s *BaseProtobufListener) ExitExtendDef(ctx *ExtendDefContext) {} + +// EnterServiceDef is called when production serviceDef is entered. +func (s *BaseProtobufListener) EnterServiceDef(ctx *ServiceDefContext) {} + +// ExitServiceDef is called when production serviceDef is exited. +func (s *BaseProtobufListener) ExitServiceDef(ctx *ServiceDefContext) {} + +// EnterServiceElement is called when production serviceElement is entered. +func (s *BaseProtobufListener) EnterServiceElement(ctx *ServiceElementContext) {} + +// ExitServiceElement is called when production serviceElement is exited. +func (s *BaseProtobufListener) ExitServiceElement(ctx *ServiceElementContext) {} + +// EnterRpc is called when production rpc is entered. +func (s *BaseProtobufListener) EnterRpc(ctx *RpcContext) {} + +// ExitRpc is called when production rpc is exited. +func (s *BaseProtobufListener) ExitRpc(ctx *RpcContext) {} + +// EnterConstant is called when production constant is entered. +func (s *BaseProtobufListener) EnterConstant(ctx *ConstantContext) {} + +// ExitConstant is called when production constant is exited. +func (s *BaseProtobufListener) ExitConstant(ctx *ConstantContext) {} + +// EnterBlockLit is called when production blockLit is entered. +func (s *BaseProtobufListener) EnterBlockLit(ctx *BlockLitContext) {} + +// ExitBlockLit is called when production blockLit is exited. +func (s *BaseProtobufListener) ExitBlockLit(ctx *BlockLitContext) {} + +// EnterEmptyStatement is called when production emptyStatement is entered. +func (s *BaseProtobufListener) EnterEmptyStatement(ctx *EmptyStatementContext) {} + +// ExitEmptyStatement is called when production emptyStatement is exited. +func (s *BaseProtobufListener) ExitEmptyStatement(ctx *EmptyStatementContext) {} + +// EnterIdent is called when production ident is entered. +func (s *BaseProtobufListener) EnterIdent(ctx *IdentContext) {} + +// ExitIdent is called when production ident is exited. +func (s *BaseProtobufListener) ExitIdent(ctx *IdentContext) {} + +// EnterFullIdent is called when production fullIdent is entered. +func (s *BaseProtobufListener) EnterFullIdent(ctx *FullIdentContext) {} + +// ExitFullIdent is called when production fullIdent is exited. +func (s *BaseProtobufListener) ExitFullIdent(ctx *FullIdentContext) {} + +// EnterMessageName is called when production messageName is entered. +func (s *BaseProtobufListener) EnterMessageName(ctx *MessageNameContext) {} + +// ExitMessageName is called when production messageName is exited. +func (s *BaseProtobufListener) ExitMessageName(ctx *MessageNameContext) {} + +// EnterEnumName is called when production enumName is entered. +func (s *BaseProtobufListener) EnterEnumName(ctx *EnumNameContext) {} + +// ExitEnumName is called when production enumName is exited. +func (s *BaseProtobufListener) ExitEnumName(ctx *EnumNameContext) {} + +// EnterFieldName is called when production fieldName is entered. +func (s *BaseProtobufListener) EnterFieldName(ctx *FieldNameContext) {} + +// ExitFieldName is called when production fieldName is exited. +func (s *BaseProtobufListener) ExitFieldName(ctx *FieldNameContext) {} + +// EnterOneofName is called when production oneofName is entered. +func (s *BaseProtobufListener) EnterOneofName(ctx *OneofNameContext) {} + +// ExitOneofName is called when production oneofName is exited. +func (s *BaseProtobufListener) ExitOneofName(ctx *OneofNameContext) {} + +// EnterServiceName is called when production serviceName is entered. +func (s *BaseProtobufListener) EnterServiceName(ctx *ServiceNameContext) {} + +// ExitServiceName is called when production serviceName is exited. +func (s *BaseProtobufListener) ExitServiceName(ctx *ServiceNameContext) {} + +// EnterRpcName is called when production rpcName is entered. +func (s *BaseProtobufListener) EnterRpcName(ctx *RpcNameContext) {} + +// ExitRpcName is called when production rpcName is exited. +func (s *BaseProtobufListener) ExitRpcName(ctx *RpcNameContext) {} + +// EnterMessageType is called when production messageType is entered. +func (s *BaseProtobufListener) EnterMessageType(ctx *MessageTypeContext) {} + +// ExitMessageType is called when production messageType is exited. +func (s *BaseProtobufListener) ExitMessageType(ctx *MessageTypeContext) {} + +// EnterEnumType is called when production enumType is entered. +func (s *BaseProtobufListener) EnterEnumType(ctx *EnumTypeContext) {} + +// ExitEnumType is called when production enumType is exited. +func (s *BaseProtobufListener) ExitEnumType(ctx *EnumTypeContext) {} + +// EnterIntLit is called when production intLit is entered. +func (s *BaseProtobufListener) EnterIntLit(ctx *IntLitContext) {} + +// ExitIntLit is called when production intLit is exited. +func (s *BaseProtobufListener) ExitIntLit(ctx *IntLitContext) {} + +// EnterStrLit is called when production strLit is entered. +func (s *BaseProtobufListener) EnterStrLit(ctx *StrLitContext) {} + +// ExitStrLit is called when production strLit is exited. +func (s *BaseProtobufListener) ExitStrLit(ctx *StrLitContext) {} + +// EnterBoolLit is called when production boolLit is entered. +func (s *BaseProtobufListener) EnterBoolLit(ctx *BoolLitContext) {} + +// ExitBoolLit is called when production boolLit is exited. +func (s *BaseProtobufListener) ExitBoolLit(ctx *BoolLitContext) {} + +// EnterFloatLit is called when production floatLit is entered. +func (s *BaseProtobufListener) EnterFloatLit(ctx *FloatLitContext) {} + +// ExitFloatLit is called when production floatLit is exited. +func (s *BaseProtobufListener) ExitFloatLit(ctx *FloatLitContext) {} + +// EnterKeywords is called when production keywords is entered. +func (s *BaseProtobufListener) EnterKeywords(ctx *KeywordsContext) {} + +// ExitKeywords is called when production keywords is exited. +func (s *BaseProtobufListener) ExitKeywords(ctx *KeywordsContext) {} diff --git a/prutalgen/internal/parser/protobuf_lexer.go b/prutalgen/internal/parser/protobuf_lexer.go new file mode 100644 index 0000000..bb7c300 --- /dev/null +++ b/prutalgen/internal/parser/protobuf_lexer.go @@ -0,0 +1,448 @@ +// Code generated from ./Protobuf.g4 by ANTLR 4.13.2. DO NOT EDIT. + +package parser + +import ( + "fmt" + "github.com/cloudwego/prutal/prutalgen/internal/antlr" + "sync" + "unicode" +) + +// Suppress unused import error +var _ = fmt.Printf +var _ = sync.Once{} +var _ = unicode.IsLetter + +type ProtobufLexer struct { + *antlr.BaseLexer + channelNames []string + modeNames []string + // TODO: EOF string +} + +var ProtobufLexerLexerStaticData struct { + once sync.Once + serializedATN []int32 + ChannelNames []string + ModeNames []string + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func protobuflexerLexerInit() { + staticData := &ProtobufLexerLexerStaticData + staticData.ChannelNames = []string{ + "DEFAULT_TOKEN_CHANNEL", "HIDDEN", + } + staticData.ModeNames = []string{ + "DEFAULT_MODE", + } + staticData.LiteralNames = []string{ + "", "'syntax'", "'edition'", "'import'", "'weak'", "'public'", "'package'", + "'option'", "'optional'", "'required'", "'repeated'", "'oneof'", "'map'", + "'int32'", "'int64'", "'uint32'", "'uint64'", "'sint32'", "'sint64'", + "'fixed32'", "'fixed64'", "'sfixed32'", "'sfixed64'", "'bool'", "'string'", + "'double'", "'float'", "'bytes'", "'reserved'", "'extensions'", "'to'", + "'max'", "'enum'", "'message'", "'service'", "'extend'", "'rpc'", "'stream'", + "'returns'", "';'", "'='", "'('", "')'", "'['", "']'", "'{'", "'}'", + "'<'", "'>'", "'.'", "','", "':'", "'+'", "'-'", + } + staticData.SymbolicNames = []string{ + "", "SYNTAX", "EDITION", "IMPORT", "WEAK", "PUBLIC", "PACKAGE", "OPTION", + "OPTIONAL", "REQUIRED", "REPEATED", "ONEOF", "MAP", "INT32", "INT64", + "UINT32", "UINT64", "SINT32", "SINT64", "FIXED32", "FIXED64", "SFIXED32", + "SFIXED64", "BOOL", "STRING", "DOUBLE", "FLOAT", "BYTES", "RESERVED", + "EXTENSIONS", "TO", "MAX", "ENUM", "MESSAGE", "SERVICE", "EXTEND", "RPC", + "STREAM", "RETURNS", "SEMI", "EQ", "LP", "RP", "LB", "RB", "LC", "RC", + "LT", "GT", "DOT", "COMMA", "COLON", "PLUS", "MINUS", "STR_LIT_SINGLE", + "BOOL_LIT", "FLOAT_LIT", "INT_LIT", "IDENTIFIER", "WS", "LINE_COMMENT", + "COMMENT", + } + staticData.RuleNames = []string{ + "SYNTAX", "EDITION", "IMPORT", "WEAK", "PUBLIC", "PACKAGE", "OPTION", + "OPTIONAL", "REQUIRED", "REPEATED", "ONEOF", "MAP", "INT32", "INT64", + "UINT32", "UINT64", "SINT32", "SINT64", "FIXED32", "FIXED64", "SFIXED32", + "SFIXED64", "BOOL", "STRING", "DOUBLE", "FLOAT", "BYTES", "RESERVED", + "EXTENSIONS", "TO", "MAX", "ENUM", "MESSAGE", "SERVICE", "EXTEND", "RPC", + "STREAM", "RETURNS", "SEMI", "EQ", "LP", "RP", "LB", "RB", "LC", "RC", + "LT", "GT", "DOT", "COMMA", "COLON", "PLUS", "MINUS", "STR_LIT_SINGLE", + "CHAR_VALUE", "HEX_ESCAPE", "OCT_ESCAPE", "CHAR_ESCAPE", "BOOL_LIT", + "FLOAT_LIT", "EXPONENT", "DECIMALS", "INT_LIT", "DECIMAL_LIT", "OCTAL_LIT", + "HEX_LIT", "IDENTIFIER", "LETTER", "DECIMAL_DIGIT", "OCTAL_DIGIT", "HEX_DIGIT", + "WS", "LINE_COMMENT", "COMMENT", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 0, 61, 604, 6, -1, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, + 4, 7, 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, + 10, 7, 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, + 7, 15, 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, + 20, 2, 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, + 2, 26, 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, + 31, 7, 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, + 7, 36, 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, + 41, 2, 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, + 2, 47, 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, + 52, 7, 52, 2, 53, 7, 53, 2, 54, 7, 54, 2, 55, 7, 55, 2, 56, 7, 56, 2, 57, + 7, 57, 2, 58, 7, 58, 2, 59, 7, 59, 2, 60, 7, 60, 2, 61, 7, 61, 2, 62, 7, + 62, 2, 63, 7, 63, 2, 64, 7, 64, 2, 65, 7, 65, 2, 66, 7, 66, 2, 67, 7, 67, + 2, 68, 7, 68, 2, 69, 7, 69, 2, 70, 7, 70, 2, 71, 7, 71, 2, 72, 7, 72, 2, + 73, 7, 73, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, + 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, + 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 6, 1, 6, 1, 6, 1, 6, + 1, 6, 1, 6, 1, 6, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, + 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 8, 1, 9, 1, 9, 1, 9, + 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 10, 1, 10, 1, 10, 1, 10, 1, 10, + 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, + 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, 14, 1, 14, + 1, 14, 1, 14, 1, 14, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, + 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, + 1, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, 18, 1, + 18, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 19, 1, 20, 1, 20, + 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 20, 1, 21, 1, 21, 1, 21, 1, + 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 22, + 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, + 24, 1, 24, 1, 24, 1, 24, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 25, 1, 26, + 1, 26, 1, 26, 1, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, 27, 1, + 27, 1, 27, 1, 27, 1, 27, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, 1, 28, + 1, 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, + 30, 1, 31, 1, 31, 1, 31, 1, 31, 1, 31, 1, 32, 1, 32, 1, 32, 1, 32, 1, 32, + 1, 32, 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, 33, 1, + 33, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 35, 1, 35, 1, 35, + 1, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 1, 37, 1, 37, 1, + 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 37, 1, 38, 1, 38, 1, 39, 1, 39, 1, 40, + 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, + 45, 1, 46, 1, 46, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 1, 49, 1, 50, 1, 50, + 1, 51, 1, 51, 1, 52, 1, 52, 1, 53, 1, 53, 5, 53, 447, 8, 53, 10, 53, 12, + 53, 450, 9, 53, 1, 53, 1, 53, 1, 53, 5, 53, 455, 8, 53, 10, 53, 12, 53, + 458, 9, 53, 1, 53, 3, 53, 461, 8, 53, 1, 54, 1, 54, 1, 54, 1, 54, 3, 54, + 467, 8, 54, 1, 55, 1, 55, 1, 55, 1, 55, 1, 55, 1, 56, 1, 56, 1, 56, 1, + 56, 1, 56, 1, 57, 1, 57, 1, 57, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, 1, 58, + 1, 58, 1, 58, 1, 58, 3, 58, 491, 8, 58, 1, 59, 1, 59, 1, 59, 3, 59, 496, + 8, 59, 1, 59, 3, 59, 499, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, + 59, 3, 59, 507, 8, 59, 3, 59, 509, 8, 59, 1, 59, 1, 59, 1, 59, 1, 59, 1, + 59, 1, 59, 3, 59, 517, 8, 59, 1, 60, 1, 60, 1, 60, 3, 60, 522, 8, 60, 1, + 60, 1, 60, 1, 61, 4, 61, 527, 8, 61, 11, 61, 12, 61, 528, 1, 62, 1, 62, + 1, 62, 3, 62, 534, 8, 62, 1, 63, 1, 63, 5, 63, 538, 8, 63, 10, 63, 12, + 63, 541, 9, 63, 1, 64, 1, 64, 5, 64, 545, 8, 64, 10, 64, 12, 64, 548, 9, + 64, 1, 65, 1, 65, 1, 65, 4, 65, 553, 8, 65, 11, 65, 12, 65, 554, 1, 66, + 1, 66, 1, 66, 5, 66, 560, 8, 66, 10, 66, 12, 66, 563, 9, 66, 1, 67, 1, + 67, 1, 68, 1, 68, 1, 69, 1, 69, 1, 70, 1, 70, 1, 71, 4, 71, 574, 8, 71, + 11, 71, 12, 71, 575, 1, 71, 1, 71, 1, 72, 1, 72, 1, 72, 1, 72, 5, 72, 584, + 8, 72, 10, 72, 12, 72, 587, 9, 72, 1, 72, 1, 72, 1, 73, 1, 73, 1, 73, 1, + 73, 5, 73, 595, 8, 73, 10, 73, 12, 73, 598, 9, 73, 1, 73, 1, 73, 1, 73, + 1, 73, 1, 73, 3, 448, 456, 596, 0, 74, 1, 1, 3, 2, 5, 3, 7, 4, 9, 5, 11, + 6, 13, 7, 15, 8, 17, 9, 19, 10, 21, 11, 23, 12, 25, 13, 27, 14, 29, 15, + 31, 16, 33, 17, 35, 18, 37, 19, 39, 20, 41, 21, 43, 22, 45, 23, 47, 24, + 49, 25, 51, 26, 53, 27, 55, 28, 57, 29, 59, 30, 61, 31, 63, 32, 65, 33, + 67, 34, 69, 35, 71, 36, 73, 37, 75, 38, 77, 39, 79, 40, 81, 41, 83, 42, + 85, 43, 87, 44, 89, 45, 91, 46, 93, 47, 95, 48, 97, 49, 99, 50, 101, 51, + 103, 52, 105, 53, 107, 54, 109, 0, 111, 0, 113, 0, 115, 0, 117, 55, 119, + 56, 121, 0, 123, 0, 125, 57, 127, 0, 129, 0, 131, 0, 133, 58, 135, 0, 137, + 0, 139, 0, 141, 0, 143, 59, 145, 60, 147, 61, 1, 0, 11, 3, 0, 0, 0, 10, + 10, 92, 92, 2, 0, 88, 88, 120, 120, 9, 0, 34, 34, 39, 39, 92, 92, 97, 98, + 102, 102, 110, 110, 114, 114, 116, 116, 118, 118, 2, 0, 69, 69, 101, 101, + 1, 0, 49, 57, 3, 0, 65, 90, 95, 95, 97, 122, 1, 0, 48, 57, 1, 0, 48, 55, + 3, 0, 48, 57, 65, 70, 97, 102, 3, 0, 9, 10, 12, 13, 32, 32, 2, 0, 10, 10, + 13, 13, 617, 0, 1, 1, 0, 0, 0, 0, 3, 1, 0, 0, 0, 0, 5, 1, 0, 0, 0, 0, 7, + 1, 0, 0, 0, 0, 9, 1, 0, 0, 0, 0, 11, 1, 0, 0, 0, 0, 13, 1, 0, 0, 0, 0, + 15, 1, 0, 0, 0, 0, 17, 1, 0, 0, 0, 0, 19, 1, 0, 0, 0, 0, 21, 1, 0, 0, 0, + 0, 23, 1, 0, 0, 0, 0, 25, 1, 0, 0, 0, 0, 27, 1, 0, 0, 0, 0, 29, 1, 0, 0, + 0, 0, 31, 1, 0, 0, 0, 0, 33, 1, 0, 0, 0, 0, 35, 1, 0, 0, 0, 0, 37, 1, 0, + 0, 0, 0, 39, 1, 0, 0, 0, 0, 41, 1, 0, 0, 0, 0, 43, 1, 0, 0, 0, 0, 45, 1, + 0, 0, 0, 0, 47, 1, 0, 0, 0, 0, 49, 1, 0, 0, 0, 0, 51, 1, 0, 0, 0, 0, 53, + 1, 0, 0, 0, 0, 55, 1, 0, 0, 0, 0, 57, 1, 0, 0, 0, 0, 59, 1, 0, 0, 0, 0, + 61, 1, 0, 0, 0, 0, 63, 1, 0, 0, 0, 0, 65, 1, 0, 0, 0, 0, 67, 1, 0, 0, 0, + 0, 69, 1, 0, 0, 0, 0, 71, 1, 0, 0, 0, 0, 73, 1, 0, 0, 0, 0, 75, 1, 0, 0, + 0, 0, 77, 1, 0, 0, 0, 0, 79, 1, 0, 0, 0, 0, 81, 1, 0, 0, 0, 0, 83, 1, 0, + 0, 0, 0, 85, 1, 0, 0, 0, 0, 87, 1, 0, 0, 0, 0, 89, 1, 0, 0, 0, 0, 91, 1, + 0, 0, 0, 0, 93, 1, 0, 0, 0, 0, 95, 1, 0, 0, 0, 0, 97, 1, 0, 0, 0, 0, 99, + 1, 0, 0, 0, 0, 101, 1, 0, 0, 0, 0, 103, 1, 0, 0, 0, 0, 105, 1, 0, 0, 0, + 0, 107, 1, 0, 0, 0, 0, 117, 1, 0, 0, 0, 0, 119, 1, 0, 0, 0, 0, 125, 1, + 0, 0, 0, 0, 133, 1, 0, 0, 0, 0, 143, 1, 0, 0, 0, 0, 145, 1, 0, 0, 0, 0, + 147, 1, 0, 0, 0, 1, 149, 1, 0, 0, 0, 3, 156, 1, 0, 0, 0, 5, 164, 1, 0, + 0, 0, 7, 171, 1, 0, 0, 0, 9, 176, 1, 0, 0, 0, 11, 183, 1, 0, 0, 0, 13, + 191, 1, 0, 0, 0, 15, 198, 1, 0, 0, 0, 17, 207, 1, 0, 0, 0, 19, 216, 1, + 0, 0, 0, 21, 225, 1, 0, 0, 0, 23, 231, 1, 0, 0, 0, 25, 235, 1, 0, 0, 0, + 27, 241, 1, 0, 0, 0, 29, 247, 1, 0, 0, 0, 31, 254, 1, 0, 0, 0, 33, 261, + 1, 0, 0, 0, 35, 268, 1, 0, 0, 0, 37, 275, 1, 0, 0, 0, 39, 283, 1, 0, 0, + 0, 41, 291, 1, 0, 0, 0, 43, 300, 1, 0, 0, 0, 45, 309, 1, 0, 0, 0, 47, 314, + 1, 0, 0, 0, 49, 321, 1, 0, 0, 0, 51, 328, 1, 0, 0, 0, 53, 334, 1, 0, 0, + 0, 55, 340, 1, 0, 0, 0, 57, 349, 1, 0, 0, 0, 59, 360, 1, 0, 0, 0, 61, 363, + 1, 0, 0, 0, 63, 367, 1, 0, 0, 0, 65, 372, 1, 0, 0, 0, 67, 380, 1, 0, 0, + 0, 69, 388, 1, 0, 0, 0, 71, 395, 1, 0, 0, 0, 73, 399, 1, 0, 0, 0, 75, 406, + 1, 0, 0, 0, 77, 414, 1, 0, 0, 0, 79, 416, 1, 0, 0, 0, 81, 418, 1, 0, 0, + 0, 83, 420, 1, 0, 0, 0, 85, 422, 1, 0, 0, 0, 87, 424, 1, 0, 0, 0, 89, 426, + 1, 0, 0, 0, 91, 428, 1, 0, 0, 0, 93, 430, 1, 0, 0, 0, 95, 432, 1, 0, 0, + 0, 97, 434, 1, 0, 0, 0, 99, 436, 1, 0, 0, 0, 101, 438, 1, 0, 0, 0, 103, + 440, 1, 0, 0, 0, 105, 442, 1, 0, 0, 0, 107, 460, 1, 0, 0, 0, 109, 466, + 1, 0, 0, 0, 111, 468, 1, 0, 0, 0, 113, 473, 1, 0, 0, 0, 115, 478, 1, 0, + 0, 0, 117, 490, 1, 0, 0, 0, 119, 516, 1, 0, 0, 0, 121, 518, 1, 0, 0, 0, + 123, 526, 1, 0, 0, 0, 125, 533, 1, 0, 0, 0, 127, 535, 1, 0, 0, 0, 129, + 542, 1, 0, 0, 0, 131, 549, 1, 0, 0, 0, 133, 556, 1, 0, 0, 0, 135, 564, + 1, 0, 0, 0, 137, 566, 1, 0, 0, 0, 139, 568, 1, 0, 0, 0, 141, 570, 1, 0, + 0, 0, 143, 573, 1, 0, 0, 0, 145, 579, 1, 0, 0, 0, 147, 590, 1, 0, 0, 0, + 149, 150, 5, 115, 0, 0, 150, 151, 5, 121, 0, 0, 151, 152, 5, 110, 0, 0, + 152, 153, 5, 116, 0, 0, 153, 154, 5, 97, 0, 0, 154, 155, 5, 120, 0, 0, + 155, 2, 1, 0, 0, 0, 156, 157, 5, 101, 0, 0, 157, 158, 5, 100, 0, 0, 158, + 159, 5, 105, 0, 0, 159, 160, 5, 116, 0, 0, 160, 161, 5, 105, 0, 0, 161, + 162, 5, 111, 0, 0, 162, 163, 5, 110, 0, 0, 163, 4, 1, 0, 0, 0, 164, 165, + 5, 105, 0, 0, 165, 166, 5, 109, 0, 0, 166, 167, 5, 112, 0, 0, 167, 168, + 5, 111, 0, 0, 168, 169, 5, 114, 0, 0, 169, 170, 5, 116, 0, 0, 170, 6, 1, + 0, 0, 0, 171, 172, 5, 119, 0, 0, 172, 173, 5, 101, 0, 0, 173, 174, 5, 97, + 0, 0, 174, 175, 5, 107, 0, 0, 175, 8, 1, 0, 0, 0, 176, 177, 5, 112, 0, + 0, 177, 178, 5, 117, 0, 0, 178, 179, 5, 98, 0, 0, 179, 180, 5, 108, 0, + 0, 180, 181, 5, 105, 0, 0, 181, 182, 5, 99, 0, 0, 182, 10, 1, 0, 0, 0, + 183, 184, 5, 112, 0, 0, 184, 185, 5, 97, 0, 0, 185, 186, 5, 99, 0, 0, 186, + 187, 5, 107, 0, 0, 187, 188, 5, 97, 0, 0, 188, 189, 5, 103, 0, 0, 189, + 190, 5, 101, 0, 0, 190, 12, 1, 0, 0, 0, 191, 192, 5, 111, 0, 0, 192, 193, + 5, 112, 0, 0, 193, 194, 5, 116, 0, 0, 194, 195, 5, 105, 0, 0, 195, 196, + 5, 111, 0, 0, 196, 197, 5, 110, 0, 0, 197, 14, 1, 0, 0, 0, 198, 199, 5, + 111, 0, 0, 199, 200, 5, 112, 0, 0, 200, 201, 5, 116, 0, 0, 201, 202, 5, + 105, 0, 0, 202, 203, 5, 111, 0, 0, 203, 204, 5, 110, 0, 0, 204, 205, 5, + 97, 0, 0, 205, 206, 5, 108, 0, 0, 206, 16, 1, 0, 0, 0, 207, 208, 5, 114, + 0, 0, 208, 209, 5, 101, 0, 0, 209, 210, 5, 113, 0, 0, 210, 211, 5, 117, + 0, 0, 211, 212, 5, 105, 0, 0, 212, 213, 5, 114, 0, 0, 213, 214, 5, 101, + 0, 0, 214, 215, 5, 100, 0, 0, 215, 18, 1, 0, 0, 0, 216, 217, 5, 114, 0, + 0, 217, 218, 5, 101, 0, 0, 218, 219, 5, 112, 0, 0, 219, 220, 5, 101, 0, + 0, 220, 221, 5, 97, 0, 0, 221, 222, 5, 116, 0, 0, 222, 223, 5, 101, 0, + 0, 223, 224, 5, 100, 0, 0, 224, 20, 1, 0, 0, 0, 225, 226, 5, 111, 0, 0, + 226, 227, 5, 110, 0, 0, 227, 228, 5, 101, 0, 0, 228, 229, 5, 111, 0, 0, + 229, 230, 5, 102, 0, 0, 230, 22, 1, 0, 0, 0, 231, 232, 5, 109, 0, 0, 232, + 233, 5, 97, 0, 0, 233, 234, 5, 112, 0, 0, 234, 24, 1, 0, 0, 0, 235, 236, + 5, 105, 0, 0, 236, 237, 5, 110, 0, 0, 237, 238, 5, 116, 0, 0, 238, 239, + 5, 51, 0, 0, 239, 240, 5, 50, 0, 0, 240, 26, 1, 0, 0, 0, 241, 242, 5, 105, + 0, 0, 242, 243, 5, 110, 0, 0, 243, 244, 5, 116, 0, 0, 244, 245, 5, 54, + 0, 0, 245, 246, 5, 52, 0, 0, 246, 28, 1, 0, 0, 0, 247, 248, 5, 117, 0, + 0, 248, 249, 5, 105, 0, 0, 249, 250, 5, 110, 0, 0, 250, 251, 5, 116, 0, + 0, 251, 252, 5, 51, 0, 0, 252, 253, 5, 50, 0, 0, 253, 30, 1, 0, 0, 0, 254, + 255, 5, 117, 0, 0, 255, 256, 5, 105, 0, 0, 256, 257, 5, 110, 0, 0, 257, + 258, 5, 116, 0, 0, 258, 259, 5, 54, 0, 0, 259, 260, 5, 52, 0, 0, 260, 32, + 1, 0, 0, 0, 261, 262, 5, 115, 0, 0, 262, 263, 5, 105, 0, 0, 263, 264, 5, + 110, 0, 0, 264, 265, 5, 116, 0, 0, 265, 266, 5, 51, 0, 0, 266, 267, 5, + 50, 0, 0, 267, 34, 1, 0, 0, 0, 268, 269, 5, 115, 0, 0, 269, 270, 5, 105, + 0, 0, 270, 271, 5, 110, 0, 0, 271, 272, 5, 116, 0, 0, 272, 273, 5, 54, + 0, 0, 273, 274, 5, 52, 0, 0, 274, 36, 1, 0, 0, 0, 275, 276, 5, 102, 0, + 0, 276, 277, 5, 105, 0, 0, 277, 278, 5, 120, 0, 0, 278, 279, 5, 101, 0, + 0, 279, 280, 5, 100, 0, 0, 280, 281, 5, 51, 0, 0, 281, 282, 5, 50, 0, 0, + 282, 38, 1, 0, 0, 0, 283, 284, 5, 102, 0, 0, 284, 285, 5, 105, 0, 0, 285, + 286, 5, 120, 0, 0, 286, 287, 5, 101, 0, 0, 287, 288, 5, 100, 0, 0, 288, + 289, 5, 54, 0, 0, 289, 290, 5, 52, 0, 0, 290, 40, 1, 0, 0, 0, 291, 292, + 5, 115, 0, 0, 292, 293, 5, 102, 0, 0, 293, 294, 5, 105, 0, 0, 294, 295, + 5, 120, 0, 0, 295, 296, 5, 101, 0, 0, 296, 297, 5, 100, 0, 0, 297, 298, + 5, 51, 0, 0, 298, 299, 5, 50, 0, 0, 299, 42, 1, 0, 0, 0, 300, 301, 5, 115, + 0, 0, 301, 302, 5, 102, 0, 0, 302, 303, 5, 105, 0, 0, 303, 304, 5, 120, + 0, 0, 304, 305, 5, 101, 0, 0, 305, 306, 5, 100, 0, 0, 306, 307, 5, 54, + 0, 0, 307, 308, 5, 52, 0, 0, 308, 44, 1, 0, 0, 0, 309, 310, 5, 98, 0, 0, + 310, 311, 5, 111, 0, 0, 311, 312, 5, 111, 0, 0, 312, 313, 5, 108, 0, 0, + 313, 46, 1, 0, 0, 0, 314, 315, 5, 115, 0, 0, 315, 316, 5, 116, 0, 0, 316, + 317, 5, 114, 0, 0, 317, 318, 5, 105, 0, 0, 318, 319, 5, 110, 0, 0, 319, + 320, 5, 103, 0, 0, 320, 48, 1, 0, 0, 0, 321, 322, 5, 100, 0, 0, 322, 323, + 5, 111, 0, 0, 323, 324, 5, 117, 0, 0, 324, 325, 5, 98, 0, 0, 325, 326, + 5, 108, 0, 0, 326, 327, 5, 101, 0, 0, 327, 50, 1, 0, 0, 0, 328, 329, 5, + 102, 0, 0, 329, 330, 5, 108, 0, 0, 330, 331, 5, 111, 0, 0, 331, 332, 5, + 97, 0, 0, 332, 333, 5, 116, 0, 0, 333, 52, 1, 0, 0, 0, 334, 335, 5, 98, + 0, 0, 335, 336, 5, 121, 0, 0, 336, 337, 5, 116, 0, 0, 337, 338, 5, 101, + 0, 0, 338, 339, 5, 115, 0, 0, 339, 54, 1, 0, 0, 0, 340, 341, 5, 114, 0, + 0, 341, 342, 5, 101, 0, 0, 342, 343, 5, 115, 0, 0, 343, 344, 5, 101, 0, + 0, 344, 345, 5, 114, 0, 0, 345, 346, 5, 118, 0, 0, 346, 347, 5, 101, 0, + 0, 347, 348, 5, 100, 0, 0, 348, 56, 1, 0, 0, 0, 349, 350, 5, 101, 0, 0, + 350, 351, 5, 120, 0, 0, 351, 352, 5, 116, 0, 0, 352, 353, 5, 101, 0, 0, + 353, 354, 5, 110, 0, 0, 354, 355, 5, 115, 0, 0, 355, 356, 5, 105, 0, 0, + 356, 357, 5, 111, 0, 0, 357, 358, 5, 110, 0, 0, 358, 359, 5, 115, 0, 0, + 359, 58, 1, 0, 0, 0, 360, 361, 5, 116, 0, 0, 361, 362, 5, 111, 0, 0, 362, + 60, 1, 0, 0, 0, 363, 364, 5, 109, 0, 0, 364, 365, 5, 97, 0, 0, 365, 366, + 5, 120, 0, 0, 366, 62, 1, 0, 0, 0, 367, 368, 5, 101, 0, 0, 368, 369, 5, + 110, 0, 0, 369, 370, 5, 117, 0, 0, 370, 371, 5, 109, 0, 0, 371, 64, 1, + 0, 0, 0, 372, 373, 5, 109, 0, 0, 373, 374, 5, 101, 0, 0, 374, 375, 5, 115, + 0, 0, 375, 376, 5, 115, 0, 0, 376, 377, 5, 97, 0, 0, 377, 378, 5, 103, + 0, 0, 378, 379, 5, 101, 0, 0, 379, 66, 1, 0, 0, 0, 380, 381, 5, 115, 0, + 0, 381, 382, 5, 101, 0, 0, 382, 383, 5, 114, 0, 0, 383, 384, 5, 118, 0, + 0, 384, 385, 5, 105, 0, 0, 385, 386, 5, 99, 0, 0, 386, 387, 5, 101, 0, + 0, 387, 68, 1, 0, 0, 0, 388, 389, 5, 101, 0, 0, 389, 390, 5, 120, 0, 0, + 390, 391, 5, 116, 0, 0, 391, 392, 5, 101, 0, 0, 392, 393, 5, 110, 0, 0, + 393, 394, 5, 100, 0, 0, 394, 70, 1, 0, 0, 0, 395, 396, 5, 114, 0, 0, 396, + 397, 5, 112, 0, 0, 397, 398, 5, 99, 0, 0, 398, 72, 1, 0, 0, 0, 399, 400, + 5, 115, 0, 0, 400, 401, 5, 116, 0, 0, 401, 402, 5, 114, 0, 0, 402, 403, + 5, 101, 0, 0, 403, 404, 5, 97, 0, 0, 404, 405, 5, 109, 0, 0, 405, 74, 1, + 0, 0, 0, 406, 407, 5, 114, 0, 0, 407, 408, 5, 101, 0, 0, 408, 409, 5, 116, + 0, 0, 409, 410, 5, 117, 0, 0, 410, 411, 5, 114, 0, 0, 411, 412, 5, 110, + 0, 0, 412, 413, 5, 115, 0, 0, 413, 76, 1, 0, 0, 0, 414, 415, 5, 59, 0, + 0, 415, 78, 1, 0, 0, 0, 416, 417, 5, 61, 0, 0, 417, 80, 1, 0, 0, 0, 418, + 419, 5, 40, 0, 0, 419, 82, 1, 0, 0, 0, 420, 421, 5, 41, 0, 0, 421, 84, + 1, 0, 0, 0, 422, 423, 5, 91, 0, 0, 423, 86, 1, 0, 0, 0, 424, 425, 5, 93, + 0, 0, 425, 88, 1, 0, 0, 0, 426, 427, 5, 123, 0, 0, 427, 90, 1, 0, 0, 0, + 428, 429, 5, 125, 0, 0, 429, 92, 1, 0, 0, 0, 430, 431, 5, 60, 0, 0, 431, + 94, 1, 0, 0, 0, 432, 433, 5, 62, 0, 0, 433, 96, 1, 0, 0, 0, 434, 435, 5, + 46, 0, 0, 435, 98, 1, 0, 0, 0, 436, 437, 5, 44, 0, 0, 437, 100, 1, 0, 0, + 0, 438, 439, 5, 58, 0, 0, 439, 102, 1, 0, 0, 0, 440, 441, 5, 43, 0, 0, + 441, 104, 1, 0, 0, 0, 442, 443, 5, 45, 0, 0, 443, 106, 1, 0, 0, 0, 444, + 448, 5, 39, 0, 0, 445, 447, 3, 109, 54, 0, 446, 445, 1, 0, 0, 0, 447, 450, + 1, 0, 0, 0, 448, 449, 1, 0, 0, 0, 448, 446, 1, 0, 0, 0, 449, 451, 1, 0, + 0, 0, 450, 448, 1, 0, 0, 0, 451, 461, 5, 39, 0, 0, 452, 456, 5, 34, 0, + 0, 453, 455, 3, 109, 54, 0, 454, 453, 1, 0, 0, 0, 455, 458, 1, 0, 0, 0, + 456, 457, 1, 0, 0, 0, 456, 454, 1, 0, 0, 0, 457, 459, 1, 0, 0, 0, 458, + 456, 1, 0, 0, 0, 459, 461, 5, 34, 0, 0, 460, 444, 1, 0, 0, 0, 460, 452, + 1, 0, 0, 0, 461, 108, 1, 0, 0, 0, 462, 467, 3, 111, 55, 0, 463, 467, 3, + 113, 56, 0, 464, 467, 3, 115, 57, 0, 465, 467, 8, 0, 0, 0, 466, 462, 1, + 0, 0, 0, 466, 463, 1, 0, 0, 0, 466, 464, 1, 0, 0, 0, 466, 465, 1, 0, 0, + 0, 467, 110, 1, 0, 0, 0, 468, 469, 5, 92, 0, 0, 469, 470, 7, 1, 0, 0, 470, + 471, 3, 141, 70, 0, 471, 472, 3, 141, 70, 0, 472, 112, 1, 0, 0, 0, 473, + 474, 5, 92, 0, 0, 474, 475, 3, 139, 69, 0, 475, 476, 3, 139, 69, 0, 476, + 477, 3, 139, 69, 0, 477, 114, 1, 0, 0, 0, 478, 479, 5, 92, 0, 0, 479, 480, + 7, 2, 0, 0, 480, 116, 1, 0, 0, 0, 481, 482, 5, 116, 0, 0, 482, 483, 5, + 114, 0, 0, 483, 484, 5, 117, 0, 0, 484, 491, 5, 101, 0, 0, 485, 486, 5, + 102, 0, 0, 486, 487, 5, 97, 0, 0, 487, 488, 5, 108, 0, 0, 488, 489, 5, + 115, 0, 0, 489, 491, 5, 101, 0, 0, 490, 481, 1, 0, 0, 0, 490, 485, 1, 0, + 0, 0, 491, 118, 1, 0, 0, 0, 492, 493, 3, 123, 61, 0, 493, 495, 3, 97, 48, + 0, 494, 496, 3, 123, 61, 0, 495, 494, 1, 0, 0, 0, 495, 496, 1, 0, 0, 0, + 496, 498, 1, 0, 0, 0, 497, 499, 3, 121, 60, 0, 498, 497, 1, 0, 0, 0, 498, + 499, 1, 0, 0, 0, 499, 509, 1, 0, 0, 0, 500, 501, 3, 123, 61, 0, 501, 502, + 3, 121, 60, 0, 502, 509, 1, 0, 0, 0, 503, 504, 3, 97, 48, 0, 504, 506, + 3, 123, 61, 0, 505, 507, 3, 121, 60, 0, 506, 505, 1, 0, 0, 0, 506, 507, + 1, 0, 0, 0, 507, 509, 1, 0, 0, 0, 508, 492, 1, 0, 0, 0, 508, 500, 1, 0, + 0, 0, 508, 503, 1, 0, 0, 0, 509, 517, 1, 0, 0, 0, 510, 511, 5, 105, 0, + 0, 511, 512, 5, 110, 0, 0, 512, 517, 5, 102, 0, 0, 513, 514, 5, 110, 0, + 0, 514, 515, 5, 97, 0, 0, 515, 517, 5, 110, 0, 0, 516, 508, 1, 0, 0, 0, + 516, 510, 1, 0, 0, 0, 516, 513, 1, 0, 0, 0, 517, 120, 1, 0, 0, 0, 518, + 521, 7, 3, 0, 0, 519, 522, 3, 103, 51, 0, 520, 522, 3, 105, 52, 0, 521, + 519, 1, 0, 0, 0, 521, 520, 1, 0, 0, 0, 521, 522, 1, 0, 0, 0, 522, 523, + 1, 0, 0, 0, 523, 524, 3, 123, 61, 0, 524, 122, 1, 0, 0, 0, 525, 527, 3, + 137, 68, 0, 526, 525, 1, 0, 0, 0, 527, 528, 1, 0, 0, 0, 528, 526, 1, 0, + 0, 0, 528, 529, 1, 0, 0, 0, 529, 124, 1, 0, 0, 0, 530, 534, 3, 127, 63, + 0, 531, 534, 3, 129, 64, 0, 532, 534, 3, 131, 65, 0, 533, 530, 1, 0, 0, + 0, 533, 531, 1, 0, 0, 0, 533, 532, 1, 0, 0, 0, 534, 126, 1, 0, 0, 0, 535, + 539, 7, 4, 0, 0, 536, 538, 3, 137, 68, 0, 537, 536, 1, 0, 0, 0, 538, 541, + 1, 0, 0, 0, 539, 537, 1, 0, 0, 0, 539, 540, 1, 0, 0, 0, 540, 128, 1, 0, + 0, 0, 541, 539, 1, 0, 0, 0, 542, 546, 5, 48, 0, 0, 543, 545, 3, 139, 69, + 0, 544, 543, 1, 0, 0, 0, 545, 548, 1, 0, 0, 0, 546, 544, 1, 0, 0, 0, 546, + 547, 1, 0, 0, 0, 547, 130, 1, 0, 0, 0, 548, 546, 1, 0, 0, 0, 549, 550, + 5, 48, 0, 0, 550, 552, 7, 1, 0, 0, 551, 553, 3, 141, 70, 0, 552, 551, 1, + 0, 0, 0, 553, 554, 1, 0, 0, 0, 554, 552, 1, 0, 0, 0, 554, 555, 1, 0, 0, + 0, 555, 132, 1, 0, 0, 0, 556, 561, 3, 135, 67, 0, 557, 560, 3, 135, 67, + 0, 558, 560, 3, 137, 68, 0, 559, 557, 1, 0, 0, 0, 559, 558, 1, 0, 0, 0, + 560, 563, 1, 0, 0, 0, 561, 559, 1, 0, 0, 0, 561, 562, 1, 0, 0, 0, 562, + 134, 1, 0, 0, 0, 563, 561, 1, 0, 0, 0, 564, 565, 7, 5, 0, 0, 565, 136, + 1, 0, 0, 0, 566, 567, 7, 6, 0, 0, 567, 138, 1, 0, 0, 0, 568, 569, 7, 7, + 0, 0, 569, 140, 1, 0, 0, 0, 570, 571, 7, 8, 0, 0, 571, 142, 1, 0, 0, 0, + 572, 574, 7, 9, 0, 0, 573, 572, 1, 0, 0, 0, 574, 575, 1, 0, 0, 0, 575, + 573, 1, 0, 0, 0, 575, 576, 1, 0, 0, 0, 576, 577, 1, 0, 0, 0, 577, 578, + 6, 71, 0, 0, 578, 144, 1, 0, 0, 0, 579, 580, 5, 47, 0, 0, 580, 581, 5, + 47, 0, 0, 581, 585, 1, 0, 0, 0, 582, 584, 8, 10, 0, 0, 583, 582, 1, 0, + 0, 0, 584, 587, 1, 0, 0, 0, 585, 583, 1, 0, 0, 0, 585, 586, 1, 0, 0, 0, + 586, 588, 1, 0, 0, 0, 587, 585, 1, 0, 0, 0, 588, 589, 6, 72, 1, 0, 589, + 146, 1, 0, 0, 0, 590, 591, 5, 47, 0, 0, 591, 592, 5, 42, 0, 0, 592, 596, + 1, 0, 0, 0, 593, 595, 9, 0, 0, 0, 594, 593, 1, 0, 0, 0, 595, 598, 1, 0, + 0, 0, 596, 597, 1, 0, 0, 0, 596, 594, 1, 0, 0, 0, 597, 599, 1, 0, 0, 0, + 598, 596, 1, 0, 0, 0, 599, 600, 5, 42, 0, 0, 600, 601, 5, 47, 0, 0, 601, + 602, 1, 0, 0, 0, 602, 603, 6, 73, 1, 0, 603, 148, 1, 0, 0, 0, 22, 0, 448, + 456, 460, 466, 490, 495, 498, 506, 508, 516, 521, 528, 533, 539, 546, 554, + 559, 561, 575, 585, 596, 2, 0, 2, 0, 0, 3, 0, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// ProtobufLexerInit initializes any static state used to implement ProtobufLexer. By default the +// static state used to implement the lexer is lazily initialized during the first call to +// NewProtobufLexer(). You can call this function if you wish to initialize the static state ahead +// of time. +func ProtobufLexerInit() { + staticData := &ProtobufLexerLexerStaticData + staticData.once.Do(protobuflexerLexerInit) +} + +// NewProtobufLexer produces a new lexer instance for the optional input antlr.CharStream. +func NewProtobufLexer(input antlr.CharStream) *ProtobufLexer { + ProtobufLexerInit() + l := new(ProtobufLexer) + l.BaseLexer = antlr.NewBaseLexer(input) + staticData := &ProtobufLexerLexerStaticData + l.Interpreter = antlr.NewLexerATNSimulator(l, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + l.channelNames = staticData.ChannelNames + l.modeNames = staticData.ModeNames + l.RuleNames = staticData.RuleNames + l.LiteralNames = staticData.LiteralNames + l.SymbolicNames = staticData.SymbolicNames + l.GrammarFileName = "Protobuf.g4" + // TODO: l.EOF = antlr.TokenEOF + + return l +} + +// ProtobufLexer tokens. +const ( + ProtobufLexerSYNTAX = 1 + ProtobufLexerEDITION = 2 + ProtobufLexerIMPORT = 3 + ProtobufLexerWEAK = 4 + ProtobufLexerPUBLIC = 5 + ProtobufLexerPACKAGE = 6 + ProtobufLexerOPTION = 7 + ProtobufLexerOPTIONAL = 8 + ProtobufLexerREQUIRED = 9 + ProtobufLexerREPEATED = 10 + ProtobufLexerONEOF = 11 + ProtobufLexerMAP = 12 + ProtobufLexerINT32 = 13 + ProtobufLexerINT64 = 14 + ProtobufLexerUINT32 = 15 + ProtobufLexerUINT64 = 16 + ProtobufLexerSINT32 = 17 + ProtobufLexerSINT64 = 18 + ProtobufLexerFIXED32 = 19 + ProtobufLexerFIXED64 = 20 + ProtobufLexerSFIXED32 = 21 + ProtobufLexerSFIXED64 = 22 + ProtobufLexerBOOL = 23 + ProtobufLexerSTRING = 24 + ProtobufLexerDOUBLE = 25 + ProtobufLexerFLOAT = 26 + ProtobufLexerBYTES = 27 + ProtobufLexerRESERVED = 28 + ProtobufLexerEXTENSIONS = 29 + ProtobufLexerTO = 30 + ProtobufLexerMAX = 31 + ProtobufLexerENUM = 32 + ProtobufLexerMESSAGE = 33 + ProtobufLexerSERVICE = 34 + ProtobufLexerEXTEND = 35 + ProtobufLexerRPC = 36 + ProtobufLexerSTREAM = 37 + ProtobufLexerRETURNS = 38 + ProtobufLexerSEMI = 39 + ProtobufLexerEQ = 40 + ProtobufLexerLP = 41 + ProtobufLexerRP = 42 + ProtobufLexerLB = 43 + ProtobufLexerRB = 44 + ProtobufLexerLC = 45 + ProtobufLexerRC = 46 + ProtobufLexerLT = 47 + ProtobufLexerGT = 48 + ProtobufLexerDOT = 49 + ProtobufLexerCOMMA = 50 + ProtobufLexerCOLON = 51 + ProtobufLexerPLUS = 52 + ProtobufLexerMINUS = 53 + ProtobufLexerSTR_LIT_SINGLE = 54 + ProtobufLexerBOOL_LIT = 55 + ProtobufLexerFLOAT_LIT = 56 + ProtobufLexerINT_LIT = 57 + ProtobufLexerIDENTIFIER = 58 + ProtobufLexerWS = 59 + ProtobufLexerLINE_COMMENT = 60 + ProtobufLexerCOMMENT = 61 +) diff --git a/prutalgen/internal/parser/protobuf_listener.go b/prutalgen/internal/parser/protobuf_listener.go new file mode 100644 index 0000000..519edd4 --- /dev/null +++ b/prutalgen/internal/parser/protobuf_listener.go @@ -0,0 +1,328 @@ +// Code generated from ./Protobuf.g4 by ANTLR 4.13.2. DO NOT EDIT. + +package parser // Protobuf + +import "github.com/cloudwego/prutal/prutalgen/internal/antlr" + +// ProtobufListener is a complete listener for a parse tree produced by ProtobufParser. +type ProtobufListener interface { + antlr.ParseTreeListener + + // EnterProto is called when entering the proto production. + EnterProto(c *ProtoContext) + + // EnterEdition is called when entering the edition production. + EnterEdition(c *EditionContext) + + // EnterImportStatement is called when entering the importStatement production. + EnterImportStatement(c *ImportStatementContext) + + // EnterPackageStatement is called when entering the packageStatement production. + EnterPackageStatement(c *PackageStatementContext) + + // EnterOptionStatement is called when entering the optionStatement production. + EnterOptionStatement(c *OptionStatementContext) + + // EnterOptionName is called when entering the optionName production. + EnterOptionName(c *OptionNameContext) + + // EnterFieldLabel is called when entering the fieldLabel production. + EnterFieldLabel(c *FieldLabelContext) + + // EnterField is called when entering the field production. + EnterField(c *FieldContext) + + // EnterFieldOptions is called when entering the fieldOptions production. + EnterFieldOptions(c *FieldOptionsContext) + + // EnterFieldOption is called when entering the fieldOption production. + EnterFieldOption(c *FieldOptionContext) + + // EnterFieldNumber is called when entering the fieldNumber production. + EnterFieldNumber(c *FieldNumberContext) + + // EnterOneof is called when entering the oneof production. + EnterOneof(c *OneofContext) + + // EnterOneofField is called when entering the oneofField production. + EnterOneofField(c *OneofFieldContext) + + // EnterMapField is called when entering the mapField production. + EnterMapField(c *MapFieldContext) + + // EnterKeyType is called when entering the keyType production. + EnterKeyType(c *KeyTypeContext) + + // EnterFieldType is called when entering the fieldType production. + EnterFieldType(c *FieldTypeContext) + + // EnterReserved is called when entering the reserved production. + EnterReserved(c *ReservedContext) + + // EnterExtensions is called when entering the extensions production. + EnterExtensions(c *ExtensionsContext) + + // EnterRanges is called when entering the ranges production. + EnterRanges(c *RangesContext) + + // EnterOneRange is called when entering the oneRange production. + EnterOneRange(c *OneRangeContext) + + // EnterReservedFieldNames is called when entering the reservedFieldNames production. + EnterReservedFieldNames(c *ReservedFieldNamesContext) + + // EnterTopLevelDef is called when entering the topLevelDef production. + EnterTopLevelDef(c *TopLevelDefContext) + + // EnterEnumDef is called when entering the enumDef production. + EnterEnumDef(c *EnumDefContext) + + // EnterEnumBody is called when entering the enumBody production. + EnterEnumBody(c *EnumBodyContext) + + // EnterEnumElement is called when entering the enumElement production. + EnterEnumElement(c *EnumElementContext) + + // EnterEnumField is called when entering the enumField production. + EnterEnumField(c *EnumFieldContext) + + // EnterEnumValueOptions is called when entering the enumValueOptions production. + EnterEnumValueOptions(c *EnumValueOptionsContext) + + // EnterEnumValueOption is called when entering the enumValueOption production. + EnterEnumValueOption(c *EnumValueOptionContext) + + // EnterMessageDef is called when entering the messageDef production. + EnterMessageDef(c *MessageDefContext) + + // EnterMessageBody is called when entering the messageBody production. + EnterMessageBody(c *MessageBodyContext) + + // EnterMessageElement is called when entering the messageElement production. + EnterMessageElement(c *MessageElementContext) + + // EnterExtendDef is called when entering the extendDef production. + EnterExtendDef(c *ExtendDefContext) + + // EnterServiceDef is called when entering the serviceDef production. + EnterServiceDef(c *ServiceDefContext) + + // EnterServiceElement is called when entering the serviceElement production. + EnterServiceElement(c *ServiceElementContext) + + // EnterRpc is called when entering the rpc production. + EnterRpc(c *RpcContext) + + // EnterConstant is called when entering the constant production. + EnterConstant(c *ConstantContext) + + // EnterBlockLit is called when entering the blockLit production. + EnterBlockLit(c *BlockLitContext) + + // EnterEmptyStatement is called when entering the emptyStatement production. + EnterEmptyStatement(c *EmptyStatementContext) + + // EnterIdent is called when entering the ident production. + EnterIdent(c *IdentContext) + + // EnterFullIdent is called when entering the fullIdent production. + EnterFullIdent(c *FullIdentContext) + + // EnterMessageName is called when entering the messageName production. + EnterMessageName(c *MessageNameContext) + + // EnterEnumName is called when entering the enumName production. + EnterEnumName(c *EnumNameContext) + + // EnterFieldName is called when entering the fieldName production. + EnterFieldName(c *FieldNameContext) + + // EnterOneofName is called when entering the oneofName production. + EnterOneofName(c *OneofNameContext) + + // EnterServiceName is called when entering the serviceName production. + EnterServiceName(c *ServiceNameContext) + + // EnterRpcName is called when entering the rpcName production. + EnterRpcName(c *RpcNameContext) + + // EnterMessageType is called when entering the messageType production. + EnterMessageType(c *MessageTypeContext) + + // EnterEnumType is called when entering the enumType production. + EnterEnumType(c *EnumTypeContext) + + // EnterIntLit is called when entering the intLit production. + EnterIntLit(c *IntLitContext) + + // EnterStrLit is called when entering the strLit production. + EnterStrLit(c *StrLitContext) + + // EnterBoolLit is called when entering the boolLit production. + EnterBoolLit(c *BoolLitContext) + + // EnterFloatLit is called when entering the floatLit production. + EnterFloatLit(c *FloatLitContext) + + // EnterKeywords is called when entering the keywords production. + EnterKeywords(c *KeywordsContext) + + // ExitProto is called when exiting the proto production. + ExitProto(c *ProtoContext) + + // ExitEdition is called when exiting the edition production. + ExitEdition(c *EditionContext) + + // ExitImportStatement is called when exiting the importStatement production. + ExitImportStatement(c *ImportStatementContext) + + // ExitPackageStatement is called when exiting the packageStatement production. + ExitPackageStatement(c *PackageStatementContext) + + // ExitOptionStatement is called when exiting the optionStatement production. + ExitOptionStatement(c *OptionStatementContext) + + // ExitOptionName is called when exiting the optionName production. + ExitOptionName(c *OptionNameContext) + + // ExitFieldLabel is called when exiting the fieldLabel production. + ExitFieldLabel(c *FieldLabelContext) + + // ExitField is called when exiting the field production. + ExitField(c *FieldContext) + + // ExitFieldOptions is called when exiting the fieldOptions production. + ExitFieldOptions(c *FieldOptionsContext) + + // ExitFieldOption is called when exiting the fieldOption production. + ExitFieldOption(c *FieldOptionContext) + + // ExitFieldNumber is called when exiting the fieldNumber production. + ExitFieldNumber(c *FieldNumberContext) + + // ExitOneof is called when exiting the oneof production. + ExitOneof(c *OneofContext) + + // ExitOneofField is called when exiting the oneofField production. + ExitOneofField(c *OneofFieldContext) + + // ExitMapField is called when exiting the mapField production. + ExitMapField(c *MapFieldContext) + + // ExitKeyType is called when exiting the keyType production. + ExitKeyType(c *KeyTypeContext) + + // ExitFieldType is called when exiting the fieldType production. + ExitFieldType(c *FieldTypeContext) + + // ExitReserved is called when exiting the reserved production. + ExitReserved(c *ReservedContext) + + // ExitExtensions is called when exiting the extensions production. + ExitExtensions(c *ExtensionsContext) + + // ExitRanges is called when exiting the ranges production. + ExitRanges(c *RangesContext) + + // ExitOneRange is called when exiting the oneRange production. + ExitOneRange(c *OneRangeContext) + + // ExitReservedFieldNames is called when exiting the reservedFieldNames production. + ExitReservedFieldNames(c *ReservedFieldNamesContext) + + // ExitTopLevelDef is called when exiting the topLevelDef production. + ExitTopLevelDef(c *TopLevelDefContext) + + // ExitEnumDef is called when exiting the enumDef production. + ExitEnumDef(c *EnumDefContext) + + // ExitEnumBody is called when exiting the enumBody production. + ExitEnumBody(c *EnumBodyContext) + + // ExitEnumElement is called when exiting the enumElement production. + ExitEnumElement(c *EnumElementContext) + + // ExitEnumField is called when exiting the enumField production. + ExitEnumField(c *EnumFieldContext) + + // ExitEnumValueOptions is called when exiting the enumValueOptions production. + ExitEnumValueOptions(c *EnumValueOptionsContext) + + // ExitEnumValueOption is called when exiting the enumValueOption production. + ExitEnumValueOption(c *EnumValueOptionContext) + + // ExitMessageDef is called when exiting the messageDef production. + ExitMessageDef(c *MessageDefContext) + + // ExitMessageBody is called when exiting the messageBody production. + ExitMessageBody(c *MessageBodyContext) + + // ExitMessageElement is called when exiting the messageElement production. + ExitMessageElement(c *MessageElementContext) + + // ExitExtendDef is called when exiting the extendDef production. + ExitExtendDef(c *ExtendDefContext) + + // ExitServiceDef is called when exiting the serviceDef production. + ExitServiceDef(c *ServiceDefContext) + + // ExitServiceElement is called when exiting the serviceElement production. + ExitServiceElement(c *ServiceElementContext) + + // ExitRpc is called when exiting the rpc production. + ExitRpc(c *RpcContext) + + // ExitConstant is called when exiting the constant production. + ExitConstant(c *ConstantContext) + + // ExitBlockLit is called when exiting the blockLit production. + ExitBlockLit(c *BlockLitContext) + + // ExitEmptyStatement is called when exiting the emptyStatement production. + ExitEmptyStatement(c *EmptyStatementContext) + + // ExitIdent is called when exiting the ident production. + ExitIdent(c *IdentContext) + + // ExitFullIdent is called when exiting the fullIdent production. + ExitFullIdent(c *FullIdentContext) + + // ExitMessageName is called when exiting the messageName production. + ExitMessageName(c *MessageNameContext) + + // ExitEnumName is called when exiting the enumName production. + ExitEnumName(c *EnumNameContext) + + // ExitFieldName is called when exiting the fieldName production. + ExitFieldName(c *FieldNameContext) + + // ExitOneofName is called when exiting the oneofName production. + ExitOneofName(c *OneofNameContext) + + // ExitServiceName is called when exiting the serviceName production. + ExitServiceName(c *ServiceNameContext) + + // ExitRpcName is called when exiting the rpcName production. + ExitRpcName(c *RpcNameContext) + + // ExitMessageType is called when exiting the messageType production. + ExitMessageType(c *MessageTypeContext) + + // ExitEnumType is called when exiting the enumType production. + ExitEnumType(c *EnumTypeContext) + + // ExitIntLit is called when exiting the intLit production. + ExitIntLit(c *IntLitContext) + + // ExitStrLit is called when exiting the strLit production. + ExitStrLit(c *StrLitContext) + + // ExitBoolLit is called when exiting the boolLit production. + ExitBoolLit(c *BoolLitContext) + + // ExitFloatLit is called when exiting the floatLit production. + ExitFloatLit(c *FloatLitContext) + + // ExitKeywords is called when exiting the keywords production. + ExitKeywords(c *KeywordsContext) +} diff --git a/prutalgen/internal/parser/protobuf_parser.go b/prutalgen/internal/parser/protobuf_parser.go new file mode 100644 index 0000000..c684e88 --- /dev/null +++ b/prutalgen/internal/parser/protobuf_parser.go @@ -0,0 +1,10412 @@ +// Code generated from ./Protobuf.g4 by ANTLR 4.13.2. DO NOT EDIT. + +package parser // Protobuf + +import ( + "fmt" + "strconv" + "sync" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" +) + +// Suppress unused import errors +var _ = fmt.Printf +var _ = strconv.Itoa +var _ = sync.Once{} + +type ProtobufParser struct { + *antlr.BaseParser +} + +var ProtobufParserStaticData struct { + once sync.Once + serializedATN []int32 + LiteralNames []string + SymbolicNames []string + RuleNames []string + PredictionContextCache *antlr.PredictionContextCache + atn *antlr.ATN + decisionToDFA []*antlr.DFA +} + +func protobufParserInit() { + staticData := &ProtobufParserStaticData + staticData.LiteralNames = []string{ + "", "'syntax'", "'edition'", "'import'", "'weak'", "'public'", "'package'", + "'option'", "'optional'", "'required'", "'repeated'", "'oneof'", "'map'", + "'int32'", "'int64'", "'uint32'", "'uint64'", "'sint32'", "'sint64'", + "'fixed32'", "'fixed64'", "'sfixed32'", "'sfixed64'", "'bool'", "'string'", + "'double'", "'float'", "'bytes'", "'reserved'", "'extensions'", "'to'", + "'max'", "'enum'", "'message'", "'service'", "'extend'", "'rpc'", "'stream'", + "'returns'", "';'", "'='", "'('", "')'", "'['", "']'", "'{'", "'}'", + "'<'", "'>'", "'.'", "','", "':'", "'+'", "'-'", + } + staticData.SymbolicNames = []string{ + "", "SYNTAX", "EDITION", "IMPORT", "WEAK", "PUBLIC", "PACKAGE", "OPTION", + "OPTIONAL", "REQUIRED", "REPEATED", "ONEOF", "MAP", "INT32", "INT64", + "UINT32", "UINT64", "SINT32", "SINT64", "FIXED32", "FIXED64", "SFIXED32", + "SFIXED64", "BOOL", "STRING", "DOUBLE", "FLOAT", "BYTES", "RESERVED", + "EXTENSIONS", "TO", "MAX", "ENUM", "MESSAGE", "SERVICE", "EXTEND", "RPC", + "STREAM", "RETURNS", "SEMI", "EQ", "LP", "RP", "LB", "RB", "LC", "RC", + "LT", "GT", "DOT", "COMMA", "COLON", "PLUS", "MINUS", "STR_LIT_SINGLE", + "BOOL_LIT", "FLOAT_LIT", "INT_LIT", "IDENTIFIER", "WS", "LINE_COMMENT", + "COMMENT", + } + staticData.RuleNames = []string{ + "proto", "edition", "importStatement", "packageStatement", "optionStatement", + "optionName", "fieldLabel", "field", "fieldOptions", "fieldOption", + "fieldNumber", "oneof", "oneofField", "mapField", "keyType", "fieldType", + "reserved", "extensions", "ranges", "oneRange", "reservedFieldNames", + "topLevelDef", "enumDef", "enumBody", "enumElement", "enumField", "enumValueOptions", + "enumValueOption", "messageDef", "messageBody", "messageElement", "extendDef", + "serviceDef", "serviceElement", "rpc", "constant", "blockLit", "emptyStatement", + "ident", "fullIdent", "messageName", "enumName", "fieldName", "oneofName", + "serviceName", "rpcName", "messageType", "enumType", "intLit", "strLit", + "boolLit", "floatLit", "keywords", + } + staticData.PredictionContextCache = antlr.NewPredictionContextCache() + staticData.serializedATN = []int32{ + 4, 1, 61, 513, 2, 0, 7, 0, 2, 1, 7, 1, 2, 2, 7, 2, 2, 3, 7, 3, 2, 4, 7, + 4, 2, 5, 7, 5, 2, 6, 7, 6, 2, 7, 7, 7, 2, 8, 7, 8, 2, 9, 7, 9, 2, 10, 7, + 10, 2, 11, 7, 11, 2, 12, 7, 12, 2, 13, 7, 13, 2, 14, 7, 14, 2, 15, 7, 15, + 2, 16, 7, 16, 2, 17, 7, 17, 2, 18, 7, 18, 2, 19, 7, 19, 2, 20, 7, 20, 2, + 21, 7, 21, 2, 22, 7, 22, 2, 23, 7, 23, 2, 24, 7, 24, 2, 25, 7, 25, 2, 26, + 7, 26, 2, 27, 7, 27, 2, 28, 7, 28, 2, 29, 7, 29, 2, 30, 7, 30, 2, 31, 7, + 31, 2, 32, 7, 32, 2, 33, 7, 33, 2, 34, 7, 34, 2, 35, 7, 35, 2, 36, 7, 36, + 2, 37, 7, 37, 2, 38, 7, 38, 2, 39, 7, 39, 2, 40, 7, 40, 2, 41, 7, 41, 2, + 42, 7, 42, 2, 43, 7, 43, 2, 44, 7, 44, 2, 45, 7, 45, 2, 46, 7, 46, 2, 47, + 7, 47, 2, 48, 7, 48, 2, 49, 7, 49, 2, 50, 7, 50, 2, 51, 7, 51, 2, 52, 7, + 52, 1, 0, 3, 0, 108, 8, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 5, 0, 115, 8, + 0, 10, 0, 12, 0, 118, 9, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 2, 1, 2, 3, 2, 129, 8, 2, 1, 2, 1, 2, 1, 2, 1, 3, 1, 3, 1, 3, 1, 3, 1, + 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 4, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 1, 5, 3, + 5, 150, 8, 5, 3, 5, 152, 8, 5, 1, 6, 1, 6, 1, 7, 3, 7, 157, 8, 7, 1, 7, + 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 1, 7, 3, 7, 167, 8, 7, 1, 7, 1, 7, + 1, 8, 1, 8, 1, 8, 5, 8, 174, 8, 8, 10, 8, 12, 8, 177, 9, 8, 1, 9, 1, 9, + 1, 9, 1, 9, 1, 10, 1, 10, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 1, 11, 5, + 11, 191, 8, 11, 10, 11, 12, 11, 194, 9, 11, 1, 11, 1, 11, 1, 12, 1, 12, + 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 1, 12, 3, 12, 206, 8, 12, 1, 12, 1, + 12, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, 1, 13, + 1, 13, 1, 13, 1, 13, 3, 13, 223, 8, 13, 1, 13, 1, 13, 1, 14, 1, 14, 1, + 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, + 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 1, 15, 3, 15, 246, 8, 15, 1, 16, 1, + 16, 1, 16, 3, 16, 251, 8, 16, 1, 16, 1, 16, 1, 17, 1, 17, 1, 17, 1, 17, + 1, 17, 1, 17, 3, 17, 261, 8, 17, 1, 17, 1, 17, 1, 18, 1, 18, 1, 18, 5, + 18, 268, 8, 18, 10, 18, 12, 18, 271, 9, 18, 1, 19, 1, 19, 1, 19, 1, 19, + 3, 19, 277, 8, 19, 3, 19, 279, 8, 19, 1, 20, 1, 20, 1, 20, 5, 20, 284, + 8, 20, 10, 20, 12, 20, 287, 9, 20, 1, 21, 1, 21, 1, 21, 1, 21, 3, 21, 293, + 8, 21, 1, 22, 1, 22, 1, 22, 1, 22, 1, 23, 1, 23, 5, 23, 301, 8, 23, 10, + 23, 12, 23, 304, 9, 23, 1, 23, 1, 23, 1, 24, 1, 24, 1, 24, 1, 24, 3, 24, + 312, 8, 24, 1, 25, 1, 25, 1, 25, 3, 25, 317, 8, 25, 1, 25, 1, 25, 3, 25, + 321, 8, 25, 1, 25, 1, 25, 1, 26, 1, 26, 1, 26, 1, 26, 5, 26, 329, 8, 26, + 10, 26, 12, 26, 332, 9, 26, 1, 26, 1, 26, 1, 27, 1, 27, 1, 27, 1, 27, 1, + 28, 1, 28, 1, 28, 1, 28, 1, 29, 1, 29, 5, 29, 346, 8, 29, 10, 29, 12, 29, + 349, 9, 29, 1, 29, 1, 29, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, 30, 1, + 30, 1, 30, 1, 30, 1, 30, 3, 30, 363, 8, 30, 1, 31, 1, 31, 1, 31, 1, 31, + 1, 31, 5, 31, 370, 8, 31, 10, 31, 12, 31, 373, 9, 31, 1, 31, 1, 31, 1, + 32, 1, 32, 1, 32, 1, 32, 5, 32, 381, 8, 32, 10, 32, 12, 32, 384, 9, 32, + 1, 32, 1, 32, 1, 33, 1, 33, 1, 33, 3, 33, 391, 8, 33, 1, 34, 1, 34, 1, + 34, 1, 34, 3, 34, 397, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 3, 34, + 404, 8, 34, 1, 34, 1, 34, 1, 34, 1, 34, 1, 34, 5, 34, 411, 8, 34, 10, 34, + 12, 34, 414, 9, 34, 1, 34, 1, 34, 3, 34, 418, 8, 34, 1, 35, 1, 35, 3, 35, + 422, 8, 35, 1, 35, 1, 35, 3, 35, 426, 8, 35, 1, 35, 1, 35, 1, 35, 1, 35, + 3, 35, 432, 8, 35, 1, 36, 1, 36, 1, 36, 1, 36, 1, 36, 3, 36, 439, 8, 36, + 5, 36, 441, 8, 36, 10, 36, 12, 36, 444, 9, 36, 1, 36, 1, 36, 1, 37, 1, + 37, 1, 38, 1, 38, 3, 38, 452, 8, 38, 1, 39, 1, 39, 1, 39, 5, 39, 457, 8, + 39, 10, 39, 12, 39, 460, 9, 39, 1, 40, 1, 40, 1, 41, 1, 41, 1, 42, 1, 42, + 1, 43, 1, 43, 1, 44, 1, 44, 1, 45, 1, 45, 1, 46, 3, 46, 475, 8, 46, 1, + 46, 1, 46, 1, 46, 5, 46, 480, 8, 46, 10, 46, 12, 46, 483, 9, 46, 1, 46, + 1, 46, 1, 47, 3, 47, 488, 8, 47, 1, 47, 1, 47, 1, 47, 5, 47, 493, 8, 47, + 10, 47, 12, 47, 496, 9, 47, 1, 47, 1, 47, 1, 48, 1, 48, 1, 49, 4, 49, 503, + 8, 49, 11, 49, 12, 49, 504, 1, 50, 1, 50, 1, 51, 1, 51, 1, 52, 1, 52, 1, + 52, 0, 0, 53, 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, + 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, + 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, + 104, 0, 7, 1, 0, 1, 2, 1, 0, 4, 5, 1, 0, 8, 10, 1, 0, 13, 24, 1, 0, 52, + 53, 2, 0, 39, 39, 50, 50, 3, 0, 1, 8, 10, 38, 55, 55, 544, 0, 107, 1, 0, + 0, 0, 2, 121, 1, 0, 0, 0, 4, 126, 1, 0, 0, 0, 6, 133, 1, 0, 0, 0, 8, 137, + 1, 0, 0, 0, 10, 151, 1, 0, 0, 0, 12, 153, 1, 0, 0, 0, 14, 156, 1, 0, 0, + 0, 16, 170, 1, 0, 0, 0, 18, 178, 1, 0, 0, 0, 20, 182, 1, 0, 0, 0, 22, 184, + 1, 0, 0, 0, 24, 197, 1, 0, 0, 0, 26, 209, 1, 0, 0, 0, 28, 226, 1, 0, 0, + 0, 30, 245, 1, 0, 0, 0, 32, 247, 1, 0, 0, 0, 34, 254, 1, 0, 0, 0, 36, 264, + 1, 0, 0, 0, 38, 272, 1, 0, 0, 0, 40, 280, 1, 0, 0, 0, 42, 292, 1, 0, 0, + 0, 44, 294, 1, 0, 0, 0, 46, 298, 1, 0, 0, 0, 48, 311, 1, 0, 0, 0, 50, 313, + 1, 0, 0, 0, 52, 324, 1, 0, 0, 0, 54, 335, 1, 0, 0, 0, 56, 339, 1, 0, 0, + 0, 58, 343, 1, 0, 0, 0, 60, 362, 1, 0, 0, 0, 62, 364, 1, 0, 0, 0, 64, 376, + 1, 0, 0, 0, 66, 390, 1, 0, 0, 0, 68, 392, 1, 0, 0, 0, 70, 431, 1, 0, 0, + 0, 72, 433, 1, 0, 0, 0, 74, 447, 1, 0, 0, 0, 76, 451, 1, 0, 0, 0, 78, 453, + 1, 0, 0, 0, 80, 461, 1, 0, 0, 0, 82, 463, 1, 0, 0, 0, 84, 465, 1, 0, 0, + 0, 86, 467, 1, 0, 0, 0, 88, 469, 1, 0, 0, 0, 90, 471, 1, 0, 0, 0, 92, 474, + 1, 0, 0, 0, 94, 487, 1, 0, 0, 0, 96, 499, 1, 0, 0, 0, 98, 502, 1, 0, 0, + 0, 100, 506, 1, 0, 0, 0, 102, 508, 1, 0, 0, 0, 104, 510, 1, 0, 0, 0, 106, + 108, 3, 2, 1, 0, 107, 106, 1, 0, 0, 0, 107, 108, 1, 0, 0, 0, 108, 116, + 1, 0, 0, 0, 109, 115, 3, 4, 2, 0, 110, 115, 3, 6, 3, 0, 111, 115, 3, 8, + 4, 0, 112, 115, 3, 42, 21, 0, 113, 115, 3, 74, 37, 0, 114, 109, 1, 0, 0, + 0, 114, 110, 1, 0, 0, 0, 114, 111, 1, 0, 0, 0, 114, 112, 1, 0, 0, 0, 114, + 113, 1, 0, 0, 0, 115, 118, 1, 0, 0, 0, 116, 114, 1, 0, 0, 0, 116, 117, + 1, 0, 0, 0, 117, 119, 1, 0, 0, 0, 118, 116, 1, 0, 0, 0, 119, 120, 5, 0, + 0, 1, 120, 1, 1, 0, 0, 0, 121, 122, 7, 0, 0, 0, 122, 123, 5, 40, 0, 0, + 123, 124, 3, 98, 49, 0, 124, 125, 5, 39, 0, 0, 125, 3, 1, 0, 0, 0, 126, + 128, 5, 3, 0, 0, 127, 129, 7, 1, 0, 0, 128, 127, 1, 0, 0, 0, 128, 129, + 1, 0, 0, 0, 129, 130, 1, 0, 0, 0, 130, 131, 3, 98, 49, 0, 131, 132, 5, + 39, 0, 0, 132, 5, 1, 0, 0, 0, 133, 134, 5, 6, 0, 0, 134, 135, 3, 78, 39, + 0, 135, 136, 5, 39, 0, 0, 136, 7, 1, 0, 0, 0, 137, 138, 5, 7, 0, 0, 138, + 139, 3, 10, 5, 0, 139, 140, 5, 40, 0, 0, 140, 141, 3, 70, 35, 0, 141, 142, + 5, 39, 0, 0, 142, 9, 1, 0, 0, 0, 143, 152, 3, 78, 39, 0, 144, 145, 5, 41, + 0, 0, 145, 146, 3, 78, 39, 0, 146, 149, 5, 42, 0, 0, 147, 148, 5, 49, 0, + 0, 148, 150, 3, 78, 39, 0, 149, 147, 1, 0, 0, 0, 149, 150, 1, 0, 0, 0, + 150, 152, 1, 0, 0, 0, 151, 143, 1, 0, 0, 0, 151, 144, 1, 0, 0, 0, 152, + 11, 1, 0, 0, 0, 153, 154, 7, 2, 0, 0, 154, 13, 1, 0, 0, 0, 155, 157, 3, + 12, 6, 0, 156, 155, 1, 0, 0, 0, 156, 157, 1, 0, 0, 0, 157, 158, 1, 0, 0, + 0, 158, 159, 3, 30, 15, 0, 159, 160, 3, 84, 42, 0, 160, 161, 5, 40, 0, + 0, 161, 166, 3, 20, 10, 0, 162, 163, 5, 43, 0, 0, 163, 164, 3, 16, 8, 0, + 164, 165, 5, 44, 0, 0, 165, 167, 1, 0, 0, 0, 166, 162, 1, 0, 0, 0, 166, + 167, 1, 0, 0, 0, 167, 168, 1, 0, 0, 0, 168, 169, 5, 39, 0, 0, 169, 15, + 1, 0, 0, 0, 170, 175, 3, 18, 9, 0, 171, 172, 5, 50, 0, 0, 172, 174, 3, + 18, 9, 0, 173, 171, 1, 0, 0, 0, 174, 177, 1, 0, 0, 0, 175, 173, 1, 0, 0, + 0, 175, 176, 1, 0, 0, 0, 176, 17, 1, 0, 0, 0, 177, 175, 1, 0, 0, 0, 178, + 179, 3, 10, 5, 0, 179, 180, 5, 40, 0, 0, 180, 181, 3, 70, 35, 0, 181, 19, + 1, 0, 0, 0, 182, 183, 3, 96, 48, 0, 183, 21, 1, 0, 0, 0, 184, 185, 5, 11, + 0, 0, 185, 186, 3, 86, 43, 0, 186, 192, 5, 45, 0, 0, 187, 191, 3, 8, 4, + 0, 188, 191, 3, 24, 12, 0, 189, 191, 3, 74, 37, 0, 190, 187, 1, 0, 0, 0, + 190, 188, 1, 0, 0, 0, 190, 189, 1, 0, 0, 0, 191, 194, 1, 0, 0, 0, 192, + 190, 1, 0, 0, 0, 192, 193, 1, 0, 0, 0, 193, 195, 1, 0, 0, 0, 194, 192, + 1, 0, 0, 0, 195, 196, 5, 46, 0, 0, 196, 23, 1, 0, 0, 0, 197, 198, 3, 30, + 15, 0, 198, 199, 3, 84, 42, 0, 199, 200, 5, 40, 0, 0, 200, 205, 3, 20, + 10, 0, 201, 202, 5, 43, 0, 0, 202, 203, 3, 16, 8, 0, 203, 204, 5, 44, 0, + 0, 204, 206, 1, 0, 0, 0, 205, 201, 1, 0, 0, 0, 205, 206, 1, 0, 0, 0, 206, + 207, 1, 0, 0, 0, 207, 208, 5, 39, 0, 0, 208, 25, 1, 0, 0, 0, 209, 210, + 5, 12, 0, 0, 210, 211, 5, 47, 0, 0, 211, 212, 3, 28, 14, 0, 212, 213, 5, + 50, 0, 0, 213, 214, 3, 30, 15, 0, 214, 215, 5, 48, 0, 0, 215, 216, 3, 84, + 42, 0, 216, 217, 5, 40, 0, 0, 217, 222, 3, 20, 10, 0, 218, 219, 5, 43, + 0, 0, 219, 220, 3, 16, 8, 0, 220, 221, 5, 44, 0, 0, 221, 223, 1, 0, 0, + 0, 222, 218, 1, 0, 0, 0, 222, 223, 1, 0, 0, 0, 223, 224, 1, 0, 0, 0, 224, + 225, 5, 39, 0, 0, 225, 27, 1, 0, 0, 0, 226, 227, 7, 3, 0, 0, 227, 29, 1, + 0, 0, 0, 228, 246, 5, 25, 0, 0, 229, 246, 5, 26, 0, 0, 230, 246, 5, 13, + 0, 0, 231, 246, 5, 14, 0, 0, 232, 246, 5, 15, 0, 0, 233, 246, 5, 16, 0, + 0, 234, 246, 5, 17, 0, 0, 235, 246, 5, 18, 0, 0, 236, 246, 5, 19, 0, 0, + 237, 246, 5, 20, 0, 0, 238, 246, 5, 21, 0, 0, 239, 246, 5, 22, 0, 0, 240, + 246, 5, 23, 0, 0, 241, 246, 5, 24, 0, 0, 242, 246, 5, 27, 0, 0, 243, 246, + 3, 92, 46, 0, 244, 246, 3, 94, 47, 0, 245, 228, 1, 0, 0, 0, 245, 229, 1, + 0, 0, 0, 245, 230, 1, 0, 0, 0, 245, 231, 1, 0, 0, 0, 245, 232, 1, 0, 0, + 0, 245, 233, 1, 0, 0, 0, 245, 234, 1, 0, 0, 0, 245, 235, 1, 0, 0, 0, 245, + 236, 1, 0, 0, 0, 245, 237, 1, 0, 0, 0, 245, 238, 1, 0, 0, 0, 245, 239, + 1, 0, 0, 0, 245, 240, 1, 0, 0, 0, 245, 241, 1, 0, 0, 0, 245, 242, 1, 0, + 0, 0, 245, 243, 1, 0, 0, 0, 245, 244, 1, 0, 0, 0, 246, 31, 1, 0, 0, 0, + 247, 250, 5, 28, 0, 0, 248, 251, 3, 36, 18, 0, 249, 251, 3, 40, 20, 0, + 250, 248, 1, 0, 0, 0, 250, 249, 1, 0, 0, 0, 251, 252, 1, 0, 0, 0, 252, + 253, 5, 39, 0, 0, 253, 33, 1, 0, 0, 0, 254, 255, 5, 29, 0, 0, 255, 260, + 3, 36, 18, 0, 256, 257, 5, 43, 0, 0, 257, 258, 3, 16, 8, 0, 258, 259, 5, + 44, 0, 0, 259, 261, 1, 0, 0, 0, 260, 256, 1, 0, 0, 0, 260, 261, 1, 0, 0, + 0, 261, 262, 1, 0, 0, 0, 262, 263, 5, 39, 0, 0, 263, 35, 1, 0, 0, 0, 264, + 269, 3, 38, 19, 0, 265, 266, 5, 50, 0, 0, 266, 268, 3, 38, 19, 0, 267, + 265, 1, 0, 0, 0, 268, 271, 1, 0, 0, 0, 269, 267, 1, 0, 0, 0, 269, 270, + 1, 0, 0, 0, 270, 37, 1, 0, 0, 0, 271, 269, 1, 0, 0, 0, 272, 278, 3, 96, + 48, 0, 273, 276, 5, 30, 0, 0, 274, 277, 3, 96, 48, 0, 275, 277, 5, 31, + 0, 0, 276, 274, 1, 0, 0, 0, 276, 275, 1, 0, 0, 0, 277, 279, 1, 0, 0, 0, + 278, 273, 1, 0, 0, 0, 278, 279, 1, 0, 0, 0, 279, 39, 1, 0, 0, 0, 280, 285, + 3, 98, 49, 0, 281, 282, 5, 50, 0, 0, 282, 284, 3, 98, 49, 0, 283, 281, + 1, 0, 0, 0, 284, 287, 1, 0, 0, 0, 285, 283, 1, 0, 0, 0, 285, 286, 1, 0, + 0, 0, 286, 41, 1, 0, 0, 0, 287, 285, 1, 0, 0, 0, 288, 293, 3, 56, 28, 0, + 289, 293, 3, 44, 22, 0, 290, 293, 3, 62, 31, 0, 291, 293, 3, 64, 32, 0, + 292, 288, 1, 0, 0, 0, 292, 289, 1, 0, 0, 0, 292, 290, 1, 0, 0, 0, 292, + 291, 1, 0, 0, 0, 293, 43, 1, 0, 0, 0, 294, 295, 5, 32, 0, 0, 295, 296, + 3, 82, 41, 0, 296, 297, 3, 46, 23, 0, 297, 45, 1, 0, 0, 0, 298, 302, 5, + 45, 0, 0, 299, 301, 3, 48, 24, 0, 300, 299, 1, 0, 0, 0, 301, 304, 1, 0, + 0, 0, 302, 300, 1, 0, 0, 0, 302, 303, 1, 0, 0, 0, 303, 305, 1, 0, 0, 0, + 304, 302, 1, 0, 0, 0, 305, 306, 5, 46, 0, 0, 306, 47, 1, 0, 0, 0, 307, + 312, 3, 8, 4, 0, 308, 312, 3, 50, 25, 0, 309, 312, 3, 32, 16, 0, 310, 312, + 3, 74, 37, 0, 311, 307, 1, 0, 0, 0, 311, 308, 1, 0, 0, 0, 311, 309, 1, + 0, 0, 0, 311, 310, 1, 0, 0, 0, 312, 49, 1, 0, 0, 0, 313, 314, 3, 76, 38, + 0, 314, 316, 5, 40, 0, 0, 315, 317, 5, 53, 0, 0, 316, 315, 1, 0, 0, 0, + 316, 317, 1, 0, 0, 0, 317, 318, 1, 0, 0, 0, 318, 320, 3, 96, 48, 0, 319, + 321, 3, 52, 26, 0, 320, 319, 1, 0, 0, 0, 320, 321, 1, 0, 0, 0, 321, 322, + 1, 0, 0, 0, 322, 323, 5, 39, 0, 0, 323, 51, 1, 0, 0, 0, 324, 325, 5, 43, + 0, 0, 325, 330, 3, 54, 27, 0, 326, 327, 5, 50, 0, 0, 327, 329, 3, 54, 27, + 0, 328, 326, 1, 0, 0, 0, 329, 332, 1, 0, 0, 0, 330, 328, 1, 0, 0, 0, 330, + 331, 1, 0, 0, 0, 331, 333, 1, 0, 0, 0, 332, 330, 1, 0, 0, 0, 333, 334, + 5, 44, 0, 0, 334, 53, 1, 0, 0, 0, 335, 336, 3, 10, 5, 0, 336, 337, 5, 40, + 0, 0, 337, 338, 3, 70, 35, 0, 338, 55, 1, 0, 0, 0, 339, 340, 5, 33, 0, + 0, 340, 341, 3, 80, 40, 0, 341, 342, 3, 58, 29, 0, 342, 57, 1, 0, 0, 0, + 343, 347, 5, 45, 0, 0, 344, 346, 3, 60, 30, 0, 345, 344, 1, 0, 0, 0, 346, + 349, 1, 0, 0, 0, 347, 345, 1, 0, 0, 0, 347, 348, 1, 0, 0, 0, 348, 350, + 1, 0, 0, 0, 349, 347, 1, 0, 0, 0, 350, 351, 5, 46, 0, 0, 351, 59, 1, 0, + 0, 0, 352, 363, 3, 14, 7, 0, 353, 363, 3, 44, 22, 0, 354, 363, 3, 56, 28, + 0, 355, 363, 3, 62, 31, 0, 356, 363, 3, 8, 4, 0, 357, 363, 3, 22, 11, 0, + 358, 363, 3, 26, 13, 0, 359, 363, 3, 32, 16, 0, 360, 363, 3, 34, 17, 0, + 361, 363, 3, 74, 37, 0, 362, 352, 1, 0, 0, 0, 362, 353, 1, 0, 0, 0, 362, + 354, 1, 0, 0, 0, 362, 355, 1, 0, 0, 0, 362, 356, 1, 0, 0, 0, 362, 357, + 1, 0, 0, 0, 362, 358, 1, 0, 0, 0, 362, 359, 1, 0, 0, 0, 362, 360, 1, 0, + 0, 0, 362, 361, 1, 0, 0, 0, 363, 61, 1, 0, 0, 0, 364, 365, 5, 35, 0, 0, + 365, 366, 3, 92, 46, 0, 366, 371, 5, 45, 0, 0, 367, 370, 3, 14, 7, 0, 368, + 370, 3, 74, 37, 0, 369, 367, 1, 0, 0, 0, 369, 368, 1, 0, 0, 0, 370, 373, + 1, 0, 0, 0, 371, 369, 1, 0, 0, 0, 371, 372, 1, 0, 0, 0, 372, 374, 1, 0, + 0, 0, 373, 371, 1, 0, 0, 0, 374, 375, 5, 46, 0, 0, 375, 63, 1, 0, 0, 0, + 376, 377, 5, 34, 0, 0, 377, 378, 3, 88, 44, 0, 378, 382, 5, 45, 0, 0, 379, + 381, 3, 66, 33, 0, 380, 379, 1, 0, 0, 0, 381, 384, 1, 0, 0, 0, 382, 380, + 1, 0, 0, 0, 382, 383, 1, 0, 0, 0, 383, 385, 1, 0, 0, 0, 384, 382, 1, 0, + 0, 0, 385, 386, 5, 46, 0, 0, 386, 65, 1, 0, 0, 0, 387, 391, 3, 8, 4, 0, + 388, 391, 3, 68, 34, 0, 389, 391, 3, 74, 37, 0, 390, 387, 1, 0, 0, 0, 390, + 388, 1, 0, 0, 0, 390, 389, 1, 0, 0, 0, 391, 67, 1, 0, 0, 0, 392, 393, 5, + 36, 0, 0, 393, 394, 3, 90, 45, 0, 394, 396, 5, 41, 0, 0, 395, 397, 5, 37, + 0, 0, 396, 395, 1, 0, 0, 0, 396, 397, 1, 0, 0, 0, 397, 398, 1, 0, 0, 0, + 398, 399, 3, 92, 46, 0, 399, 400, 5, 42, 0, 0, 400, 401, 5, 38, 0, 0, 401, + 403, 5, 41, 0, 0, 402, 404, 5, 37, 0, 0, 403, 402, 1, 0, 0, 0, 403, 404, + 1, 0, 0, 0, 404, 405, 1, 0, 0, 0, 405, 406, 3, 92, 46, 0, 406, 417, 5, + 42, 0, 0, 407, 412, 5, 45, 0, 0, 408, 411, 3, 8, 4, 0, 409, 411, 3, 74, + 37, 0, 410, 408, 1, 0, 0, 0, 410, 409, 1, 0, 0, 0, 411, 414, 1, 0, 0, 0, + 412, 410, 1, 0, 0, 0, 412, 413, 1, 0, 0, 0, 413, 415, 1, 0, 0, 0, 414, + 412, 1, 0, 0, 0, 415, 418, 5, 46, 0, 0, 416, 418, 5, 39, 0, 0, 417, 407, + 1, 0, 0, 0, 417, 416, 1, 0, 0, 0, 418, 69, 1, 0, 0, 0, 419, 432, 3, 78, + 39, 0, 420, 422, 7, 4, 0, 0, 421, 420, 1, 0, 0, 0, 421, 422, 1, 0, 0, 0, + 422, 423, 1, 0, 0, 0, 423, 432, 3, 96, 48, 0, 424, 426, 7, 4, 0, 0, 425, + 424, 1, 0, 0, 0, 425, 426, 1, 0, 0, 0, 426, 427, 1, 0, 0, 0, 427, 432, + 3, 102, 51, 0, 428, 432, 3, 98, 49, 0, 429, 432, 3, 100, 50, 0, 430, 432, + 3, 72, 36, 0, 431, 419, 1, 0, 0, 0, 431, 421, 1, 0, 0, 0, 431, 425, 1, + 0, 0, 0, 431, 428, 1, 0, 0, 0, 431, 429, 1, 0, 0, 0, 431, 430, 1, 0, 0, + 0, 432, 71, 1, 0, 0, 0, 433, 442, 5, 45, 0, 0, 434, 435, 3, 76, 38, 0, + 435, 436, 5, 51, 0, 0, 436, 438, 3, 70, 35, 0, 437, 439, 7, 5, 0, 0, 438, + 437, 1, 0, 0, 0, 438, 439, 1, 0, 0, 0, 439, 441, 1, 0, 0, 0, 440, 434, + 1, 0, 0, 0, 441, 444, 1, 0, 0, 0, 442, 440, 1, 0, 0, 0, 442, 443, 1, 0, + 0, 0, 443, 445, 1, 0, 0, 0, 444, 442, 1, 0, 0, 0, 445, 446, 5, 46, 0, 0, + 446, 73, 1, 0, 0, 0, 447, 448, 5, 39, 0, 0, 448, 75, 1, 0, 0, 0, 449, 452, + 5, 58, 0, 0, 450, 452, 3, 104, 52, 0, 451, 449, 1, 0, 0, 0, 451, 450, 1, + 0, 0, 0, 452, 77, 1, 0, 0, 0, 453, 458, 3, 76, 38, 0, 454, 455, 5, 49, + 0, 0, 455, 457, 3, 76, 38, 0, 456, 454, 1, 0, 0, 0, 457, 460, 1, 0, 0, + 0, 458, 456, 1, 0, 0, 0, 458, 459, 1, 0, 0, 0, 459, 79, 1, 0, 0, 0, 460, + 458, 1, 0, 0, 0, 461, 462, 3, 76, 38, 0, 462, 81, 1, 0, 0, 0, 463, 464, + 3, 76, 38, 0, 464, 83, 1, 0, 0, 0, 465, 466, 3, 76, 38, 0, 466, 85, 1, + 0, 0, 0, 467, 468, 3, 76, 38, 0, 468, 87, 1, 0, 0, 0, 469, 470, 3, 76, + 38, 0, 470, 89, 1, 0, 0, 0, 471, 472, 3, 76, 38, 0, 472, 91, 1, 0, 0, 0, + 473, 475, 5, 49, 0, 0, 474, 473, 1, 0, 0, 0, 474, 475, 1, 0, 0, 0, 475, + 481, 1, 0, 0, 0, 476, 477, 3, 76, 38, 0, 477, 478, 5, 49, 0, 0, 478, 480, + 1, 0, 0, 0, 479, 476, 1, 0, 0, 0, 480, 483, 1, 0, 0, 0, 481, 479, 1, 0, + 0, 0, 481, 482, 1, 0, 0, 0, 482, 484, 1, 0, 0, 0, 483, 481, 1, 0, 0, 0, + 484, 485, 3, 80, 40, 0, 485, 93, 1, 0, 0, 0, 486, 488, 5, 49, 0, 0, 487, + 486, 1, 0, 0, 0, 487, 488, 1, 0, 0, 0, 488, 494, 1, 0, 0, 0, 489, 490, + 3, 76, 38, 0, 490, 491, 5, 49, 0, 0, 491, 493, 1, 0, 0, 0, 492, 489, 1, + 0, 0, 0, 493, 496, 1, 0, 0, 0, 494, 492, 1, 0, 0, 0, 494, 495, 1, 0, 0, + 0, 495, 497, 1, 0, 0, 0, 496, 494, 1, 0, 0, 0, 497, 498, 3, 82, 41, 0, + 498, 95, 1, 0, 0, 0, 499, 500, 5, 57, 0, 0, 500, 97, 1, 0, 0, 0, 501, 503, + 5, 54, 0, 0, 502, 501, 1, 0, 0, 0, 503, 504, 1, 0, 0, 0, 504, 502, 1, 0, + 0, 0, 504, 505, 1, 0, 0, 0, 505, 99, 1, 0, 0, 0, 506, 507, 5, 55, 0, 0, + 507, 101, 1, 0, 0, 0, 508, 509, 5, 56, 0, 0, 509, 103, 1, 0, 0, 0, 510, + 511, 7, 6, 0, 0, 511, 105, 1, 0, 0, 0, 49, 107, 114, 116, 128, 149, 151, + 156, 166, 175, 190, 192, 205, 222, 245, 250, 260, 269, 276, 278, 285, 292, + 302, 311, 316, 320, 330, 347, 362, 369, 371, 382, 390, 396, 403, 410, 412, + 417, 421, 425, 431, 438, 442, 451, 458, 474, 481, 487, 494, 504, + } + deserializer := antlr.NewATNDeserializer(nil) + staticData.atn = deserializer.Deserialize(staticData.serializedATN) + atn := staticData.atn + staticData.decisionToDFA = make([]*antlr.DFA, len(atn.DecisionToState)) + decisionToDFA := staticData.decisionToDFA + for index, state := range atn.DecisionToState { + decisionToDFA[index] = antlr.NewDFA(state, index) + } +} + +// ProtobufParserInit initializes any static state used to implement ProtobufParser. By default the +// static state used to implement the parser is lazily initialized during the first call to +// NewProtobufParser(). You can call this function if you wish to initialize the static state ahead +// of time. +func ProtobufParserInit() { + staticData := &ProtobufParserStaticData + staticData.once.Do(protobufParserInit) +} + +// NewProtobufParser produces a new parser instance for the optional input antlr.TokenStream. +func NewProtobufParser(input antlr.TokenStream) *ProtobufParser { + ProtobufParserInit() + this := new(ProtobufParser) + this.BaseParser = antlr.NewBaseParser(input) + staticData := &ProtobufParserStaticData + this.Interpreter = antlr.NewParserATNSimulator(this, staticData.atn, staticData.decisionToDFA, staticData.PredictionContextCache) + this.RuleNames = staticData.RuleNames + this.LiteralNames = staticData.LiteralNames + this.SymbolicNames = staticData.SymbolicNames + this.GrammarFileName = "Protobuf.g4" + + return this +} + +// ProtobufParser tokens. +const ( + ProtobufParserEOF = antlr.TokenEOF + ProtobufParserSYNTAX = 1 + ProtobufParserEDITION = 2 + ProtobufParserIMPORT = 3 + ProtobufParserWEAK = 4 + ProtobufParserPUBLIC = 5 + ProtobufParserPACKAGE = 6 + ProtobufParserOPTION = 7 + ProtobufParserOPTIONAL = 8 + ProtobufParserREQUIRED = 9 + ProtobufParserREPEATED = 10 + ProtobufParserONEOF = 11 + ProtobufParserMAP = 12 + ProtobufParserINT32 = 13 + ProtobufParserINT64 = 14 + ProtobufParserUINT32 = 15 + ProtobufParserUINT64 = 16 + ProtobufParserSINT32 = 17 + ProtobufParserSINT64 = 18 + ProtobufParserFIXED32 = 19 + ProtobufParserFIXED64 = 20 + ProtobufParserSFIXED32 = 21 + ProtobufParserSFIXED64 = 22 + ProtobufParserBOOL = 23 + ProtobufParserSTRING = 24 + ProtobufParserDOUBLE = 25 + ProtobufParserFLOAT = 26 + ProtobufParserBYTES = 27 + ProtobufParserRESERVED = 28 + ProtobufParserEXTENSIONS = 29 + ProtobufParserTO = 30 + ProtobufParserMAX = 31 + ProtobufParserENUM = 32 + ProtobufParserMESSAGE = 33 + ProtobufParserSERVICE = 34 + ProtobufParserEXTEND = 35 + ProtobufParserRPC = 36 + ProtobufParserSTREAM = 37 + ProtobufParserRETURNS = 38 + ProtobufParserSEMI = 39 + ProtobufParserEQ = 40 + ProtobufParserLP = 41 + ProtobufParserRP = 42 + ProtobufParserLB = 43 + ProtobufParserRB = 44 + ProtobufParserLC = 45 + ProtobufParserRC = 46 + ProtobufParserLT = 47 + ProtobufParserGT = 48 + ProtobufParserDOT = 49 + ProtobufParserCOMMA = 50 + ProtobufParserCOLON = 51 + ProtobufParserPLUS = 52 + ProtobufParserMINUS = 53 + ProtobufParserSTR_LIT_SINGLE = 54 + ProtobufParserBOOL_LIT = 55 + ProtobufParserFLOAT_LIT = 56 + ProtobufParserINT_LIT = 57 + ProtobufParserIDENTIFIER = 58 + ProtobufParserWS = 59 + ProtobufParserLINE_COMMENT = 60 + ProtobufParserCOMMENT = 61 +) + +// ProtobufParser rules. +const ( + ProtobufParserRULE_proto = 0 + ProtobufParserRULE_edition = 1 + ProtobufParserRULE_importStatement = 2 + ProtobufParserRULE_packageStatement = 3 + ProtobufParserRULE_optionStatement = 4 + ProtobufParserRULE_optionName = 5 + ProtobufParserRULE_fieldLabel = 6 + ProtobufParserRULE_field = 7 + ProtobufParserRULE_fieldOptions = 8 + ProtobufParserRULE_fieldOption = 9 + ProtobufParserRULE_fieldNumber = 10 + ProtobufParserRULE_oneof = 11 + ProtobufParserRULE_oneofField = 12 + ProtobufParserRULE_mapField = 13 + ProtobufParserRULE_keyType = 14 + ProtobufParserRULE_fieldType = 15 + ProtobufParserRULE_reserved = 16 + ProtobufParserRULE_extensions = 17 + ProtobufParserRULE_ranges = 18 + ProtobufParserRULE_oneRange = 19 + ProtobufParserRULE_reservedFieldNames = 20 + ProtobufParserRULE_topLevelDef = 21 + ProtobufParserRULE_enumDef = 22 + ProtobufParserRULE_enumBody = 23 + ProtobufParserRULE_enumElement = 24 + ProtobufParserRULE_enumField = 25 + ProtobufParserRULE_enumValueOptions = 26 + ProtobufParserRULE_enumValueOption = 27 + ProtobufParserRULE_messageDef = 28 + ProtobufParserRULE_messageBody = 29 + ProtobufParserRULE_messageElement = 30 + ProtobufParserRULE_extendDef = 31 + ProtobufParserRULE_serviceDef = 32 + ProtobufParserRULE_serviceElement = 33 + ProtobufParserRULE_rpc = 34 + ProtobufParserRULE_constant = 35 + ProtobufParserRULE_blockLit = 36 + ProtobufParserRULE_emptyStatement = 37 + ProtobufParserRULE_ident = 38 + ProtobufParserRULE_fullIdent = 39 + ProtobufParserRULE_messageName = 40 + ProtobufParserRULE_enumName = 41 + ProtobufParserRULE_fieldName = 42 + ProtobufParserRULE_oneofName = 43 + ProtobufParserRULE_serviceName = 44 + ProtobufParserRULE_rpcName = 45 + ProtobufParserRULE_messageType = 46 + ProtobufParserRULE_enumType = 47 + ProtobufParserRULE_intLit = 48 + ProtobufParserRULE_strLit = 49 + ProtobufParserRULE_boolLit = 50 + ProtobufParserRULE_floatLit = 51 + ProtobufParserRULE_keywords = 52 +) + +// IProtoContext is an interface to support dynamic dispatch. +type IProtoContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EOF() antlr.TerminalNode + Edition() IEditionContext + AllImportStatement() []IImportStatementContext + ImportStatement(i int) IImportStatementContext + AllPackageStatement() []IPackageStatementContext + PackageStatement(i int) IPackageStatementContext + AllOptionStatement() []IOptionStatementContext + OptionStatement(i int) IOptionStatementContext + AllTopLevelDef() []ITopLevelDefContext + TopLevelDef(i int) ITopLevelDefContext + AllEmptyStatement() []IEmptyStatementContext + EmptyStatement(i int) IEmptyStatementContext + + // IsProtoContext differentiates from other interfaces. + IsProtoContext() +} + +type ProtoContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyProtoContext() *ProtoContext { + var p = new(ProtoContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_proto + return p +} + +func InitEmptyProtoContext(p *ProtoContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_proto +} + +func (*ProtoContext) IsProtoContext() {} + +func NewProtoContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ProtoContext { + var p = new(ProtoContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_proto + + return p +} + +func (s *ProtoContext) GetParser() antlr.Parser { return s.parser } + +func (s *ProtoContext) EOF() antlr.TerminalNode { + return s.GetToken(ProtobufParserEOF, 0) +} + +func (s *ProtoContext) Edition() IEditionContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEditionContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEditionContext) +} + +func (s *ProtoContext) AllImportStatement() []IImportStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IImportStatementContext); ok { + len++ + } + } + + tst := make([]IImportStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IImportStatementContext); ok { + tst[i] = t.(IImportStatementContext) + i++ + } + } + + return tst +} + +func (s *ProtoContext) ImportStatement(i int) IImportStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IImportStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IImportStatementContext) +} + +func (s *ProtoContext) AllPackageStatement() []IPackageStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IPackageStatementContext); ok { + len++ + } + } + + tst := make([]IPackageStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IPackageStatementContext); ok { + tst[i] = t.(IPackageStatementContext) + i++ + } + } + + return tst +} + +func (s *ProtoContext) PackageStatement(i int) IPackageStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IPackageStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IPackageStatementContext) +} + +func (s *ProtoContext) AllOptionStatement() []IOptionStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOptionStatementContext); ok { + len++ + } + } + + tst := make([]IOptionStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOptionStatementContext); ok { + tst[i] = t.(IOptionStatementContext) + i++ + } + } + + return tst +} + +func (s *ProtoContext) OptionStatement(i int) IOptionStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *ProtoContext) AllTopLevelDef() []ITopLevelDefContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(ITopLevelDefContext); ok { + len++ + } + } + + tst := make([]ITopLevelDefContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(ITopLevelDefContext); ok { + tst[i] = t.(ITopLevelDefContext) + i++ + } + } + + return tst +} + +func (s *ProtoContext) TopLevelDef(i int) ITopLevelDefContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(ITopLevelDefContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(ITopLevelDefContext) +} + +func (s *ProtoContext) AllEmptyStatement() []IEmptyStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEmptyStatementContext); ok { + len++ + } + } + + tst := make([]IEmptyStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEmptyStatementContext); ok { + tst[i] = t.(IEmptyStatementContext) + i++ + } + } + + return tst +} + +func (s *ProtoContext) EmptyStatement(i int) IEmptyStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *ProtoContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ProtoContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ProtoContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterProto(s) + } +} + +func (s *ProtoContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitProto(s) + } +} + +func (p *ProtobufParser) Proto() (localctx IProtoContext) { + localctx = NewProtoContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 0, ProtobufParserRULE_proto) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(107) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserSYNTAX || _la == ProtobufParserEDITION { + { + p.SetState(106) + p.Edition() + } + + } + p.SetState(116) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&614180323528) != 0 { + p.SetState(114) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserIMPORT: + { + p.SetState(109) + p.ImportStatement() + } + + case ProtobufParserPACKAGE: + { + p.SetState(110) + p.PackageStatement() + } + + case ProtobufParserOPTION: + { + p.SetState(111) + p.OptionStatement() + } + + case ProtobufParserENUM, ProtobufParserMESSAGE, ProtobufParserSERVICE, ProtobufParserEXTEND: + { + p.SetState(112) + p.TopLevelDef() + } + + case ProtobufParserSEMI: + { + p.SetState(113) + p.EmptyStatement() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + p.SetState(118) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(119) + p.Match(ProtobufParserEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEditionContext is an interface to support dynamic dispatch. +type IEditionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EQ() antlr.TerminalNode + StrLit() IStrLitContext + SEMI() antlr.TerminalNode + SYNTAX() antlr.TerminalNode + EDITION() antlr.TerminalNode + + // IsEditionContext differentiates from other interfaces. + IsEditionContext() +} + +type EditionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEditionContext() *EditionContext { + var p = new(EditionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_edition + return p +} + +func InitEmptyEditionContext(p *EditionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_edition +} + +func (*EditionContext) IsEditionContext() {} + +func NewEditionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EditionContext { + var p = new(EditionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_edition + + return p +} + +func (s *EditionContext) GetParser() antlr.Parser { return s.parser } + +func (s *EditionContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *EditionContext) StrLit() IStrLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStrLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStrLitContext) +} + +func (s *EditionContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *EditionContext) SYNTAX() antlr.TerminalNode { + return s.GetToken(ProtobufParserSYNTAX, 0) +} + +func (s *EditionContext) EDITION() antlr.TerminalNode { + return s.GetToken(ProtobufParserEDITION, 0) +} + +func (s *EditionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EditionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EditionContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEdition(s) + } +} + +func (s *EditionContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEdition(s) + } +} + +func (p *ProtobufParser) Edition() (localctx IEditionContext) { + localctx = NewEditionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 2, ProtobufParserRULE_edition) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(121) + _la = p.GetTokenStream().LA(1) + + if !(_la == ProtobufParserSYNTAX || _la == ProtobufParserEDITION) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + { + p.SetState(122) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(123) + p.StrLit() + } + { + p.SetState(124) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IImportStatementContext is an interface to support dynamic dispatch. +type IImportStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IMPORT() antlr.TerminalNode + StrLit() IStrLitContext + SEMI() antlr.TerminalNode + WEAK() antlr.TerminalNode + PUBLIC() antlr.TerminalNode + + // IsImportStatementContext differentiates from other interfaces. + IsImportStatementContext() +} + +type ImportStatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyImportStatementContext() *ImportStatementContext { + var p = new(ImportStatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_importStatement + return p +} + +func InitEmptyImportStatementContext(p *ImportStatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_importStatement +} + +func (*ImportStatementContext) IsImportStatementContext() {} + +func NewImportStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ImportStatementContext { + var p = new(ImportStatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_importStatement + + return p +} + +func (s *ImportStatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *ImportStatementContext) IMPORT() antlr.TerminalNode { + return s.GetToken(ProtobufParserIMPORT, 0) +} + +func (s *ImportStatementContext) StrLit() IStrLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStrLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStrLitContext) +} + +func (s *ImportStatementContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *ImportStatementContext) WEAK() antlr.TerminalNode { + return s.GetToken(ProtobufParserWEAK, 0) +} + +func (s *ImportStatementContext) PUBLIC() antlr.TerminalNode { + return s.GetToken(ProtobufParserPUBLIC, 0) +} + +func (s *ImportStatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ImportStatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ImportStatementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterImportStatement(s) + } +} + +func (s *ImportStatementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitImportStatement(s) + } +} + +func (p *ProtobufParser) ImportStatement() (localctx IImportStatementContext) { + localctx = NewImportStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 4, ProtobufParserRULE_importStatement) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(126) + p.Match(ProtobufParserIMPORT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(128) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserWEAK || _la == ProtobufParserPUBLIC { + { + p.SetState(127) + _la = p.GetTokenStream().LA(1) + + if !(_la == ProtobufParserWEAK || _la == ProtobufParserPUBLIC) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + { + p.SetState(130) + p.StrLit() + } + { + p.SetState(131) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IPackageStatementContext is an interface to support dynamic dispatch. +type IPackageStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + PACKAGE() antlr.TerminalNode + FullIdent() IFullIdentContext + SEMI() antlr.TerminalNode + + // IsPackageStatementContext differentiates from other interfaces. + IsPackageStatementContext() +} + +type PackageStatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyPackageStatementContext() *PackageStatementContext { + var p = new(PackageStatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_packageStatement + return p +} + +func InitEmptyPackageStatementContext(p *PackageStatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_packageStatement +} + +func (*PackageStatementContext) IsPackageStatementContext() {} + +func NewPackageStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *PackageStatementContext { + var p = new(PackageStatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_packageStatement + + return p +} + +func (s *PackageStatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *PackageStatementContext) PACKAGE() antlr.TerminalNode { + return s.GetToken(ProtobufParserPACKAGE, 0) +} + +func (s *PackageStatementContext) FullIdent() IFullIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFullIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFullIdentContext) +} + +func (s *PackageStatementContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *PackageStatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *PackageStatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *PackageStatementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterPackageStatement(s) + } +} + +func (s *PackageStatementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitPackageStatement(s) + } +} + +func (p *ProtobufParser) PackageStatement() (localctx IPackageStatementContext) { + localctx = NewPackageStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 6, ProtobufParserRULE_packageStatement) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(133) + p.Match(ProtobufParserPACKAGE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(134) + p.FullIdent() + } + { + p.SetState(135) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOptionStatementContext is an interface to support dynamic dispatch. +type IOptionStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OPTION() antlr.TerminalNode + OptionName() IOptionNameContext + EQ() antlr.TerminalNode + Constant() IConstantContext + SEMI() antlr.TerminalNode + + // IsOptionStatementContext differentiates from other interfaces. + IsOptionStatementContext() +} + +type OptionStatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOptionStatementContext() *OptionStatementContext { + var p = new(OptionStatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_optionStatement + return p +} + +func InitEmptyOptionStatementContext(p *OptionStatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_optionStatement +} + +func (*OptionStatementContext) IsOptionStatementContext() {} + +func NewOptionStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OptionStatementContext { + var p = new(OptionStatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_optionStatement + + return p +} + +func (s *OptionStatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *OptionStatementContext) OPTION() antlr.TerminalNode { + return s.GetToken(ProtobufParserOPTION, 0) +} + +func (s *OptionStatementContext) OptionName() IOptionNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionNameContext) +} + +func (s *OptionStatementContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *OptionStatementContext) Constant() IConstantContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConstantContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IConstantContext) +} + +func (s *OptionStatementContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *OptionStatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OptionStatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OptionStatementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOptionStatement(s) + } +} + +func (s *OptionStatementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOptionStatement(s) + } +} + +func (p *ProtobufParser) OptionStatement() (localctx IOptionStatementContext) { + localctx = NewOptionStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 8, ProtobufParserRULE_optionStatement) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(137) + p.Match(ProtobufParserOPTION) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(138) + p.OptionName() + } + { + p.SetState(139) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(140) + p.Constant() + } + { + p.SetState(141) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOptionNameContext is an interface to support dynamic dispatch. +type IOptionNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllFullIdent() []IFullIdentContext + FullIdent(i int) IFullIdentContext + LP() antlr.TerminalNode + RP() antlr.TerminalNode + DOT() antlr.TerminalNode + + // IsOptionNameContext differentiates from other interfaces. + IsOptionNameContext() +} + +type OptionNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOptionNameContext() *OptionNameContext { + var p = new(OptionNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_optionName + return p +} + +func InitEmptyOptionNameContext(p *OptionNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_optionName +} + +func (*OptionNameContext) IsOptionNameContext() {} + +func NewOptionNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OptionNameContext { + var p = new(OptionNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_optionName + + return p +} + +func (s *OptionNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *OptionNameContext) AllFullIdent() []IFullIdentContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IFullIdentContext); ok { + len++ + } + } + + tst := make([]IFullIdentContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IFullIdentContext); ok { + tst[i] = t.(IFullIdentContext) + i++ + } + } + + return tst +} + +func (s *OptionNameContext) FullIdent(i int) IFullIdentContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFullIdentContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IFullIdentContext) +} + +func (s *OptionNameContext) LP() antlr.TerminalNode { + return s.GetToken(ProtobufParserLP, 0) +} + +func (s *OptionNameContext) RP() antlr.TerminalNode { + return s.GetToken(ProtobufParserRP, 0) +} + +func (s *OptionNameContext) DOT() antlr.TerminalNode { + return s.GetToken(ProtobufParserDOT, 0) +} + +func (s *OptionNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OptionNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OptionNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOptionName(s) + } +} + +func (s *OptionNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOptionName(s) + } +} + +func (p *ProtobufParser) OptionName() (localctx IOptionNameContext) { + localctx = NewOptionNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 10, ProtobufParserRULE_optionName) + var _la int + + p.SetState(151) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserSYNTAX, ProtobufParserEDITION, ProtobufParserIMPORT, ProtobufParserWEAK, ProtobufParserPUBLIC, ProtobufParserPACKAGE, ProtobufParserOPTION, ProtobufParserOPTIONAL, ProtobufParserREPEATED, ProtobufParserONEOF, ProtobufParserMAP, ProtobufParserINT32, ProtobufParserINT64, ProtobufParserUINT32, ProtobufParserUINT64, ProtobufParserSINT32, ProtobufParserSINT64, ProtobufParserFIXED32, ProtobufParserFIXED64, ProtobufParserSFIXED32, ProtobufParserSFIXED64, ProtobufParserBOOL, ProtobufParserSTRING, ProtobufParserDOUBLE, ProtobufParserFLOAT, ProtobufParserBYTES, ProtobufParserRESERVED, ProtobufParserEXTENSIONS, ProtobufParserTO, ProtobufParserMAX, ProtobufParserENUM, ProtobufParserMESSAGE, ProtobufParserSERVICE, ProtobufParserEXTEND, ProtobufParserRPC, ProtobufParserSTREAM, ProtobufParserRETURNS, ProtobufParserBOOL_LIT, ProtobufParserIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(143) + p.FullIdent() + } + + case ProtobufParserLP: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(144) + p.Match(ProtobufParserLP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(145) + p.FullIdent() + } + { + p.SetState(146) + p.Match(ProtobufParserRP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(149) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserDOT { + { + p.SetState(147) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(148) + p.FullIdent() + } + + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldLabelContext is an interface to support dynamic dispatch. +type IFieldLabelContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OPTIONAL() antlr.TerminalNode + REQUIRED() antlr.TerminalNode + REPEATED() antlr.TerminalNode + + // IsFieldLabelContext differentiates from other interfaces. + IsFieldLabelContext() +} + +type FieldLabelContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldLabelContext() *FieldLabelContext { + var p = new(FieldLabelContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldLabel + return p +} + +func InitEmptyFieldLabelContext(p *FieldLabelContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldLabel +} + +func (*FieldLabelContext) IsFieldLabelContext() {} + +func NewFieldLabelContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldLabelContext { + var p = new(FieldLabelContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldLabel + + return p +} + +func (s *FieldLabelContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldLabelContext) OPTIONAL() antlr.TerminalNode { + return s.GetToken(ProtobufParserOPTIONAL, 0) +} + +func (s *FieldLabelContext) REQUIRED() antlr.TerminalNode { + return s.GetToken(ProtobufParserREQUIRED, 0) +} + +func (s *FieldLabelContext) REPEATED() antlr.TerminalNode { + return s.GetToken(ProtobufParserREPEATED, 0) +} + +func (s *FieldLabelContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldLabelContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldLabelContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldLabel(s) + } +} + +func (s *FieldLabelContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldLabel(s) + } +} + +func (p *ProtobufParser) FieldLabel() (localctx IFieldLabelContext) { + localctx = NewFieldLabelContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 12, ProtobufParserRULE_fieldLabel) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(153) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&1792) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldContext is an interface to support dynamic dispatch. +type IFieldContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + FieldType() IFieldTypeContext + FieldName() IFieldNameContext + EQ() antlr.TerminalNode + FieldNumber() IFieldNumberContext + SEMI() antlr.TerminalNode + FieldLabel() IFieldLabelContext + LB() antlr.TerminalNode + FieldOptions() IFieldOptionsContext + RB() antlr.TerminalNode + + // IsFieldContext differentiates from other interfaces. + IsFieldContext() +} + +type FieldContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldContext() *FieldContext { + var p = new(FieldContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_field + return p +} + +func InitEmptyFieldContext(p *FieldContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_field +} + +func (*FieldContext) IsFieldContext() {} + +func NewFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldContext { + var p = new(FieldContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_field + + return p +} + +func (s *FieldContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldContext) FieldType() IFieldTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldTypeContext) +} + +func (s *FieldContext) FieldName() IFieldNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNameContext) +} + +func (s *FieldContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *FieldContext) FieldNumber() IFieldNumberContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNumberContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNumberContext) +} + +func (s *FieldContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *FieldContext) FieldLabel() IFieldLabelContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldLabelContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldLabelContext) +} + +func (s *FieldContext) LB() antlr.TerminalNode { + return s.GetToken(ProtobufParserLB, 0) +} + +func (s *FieldContext) FieldOptions() IFieldOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldOptionsContext) +} + +func (s *FieldContext) RB() antlr.TerminalNode { + return s.GetToken(ProtobufParserRB, 0) +} + +func (s *FieldContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterField(s) + } +} + +func (s *FieldContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitField(s) + } +} + +func (p *ProtobufParser) Field() (localctx IFieldContext) { + localctx = NewFieldContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 14, ProtobufParserRULE_field) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(156) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 6, p.GetParserRuleContext()) == 1 { + { + p.SetState(155) + p.FieldLabel() + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(158) + p.FieldType() + } + { + p.SetState(159) + p.FieldName() + } + { + p.SetState(160) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(161) + p.FieldNumber() + } + p.SetState(166) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserLB { + { + p.SetState(162) + p.Match(ProtobufParserLB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(163) + p.FieldOptions() + } + { + p.SetState(164) + p.Match(ProtobufParserRB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(168) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldOptionsContext is an interface to support dynamic dispatch. +type IFieldOptionsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllFieldOption() []IFieldOptionContext + FieldOption(i int) IFieldOptionContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsFieldOptionsContext differentiates from other interfaces. + IsFieldOptionsContext() +} + +type FieldOptionsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldOptionsContext() *FieldOptionsContext { + var p = new(FieldOptionsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldOptions + return p +} + +func InitEmptyFieldOptionsContext(p *FieldOptionsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldOptions +} + +func (*FieldOptionsContext) IsFieldOptionsContext() {} + +func NewFieldOptionsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldOptionsContext { + var p = new(FieldOptionsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldOptions + + return p +} + +func (s *FieldOptionsContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldOptionsContext) AllFieldOption() []IFieldOptionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IFieldOptionContext); ok { + len++ + } + } + + tst := make([]IFieldOptionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IFieldOptionContext); ok { + tst[i] = t.(IFieldOptionContext) + i++ + } + } + + return tst +} + +func (s *FieldOptionsContext) FieldOption(i int) IFieldOptionContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldOptionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IFieldOptionContext) +} + +func (s *FieldOptionsContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOMMA) +} + +func (s *FieldOptionsContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, i) +} + +func (s *FieldOptionsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldOptionsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldOptionsContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldOptions(s) + } +} + +func (s *FieldOptionsContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldOptions(s) + } +} + +func (p *ProtobufParser) FieldOptions() (localctx IFieldOptionsContext) { + localctx = NewFieldOptionsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 16, ProtobufParserRULE_fieldOptions) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(170) + p.FieldOption() + } + p.SetState(175) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserCOMMA { + { + p.SetState(171) + p.Match(ProtobufParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(172) + p.FieldOption() + } + + p.SetState(177) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldOptionContext is an interface to support dynamic dispatch. +type IFieldOptionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OptionName() IOptionNameContext + EQ() antlr.TerminalNode + Constant() IConstantContext + + // IsFieldOptionContext differentiates from other interfaces. + IsFieldOptionContext() +} + +type FieldOptionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldOptionContext() *FieldOptionContext { + var p = new(FieldOptionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldOption + return p +} + +func InitEmptyFieldOptionContext(p *FieldOptionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldOption +} + +func (*FieldOptionContext) IsFieldOptionContext() {} + +func NewFieldOptionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldOptionContext { + var p = new(FieldOptionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldOption + + return p +} + +func (s *FieldOptionContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldOptionContext) OptionName() IOptionNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionNameContext) +} + +func (s *FieldOptionContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *FieldOptionContext) Constant() IConstantContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConstantContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IConstantContext) +} + +func (s *FieldOptionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldOptionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldOptionContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldOption(s) + } +} + +func (s *FieldOptionContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldOption(s) + } +} + +func (p *ProtobufParser) FieldOption() (localctx IFieldOptionContext) { + localctx = NewFieldOptionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 18, ProtobufParserRULE_fieldOption) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(178) + p.OptionName() + } + { + p.SetState(179) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(180) + p.Constant() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldNumberContext is an interface to support dynamic dispatch. +type IFieldNumberContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IntLit() IIntLitContext + + // IsFieldNumberContext differentiates from other interfaces. + IsFieldNumberContext() +} + +type FieldNumberContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldNumberContext() *FieldNumberContext { + var p = new(FieldNumberContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldNumber + return p +} + +func InitEmptyFieldNumberContext(p *FieldNumberContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldNumber +} + +func (*FieldNumberContext) IsFieldNumberContext() {} + +func NewFieldNumberContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldNumberContext { + var p = new(FieldNumberContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldNumber + + return p +} + +func (s *FieldNumberContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldNumberContext) IntLit() IIntLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIntLitContext) +} + +func (s *FieldNumberContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldNumberContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldNumberContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldNumber(s) + } +} + +func (s *FieldNumberContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldNumber(s) + } +} + +func (p *ProtobufParser) FieldNumber() (localctx IFieldNumberContext) { + localctx = NewFieldNumberContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 20, ProtobufParserRULE_fieldNumber) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(182) + p.IntLit() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOneofContext is an interface to support dynamic dispatch. +type IOneofContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ONEOF() antlr.TerminalNode + OneofName() IOneofNameContext + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllOptionStatement() []IOptionStatementContext + OptionStatement(i int) IOptionStatementContext + AllOneofField() []IOneofFieldContext + OneofField(i int) IOneofFieldContext + AllEmptyStatement() []IEmptyStatementContext + EmptyStatement(i int) IEmptyStatementContext + + // IsOneofContext differentiates from other interfaces. + IsOneofContext() +} + +type OneofContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOneofContext() *OneofContext { + var p = new(OneofContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneof + return p +} + +func InitEmptyOneofContext(p *OneofContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneof +} + +func (*OneofContext) IsOneofContext() {} + +func NewOneofContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OneofContext { + var p = new(OneofContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_oneof + + return p +} + +func (s *OneofContext) GetParser() antlr.Parser { return s.parser } + +func (s *OneofContext) ONEOF() antlr.TerminalNode { + return s.GetToken(ProtobufParserONEOF, 0) +} + +func (s *OneofContext) OneofName() IOneofNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOneofNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOneofNameContext) +} + +func (s *OneofContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *OneofContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *OneofContext) AllOptionStatement() []IOptionStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOptionStatementContext); ok { + len++ + } + } + + tst := make([]IOptionStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOptionStatementContext); ok { + tst[i] = t.(IOptionStatementContext) + i++ + } + } + + return tst +} + +func (s *OneofContext) OptionStatement(i int) IOptionStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *OneofContext) AllOneofField() []IOneofFieldContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOneofFieldContext); ok { + len++ + } + } + + tst := make([]IOneofFieldContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOneofFieldContext); ok { + tst[i] = t.(IOneofFieldContext) + i++ + } + } + + return tst +} + +func (s *OneofContext) OneofField(i int) IOneofFieldContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOneofFieldContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOneofFieldContext) +} + +func (s *OneofContext) AllEmptyStatement() []IEmptyStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEmptyStatementContext); ok { + len++ + } + } + + tst := make([]IEmptyStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEmptyStatementContext); ok { + tst[i] = t.(IEmptyStatementContext) + i++ + } + } + + return tst +} + +func (s *OneofContext) EmptyStatement(i int) IEmptyStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *OneofContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OneofContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OneofContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOneof(s) + } +} + +func (s *OneofContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOneof(s) + } +} + +func (p *ProtobufParser) Oneof() (localctx IOneofContext) { + localctx = NewOneofContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 22, ProtobufParserRULE_oneof) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(184) + p.Match(ProtobufParserONEOF) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(185) + p.OneofName() + } + { + p.SetState(186) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(192) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&324823222635724286) != 0 { + p.SetState(190) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 9, p.GetParserRuleContext()) { + case 1: + { + p.SetState(187) + p.OptionStatement() + } + + case 2: + { + p.SetState(188) + p.OneofField() + } + + case 3: + { + p.SetState(189) + p.EmptyStatement() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + + p.SetState(194) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(195) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOneofFieldContext is an interface to support dynamic dispatch. +type IOneofFieldContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + FieldType() IFieldTypeContext + FieldName() IFieldNameContext + EQ() antlr.TerminalNode + FieldNumber() IFieldNumberContext + SEMI() antlr.TerminalNode + LB() antlr.TerminalNode + FieldOptions() IFieldOptionsContext + RB() antlr.TerminalNode + + // IsOneofFieldContext differentiates from other interfaces. + IsOneofFieldContext() +} + +type OneofFieldContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOneofFieldContext() *OneofFieldContext { + var p = new(OneofFieldContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneofField + return p +} + +func InitEmptyOneofFieldContext(p *OneofFieldContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneofField +} + +func (*OneofFieldContext) IsOneofFieldContext() {} + +func NewOneofFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OneofFieldContext { + var p = new(OneofFieldContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_oneofField + + return p +} + +func (s *OneofFieldContext) GetParser() antlr.Parser { return s.parser } + +func (s *OneofFieldContext) FieldType() IFieldTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldTypeContext) +} + +func (s *OneofFieldContext) FieldName() IFieldNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNameContext) +} + +func (s *OneofFieldContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *OneofFieldContext) FieldNumber() IFieldNumberContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNumberContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNumberContext) +} + +func (s *OneofFieldContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *OneofFieldContext) LB() antlr.TerminalNode { + return s.GetToken(ProtobufParserLB, 0) +} + +func (s *OneofFieldContext) FieldOptions() IFieldOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldOptionsContext) +} + +func (s *OneofFieldContext) RB() antlr.TerminalNode { + return s.GetToken(ProtobufParserRB, 0) +} + +func (s *OneofFieldContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OneofFieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OneofFieldContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOneofField(s) + } +} + +func (s *OneofFieldContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOneofField(s) + } +} + +func (p *ProtobufParser) OneofField() (localctx IOneofFieldContext) { + localctx = NewOneofFieldContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 24, ProtobufParserRULE_oneofField) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(197) + p.FieldType() + } + { + p.SetState(198) + p.FieldName() + } + { + p.SetState(199) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(200) + p.FieldNumber() + } + p.SetState(205) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserLB { + { + p.SetState(201) + p.Match(ProtobufParserLB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(202) + p.FieldOptions() + } + { + p.SetState(203) + p.Match(ProtobufParserRB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(207) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMapFieldContext is an interface to support dynamic dispatch. +type IMapFieldContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + MAP() antlr.TerminalNode + LT() antlr.TerminalNode + KeyType() IKeyTypeContext + COMMA() antlr.TerminalNode + FieldType() IFieldTypeContext + GT() antlr.TerminalNode + FieldName() IFieldNameContext + EQ() antlr.TerminalNode + FieldNumber() IFieldNumberContext + SEMI() antlr.TerminalNode + LB() antlr.TerminalNode + FieldOptions() IFieldOptionsContext + RB() antlr.TerminalNode + + // IsMapFieldContext differentiates from other interfaces. + IsMapFieldContext() +} + +type MapFieldContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMapFieldContext() *MapFieldContext { + var p = new(MapFieldContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_mapField + return p +} + +func InitEmptyMapFieldContext(p *MapFieldContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_mapField +} + +func (*MapFieldContext) IsMapFieldContext() {} + +func NewMapFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MapFieldContext { + var p = new(MapFieldContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_mapField + + return p +} + +func (s *MapFieldContext) GetParser() antlr.Parser { return s.parser } + +func (s *MapFieldContext) MAP() antlr.TerminalNode { + return s.GetToken(ProtobufParserMAP, 0) +} + +func (s *MapFieldContext) LT() antlr.TerminalNode { + return s.GetToken(ProtobufParserLT, 0) +} + +func (s *MapFieldContext) KeyType() IKeyTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IKeyTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IKeyTypeContext) +} + +func (s *MapFieldContext) COMMA() antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, 0) +} + +func (s *MapFieldContext) FieldType() IFieldTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldTypeContext) +} + +func (s *MapFieldContext) GT() antlr.TerminalNode { + return s.GetToken(ProtobufParserGT, 0) +} + +func (s *MapFieldContext) FieldName() IFieldNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNameContext) +} + +func (s *MapFieldContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *MapFieldContext) FieldNumber() IFieldNumberContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldNumberContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldNumberContext) +} + +func (s *MapFieldContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *MapFieldContext) LB() antlr.TerminalNode { + return s.GetToken(ProtobufParserLB, 0) +} + +func (s *MapFieldContext) FieldOptions() IFieldOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldOptionsContext) +} + +func (s *MapFieldContext) RB() antlr.TerminalNode { + return s.GetToken(ProtobufParserRB, 0) +} + +func (s *MapFieldContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MapFieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MapFieldContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMapField(s) + } +} + +func (s *MapFieldContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMapField(s) + } +} + +func (p *ProtobufParser) MapField() (localctx IMapFieldContext) { + localctx = NewMapFieldContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 26, ProtobufParserRULE_mapField) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(209) + p.Match(ProtobufParserMAP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(210) + p.Match(ProtobufParserLT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(211) + p.KeyType() + } + { + p.SetState(212) + p.Match(ProtobufParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(213) + p.FieldType() + } + { + p.SetState(214) + p.Match(ProtobufParserGT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(215) + p.FieldName() + } + { + p.SetState(216) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(217) + p.FieldNumber() + } + p.SetState(222) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserLB { + { + p.SetState(218) + p.Match(ProtobufParserLB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(219) + p.FieldOptions() + } + { + p.SetState(220) + p.Match(ProtobufParserRB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(224) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IKeyTypeContext is an interface to support dynamic dispatch. +type IKeyTypeContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + INT32() antlr.TerminalNode + INT64() antlr.TerminalNode + UINT32() antlr.TerminalNode + UINT64() antlr.TerminalNode + SINT32() antlr.TerminalNode + SINT64() antlr.TerminalNode + FIXED32() antlr.TerminalNode + FIXED64() antlr.TerminalNode + SFIXED32() antlr.TerminalNode + SFIXED64() antlr.TerminalNode + BOOL() antlr.TerminalNode + STRING() antlr.TerminalNode + + // IsKeyTypeContext differentiates from other interfaces. + IsKeyTypeContext() +} + +type KeyTypeContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeyTypeContext() *KeyTypeContext { + var p = new(KeyTypeContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_keyType + return p +} + +func InitEmptyKeyTypeContext(p *KeyTypeContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_keyType +} + +func (*KeyTypeContext) IsKeyTypeContext() {} + +func NewKeyTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeyTypeContext { + var p = new(KeyTypeContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_keyType + + return p +} + +func (s *KeyTypeContext) GetParser() antlr.Parser { return s.parser } + +func (s *KeyTypeContext) INT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT32, 0) +} + +func (s *KeyTypeContext) INT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT64, 0) +} + +func (s *KeyTypeContext) UINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT32, 0) +} + +func (s *KeyTypeContext) UINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT64, 0) +} + +func (s *KeyTypeContext) SINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT32, 0) +} + +func (s *KeyTypeContext) SINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT64, 0) +} + +func (s *KeyTypeContext) FIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED32, 0) +} + +func (s *KeyTypeContext) FIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED64, 0) +} + +func (s *KeyTypeContext) SFIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED32, 0) +} + +func (s *KeyTypeContext) SFIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED64, 0) +} + +func (s *KeyTypeContext) BOOL() antlr.TerminalNode { + return s.GetToken(ProtobufParserBOOL, 0) +} + +func (s *KeyTypeContext) STRING() antlr.TerminalNode { + return s.GetToken(ProtobufParserSTRING, 0) +} + +func (s *KeyTypeContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeyTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *KeyTypeContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterKeyType(s) + } +} + +func (s *KeyTypeContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitKeyType(s) + } +} + +func (p *ProtobufParser) KeyType() (localctx IKeyTypeContext) { + localctx = NewKeyTypeContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 28, ProtobufParserRULE_keyType) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(226) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&33546240) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldTypeContext is an interface to support dynamic dispatch. +type IFieldTypeContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + DOUBLE() antlr.TerminalNode + FLOAT() antlr.TerminalNode + INT32() antlr.TerminalNode + INT64() antlr.TerminalNode + UINT32() antlr.TerminalNode + UINT64() antlr.TerminalNode + SINT32() antlr.TerminalNode + SINT64() antlr.TerminalNode + FIXED32() antlr.TerminalNode + FIXED64() antlr.TerminalNode + SFIXED32() antlr.TerminalNode + SFIXED64() antlr.TerminalNode + BOOL() antlr.TerminalNode + STRING() antlr.TerminalNode + BYTES() antlr.TerminalNode + MessageType() IMessageTypeContext + EnumType() IEnumTypeContext + + // IsFieldTypeContext differentiates from other interfaces. + IsFieldTypeContext() +} + +type FieldTypeContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldTypeContext() *FieldTypeContext { + var p = new(FieldTypeContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldType + return p +} + +func InitEmptyFieldTypeContext(p *FieldTypeContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldType +} + +func (*FieldTypeContext) IsFieldTypeContext() {} + +func NewFieldTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldTypeContext { + var p = new(FieldTypeContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldType + + return p +} + +func (s *FieldTypeContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldTypeContext) DOUBLE() antlr.TerminalNode { + return s.GetToken(ProtobufParserDOUBLE, 0) +} + +func (s *FieldTypeContext) FLOAT() antlr.TerminalNode { + return s.GetToken(ProtobufParserFLOAT, 0) +} + +func (s *FieldTypeContext) INT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT32, 0) +} + +func (s *FieldTypeContext) INT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT64, 0) +} + +func (s *FieldTypeContext) UINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT32, 0) +} + +func (s *FieldTypeContext) UINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT64, 0) +} + +func (s *FieldTypeContext) SINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT32, 0) +} + +func (s *FieldTypeContext) SINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT64, 0) +} + +func (s *FieldTypeContext) FIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED32, 0) +} + +func (s *FieldTypeContext) FIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED64, 0) +} + +func (s *FieldTypeContext) SFIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED32, 0) +} + +func (s *FieldTypeContext) SFIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED64, 0) +} + +func (s *FieldTypeContext) BOOL() antlr.TerminalNode { + return s.GetToken(ProtobufParserBOOL, 0) +} + +func (s *FieldTypeContext) STRING() antlr.TerminalNode { + return s.GetToken(ProtobufParserSTRING, 0) +} + +func (s *FieldTypeContext) BYTES() antlr.TerminalNode { + return s.GetToken(ProtobufParserBYTES, 0) +} + +func (s *FieldTypeContext) MessageType() IMessageTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageTypeContext) +} + +func (s *FieldTypeContext) EnumType() IEnumTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumTypeContext) +} + +func (s *FieldTypeContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldTypeContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldType(s) + } +} + +func (s *FieldTypeContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldType(s) + } +} + +func (p *ProtobufParser) FieldType() (localctx IFieldTypeContext) { + localctx = NewFieldTypeContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 30, ProtobufParserRULE_fieldType) + p.SetState(245) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 13, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(228) + p.Match(ProtobufParserDOUBLE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(229) + p.Match(ProtobufParserFLOAT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 3: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(230) + p.Match(ProtobufParserINT32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 4: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(231) + p.Match(ProtobufParserINT64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 5: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(232) + p.Match(ProtobufParserUINT32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 6: + p.EnterOuterAlt(localctx, 6) + { + p.SetState(233) + p.Match(ProtobufParserUINT64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 7: + p.EnterOuterAlt(localctx, 7) + { + p.SetState(234) + p.Match(ProtobufParserSINT32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 8: + p.EnterOuterAlt(localctx, 8) + { + p.SetState(235) + p.Match(ProtobufParserSINT64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 9: + p.EnterOuterAlt(localctx, 9) + { + p.SetState(236) + p.Match(ProtobufParserFIXED32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 10: + p.EnterOuterAlt(localctx, 10) + { + p.SetState(237) + p.Match(ProtobufParserFIXED64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 11: + p.EnterOuterAlt(localctx, 11) + { + p.SetState(238) + p.Match(ProtobufParserSFIXED32) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 12: + p.EnterOuterAlt(localctx, 12) + { + p.SetState(239) + p.Match(ProtobufParserSFIXED64) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 13: + p.EnterOuterAlt(localctx, 13) + { + p.SetState(240) + p.Match(ProtobufParserBOOL) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 14: + p.EnterOuterAlt(localctx, 14) + { + p.SetState(241) + p.Match(ProtobufParserSTRING) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 15: + p.EnterOuterAlt(localctx, 15) + { + p.SetState(242) + p.Match(ProtobufParserBYTES) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case 16: + p.EnterOuterAlt(localctx, 16) + { + p.SetState(243) + p.MessageType() + } + + case 17: + p.EnterOuterAlt(localctx, 17) + { + p.SetState(244) + p.EnumType() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IReservedContext is an interface to support dynamic dispatch. +type IReservedContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + RESERVED() antlr.TerminalNode + SEMI() antlr.TerminalNode + Ranges() IRangesContext + ReservedFieldNames() IReservedFieldNamesContext + + // IsReservedContext differentiates from other interfaces. + IsReservedContext() +} + +type ReservedContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReservedContext() *ReservedContext { + var p = new(ReservedContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_reserved + return p +} + +func InitEmptyReservedContext(p *ReservedContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_reserved +} + +func (*ReservedContext) IsReservedContext() {} + +func NewReservedContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReservedContext { + var p = new(ReservedContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_reserved + + return p +} + +func (s *ReservedContext) GetParser() antlr.Parser { return s.parser } + +func (s *ReservedContext) RESERVED() antlr.TerminalNode { + return s.GetToken(ProtobufParserRESERVED, 0) +} + +func (s *ReservedContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *ReservedContext) Ranges() IRangesContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRangesContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IRangesContext) +} + +func (s *ReservedContext) ReservedFieldNames() IReservedFieldNamesContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReservedFieldNamesContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IReservedFieldNamesContext) +} + +func (s *ReservedContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReservedContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ReservedContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterReserved(s) + } +} + +func (s *ReservedContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitReserved(s) + } +} + +func (p *ProtobufParser) Reserved() (localctx IReservedContext) { + localctx = NewReservedContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 32, ProtobufParserRULE_reserved) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(247) + p.Match(ProtobufParserRESERVED) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(250) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserINT_LIT: + { + p.SetState(248) + p.Ranges() + } + + case ProtobufParserSTR_LIT_SINGLE: + { + p.SetState(249) + p.ReservedFieldNames() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + { + p.SetState(252) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IExtensionsContext is an interface to support dynamic dispatch. +type IExtensionsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EXTENSIONS() antlr.TerminalNode + Ranges() IRangesContext + SEMI() antlr.TerminalNode + LB() antlr.TerminalNode + FieldOptions() IFieldOptionsContext + RB() antlr.TerminalNode + + // IsExtensionsContext differentiates from other interfaces. + IsExtensionsContext() +} + +type ExtensionsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExtensionsContext() *ExtensionsContext { + var p = new(ExtensionsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_extensions + return p +} + +func InitEmptyExtensionsContext(p *ExtensionsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_extensions +} + +func (*ExtensionsContext) IsExtensionsContext() {} + +func NewExtensionsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExtensionsContext { + var p = new(ExtensionsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_extensions + + return p +} + +func (s *ExtensionsContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExtensionsContext) EXTENSIONS() antlr.TerminalNode { + return s.GetToken(ProtobufParserEXTENSIONS, 0) +} + +func (s *ExtensionsContext) Ranges() IRangesContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRangesContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IRangesContext) +} + +func (s *ExtensionsContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *ExtensionsContext) LB() antlr.TerminalNode { + return s.GetToken(ProtobufParserLB, 0) +} + +func (s *ExtensionsContext) FieldOptions() IFieldOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldOptionsContext) +} + +func (s *ExtensionsContext) RB() antlr.TerminalNode { + return s.GetToken(ProtobufParserRB, 0) +} + +func (s *ExtensionsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExtensionsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ExtensionsContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterExtensions(s) + } +} + +func (s *ExtensionsContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitExtensions(s) + } +} + +func (p *ProtobufParser) Extensions() (localctx IExtensionsContext) { + localctx = NewExtensionsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 34, ProtobufParserRULE_extensions) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(254) + p.Match(ProtobufParserEXTENSIONS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(255) + p.Ranges() + } + p.SetState(260) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserLB { + { + p.SetState(256) + p.Match(ProtobufParserLB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(257) + p.FieldOptions() + } + { + p.SetState(258) + p.Match(ProtobufParserRB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(262) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IRangesContext is an interface to support dynamic dispatch. +type IRangesContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllOneRange() []IOneRangeContext + OneRange(i int) IOneRangeContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsRangesContext differentiates from other interfaces. + IsRangesContext() +} + +type RangesContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRangesContext() *RangesContext { + var p = new(RangesContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_ranges + return p +} + +func InitEmptyRangesContext(p *RangesContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_ranges +} + +func (*RangesContext) IsRangesContext() {} + +func NewRangesContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RangesContext { + var p = new(RangesContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_ranges + + return p +} + +func (s *RangesContext) GetParser() antlr.Parser { return s.parser } + +func (s *RangesContext) AllOneRange() []IOneRangeContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOneRangeContext); ok { + len++ + } + } + + tst := make([]IOneRangeContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOneRangeContext); ok { + tst[i] = t.(IOneRangeContext) + i++ + } + } + + return tst +} + +func (s *RangesContext) OneRange(i int) IOneRangeContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOneRangeContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOneRangeContext) +} + +func (s *RangesContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOMMA) +} + +func (s *RangesContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, i) +} + +func (s *RangesContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RangesContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *RangesContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterRanges(s) + } +} + +func (s *RangesContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitRanges(s) + } +} + +func (p *ProtobufParser) Ranges() (localctx IRangesContext) { + localctx = NewRangesContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 36, ProtobufParserRULE_ranges) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(264) + p.OneRange() + } + p.SetState(269) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserCOMMA { + { + p.SetState(265) + p.Match(ProtobufParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(266) + p.OneRange() + } + + p.SetState(271) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOneRangeContext is an interface to support dynamic dispatch. +type IOneRangeContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllIntLit() []IIntLitContext + IntLit(i int) IIntLitContext + TO() antlr.TerminalNode + MAX() antlr.TerminalNode + + // IsOneRangeContext differentiates from other interfaces. + IsOneRangeContext() +} + +type OneRangeContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOneRangeContext() *OneRangeContext { + var p = new(OneRangeContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneRange + return p +} + +func InitEmptyOneRangeContext(p *OneRangeContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneRange +} + +func (*OneRangeContext) IsOneRangeContext() {} + +func NewOneRangeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OneRangeContext { + var p = new(OneRangeContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_oneRange + + return p +} + +func (s *OneRangeContext) GetParser() antlr.Parser { return s.parser } + +func (s *OneRangeContext) AllIntLit() []IIntLitContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIntLitContext); ok { + len++ + } + } + + tst := make([]IIntLitContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIntLitContext); ok { + tst[i] = t.(IIntLitContext) + i++ + } + } + + return tst +} + +func (s *OneRangeContext) IntLit(i int) IIntLitContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntLitContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIntLitContext) +} + +func (s *OneRangeContext) TO() antlr.TerminalNode { + return s.GetToken(ProtobufParserTO, 0) +} + +func (s *OneRangeContext) MAX() antlr.TerminalNode { + return s.GetToken(ProtobufParserMAX, 0) +} + +func (s *OneRangeContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OneRangeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OneRangeContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOneRange(s) + } +} + +func (s *OneRangeContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOneRange(s) + } +} + +func (p *ProtobufParser) OneRange() (localctx IOneRangeContext) { + localctx = NewOneRangeContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 38, ProtobufParserRULE_oneRange) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(272) + p.IntLit() + } + p.SetState(278) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserTO { + { + p.SetState(273) + p.Match(ProtobufParserTO) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(276) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserINT_LIT: + { + p.SetState(274) + p.IntLit() + } + + case ProtobufParserMAX: + { + p.SetState(275) + p.Match(ProtobufParserMAX) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IReservedFieldNamesContext is an interface to support dynamic dispatch. +type IReservedFieldNamesContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllStrLit() []IStrLitContext + StrLit(i int) IStrLitContext + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsReservedFieldNamesContext differentiates from other interfaces. + IsReservedFieldNamesContext() +} + +type ReservedFieldNamesContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyReservedFieldNamesContext() *ReservedFieldNamesContext { + var p = new(ReservedFieldNamesContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_reservedFieldNames + return p +} + +func InitEmptyReservedFieldNamesContext(p *ReservedFieldNamesContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_reservedFieldNames +} + +func (*ReservedFieldNamesContext) IsReservedFieldNamesContext() {} + +func NewReservedFieldNamesContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ReservedFieldNamesContext { + var p = new(ReservedFieldNamesContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_reservedFieldNames + + return p +} + +func (s *ReservedFieldNamesContext) GetParser() antlr.Parser { return s.parser } + +func (s *ReservedFieldNamesContext) AllStrLit() []IStrLitContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IStrLitContext); ok { + len++ + } + } + + tst := make([]IStrLitContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IStrLitContext); ok { + tst[i] = t.(IStrLitContext) + i++ + } + } + + return tst +} + +func (s *ReservedFieldNamesContext) StrLit(i int) IStrLitContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStrLitContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IStrLitContext) +} + +func (s *ReservedFieldNamesContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOMMA) +} + +func (s *ReservedFieldNamesContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, i) +} + +func (s *ReservedFieldNamesContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ReservedFieldNamesContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ReservedFieldNamesContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterReservedFieldNames(s) + } +} + +func (s *ReservedFieldNamesContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitReservedFieldNames(s) + } +} + +func (p *ProtobufParser) ReservedFieldNames() (localctx IReservedFieldNamesContext) { + localctx = NewReservedFieldNamesContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 40, ProtobufParserRULE_reservedFieldNames) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(280) + p.StrLit() + } + p.SetState(285) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserCOMMA { + { + p.SetState(281) + p.Match(ProtobufParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(282) + p.StrLit() + } + + p.SetState(287) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// ITopLevelDefContext is an interface to support dynamic dispatch. +type ITopLevelDefContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + MessageDef() IMessageDefContext + EnumDef() IEnumDefContext + ExtendDef() IExtendDefContext + ServiceDef() IServiceDefContext + + // IsTopLevelDefContext differentiates from other interfaces. + IsTopLevelDefContext() +} + +type TopLevelDefContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyTopLevelDefContext() *TopLevelDefContext { + var p = new(TopLevelDefContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_topLevelDef + return p +} + +func InitEmptyTopLevelDefContext(p *TopLevelDefContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_topLevelDef +} + +func (*TopLevelDefContext) IsTopLevelDefContext() {} + +func NewTopLevelDefContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TopLevelDefContext { + var p = new(TopLevelDefContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_topLevelDef + + return p +} + +func (s *TopLevelDefContext) GetParser() antlr.Parser { return s.parser } + +func (s *TopLevelDefContext) MessageDef() IMessageDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageDefContext) +} + +func (s *TopLevelDefContext) EnumDef() IEnumDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumDefContext) +} + +func (s *TopLevelDefContext) ExtendDef() IExtendDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExtendDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IExtendDefContext) +} + +func (s *TopLevelDefContext) ServiceDef() IServiceDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IServiceDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IServiceDefContext) +} + +func (s *TopLevelDefContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *TopLevelDefContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *TopLevelDefContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterTopLevelDef(s) + } +} + +func (s *TopLevelDefContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitTopLevelDef(s) + } +} + +func (p *ProtobufParser) TopLevelDef() (localctx ITopLevelDefContext) { + localctx = NewTopLevelDefContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 42, ProtobufParserRULE_topLevelDef) + p.SetState(292) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserMESSAGE: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(288) + p.MessageDef() + } + + case ProtobufParserENUM: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(289) + p.EnumDef() + } + + case ProtobufParserEXTEND: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(290) + p.ExtendDef() + } + + case ProtobufParserSERVICE: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(291) + p.ServiceDef() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumDefContext is an interface to support dynamic dispatch. +type IEnumDefContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + ENUM() antlr.TerminalNode + EnumName() IEnumNameContext + EnumBody() IEnumBodyContext + + // IsEnumDefContext differentiates from other interfaces. + IsEnumDefContext() +} + +type EnumDefContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumDefContext() *EnumDefContext { + var p = new(EnumDefContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumDef + return p +} + +func InitEmptyEnumDefContext(p *EnumDefContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumDef +} + +func (*EnumDefContext) IsEnumDefContext() {} + +func NewEnumDefContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumDefContext { + var p = new(EnumDefContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumDef + + return p +} + +func (s *EnumDefContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumDefContext) ENUM() antlr.TerminalNode { + return s.GetToken(ProtobufParserENUM, 0) +} + +func (s *EnumDefContext) EnumName() IEnumNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumNameContext) +} + +func (s *EnumDefContext) EnumBody() IEnumBodyContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumBodyContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumBodyContext) +} + +func (s *EnumDefContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumDefContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumDefContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumDef(s) + } +} + +func (s *EnumDefContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumDef(s) + } +} + +func (p *ProtobufParser) EnumDef() (localctx IEnumDefContext) { + localctx = NewEnumDefContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 44, ProtobufParserRULE_enumDef) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(294) + p.Match(ProtobufParserENUM) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(295) + p.EnumName() + } + { + p.SetState(296) + p.EnumBody() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumBodyContext is an interface to support dynamic dispatch. +type IEnumBodyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllEnumElement() []IEnumElementContext + EnumElement(i int) IEnumElementContext + + // IsEnumBodyContext differentiates from other interfaces. + IsEnumBodyContext() +} + +type EnumBodyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumBodyContext() *EnumBodyContext { + var p = new(EnumBodyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumBody + return p +} + +func InitEmptyEnumBodyContext(p *EnumBodyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumBody +} + +func (*EnumBodyContext) IsEnumBodyContext() {} + +func NewEnumBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumBodyContext { + var p = new(EnumBodyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumBody + + return p +} + +func (s *EnumBodyContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumBodyContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *EnumBodyContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *EnumBodyContext) AllEnumElement() []IEnumElementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEnumElementContext); ok { + len++ + } + } + + tst := make([]IEnumElementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEnumElementContext); ok { + tst[i] = t.(IEnumElementContext) + i++ + } + } + + return tst +} + +func (s *EnumBodyContext) EnumElement(i int) IEnumElementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumElementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEnumElementContext) +} + +func (s *EnumBodyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumBodyContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumBody(s) + } +} + +func (s *EnumBodyContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumBody(s) + } +} + +func (p *ProtobufParser) EnumBody() (localctx IEnumBodyContext) { + localctx = NewEnumBodyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 46, ProtobufParserRULE_enumBody) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(298) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(302) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&324260272682302974) != 0 { + { + p.SetState(299) + p.EnumElement() + } + + p.SetState(304) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(305) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumElementContext is an interface to support dynamic dispatch. +type IEnumElementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OptionStatement() IOptionStatementContext + EnumField() IEnumFieldContext + Reserved() IReservedContext + EmptyStatement() IEmptyStatementContext + + // IsEnumElementContext differentiates from other interfaces. + IsEnumElementContext() +} + +type EnumElementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumElementContext() *EnumElementContext { + var p = new(EnumElementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumElement + return p +} + +func InitEmptyEnumElementContext(p *EnumElementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumElement +} + +func (*EnumElementContext) IsEnumElementContext() {} + +func NewEnumElementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumElementContext { + var p = new(EnumElementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumElement + + return p +} + +func (s *EnumElementContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumElementContext) OptionStatement() IOptionStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *EnumElementContext) EnumField() IEnumFieldContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumFieldContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumFieldContext) +} + +func (s *EnumElementContext) Reserved() IReservedContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReservedContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IReservedContext) +} + +func (s *EnumElementContext) EmptyStatement() IEmptyStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *EnumElementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumElementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumElementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumElement(s) + } +} + +func (s *EnumElementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumElement(s) + } +} + +func (p *ProtobufParser) EnumElement() (localctx IEnumElementContext) { + localctx = NewEnumElementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 48, ProtobufParserRULE_enumElement) + p.SetState(311) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 22, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(307) + p.OptionStatement() + } + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(308) + p.EnumField() + } + + case 3: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(309) + p.Reserved() + } + + case 4: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(310) + p.EmptyStatement() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumFieldContext is an interface to support dynamic dispatch. +type IEnumFieldContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + EQ() antlr.TerminalNode + IntLit() IIntLitContext + SEMI() antlr.TerminalNode + MINUS() antlr.TerminalNode + EnumValueOptions() IEnumValueOptionsContext + + // IsEnumFieldContext differentiates from other interfaces. + IsEnumFieldContext() +} + +type EnumFieldContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumFieldContext() *EnumFieldContext { + var p = new(EnumFieldContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumField + return p +} + +func InitEmptyEnumFieldContext(p *EnumFieldContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumField +} + +func (*EnumFieldContext) IsEnumFieldContext() {} + +func NewEnumFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumFieldContext { + var p = new(EnumFieldContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumField + + return p +} + +func (s *EnumFieldContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumFieldContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *EnumFieldContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *EnumFieldContext) IntLit() IIntLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIntLitContext) +} + +func (s *EnumFieldContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *EnumFieldContext) MINUS() antlr.TerminalNode { + return s.GetToken(ProtobufParserMINUS, 0) +} + +func (s *EnumFieldContext) EnumValueOptions() IEnumValueOptionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumValueOptionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumValueOptionsContext) +} + +func (s *EnumFieldContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumFieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumFieldContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumField(s) + } +} + +func (s *EnumFieldContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumField(s) + } +} + +func (p *ProtobufParser) EnumField() (localctx IEnumFieldContext) { + localctx = NewEnumFieldContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 50, ProtobufParserRULE_enumField) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(313) + p.Ident() + } + { + p.SetState(314) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(316) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserMINUS { + { + p.SetState(315) + p.Match(ProtobufParserMINUS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + { + p.SetState(318) + p.IntLit() + } + p.SetState(320) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserLB { + { + p.SetState(319) + p.EnumValueOptions() + } + + } + { + p.SetState(322) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumValueOptionsContext is an interface to support dynamic dispatch. +type IEnumValueOptionsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + LB() antlr.TerminalNode + AllEnumValueOption() []IEnumValueOptionContext + EnumValueOption(i int) IEnumValueOptionContext + RB() antlr.TerminalNode + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsEnumValueOptionsContext differentiates from other interfaces. + IsEnumValueOptionsContext() +} + +type EnumValueOptionsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumValueOptionsContext() *EnumValueOptionsContext { + var p = new(EnumValueOptionsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumValueOptions + return p +} + +func InitEmptyEnumValueOptionsContext(p *EnumValueOptionsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumValueOptions +} + +func (*EnumValueOptionsContext) IsEnumValueOptionsContext() {} + +func NewEnumValueOptionsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumValueOptionsContext { + var p = new(EnumValueOptionsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumValueOptions + + return p +} + +func (s *EnumValueOptionsContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumValueOptionsContext) LB() antlr.TerminalNode { + return s.GetToken(ProtobufParserLB, 0) +} + +func (s *EnumValueOptionsContext) AllEnumValueOption() []IEnumValueOptionContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEnumValueOptionContext); ok { + len++ + } + } + + tst := make([]IEnumValueOptionContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEnumValueOptionContext); ok { + tst[i] = t.(IEnumValueOptionContext) + i++ + } + } + + return tst +} + +func (s *EnumValueOptionsContext) EnumValueOption(i int) IEnumValueOptionContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumValueOptionContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEnumValueOptionContext) +} + +func (s *EnumValueOptionsContext) RB() antlr.TerminalNode { + return s.GetToken(ProtobufParserRB, 0) +} + +func (s *EnumValueOptionsContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOMMA) +} + +func (s *EnumValueOptionsContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, i) +} + +func (s *EnumValueOptionsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumValueOptionsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumValueOptionsContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumValueOptions(s) + } +} + +func (s *EnumValueOptionsContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumValueOptions(s) + } +} + +func (p *ProtobufParser) EnumValueOptions() (localctx IEnumValueOptionsContext) { + localctx = NewEnumValueOptionsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 52, ProtobufParserRULE_enumValueOptions) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(324) + p.Match(ProtobufParserLB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(325) + p.EnumValueOption() + } + p.SetState(330) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserCOMMA { + { + p.SetState(326) + p.Match(ProtobufParserCOMMA) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(327) + p.EnumValueOption() + } + + p.SetState(332) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(333) + p.Match(ProtobufParserRB) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumValueOptionContext is an interface to support dynamic dispatch. +type IEnumValueOptionContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OptionName() IOptionNameContext + EQ() antlr.TerminalNode + Constant() IConstantContext + + // IsEnumValueOptionContext differentiates from other interfaces. + IsEnumValueOptionContext() +} + +type EnumValueOptionContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumValueOptionContext() *EnumValueOptionContext { + var p = new(EnumValueOptionContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumValueOption + return p +} + +func InitEmptyEnumValueOptionContext(p *EnumValueOptionContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumValueOption +} + +func (*EnumValueOptionContext) IsEnumValueOptionContext() {} + +func NewEnumValueOptionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumValueOptionContext { + var p = new(EnumValueOptionContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumValueOption + + return p +} + +func (s *EnumValueOptionContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumValueOptionContext) OptionName() IOptionNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionNameContext) +} + +func (s *EnumValueOptionContext) EQ() antlr.TerminalNode { + return s.GetToken(ProtobufParserEQ, 0) +} + +func (s *EnumValueOptionContext) Constant() IConstantContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConstantContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IConstantContext) +} + +func (s *EnumValueOptionContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumValueOptionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumValueOptionContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumValueOption(s) + } +} + +func (s *EnumValueOptionContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumValueOption(s) + } +} + +func (p *ProtobufParser) EnumValueOption() (localctx IEnumValueOptionContext) { + localctx = NewEnumValueOptionContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 54, ProtobufParserRULE_enumValueOption) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(335) + p.OptionName() + } + { + p.SetState(336) + p.Match(ProtobufParserEQ) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(337) + p.Constant() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMessageDefContext is an interface to support dynamic dispatch. +type IMessageDefContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + MESSAGE() antlr.TerminalNode + MessageName() IMessageNameContext + MessageBody() IMessageBodyContext + + // IsMessageDefContext differentiates from other interfaces. + IsMessageDefContext() +} + +type MessageDefContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMessageDefContext() *MessageDefContext { + var p = new(MessageDefContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageDef + return p +} + +func InitEmptyMessageDefContext(p *MessageDefContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageDef +} + +func (*MessageDefContext) IsMessageDefContext() {} + +func NewMessageDefContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MessageDefContext { + var p = new(MessageDefContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_messageDef + + return p +} + +func (s *MessageDefContext) GetParser() antlr.Parser { return s.parser } + +func (s *MessageDefContext) MESSAGE() antlr.TerminalNode { + return s.GetToken(ProtobufParserMESSAGE, 0) +} + +func (s *MessageDefContext) MessageName() IMessageNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageNameContext) +} + +func (s *MessageDefContext) MessageBody() IMessageBodyContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageBodyContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageBodyContext) +} + +func (s *MessageDefContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MessageDefContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MessageDefContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMessageDef(s) + } +} + +func (s *MessageDefContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMessageDef(s) + } +} + +func (p *ProtobufParser) MessageDef() (localctx IMessageDefContext) { + localctx = NewMessageDefContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 56, ProtobufParserRULE_messageDef) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(339) + p.Match(ProtobufParserMESSAGE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(340) + p.MessageName() + } + { + p.SetState(341) + p.MessageBody() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMessageBodyContext is an interface to support dynamic dispatch. +type IMessageBodyContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllMessageElement() []IMessageElementContext + MessageElement(i int) IMessageElementContext + + // IsMessageBodyContext differentiates from other interfaces. + IsMessageBodyContext() +} + +type MessageBodyContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMessageBodyContext() *MessageBodyContext { + var p = new(MessageBodyContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageBody + return p +} + +func InitEmptyMessageBodyContext(p *MessageBodyContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageBody +} + +func (*MessageBodyContext) IsMessageBodyContext() {} + +func NewMessageBodyContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MessageBodyContext { + var p = new(MessageBodyContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_messageBody + + return p +} + +func (s *MessageBodyContext) GetParser() antlr.Parser { return s.parser } + +func (s *MessageBodyContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *MessageBodyContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *MessageBodyContext) AllMessageElement() []IMessageElementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IMessageElementContext); ok { + len++ + } + } + + tst := make([]IMessageElementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IMessageElementContext); ok { + tst[i] = t.(IMessageElementContext) + i++ + } + } + + return tst +} + +func (s *MessageBodyContext) MessageElement(i int) IMessageElementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageElementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IMessageElementContext) +} + +func (s *MessageBodyContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MessageBodyContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MessageBodyContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMessageBody(s) + } +} + +func (s *MessageBodyContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMessageBody(s) + } +} + +func (p *ProtobufParser) MessageBody() (localctx IMessageBodyContext) { + localctx = NewMessageBodyContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 58, ProtobufParserRULE_messageBody) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(343) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(347) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&324823222635724798) != 0 { + { + p.SetState(344) + p.MessageElement() + } + + p.SetState(349) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(350) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMessageElementContext is an interface to support dynamic dispatch. +type IMessageElementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Field() IFieldContext + EnumDef() IEnumDefContext + MessageDef() IMessageDefContext + ExtendDef() IExtendDefContext + OptionStatement() IOptionStatementContext + Oneof() IOneofContext + MapField() IMapFieldContext + Reserved() IReservedContext + Extensions() IExtensionsContext + EmptyStatement() IEmptyStatementContext + + // IsMessageElementContext differentiates from other interfaces. + IsMessageElementContext() +} + +type MessageElementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMessageElementContext() *MessageElementContext { + var p = new(MessageElementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageElement + return p +} + +func InitEmptyMessageElementContext(p *MessageElementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageElement +} + +func (*MessageElementContext) IsMessageElementContext() {} + +func NewMessageElementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MessageElementContext { + var p = new(MessageElementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_messageElement + + return p +} + +func (s *MessageElementContext) GetParser() antlr.Parser { return s.parser } + +func (s *MessageElementContext) Field() IFieldContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFieldContext) +} + +func (s *MessageElementContext) EnumDef() IEnumDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumDefContext) +} + +func (s *MessageElementContext) MessageDef() IMessageDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageDefContext) +} + +func (s *MessageElementContext) ExtendDef() IExtendDefContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExtendDefContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IExtendDefContext) +} + +func (s *MessageElementContext) OptionStatement() IOptionStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *MessageElementContext) Oneof() IOneofContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOneofContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOneofContext) +} + +func (s *MessageElementContext) MapField() IMapFieldContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMapFieldContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMapFieldContext) +} + +func (s *MessageElementContext) Reserved() IReservedContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IReservedContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IReservedContext) +} + +func (s *MessageElementContext) Extensions() IExtensionsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IExtensionsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IExtensionsContext) +} + +func (s *MessageElementContext) EmptyStatement() IEmptyStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *MessageElementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MessageElementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MessageElementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMessageElement(s) + } +} + +func (s *MessageElementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMessageElement(s) + } +} + +func (p *ProtobufParser) MessageElement() (localctx IMessageElementContext) { + localctx = NewMessageElementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 60, ProtobufParserRULE_messageElement) + p.SetState(362) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 27, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(352) + p.Field() + } + + case 2: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(353) + p.EnumDef() + } + + case 3: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(354) + p.MessageDef() + } + + case 4: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(355) + p.ExtendDef() + } + + case 5: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(356) + p.OptionStatement() + } + + case 6: + p.EnterOuterAlt(localctx, 6) + { + p.SetState(357) + p.Oneof() + } + + case 7: + p.EnterOuterAlt(localctx, 7) + { + p.SetState(358) + p.MapField() + } + + case 8: + p.EnterOuterAlt(localctx, 8) + { + p.SetState(359) + p.Reserved() + } + + case 9: + p.EnterOuterAlt(localctx, 9) + { + p.SetState(360) + p.Extensions() + } + + case 10: + p.EnterOuterAlt(localctx, 10) + { + p.SetState(361) + p.EmptyStatement() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IExtendDefContext is an interface to support dynamic dispatch. +type IExtendDefContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EXTEND() antlr.TerminalNode + MessageType() IMessageTypeContext + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllField() []IFieldContext + Field(i int) IFieldContext + AllEmptyStatement() []IEmptyStatementContext + EmptyStatement(i int) IEmptyStatementContext + + // IsExtendDefContext differentiates from other interfaces. + IsExtendDefContext() +} + +type ExtendDefContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyExtendDefContext() *ExtendDefContext { + var p = new(ExtendDefContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_extendDef + return p +} + +func InitEmptyExtendDefContext(p *ExtendDefContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_extendDef +} + +func (*ExtendDefContext) IsExtendDefContext() {} + +func NewExtendDefContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExtendDefContext { + var p = new(ExtendDefContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_extendDef + + return p +} + +func (s *ExtendDefContext) GetParser() antlr.Parser { return s.parser } + +func (s *ExtendDefContext) EXTEND() antlr.TerminalNode { + return s.GetToken(ProtobufParserEXTEND, 0) +} + +func (s *ExtendDefContext) MessageType() IMessageTypeContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageTypeContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageTypeContext) +} + +func (s *ExtendDefContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *ExtendDefContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *ExtendDefContext) AllField() []IFieldContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IFieldContext); ok { + len++ + } + } + + tst := make([]IFieldContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IFieldContext); ok { + tst[i] = t.(IFieldContext) + i++ + } + } + + return tst +} + +func (s *ExtendDefContext) Field(i int) IFieldContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFieldContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IFieldContext) +} + +func (s *ExtendDefContext) AllEmptyStatement() []IEmptyStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEmptyStatementContext); ok { + len++ + } + } + + tst := make([]IEmptyStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEmptyStatementContext); ok { + tst[i] = t.(IEmptyStatementContext) + i++ + } + } + + return tst +} + +func (s *ExtendDefContext) EmptyStatement(i int) IEmptyStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *ExtendDefContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ExtendDefContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ExtendDefContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterExtendDef(s) + } +} + +func (s *ExtendDefContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitExtendDef(s) + } +} + +func (p *ProtobufParser) ExtendDef() (localctx IExtendDefContext) { + localctx = NewExtendDefContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 62, ProtobufParserRULE_extendDef) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(364) + p.Match(ProtobufParserEXTEND) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(365) + p.MessageType() + } + { + p.SetState(366) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(371) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&324823222635724798) != 0 { + p.SetState(369) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserSYNTAX, ProtobufParserEDITION, ProtobufParserIMPORT, ProtobufParserWEAK, ProtobufParserPUBLIC, ProtobufParserPACKAGE, ProtobufParserOPTION, ProtobufParserOPTIONAL, ProtobufParserREQUIRED, ProtobufParserREPEATED, ProtobufParserONEOF, ProtobufParserMAP, ProtobufParserINT32, ProtobufParserINT64, ProtobufParserUINT32, ProtobufParserUINT64, ProtobufParserSINT32, ProtobufParserSINT64, ProtobufParserFIXED32, ProtobufParserFIXED64, ProtobufParserSFIXED32, ProtobufParserSFIXED64, ProtobufParserBOOL, ProtobufParserSTRING, ProtobufParserDOUBLE, ProtobufParserFLOAT, ProtobufParserBYTES, ProtobufParserRESERVED, ProtobufParserEXTENSIONS, ProtobufParserTO, ProtobufParserMAX, ProtobufParserENUM, ProtobufParserMESSAGE, ProtobufParserSERVICE, ProtobufParserEXTEND, ProtobufParserRPC, ProtobufParserSTREAM, ProtobufParserRETURNS, ProtobufParserDOT, ProtobufParserBOOL_LIT, ProtobufParserIDENTIFIER: + { + p.SetState(367) + p.Field() + } + + case ProtobufParserSEMI: + { + p.SetState(368) + p.EmptyStatement() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + p.SetState(373) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(374) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IServiceDefContext is an interface to support dynamic dispatch. +type IServiceDefContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SERVICE() antlr.TerminalNode + ServiceName() IServiceNameContext + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllServiceElement() []IServiceElementContext + ServiceElement(i int) IServiceElementContext + + // IsServiceDefContext differentiates from other interfaces. + IsServiceDefContext() +} + +type ServiceDefContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyServiceDefContext() *ServiceDefContext { + var p = new(ServiceDefContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceDef + return p +} + +func InitEmptyServiceDefContext(p *ServiceDefContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceDef +} + +func (*ServiceDefContext) IsServiceDefContext() {} + +func NewServiceDefContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceDefContext { + var p = new(ServiceDefContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_serviceDef + + return p +} + +func (s *ServiceDefContext) GetParser() antlr.Parser { return s.parser } + +func (s *ServiceDefContext) SERVICE() antlr.TerminalNode { + return s.GetToken(ProtobufParserSERVICE, 0) +} + +func (s *ServiceDefContext) ServiceName() IServiceNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IServiceNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IServiceNameContext) +} + +func (s *ServiceDefContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *ServiceDefContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *ServiceDefContext) AllServiceElement() []IServiceElementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IServiceElementContext); ok { + len++ + } + } + + tst := make([]IServiceElementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IServiceElementContext); ok { + tst[i] = t.(IServiceElementContext) + i++ + } + } + + return tst +} + +func (s *ServiceDefContext) ServiceElement(i int) IServiceElementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IServiceElementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IServiceElementContext) +} + +func (s *ServiceDefContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ServiceDefContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ServiceDefContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterServiceDef(s) + } +} + +func (s *ServiceDefContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitServiceDef(s) + } +} + +func (p *ProtobufParser) ServiceDef() (localctx IServiceDefContext) { + localctx = NewServiceDefContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 64, ProtobufParserRULE_serviceDef) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(376) + p.Match(ProtobufParserSERVICE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(377) + p.ServiceName() + } + { + p.SetState(378) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(382) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&618475290752) != 0 { + { + p.SetState(379) + p.ServiceElement() + } + + p.SetState(384) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(385) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IServiceElementContext is an interface to support dynamic dispatch. +type IServiceElementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + OptionStatement() IOptionStatementContext + Rpc() IRpcContext + EmptyStatement() IEmptyStatementContext + + // IsServiceElementContext differentiates from other interfaces. + IsServiceElementContext() +} + +type ServiceElementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyServiceElementContext() *ServiceElementContext { + var p = new(ServiceElementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceElement + return p +} + +func InitEmptyServiceElementContext(p *ServiceElementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceElement +} + +func (*ServiceElementContext) IsServiceElementContext() {} + +func NewServiceElementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceElementContext { + var p = new(ServiceElementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_serviceElement + + return p +} + +func (s *ServiceElementContext) GetParser() antlr.Parser { return s.parser } + +func (s *ServiceElementContext) OptionStatement() IOptionStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *ServiceElementContext) Rpc() IRpcContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRpcContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IRpcContext) +} + +func (s *ServiceElementContext) EmptyStatement() IEmptyStatementContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *ServiceElementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ServiceElementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ServiceElementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterServiceElement(s) + } +} + +func (s *ServiceElementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitServiceElement(s) + } +} + +func (p *ProtobufParser) ServiceElement() (localctx IServiceElementContext) { + localctx = NewServiceElementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 66, ProtobufParserRULE_serviceElement) + p.SetState(390) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserOPTION: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(387) + p.OptionStatement() + } + + case ProtobufParserRPC: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(388) + p.Rpc() + } + + case ProtobufParserSEMI: + p.EnterOuterAlt(localctx, 3) + { + p.SetState(389) + p.EmptyStatement() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IRpcContext is an interface to support dynamic dispatch. +type IRpcContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + RPC() antlr.TerminalNode + RpcName() IRpcNameContext + AllLP() []antlr.TerminalNode + LP(i int) antlr.TerminalNode + AllMessageType() []IMessageTypeContext + MessageType(i int) IMessageTypeContext + AllRP() []antlr.TerminalNode + RP(i int) antlr.TerminalNode + RETURNS() antlr.TerminalNode + LC() antlr.TerminalNode + RC() antlr.TerminalNode + SEMI() antlr.TerminalNode + AllSTREAM() []antlr.TerminalNode + STREAM(i int) antlr.TerminalNode + AllOptionStatement() []IOptionStatementContext + OptionStatement(i int) IOptionStatementContext + AllEmptyStatement() []IEmptyStatementContext + EmptyStatement(i int) IEmptyStatementContext + + // IsRpcContext differentiates from other interfaces. + IsRpcContext() +} + +type RpcContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRpcContext() *RpcContext { + var p = new(RpcContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_rpc + return p +} + +func InitEmptyRpcContext(p *RpcContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_rpc +} + +func (*RpcContext) IsRpcContext() {} + +func NewRpcContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RpcContext { + var p = new(RpcContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_rpc + + return p +} + +func (s *RpcContext) GetParser() antlr.Parser { return s.parser } + +func (s *RpcContext) RPC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRPC, 0) +} + +func (s *RpcContext) RpcName() IRpcNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IRpcNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IRpcNameContext) +} + +func (s *RpcContext) AllLP() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserLP) +} + +func (s *RpcContext) LP(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserLP, i) +} + +func (s *RpcContext) AllMessageType() []IMessageTypeContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IMessageTypeContext); ok { + len++ + } + } + + tst := make([]IMessageTypeContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IMessageTypeContext); ok { + tst[i] = t.(IMessageTypeContext) + i++ + } + } + + return tst +} + +func (s *RpcContext) MessageType(i int) IMessageTypeContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageTypeContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IMessageTypeContext) +} + +func (s *RpcContext) AllRP() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserRP) +} + +func (s *RpcContext) RP(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserRP, i) +} + +func (s *RpcContext) RETURNS() antlr.TerminalNode { + return s.GetToken(ProtobufParserRETURNS, 0) +} + +func (s *RpcContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *RpcContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *RpcContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *RpcContext) AllSTREAM() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserSTREAM) +} + +func (s *RpcContext) STREAM(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserSTREAM, i) +} + +func (s *RpcContext) AllOptionStatement() []IOptionStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IOptionStatementContext); ok { + len++ + } + } + + tst := make([]IOptionStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IOptionStatementContext); ok { + tst[i] = t.(IOptionStatementContext) + i++ + } + } + + return tst +} + +func (s *RpcContext) OptionStatement(i int) IOptionStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IOptionStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IOptionStatementContext) +} + +func (s *RpcContext) AllEmptyStatement() []IEmptyStatementContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IEmptyStatementContext); ok { + len++ + } + } + + tst := make([]IEmptyStatementContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IEmptyStatementContext); ok { + tst[i] = t.(IEmptyStatementContext) + i++ + } + } + + return tst +} + +func (s *RpcContext) EmptyStatement(i int) IEmptyStatementContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEmptyStatementContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IEmptyStatementContext) +} + +func (s *RpcContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RpcContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *RpcContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterRpc(s) + } +} + +func (s *RpcContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitRpc(s) + } +} + +func (p *ProtobufParser) Rpc() (localctx IRpcContext) { + localctx = NewRpcContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 68, ProtobufParserRULE_rpc) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(392) + p.Match(ProtobufParserRPC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(393) + p.RpcName() + } + { + p.SetState(394) + p.Match(ProtobufParserLP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(396) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 32, p.GetParserRuleContext()) == 1 { + { + p.SetState(395) + p.Match(ProtobufParserSTREAM) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(398) + p.MessageType() + } + { + p.SetState(399) + p.Match(ProtobufParserRP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(400) + p.Match(ProtobufParserRETURNS) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(401) + p.Match(ProtobufParserLP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(403) + p.GetErrorHandler().Sync(p) + + if p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 33, p.GetParserRuleContext()) == 1 { + { + p.SetState(402) + p.Match(ProtobufParserSTREAM) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } else if p.HasError() { // JIM + goto errorExit + } + { + p.SetState(405) + p.MessageType() + } + { + p.SetState(406) + p.Match(ProtobufParserRP) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(417) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserLC: + { + p.SetState(407) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(412) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserOPTION || _la == ProtobufParserSEMI { + p.SetState(410) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserOPTION: + { + p.SetState(408) + p.OptionStatement() + } + + case ProtobufParserSEMI: + { + p.SetState(409) + p.EmptyStatement() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + + p.SetState(414) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(415) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case ProtobufParserSEMI: + { + p.SetState(416) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IConstantContext is an interface to support dynamic dispatch. +type IConstantContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + FullIdent() IFullIdentContext + IntLit() IIntLitContext + MINUS() antlr.TerminalNode + PLUS() antlr.TerminalNode + FloatLit() IFloatLitContext + StrLit() IStrLitContext + BoolLit() IBoolLitContext + BlockLit() IBlockLitContext + + // IsConstantContext differentiates from other interfaces. + IsConstantContext() +} + +type ConstantContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyConstantContext() *ConstantContext { + var p = new(ConstantContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_constant + return p +} + +func InitEmptyConstantContext(p *ConstantContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_constant +} + +func (*ConstantContext) IsConstantContext() {} + +func NewConstantContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ConstantContext { + var p = new(ConstantContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_constant + + return p +} + +func (s *ConstantContext) GetParser() antlr.Parser { return s.parser } + +func (s *ConstantContext) FullIdent() IFullIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFullIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFullIdentContext) +} + +func (s *ConstantContext) IntLit() IIntLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIntLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIntLitContext) +} + +func (s *ConstantContext) MINUS() antlr.TerminalNode { + return s.GetToken(ProtobufParserMINUS, 0) +} + +func (s *ConstantContext) PLUS() antlr.TerminalNode { + return s.GetToken(ProtobufParserPLUS, 0) +} + +func (s *ConstantContext) FloatLit() IFloatLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IFloatLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IFloatLitContext) +} + +func (s *ConstantContext) StrLit() IStrLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IStrLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IStrLitContext) +} + +func (s *ConstantContext) BoolLit() IBoolLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IBoolLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IBoolLitContext) +} + +func (s *ConstantContext) BlockLit() IBlockLitContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IBlockLitContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IBlockLitContext) +} + +func (s *ConstantContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ConstantContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ConstantContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterConstant(s) + } +} + +func (s *ConstantContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitConstant(s) + } +} + +func (p *ProtobufParser) Constant() (localctx IConstantContext) { + localctx = NewConstantContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 70, ProtobufParserRULE_constant) + var _la int + + p.SetState(431) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 39, p.GetParserRuleContext()) { + case 1: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(419) + p.FullIdent() + } + + case 2: + p.EnterOuterAlt(localctx, 2) + p.SetState(421) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserPLUS || _la == ProtobufParserMINUS { + { + p.SetState(420) + _la = p.GetTokenStream().LA(1) + + if !(_la == ProtobufParserPLUS || _la == ProtobufParserMINUS) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + { + p.SetState(423) + p.IntLit() + } + + case 3: + p.EnterOuterAlt(localctx, 3) + p.SetState(425) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserPLUS || _la == ProtobufParserMINUS { + { + p.SetState(424) + _la = p.GetTokenStream().LA(1) + + if !(_la == ProtobufParserPLUS || _la == ProtobufParserMINUS) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + { + p.SetState(427) + p.FloatLit() + } + + case 4: + p.EnterOuterAlt(localctx, 4) + { + p.SetState(428) + p.StrLit() + } + + case 5: + p.EnterOuterAlt(localctx, 5) + { + p.SetState(429) + p.BoolLit() + } + + case 6: + p.EnterOuterAlt(localctx, 6) + { + p.SetState(430) + p.BlockLit() + } + + case antlr.ATNInvalidAltNumber: + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IBlockLitContext is an interface to support dynamic dispatch. +type IBlockLitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + LC() antlr.TerminalNode + RC() antlr.TerminalNode + AllIdent() []IIdentContext + Ident(i int) IIdentContext + AllCOLON() []antlr.TerminalNode + COLON(i int) antlr.TerminalNode + AllConstant() []IConstantContext + Constant(i int) IConstantContext + AllSEMI() []antlr.TerminalNode + SEMI(i int) antlr.TerminalNode + AllCOMMA() []antlr.TerminalNode + COMMA(i int) antlr.TerminalNode + + // IsBlockLitContext differentiates from other interfaces. + IsBlockLitContext() +} + +type BlockLitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyBlockLitContext() *BlockLitContext { + var p = new(BlockLitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_blockLit + return p +} + +func InitEmptyBlockLitContext(p *BlockLitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_blockLit +} + +func (*BlockLitContext) IsBlockLitContext() {} + +func NewBlockLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *BlockLitContext { + var p = new(BlockLitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_blockLit + + return p +} + +func (s *BlockLitContext) GetParser() antlr.Parser { return s.parser } + +func (s *BlockLitContext) LC() antlr.TerminalNode { + return s.GetToken(ProtobufParserLC, 0) +} + +func (s *BlockLitContext) RC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRC, 0) +} + +func (s *BlockLitContext) AllIdent() []IIdentContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIdentContext); ok { + len++ + } + } + + tst := make([]IIdentContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIdentContext); ok { + tst[i] = t.(IIdentContext) + i++ + } + } + + return tst +} + +func (s *BlockLitContext) Ident(i int) IIdentContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *BlockLitContext) AllCOLON() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOLON) +} + +func (s *BlockLitContext) COLON(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOLON, i) +} + +func (s *BlockLitContext) AllConstant() []IConstantContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IConstantContext); ok { + len++ + } + } + + tst := make([]IConstantContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IConstantContext); ok { + tst[i] = t.(IConstantContext) + i++ + } + } + + return tst +} + +func (s *BlockLitContext) Constant(i int) IConstantContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IConstantContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IConstantContext) +} + +func (s *BlockLitContext) AllSEMI() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserSEMI) +} + +func (s *BlockLitContext) SEMI(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, i) +} + +func (s *BlockLitContext) AllCOMMA() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserCOMMA) +} + +func (s *BlockLitContext) COMMA(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserCOMMA, i) +} + +func (s *BlockLitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BlockLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *BlockLitContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterBlockLit(s) + } +} + +func (s *BlockLitContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitBlockLit(s) + } +} + +func (p *ProtobufParser) BlockLit() (localctx IBlockLitContext) { + localctx = NewBlockLitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 72, ProtobufParserRULE_blockLit) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(433) + p.Match(ProtobufParserLC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + p.SetState(442) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for (int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&324259722926489086) != 0 { + { + p.SetState(434) + p.Ident() + } + { + p.SetState(435) + p.Match(ProtobufParserCOLON) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(436) + p.Constant() + } + p.SetState(438) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserSEMI || _la == ProtobufParserCOMMA { + { + p.SetState(437) + _la = p.GetTokenStream().LA(1) + + if !(_la == ProtobufParserSEMI || _la == ProtobufParserCOMMA) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + + } + + p.SetState(444) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + { + p.SetState(445) + p.Match(ProtobufParserRC) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEmptyStatementContext is an interface to support dynamic dispatch. +type IEmptyStatementContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SEMI() antlr.TerminalNode + + // IsEmptyStatementContext differentiates from other interfaces. + IsEmptyStatementContext() +} + +type EmptyStatementContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEmptyStatementContext() *EmptyStatementContext { + var p = new(EmptyStatementContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_emptyStatement + return p +} + +func InitEmptyEmptyStatementContext(p *EmptyStatementContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_emptyStatement +} + +func (*EmptyStatementContext) IsEmptyStatementContext() {} + +func NewEmptyStatementContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EmptyStatementContext { + var p = new(EmptyStatementContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_emptyStatement + + return p +} + +func (s *EmptyStatementContext) GetParser() antlr.Parser { return s.parser } + +func (s *EmptyStatementContext) SEMI() antlr.TerminalNode { + return s.GetToken(ProtobufParserSEMI, 0) +} + +func (s *EmptyStatementContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EmptyStatementContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EmptyStatementContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEmptyStatement(s) + } +} + +func (s *EmptyStatementContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEmptyStatement(s) + } +} + +func (p *ProtobufParser) EmptyStatement() (localctx IEmptyStatementContext) { + localctx = NewEmptyStatementContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 74, ProtobufParserRULE_emptyStatement) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(447) + p.Match(ProtobufParserSEMI) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IIdentContext is an interface to support dynamic dispatch. +type IIdentContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + IDENTIFIER() antlr.TerminalNode + Keywords() IKeywordsContext + + // IsIdentContext differentiates from other interfaces. + IsIdentContext() +} + +type IdentContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIdentContext() *IdentContext { + var p = new(IdentContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_ident + return p +} + +func InitEmptyIdentContext(p *IdentContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_ident +} + +func (*IdentContext) IsIdentContext() {} + +func NewIdentContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IdentContext { + var p = new(IdentContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_ident + + return p +} + +func (s *IdentContext) GetParser() antlr.Parser { return s.parser } + +func (s *IdentContext) IDENTIFIER() antlr.TerminalNode { + return s.GetToken(ProtobufParserIDENTIFIER, 0) +} + +func (s *IdentContext) Keywords() IKeywordsContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IKeywordsContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IKeywordsContext) +} + +func (s *IdentContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IdentContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *IdentContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterIdent(s) + } +} + +func (s *IdentContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitIdent(s) + } +} + +func (p *ProtobufParser) Ident() (localctx IIdentContext) { + localctx = NewIdentContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 76, ProtobufParserRULE_ident) + p.SetState(451) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + + switch p.GetTokenStream().LA(1) { + case ProtobufParserIDENTIFIER: + p.EnterOuterAlt(localctx, 1) + { + p.SetState(449) + p.Match(ProtobufParserIDENTIFIER) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + case ProtobufParserSYNTAX, ProtobufParserEDITION, ProtobufParserIMPORT, ProtobufParserWEAK, ProtobufParserPUBLIC, ProtobufParserPACKAGE, ProtobufParserOPTION, ProtobufParserOPTIONAL, ProtobufParserREPEATED, ProtobufParserONEOF, ProtobufParserMAP, ProtobufParserINT32, ProtobufParserINT64, ProtobufParserUINT32, ProtobufParserUINT64, ProtobufParserSINT32, ProtobufParserSINT64, ProtobufParserFIXED32, ProtobufParserFIXED64, ProtobufParserSFIXED32, ProtobufParserSFIXED64, ProtobufParserBOOL, ProtobufParserSTRING, ProtobufParserDOUBLE, ProtobufParserFLOAT, ProtobufParserBYTES, ProtobufParserRESERVED, ProtobufParserEXTENSIONS, ProtobufParserTO, ProtobufParserMAX, ProtobufParserENUM, ProtobufParserMESSAGE, ProtobufParserSERVICE, ProtobufParserEXTEND, ProtobufParserRPC, ProtobufParserSTREAM, ProtobufParserRETURNS, ProtobufParserBOOL_LIT: + p.EnterOuterAlt(localctx, 2) + { + p.SetState(450) + p.Keywords() + } + + default: + p.SetError(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) + goto errorExit + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFullIdentContext is an interface to support dynamic dispatch. +type IFullIdentContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllIdent() []IIdentContext + Ident(i int) IIdentContext + AllDOT() []antlr.TerminalNode + DOT(i int) antlr.TerminalNode + + // IsFullIdentContext differentiates from other interfaces. + IsFullIdentContext() +} + +type FullIdentContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFullIdentContext() *FullIdentContext { + var p = new(FullIdentContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fullIdent + return p +} + +func InitEmptyFullIdentContext(p *FullIdentContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fullIdent +} + +func (*FullIdentContext) IsFullIdentContext() {} + +func NewFullIdentContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FullIdentContext { + var p = new(FullIdentContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fullIdent + + return p +} + +func (s *FullIdentContext) GetParser() antlr.Parser { return s.parser } + +func (s *FullIdentContext) AllIdent() []IIdentContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIdentContext); ok { + len++ + } + } + + tst := make([]IIdentContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIdentContext); ok { + tst[i] = t.(IIdentContext) + i++ + } + } + + return tst +} + +func (s *FullIdentContext) Ident(i int) IIdentContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *FullIdentContext) AllDOT() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserDOT) +} + +func (s *FullIdentContext) DOT(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserDOT, i) +} + +func (s *FullIdentContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FullIdentContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FullIdentContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFullIdent(s) + } +} + +func (s *FullIdentContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFullIdent(s) + } +} + +func (p *ProtobufParser) FullIdent() (localctx IFullIdentContext) { + localctx = NewFullIdentContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 78, ProtobufParserRULE_fullIdent) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(453) + p.Ident() + } + p.SetState(458) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for _la == ProtobufParserDOT { + { + p.SetState(454) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + { + p.SetState(455) + p.Ident() + } + + p.SetState(460) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMessageNameContext is an interface to support dynamic dispatch. +type IMessageNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsMessageNameContext differentiates from other interfaces. + IsMessageNameContext() +} + +type MessageNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMessageNameContext() *MessageNameContext { + var p = new(MessageNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageName + return p +} + +func InitEmptyMessageNameContext(p *MessageNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageName +} + +func (*MessageNameContext) IsMessageNameContext() {} + +func NewMessageNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MessageNameContext { + var p = new(MessageNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_messageName + + return p +} + +func (s *MessageNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *MessageNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *MessageNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MessageNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MessageNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMessageName(s) + } +} + +func (s *MessageNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMessageName(s) + } +} + +func (p *ProtobufParser) MessageName() (localctx IMessageNameContext) { + localctx = NewMessageNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 80, ProtobufParserRULE_messageName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(461) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumNameContext is an interface to support dynamic dispatch. +type IEnumNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsEnumNameContext differentiates from other interfaces. + IsEnumNameContext() +} + +type EnumNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumNameContext() *EnumNameContext { + var p = new(EnumNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumName + return p +} + +func InitEmptyEnumNameContext(p *EnumNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumName +} + +func (*EnumNameContext) IsEnumNameContext() {} + +func NewEnumNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumNameContext { + var p = new(EnumNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumName + + return p +} + +func (s *EnumNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *EnumNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumName(s) + } +} + +func (s *EnumNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumName(s) + } +} + +func (p *ProtobufParser) EnumName() (localctx IEnumNameContext) { + localctx = NewEnumNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 82, ProtobufParserRULE_enumName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(463) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFieldNameContext is an interface to support dynamic dispatch. +type IFieldNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsFieldNameContext differentiates from other interfaces. + IsFieldNameContext() +} + +type FieldNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFieldNameContext() *FieldNameContext { + var p = new(FieldNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldName + return p +} + +func InitEmptyFieldNameContext(p *FieldNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_fieldName +} + +func (*FieldNameContext) IsFieldNameContext() {} + +func NewFieldNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldNameContext { + var p = new(FieldNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_fieldName + + return p +} + +func (s *FieldNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *FieldNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *FieldNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FieldNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FieldNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFieldName(s) + } +} + +func (s *FieldNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFieldName(s) + } +} + +func (p *ProtobufParser) FieldName() (localctx IFieldNameContext) { + localctx = NewFieldNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 84, ProtobufParserRULE_fieldName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(465) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IOneofNameContext is an interface to support dynamic dispatch. +type IOneofNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsOneofNameContext differentiates from other interfaces. + IsOneofNameContext() +} + +type OneofNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyOneofNameContext() *OneofNameContext { + var p = new(OneofNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneofName + return p +} + +func InitEmptyOneofNameContext(p *OneofNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_oneofName +} + +func (*OneofNameContext) IsOneofNameContext() {} + +func NewOneofNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OneofNameContext { + var p = new(OneofNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_oneofName + + return p +} + +func (s *OneofNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *OneofNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *OneofNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *OneofNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *OneofNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterOneofName(s) + } +} + +func (s *OneofNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitOneofName(s) + } +} + +func (p *ProtobufParser) OneofName() (localctx IOneofNameContext) { + localctx = NewOneofNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 86, ProtobufParserRULE_oneofName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(467) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IServiceNameContext is an interface to support dynamic dispatch. +type IServiceNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsServiceNameContext differentiates from other interfaces. + IsServiceNameContext() +} + +type ServiceNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyServiceNameContext() *ServiceNameContext { + var p = new(ServiceNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceName + return p +} + +func InitEmptyServiceNameContext(p *ServiceNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_serviceName +} + +func (*ServiceNameContext) IsServiceNameContext() {} + +func NewServiceNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ServiceNameContext { + var p = new(ServiceNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_serviceName + + return p +} + +func (s *ServiceNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *ServiceNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *ServiceNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *ServiceNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *ServiceNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterServiceName(s) + } +} + +func (s *ServiceNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitServiceName(s) + } +} + +func (p *ProtobufParser) ServiceName() (localctx IServiceNameContext) { + localctx = NewServiceNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 88, ProtobufParserRULE_serviceName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(469) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IRpcNameContext is an interface to support dynamic dispatch. +type IRpcNameContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + Ident() IIdentContext + + // IsRpcNameContext differentiates from other interfaces. + IsRpcNameContext() +} + +type RpcNameContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyRpcNameContext() *RpcNameContext { + var p = new(RpcNameContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_rpcName + return p +} + +func InitEmptyRpcNameContext(p *RpcNameContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_rpcName +} + +func (*RpcNameContext) IsRpcNameContext() {} + +func NewRpcNameContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *RpcNameContext { + var p = new(RpcNameContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_rpcName + + return p +} + +func (s *RpcNameContext) GetParser() antlr.Parser { return s.parser } + +func (s *RpcNameContext) Ident() IIdentContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *RpcNameContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *RpcNameContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *RpcNameContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterRpcName(s) + } +} + +func (s *RpcNameContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitRpcName(s) + } +} + +func (p *ProtobufParser) RpcName() (localctx IRpcNameContext) { + localctx = NewRpcNameContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 90, ProtobufParserRULE_rpcName) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(471) + p.Ident() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IMessageTypeContext is an interface to support dynamic dispatch. +type IMessageTypeContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + MessageName() IMessageNameContext + AllDOT() []antlr.TerminalNode + DOT(i int) antlr.TerminalNode + AllIdent() []IIdentContext + Ident(i int) IIdentContext + + // IsMessageTypeContext differentiates from other interfaces. + IsMessageTypeContext() +} + +type MessageTypeContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyMessageTypeContext() *MessageTypeContext { + var p = new(MessageTypeContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageType + return p +} + +func InitEmptyMessageTypeContext(p *MessageTypeContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_messageType +} + +func (*MessageTypeContext) IsMessageTypeContext() {} + +func NewMessageTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *MessageTypeContext { + var p = new(MessageTypeContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_messageType + + return p +} + +func (s *MessageTypeContext) GetParser() antlr.Parser { return s.parser } + +func (s *MessageTypeContext) MessageName() IMessageNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IMessageNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IMessageNameContext) +} + +func (s *MessageTypeContext) AllDOT() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserDOT) +} + +func (s *MessageTypeContext) DOT(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserDOT, i) +} + +func (s *MessageTypeContext) AllIdent() []IIdentContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIdentContext); ok { + len++ + } + } + + tst := make([]IIdentContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIdentContext); ok { + tst[i] = t.(IIdentContext) + i++ + } + } + + return tst +} + +func (s *MessageTypeContext) Ident(i int) IIdentContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *MessageTypeContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *MessageTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *MessageTypeContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterMessageType(s) + } +} + +func (s *MessageTypeContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitMessageType(s) + } +} + +func (p *ProtobufParser) MessageType() (localctx IMessageTypeContext) { + localctx = NewMessageTypeContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 92, ProtobufParserRULE_messageType) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(474) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserDOT { + { + p.SetState(473) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(481) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 45, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(476) + p.Ident() + } + { + p.SetState(477) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(483) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 45, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(484) + p.MessageName() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IEnumTypeContext is an interface to support dynamic dispatch. +type IEnumTypeContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + EnumName() IEnumNameContext + AllDOT() []antlr.TerminalNode + DOT(i int) antlr.TerminalNode + AllIdent() []IIdentContext + Ident(i int) IIdentContext + + // IsEnumTypeContext differentiates from other interfaces. + IsEnumTypeContext() +} + +type EnumTypeContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyEnumTypeContext() *EnumTypeContext { + var p = new(EnumTypeContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumType + return p +} + +func InitEmptyEnumTypeContext(p *EnumTypeContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_enumType +} + +func (*EnumTypeContext) IsEnumTypeContext() {} + +func NewEnumTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *EnumTypeContext { + var p = new(EnumTypeContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_enumType + + return p +} + +func (s *EnumTypeContext) GetParser() antlr.Parser { return s.parser } + +func (s *EnumTypeContext) EnumName() IEnumNameContext { + var t antlr.RuleContext + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IEnumNameContext); ok { + t = ctx.(antlr.RuleContext) + break + } + } + + if t == nil { + return nil + } + + return t.(IEnumNameContext) +} + +func (s *EnumTypeContext) AllDOT() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserDOT) +} + +func (s *EnumTypeContext) DOT(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserDOT, i) +} + +func (s *EnumTypeContext) AllIdent() []IIdentContext { + children := s.GetChildren() + len := 0 + for _, ctx := range children { + if _, ok := ctx.(IIdentContext); ok { + len++ + } + } + + tst := make([]IIdentContext, len) + i := 0 + for _, ctx := range children { + if t, ok := ctx.(IIdentContext); ok { + tst[i] = t.(IIdentContext) + i++ + } + } + + return tst +} + +func (s *EnumTypeContext) Ident(i int) IIdentContext { + var t antlr.RuleContext + j := 0 + for _, ctx := range s.GetChildren() { + if _, ok := ctx.(IIdentContext); ok { + if j == i { + t = ctx.(antlr.RuleContext) + break + } + j++ + } + } + + if t == nil { + return nil + } + + return t.(IIdentContext) +} + +func (s *EnumTypeContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *EnumTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *EnumTypeContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterEnumType(s) + } +} + +func (s *EnumTypeContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitEnumType(s) + } +} + +func (p *ProtobufParser) EnumType() (localctx IEnumTypeContext) { + localctx = NewEnumTypeContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 94, ProtobufParserRULE_enumType) + var _la int + + var _alt int + + p.EnterOuterAlt(localctx, 1) + p.SetState(487) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + if _la == ProtobufParserDOT { + { + p.SetState(486) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(494) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 47, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + for _alt != 2 && _alt != antlr.ATNInvalidAltNumber { + if _alt == 1 { + { + p.SetState(489) + p.Ident() + } + { + p.SetState(490) + p.Match(ProtobufParserDOT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + } + p.SetState(496) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _alt = p.GetInterpreter().AdaptivePredict(p.BaseParser, p.GetTokenStream(), 47, p.GetParserRuleContext()) + if p.HasError() { + goto errorExit + } + } + { + p.SetState(497) + p.EnumName() + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IIntLitContext is an interface to support dynamic dispatch. +type IIntLitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + INT_LIT() antlr.TerminalNode + + // IsIntLitContext differentiates from other interfaces. + IsIntLitContext() +} + +type IntLitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyIntLitContext() *IntLitContext { + var p = new(IntLitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_intLit + return p +} + +func InitEmptyIntLitContext(p *IntLitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_intLit +} + +func (*IntLitContext) IsIntLitContext() {} + +func NewIntLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IntLitContext { + var p = new(IntLitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_intLit + + return p +} + +func (s *IntLitContext) GetParser() antlr.Parser { return s.parser } + +func (s *IntLitContext) INT_LIT() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT_LIT, 0) +} + +func (s *IntLitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *IntLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *IntLitContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterIntLit(s) + } +} + +func (s *IntLitContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitIntLit(s) + } +} + +func (p *ProtobufParser) IntLit() (localctx IIntLitContext) { + localctx = NewIntLitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 96, ProtobufParserRULE_intLit) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(499) + p.Match(ProtobufParserINT_LIT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IStrLitContext is an interface to support dynamic dispatch. +type IStrLitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + AllSTR_LIT_SINGLE() []antlr.TerminalNode + STR_LIT_SINGLE(i int) antlr.TerminalNode + + // IsStrLitContext differentiates from other interfaces. + IsStrLitContext() +} + +type StrLitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyStrLitContext() *StrLitContext { + var p = new(StrLitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_strLit + return p +} + +func InitEmptyStrLitContext(p *StrLitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_strLit +} + +func (*StrLitContext) IsStrLitContext() {} + +func NewStrLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *StrLitContext { + var p = new(StrLitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_strLit + + return p +} + +func (s *StrLitContext) GetParser() antlr.Parser { return s.parser } + +func (s *StrLitContext) AllSTR_LIT_SINGLE() []antlr.TerminalNode { + return s.GetTokens(ProtobufParserSTR_LIT_SINGLE) +} + +func (s *StrLitContext) STR_LIT_SINGLE(i int) antlr.TerminalNode { + return s.GetToken(ProtobufParserSTR_LIT_SINGLE, i) +} + +func (s *StrLitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *StrLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *StrLitContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterStrLit(s) + } +} + +func (s *StrLitContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitStrLit(s) + } +} + +func (p *ProtobufParser) StrLit() (localctx IStrLitContext) { + localctx = NewStrLitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 98, ProtobufParserRULE_strLit) + var _la int + + p.EnterOuterAlt(localctx, 1) + p.SetState(502) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + + for ok := true; ok; ok = _la == ProtobufParserSTR_LIT_SINGLE { + { + p.SetState(501) + p.Match(ProtobufParserSTR_LIT_SINGLE) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + + p.SetState(504) + p.GetErrorHandler().Sync(p) + if p.HasError() { + goto errorExit + } + _la = p.GetTokenStream().LA(1) + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IBoolLitContext is an interface to support dynamic dispatch. +type IBoolLitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + BOOL_LIT() antlr.TerminalNode + + // IsBoolLitContext differentiates from other interfaces. + IsBoolLitContext() +} + +type BoolLitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyBoolLitContext() *BoolLitContext { + var p = new(BoolLitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_boolLit + return p +} + +func InitEmptyBoolLitContext(p *BoolLitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_boolLit +} + +func (*BoolLitContext) IsBoolLitContext() {} + +func NewBoolLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *BoolLitContext { + var p = new(BoolLitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_boolLit + + return p +} + +func (s *BoolLitContext) GetParser() antlr.Parser { return s.parser } + +func (s *BoolLitContext) BOOL_LIT() antlr.TerminalNode { + return s.GetToken(ProtobufParserBOOL_LIT, 0) +} + +func (s *BoolLitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *BoolLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *BoolLitContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterBoolLit(s) + } +} + +func (s *BoolLitContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitBoolLit(s) + } +} + +func (p *ProtobufParser) BoolLit() (localctx IBoolLitContext) { + localctx = NewBoolLitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 100, ProtobufParserRULE_boolLit) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(506) + p.Match(ProtobufParserBOOL_LIT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IFloatLitContext is an interface to support dynamic dispatch. +type IFloatLitContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + FLOAT_LIT() antlr.TerminalNode + + // IsFloatLitContext differentiates from other interfaces. + IsFloatLitContext() +} + +type FloatLitContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyFloatLitContext() *FloatLitContext { + var p = new(FloatLitContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_floatLit + return p +} + +func InitEmptyFloatLitContext(p *FloatLitContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_floatLit +} + +func (*FloatLitContext) IsFloatLitContext() {} + +func NewFloatLitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FloatLitContext { + var p = new(FloatLitContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_floatLit + + return p +} + +func (s *FloatLitContext) GetParser() antlr.Parser { return s.parser } + +func (s *FloatLitContext) FLOAT_LIT() antlr.TerminalNode { + return s.GetToken(ProtobufParserFLOAT_LIT, 0) +} + +func (s *FloatLitContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *FloatLitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *FloatLitContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterFloatLit(s) + } +} + +func (s *FloatLitContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitFloatLit(s) + } +} + +func (p *ProtobufParser) FloatLit() (localctx IFloatLitContext) { + localctx = NewFloatLitContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 102, ProtobufParserRULE_floatLit) + p.EnterOuterAlt(localctx, 1) + { + p.SetState(508) + p.Match(ProtobufParserFLOAT_LIT) + if p.HasError() { + // Recognition error - abort rule + goto errorExit + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} + +// IKeywordsContext is an interface to support dynamic dispatch. +type IKeywordsContext interface { + antlr.ParserRuleContext + + // GetParser returns the parser. + GetParser() antlr.Parser + + // Getter signatures + SYNTAX() antlr.TerminalNode + EDITION() antlr.TerminalNode + IMPORT() antlr.TerminalNode + WEAK() antlr.TerminalNode + PUBLIC() antlr.TerminalNode + PACKAGE() antlr.TerminalNode + OPTION() antlr.TerminalNode + OPTIONAL() antlr.TerminalNode + REPEATED() antlr.TerminalNode + ONEOF() antlr.TerminalNode + MAP() antlr.TerminalNode + INT32() antlr.TerminalNode + INT64() antlr.TerminalNode + UINT32() antlr.TerminalNode + UINT64() antlr.TerminalNode + SINT32() antlr.TerminalNode + SINT64() antlr.TerminalNode + FIXED32() antlr.TerminalNode + FIXED64() antlr.TerminalNode + SFIXED32() antlr.TerminalNode + SFIXED64() antlr.TerminalNode + BOOL() antlr.TerminalNode + STRING() antlr.TerminalNode + DOUBLE() antlr.TerminalNode + FLOAT() antlr.TerminalNode + BYTES() antlr.TerminalNode + RESERVED() antlr.TerminalNode + EXTENSIONS() antlr.TerminalNode + TO() antlr.TerminalNode + MAX() antlr.TerminalNode + ENUM() antlr.TerminalNode + MESSAGE() antlr.TerminalNode + SERVICE() antlr.TerminalNode + EXTEND() antlr.TerminalNode + RPC() antlr.TerminalNode + STREAM() antlr.TerminalNode + RETURNS() antlr.TerminalNode + BOOL_LIT() antlr.TerminalNode + + // IsKeywordsContext differentiates from other interfaces. + IsKeywordsContext() +} + +type KeywordsContext struct { + antlr.BaseParserRuleContext + parser antlr.Parser +} + +func NewEmptyKeywordsContext() *KeywordsContext { + var p = new(KeywordsContext) + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_keywords + return p +} + +func InitEmptyKeywordsContext(p *KeywordsContext) { + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, nil, -1) + p.RuleIndex = ProtobufParserRULE_keywords +} + +func (*KeywordsContext) IsKeywordsContext() {} + +func NewKeywordsContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *KeywordsContext { + var p = new(KeywordsContext) + + antlr.InitBaseParserRuleContext(&p.BaseParserRuleContext, parent, invokingState) + + p.parser = parser + p.RuleIndex = ProtobufParserRULE_keywords + + return p +} + +func (s *KeywordsContext) GetParser() antlr.Parser { return s.parser } + +func (s *KeywordsContext) SYNTAX() antlr.TerminalNode { + return s.GetToken(ProtobufParserSYNTAX, 0) +} + +func (s *KeywordsContext) EDITION() antlr.TerminalNode { + return s.GetToken(ProtobufParserEDITION, 0) +} + +func (s *KeywordsContext) IMPORT() antlr.TerminalNode { + return s.GetToken(ProtobufParserIMPORT, 0) +} + +func (s *KeywordsContext) WEAK() antlr.TerminalNode { + return s.GetToken(ProtobufParserWEAK, 0) +} + +func (s *KeywordsContext) PUBLIC() antlr.TerminalNode { + return s.GetToken(ProtobufParserPUBLIC, 0) +} + +func (s *KeywordsContext) PACKAGE() antlr.TerminalNode { + return s.GetToken(ProtobufParserPACKAGE, 0) +} + +func (s *KeywordsContext) OPTION() antlr.TerminalNode { + return s.GetToken(ProtobufParserOPTION, 0) +} + +func (s *KeywordsContext) OPTIONAL() antlr.TerminalNode { + return s.GetToken(ProtobufParserOPTIONAL, 0) +} + +func (s *KeywordsContext) REPEATED() antlr.TerminalNode { + return s.GetToken(ProtobufParserREPEATED, 0) +} + +func (s *KeywordsContext) ONEOF() antlr.TerminalNode { + return s.GetToken(ProtobufParserONEOF, 0) +} + +func (s *KeywordsContext) MAP() antlr.TerminalNode { + return s.GetToken(ProtobufParserMAP, 0) +} + +func (s *KeywordsContext) INT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT32, 0) +} + +func (s *KeywordsContext) INT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserINT64, 0) +} + +func (s *KeywordsContext) UINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT32, 0) +} + +func (s *KeywordsContext) UINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserUINT64, 0) +} + +func (s *KeywordsContext) SINT32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT32, 0) +} + +func (s *KeywordsContext) SINT64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSINT64, 0) +} + +func (s *KeywordsContext) FIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED32, 0) +} + +func (s *KeywordsContext) FIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserFIXED64, 0) +} + +func (s *KeywordsContext) SFIXED32() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED32, 0) +} + +func (s *KeywordsContext) SFIXED64() antlr.TerminalNode { + return s.GetToken(ProtobufParserSFIXED64, 0) +} + +func (s *KeywordsContext) BOOL() antlr.TerminalNode { + return s.GetToken(ProtobufParserBOOL, 0) +} + +func (s *KeywordsContext) STRING() antlr.TerminalNode { + return s.GetToken(ProtobufParserSTRING, 0) +} + +func (s *KeywordsContext) DOUBLE() antlr.TerminalNode { + return s.GetToken(ProtobufParserDOUBLE, 0) +} + +func (s *KeywordsContext) FLOAT() antlr.TerminalNode { + return s.GetToken(ProtobufParserFLOAT, 0) +} + +func (s *KeywordsContext) BYTES() antlr.TerminalNode { + return s.GetToken(ProtobufParserBYTES, 0) +} + +func (s *KeywordsContext) RESERVED() antlr.TerminalNode { + return s.GetToken(ProtobufParserRESERVED, 0) +} + +func (s *KeywordsContext) EXTENSIONS() antlr.TerminalNode { + return s.GetToken(ProtobufParserEXTENSIONS, 0) +} + +func (s *KeywordsContext) TO() antlr.TerminalNode { + return s.GetToken(ProtobufParserTO, 0) +} + +func (s *KeywordsContext) MAX() antlr.TerminalNode { + return s.GetToken(ProtobufParserMAX, 0) +} + +func (s *KeywordsContext) ENUM() antlr.TerminalNode { + return s.GetToken(ProtobufParserENUM, 0) +} + +func (s *KeywordsContext) MESSAGE() antlr.TerminalNode { + return s.GetToken(ProtobufParserMESSAGE, 0) +} + +func (s *KeywordsContext) SERVICE() antlr.TerminalNode { + return s.GetToken(ProtobufParserSERVICE, 0) +} + +func (s *KeywordsContext) EXTEND() antlr.TerminalNode { + return s.GetToken(ProtobufParserEXTEND, 0) +} + +func (s *KeywordsContext) RPC() antlr.TerminalNode { + return s.GetToken(ProtobufParserRPC, 0) +} + +func (s *KeywordsContext) STREAM() antlr.TerminalNode { + return s.GetToken(ProtobufParserSTREAM, 0) +} + +func (s *KeywordsContext) RETURNS() antlr.TerminalNode { + return s.GetToken(ProtobufParserRETURNS, 0) +} + +func (s *KeywordsContext) BOOL_LIT() antlr.TerminalNode { + return s.GetToken(ProtobufParserBOOL_LIT, 0) +} + +func (s *KeywordsContext) GetRuleContext() antlr.RuleContext { + return s +} + +func (s *KeywordsContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string { + return antlr.TreesStringTree(s, ruleNames, recog) +} + +func (s *KeywordsContext) EnterRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.EnterKeywords(s) + } +} + +func (s *KeywordsContext) ExitRule(listener antlr.ParseTreeListener) { + if listenerT, ok := listener.(ProtobufListener); ok { + listenerT.ExitKeywords(s) + } +} + +func (p *ProtobufParser) Keywords() (localctx IKeywordsContext) { + localctx = NewKeywordsContext(p, p.GetParserRuleContext(), p.GetState()) + p.EnterRule(localctx, 104, ProtobufParserRULE_keywords) + var _la int + + p.EnterOuterAlt(localctx, 1) + { + p.SetState(510) + _la = p.GetTokenStream().LA(1) + + if !((int64(_la) & ^0x3f) == 0 && ((int64(1)<<_la)&36029346774777342) != 0) { + p.GetErrorHandler().RecoverInline(p) + } else { + p.GetErrorHandler().ReportMatch(p) + p.Consume() + } + } + +errorExit: + if p.HasError() { + v := p.GetError() + localctx.SetException(v) + p.GetErrorHandler().ReportError(p, v) + p.GetErrorHandler().Recover(p, v) + p.SetError(nil) + } + p.ExitRule() + return localctx + goto errorExit // Trick to prevent compiler error if the label is not used +} diff --git a/prutalgen/internal/protobuf/strs/strings.go b/prutalgen/internal/protobuf/strs/strings.go new file mode 100644 index 0000000..1859505 --- /dev/null +++ b/prutalgen/internal/protobuf/strs/strings.go @@ -0,0 +1,185 @@ +// This source code originates from `internal/strs` pkg of +// go.googlesource.com/protobuf and has been modified. +// +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the licenses/protobuf-LICENSE file. + +package strs + +import ( + "go/token" + "strings" + "unicode" + "unicode/utf8" +) + +// GoCamelCase camel-cases a protobuf name for use as a Go identifier. +// +// If there is an interior underscore followed by a lower case letter, +// drop the underscore and convert the letter to upper case. +func GoCamelCase(s string) string { + // Invariant: if the next letter is lower case, it must be converted + // to upper case. + // That is, we process a word at a time, where words are marked by _ or + // upper case letter. Digits are treated as words. + var b []byte + for i := 0; i < len(s); i++ { + c := s[i] + switch { + case c == '.' && i+1 < len(s) && isASCIILower(s[i+1]): + // Skip over '.' in ".{{lowercase}}". + case c == '.': + b = append(b, '_') // convert '.' to '_' + case c == '_' && (i == 0 || s[i-1] == '.'): + // Convert initial '_' to ensure we start with a capital letter. + // Do the same for '_' after '.' to match historic behavior. + b = append(b, 'X') // convert '_' to 'X' + case c == '_' && i+1 < len(s) && isASCIILower(s[i+1]): + // Skip over '_' in "_{{lowercase}}". + case isASCIIDigit(c): + b = append(b, c) + default: + // Assume we have a letter now - if not, it's a bogus identifier. + // The next word is a sequence of characters that must start upper case. + if isASCIILower(c) { + c -= 'a' - 'A' // convert lowercase to uppercase + } + b = append(b, c) + + // Accept lower case sequence that follows. + for ; i+1 < len(s) && isASCIILower(s[i+1]); i++ { + b = append(b, s[i+1]) + } + } + } + return string(b) +} + +// GoSanitized converts a string to a valid Go identifier. +func GoSanitized(s string) string { + // Sanitize the input to the set of valid characters, + // which must be '_' or be in the Unicode L or N categories. + s = strings.Map(func(r rune) rune { + if unicode.IsLetter(r) || unicode.IsDigit(r) { + return r + } + return '_' + }, s) + + // Prepend '_' in the event of a Go keyword conflict or if + // the identifier is invalid (does not start in the Unicode L category). + r, _ := utf8.DecodeRuneInString(s) + if token.Lookup(s).IsKeyword() || !unicode.IsLetter(r) { + return "_" + s + } + return s +} + +// JSONCamelCase converts a snake_case identifier to a camelCase identifier, +// according to the protobuf JSON specification. +func JSONCamelCase(s string) string { + var b []byte + var wasUnderscore bool + for i := 0; i < len(s); i++ { // proto identifiers are always ASCII + c := s[i] + if c != '_' { + if wasUnderscore && isASCIILower(c) { + c -= 'a' - 'A' // convert to uppercase + } + b = append(b, c) + } + wasUnderscore = c == '_' + } + return string(b) +} + +// JSONSnakeCase converts a camelCase identifier to a snake_case identifier, +// according to the protobuf JSON specification. +func JSONSnakeCase(s string) string { + var b []byte + for i := 0; i < len(s); i++ { // proto identifiers are always ASCII + c := s[i] + if isASCIIUpper(c) { + b = append(b, '_') + c += 'a' - 'A' // convert to lowercase + } + b = append(b, c) + } + return string(b) +} + +// MapEntryName derives the name of the map entry message given the field name. +// See protoc v3.8.0: src/google/protobuf/descriptor.cc:254-276,6057 +func MapEntryName(s string) string { + var b []byte + upperNext := true + for _, c := range s { + switch { + case c == '_': + upperNext = true + case upperNext: + b = append(b, byte(unicode.ToUpper(c))) + upperNext = false + default: + b = append(b, byte(c)) + } + } + b = append(b, "Entry"...) + return string(b) +} + +// EnumValueName derives the camel-cased enum value name. +// See protoc v3.8.0: src/google/protobuf/descriptor.cc:297-313 +func EnumValueName(s string) string { + var b []byte + upperNext := true + for _, c := range s { + switch { + case c == '_': + upperNext = true + case upperNext: + b = append(b, byte(unicode.ToUpper(c))) + upperNext = false + default: + b = append(b, byte(unicode.ToLower(c))) + upperNext = false + } + } + return string(b) +} + +// TrimEnumPrefix trims the enum name prefix from an enum value name, +// where the prefix is all lowercase without underscores. +// See protoc v3.8.0: src/google/protobuf/descriptor.cc:330-375 +func TrimEnumPrefix(s, prefix string) string { + s0 := s // original input + for len(s) > 0 && len(prefix) > 0 { + if s[0] == '_' { + s = s[1:] + continue + } + if unicode.ToLower(rune(s[0])) != rune(prefix[0]) { + return s0 // no prefix match + } + s, prefix = s[1:], prefix[1:] + } + if len(prefix) > 0 { + return s0 // no prefix match + } + s = strings.TrimLeft(s, "_") + if len(s) == 0 { + return s0 // avoid returning empty string + } + return s +} + +func isASCIILower(c byte) bool { + return 'a' <= c && c <= 'z' +} +func isASCIIUpper(c byte) bool { + return 'A' <= c && c <= 'Z' +} +func isASCIIDigit(c byte) bool { + return '0' <= c && c <= '9' +} diff --git a/prutalgen/internal/protobuf/strs/strings_test.go b/prutalgen/internal/protobuf/strs/strings_test.go new file mode 100644 index 0000000..095f95e --- /dev/null +++ b/prutalgen/internal/protobuf/strs/strings_test.go @@ -0,0 +1,129 @@ +// This source code originates from `internal/strs` pkg of +// go.googlesource.com/protobuf and has been modified. +// +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the licenses/protobuf-LICENSE file. + +package strs + +import ( + "testing" +) + +func TestGoCamelCase(t *testing.T) { + tests := []struct { + in, want string + }{ + {"", ""}, + {"one", "One"}, + {"one_two", "OneTwo"}, + {"_my_field_name_2", "XMyFieldName_2"}, + {"Something_Capped", "Something_Capped"}, + {"my_Name", "My_Name"}, + {"OneTwo", "OneTwo"}, + {"_", "X"}, + {"_a_", "XA_"}, + {"one.two", "OneTwo"}, + {"one.Two", "One_Two"}, + {"one_two.three_four", "OneTwoThreeFour"}, + {"one_two.Three_four", "OneTwo_ThreeFour"}, + {"_one._two", "XOne_XTwo"}, + {"SCREAMING_SNAKE_CASE", "SCREAMING_SNAKE_CASE"}, + {"double__underscore", "Double_Underscore"}, + {"camelCase", "CamelCase"}, + {"go2proto", "Go2Proto"}, + {"世界", "世界"}, + {"x世界", "X世界"}, + {"foo_bar世界", "FooBar世界"}, + } + for _, tc := range tests { + if got := GoCamelCase(tc.in); got != tc.want { + t.Errorf("GoCamelCase(%q) = %q, want %q", tc.in, got, tc.want) + } + } +} + +func TestGoSanitized(t *testing.T) { + tests := []struct { + in, want string + }{ + {"", "_"}, + {"boo", "boo"}, + {"Boo", "Boo"}, + {"ßoo", "ßoo"}, + {"default", "_default"}, + {"hello", "hello"}, + {"hello-world!!", "hello_world__"}, + {"hello-\xde\xad\xbe\xef\x00", "hello_____"}, + {"hello 世界", "hello_世界"}, + {"世界", "世界"}, + } + for _, tc := range tests { + if got := GoSanitized(tc.in); got != tc.want { + t.Errorf("GoSanitized(%q) = %q, want %q", tc.in, got, tc.want) + } + } +} + +func TestName(t *testing.T) { + tests := []struct { + in string + inEnumPrefix string + wantMapEntry string + wantEnumValue string + wantTrimValue string + wantJSONCamelCase string + wantJSONSnakeCase string + }{{ + in: "abc", + inEnumPrefix: "", + wantMapEntry: "AbcEntry", + wantEnumValue: "Abc", + wantTrimValue: "abc", + wantJSONCamelCase: "abc", + wantJSONSnakeCase: "abc", + }, { + in: "foo_baR_", + inEnumPrefix: "foo_bar", + wantMapEntry: "FooBaREntry", + wantEnumValue: "FooBar", + wantTrimValue: "foo_baR_", + wantJSONCamelCase: "fooBaR", + wantJSONSnakeCase: "foo_ba_r_", + }, { + in: "snake_caseCamelCase", + inEnumPrefix: "snakecasecamel", + wantMapEntry: "SnakeCaseCamelCaseEntry", + wantEnumValue: "SnakeCasecamelcase", + wantTrimValue: "Case", + wantJSONCamelCase: "snakeCaseCamelCase", + wantJSONSnakeCase: "snake_case_camel_case", + }, { + in: "FiZz_BuZz", + inEnumPrefix: "fizz", + wantMapEntry: "FiZzBuZzEntry", + wantEnumValue: "FizzBuzz", + wantTrimValue: "BuZz", + wantJSONCamelCase: "FiZzBuZz", + wantJSONSnakeCase: "_fi_zz__bu_zz", + }} + + for _, tt := range tests { + if got := MapEntryName(tt.in); got != tt.wantMapEntry { + t.Errorf("MapEntryName(%q) = %q, want %q", tt.in, got, tt.wantMapEntry) + } + if got := EnumValueName(tt.in); got != tt.wantEnumValue { + t.Errorf("EnumValueName(%q) = %q, want %q", tt.in, got, tt.wantEnumValue) + } + if got := TrimEnumPrefix(tt.in, tt.inEnumPrefix); got != tt.wantTrimValue { + t.Errorf("ErimEnumPrefix(%q, %q) = %q, want %q", tt.in, tt.inEnumPrefix, got, tt.wantTrimValue) + } + if got := JSONCamelCase(tt.in); got != tt.wantJSONCamelCase { + t.Errorf("JSONCamelCase(%q) = %q, want %q", tt.in, got, tt.wantJSONCamelCase) + } + if got := JSONSnakeCase(tt.in); got != tt.wantJSONSnakeCase { + t.Errorf("JSONSnakeCase(%q) = %q, want %q", tt.in, got, tt.wantJSONSnakeCase) + } + } +} diff --git a/prutalgen/internal/protobuf/text/decode.go b/prutalgen/internal/protobuf/text/decode.go new file mode 100644 index 0000000..8567408 --- /dev/null +++ b/prutalgen/internal/protobuf/text/decode.go @@ -0,0 +1,674 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package text + +import ( + "bytes" + "fmt" + "io" + "strconv" + "unicode/utf8" +) + +// Decoder is a token-based textproto decoder. +type Decoder struct { + // lastCall is last method called, either readCall or peekCall. + // Initial value is readCall. + lastCall call + + // lastToken contains the last read token. + lastToken Token + + // lastErr contains the last read error. + lastErr error + + // openStack is a stack containing the byte characters for MessageOpen and + // ListOpen kinds. The top of stack represents the message or the list that + // the current token is nested in. An empty stack means the current token is + // at the top level message. The characters '{' and '<' both represent the + // MessageOpen kind. + openStack []byte + + // orig is used in reporting line and column. + orig []byte + // in contains the unconsumed input. + in []byte +} + +// NewDecoder returns a Decoder to read the given []byte. +func NewDecoder(b []byte) *Decoder { + return &Decoder{orig: b, in: b} +} + +// ErrUnexpectedEOF means that EOF was encountered in the middle of the input. +var ErrUnexpectedEOF = fmt.Errorf("%w", io.ErrUnexpectedEOF) + +// call specifies which Decoder method was invoked. +type call uint8 + +const ( + readCall call = iota + peekCall +) + +// Peek looks ahead and returns the next token and error without advancing a read. +func (d *Decoder) Peek() (Token, error) { + defer func() { d.lastCall = peekCall }() + if d.lastCall == readCall { + d.lastToken, d.lastErr = d.Read() + } + return d.lastToken, d.lastErr +} + +// Read returns the next token. +// It will return an error if there is no valid token. +func (d *Decoder) Read() (Token, error) { + defer func() { d.lastCall = readCall }() + if d.lastCall == peekCall { + return d.lastToken, d.lastErr + } + + tok, err := d.parseNext(d.lastToken.kind) + if err != nil { + return Token{}, err + } + + switch tok.kind { + case comma, semicolon: + tok, err = d.parseNext(tok.kind) + if err != nil { + return Token{}, err + } + } + d.lastToken = tok + return tok, nil +} + +const ( + mismatchedFmt = "mismatched close character %q" + unexpectedFmt = "unexpected character %q" +) + +// parseNext parses the next Token based on given last kind. +func (d *Decoder) parseNext(lastKind Kind) (Token, error) { + // Trim leading spaces. + d.consume(0) + isEOF := false + if len(d.in) == 0 { + isEOF = true + } + + switch lastKind { + case EOF: + return d.consumeToken(EOF, 0, 0), nil + + case bof: + // Start of top level message. Next token can be EOF or Name. + if isEOF { + return d.consumeToken(EOF, 0, 0), nil + } + return d.parseFieldName() + + case Name: + // Next token can be MessageOpen, ListOpen or Scalar. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case '{', '<': + d.pushOpenStack(ch) + return d.consumeToken(MessageOpen, 1, 0), nil + case '[': + d.pushOpenStack(ch) + return d.consumeToken(ListOpen, 1, 0), nil + default: + return d.parseScalar() + } + + case Scalar: + openKind, closeCh := d.currentOpenKind() + switch openKind { + case bof: + // Top level message. + // Next token can be EOF, comma, semicolon or Name. + if isEOF { + return d.consumeToken(EOF, 0, 0), nil + } + switch d.in[0] { + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + case MessageOpen: + // Next token can be MessageClose, comma, semicolon or Name. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(MessageClose, 1, 0), nil + case otherCloseChar[closeCh]: + return Token{}, d.newSyntaxError(mismatchedFmt, ch) + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + case ListOpen: + // Next token can be ListClose or comma. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case ']': + d.popOpenStack() + return d.consumeToken(ListClose, 1, 0), nil + case ',': + return d.consumeToken(comma, 1, 0), nil + default: + return Token{}, d.newSyntaxError(unexpectedFmt, ch) + } + } + + case MessageOpen: + // Next token can be MessageClose or Name. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + _, closeCh := d.currentOpenKind() + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(MessageClose, 1, 0), nil + case otherCloseChar[closeCh]: + return Token{}, d.newSyntaxError(mismatchedFmt, ch) + default: + return d.parseFieldName() + } + + case MessageClose: + openKind, closeCh := d.currentOpenKind() + switch openKind { + case bof: + // Top level message. + // Next token can be EOF, comma, semicolon or Name. + if isEOF { + return d.consumeToken(EOF, 0, 0), nil + } + switch ch := d.in[0]; ch { + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + case MessageOpen: + // Next token can be MessageClose, comma, semicolon or Name. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(MessageClose, 1, 0), nil + case otherCloseChar[closeCh]: + return Token{}, d.newSyntaxError(mismatchedFmt, ch) + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + case ListOpen: + // Next token can be ListClose or comma + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(ListClose, 1, 0), nil + case ',': + return d.consumeToken(comma, 1, 0), nil + default: + return Token{}, d.newSyntaxError(unexpectedFmt, ch) + } + } + + case ListOpen: + // Next token can be ListClose, MessageStart or Scalar. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case ']': + d.popOpenStack() + return d.consumeToken(ListClose, 1, 0), nil + case '{', '<': + d.pushOpenStack(ch) + return d.consumeToken(MessageOpen, 1, 0), nil + default: + return d.parseScalar() + } + + case ListClose: + openKind, closeCh := d.currentOpenKind() + switch openKind { + case bof: + // Top level message. + // Next token can be EOF, comma, semicolon or Name. + if isEOF { + return d.consumeToken(EOF, 0, 0), nil + } + switch ch := d.in[0]; ch { + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + case MessageOpen: + // Next token can be MessageClose, comma, semicolon or Name. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(MessageClose, 1, 0), nil + case otherCloseChar[closeCh]: + return Token{}, d.newSyntaxError(mismatchedFmt, ch) + case ',': + return d.consumeToken(comma, 1, 0), nil + case ';': + return d.consumeToken(semicolon, 1, 0), nil + default: + return d.parseFieldName() + } + + default: + // It is not possible to have this case. Let it panic below. + } + + case comma, semicolon: + openKind, closeCh := d.currentOpenKind() + switch openKind { + case bof: + // Top level message. Next token can be EOF or Name. + if isEOF { + return d.consumeToken(EOF, 0, 0), nil + } + return d.parseFieldName() + + case MessageOpen: + // Next token can be MessageClose or Name. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case closeCh: + d.popOpenStack() + return d.consumeToken(MessageClose, 1, 0), nil + case otherCloseChar[closeCh]: + return Token{}, d.newSyntaxError(mismatchedFmt, ch) + default: + return d.parseFieldName() + } + + case ListOpen: + if lastKind == semicolon { + // It is not be possible to have this case as logic here + // should not have produced a semicolon Token when inside a + // list. Let it panic below. + break + } + // Next token can be MessageOpen or Scalar. + if isEOF { + return Token{}, ErrUnexpectedEOF + } + switch ch := d.in[0]; ch { + case '{', '<': + d.pushOpenStack(ch) + return d.consumeToken(MessageOpen, 1, 0), nil + default: + return d.parseScalar() + } + } + } + + line, column := d.Position(len(d.orig) - len(d.in)) + panic(fmt.Sprintf("Decoder.parseNext: bug at handling line %d:%d with lastKind=%v", line, column, lastKind)) +} + +var otherCloseChar = map[byte]byte{ + '}': '>', + '>': '}', +} + +// currentOpenKind indicates whether current position is inside a message, list +// or top-level message by returning MessageOpen, ListOpen or bof respectively. +// If the returned kind is either a MessageOpen or ListOpen, it also returns the +// corresponding closing character. +func (d *Decoder) currentOpenKind() (Kind, byte) { + if len(d.openStack) == 0 { + return bof, 0 + } + openCh := d.openStack[len(d.openStack)-1] + switch openCh { + case '{': + return MessageOpen, '}' + case '<': + return MessageOpen, '>' + case '[': + return ListOpen, ']' + } + panic(fmt.Sprintf("Decoder: openStack contains invalid byte %c", openCh)) +} + +func (d *Decoder) pushOpenStack(ch byte) { + d.openStack = append(d.openStack, ch) +} + +func (d *Decoder) popOpenStack() { + d.openStack = d.openStack[:len(d.openStack)-1] +} + +// parseFieldName parses field name and separator. +func (d *Decoder) parseFieldName() (tok Token, err error) { + defer func() { + if err == nil && d.tryConsumeChar(':') { + tok.attrs |= hasSeparator + } + }() + + // Extension or Any type URL. + if d.in[0] == '[' { + return d.parseTypeName() + } + + // Identifier. + if size := parseIdent(d.in, false); size > 0 { + return d.consumeToken(Name, size, uint8(IdentName)), nil + } + + // Field number. Identify if input is a valid number that is not negative + // and is decimal integer within 32-bit range. + if num := parseNumber(d.in); num.size > 0 { + str := num.string(d.in) + if !num.neg && num.kind == numDec { + if _, err := strconv.ParseInt(str, 10, 32); err == nil { + return d.consumeToken(Name, num.size, uint8(FieldNumber)), nil + } + } + return Token{}, d.newSyntaxError("invalid field number: %s", str) + } + + return Token{}, d.newSyntaxError("invalid field name: %s", errId(d.in)) +} + +// parseTypeName parses Any type URL or extension field name. The name is +// enclosed in [ and ] characters. The C++ parser does not handle many legal URL +// strings. This implementation is more liberal and allows for the pattern +// ^[-_a-zA-Z0-9]+([./][-_a-zA-Z0-9]+)*`). Whitespaces and comments are allowed +// in between [ ], '.', '/' and the sub names. +func (d *Decoder) parseTypeName() (Token, error) { + startPos := len(d.orig) - len(d.in) + // Use alias s to advance first in order to use d.in for error handling. + // Caller already checks for [ as first character. + s := consume(d.in[1:], 0) + if len(s) == 0 { + return Token{}, ErrUnexpectedEOF + } + + var name []byte + for len(s) > 0 && isTypeNameChar(s[0]) { + name = append(name, s[0]) + s = s[1:] + } + s = consume(s, 0) + + var closed bool + for len(s) > 0 && !closed { + switch { + case s[0] == ']': + s = s[1:] + closed = true + + case s[0] == '/', s[0] == '.': + if len(name) > 0 && (name[len(name)-1] == '/' || name[len(name)-1] == '.') { + return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s", + d.orig[startPos:len(d.orig)-len(s)+1]) + } + name = append(name, s[0]) + s = s[1:] + s = consume(s, 0) + for len(s) > 0 && isTypeNameChar(s[0]) { + name = append(name, s[0]) + s = s[1:] + } + s = consume(s, 0) + + default: + return Token{}, d.newSyntaxError( + "invalid type URL/extension field name: %s", d.orig[startPos:len(d.orig)-len(s)+1]) + } + } + + if !closed { + return Token{}, ErrUnexpectedEOF + } + + // First character cannot be '.'. Last character cannot be '.' or '/'. + size := len(name) + if size == 0 || name[0] == '.' || name[size-1] == '.' || name[size-1] == '/' { + return Token{}, d.newSyntaxError("invalid type URL/extension field name: %s", + d.orig[startPos:len(d.orig)-len(s)]) + } + + d.in = s + endPos := len(d.orig) - len(d.in) + d.consume(0) + + return Token{ + kind: Name, + attrs: uint8(TypeName), + pos: startPos, + raw: d.orig[startPos:endPos], + str: string(name), + }, nil +} + +func isTypeNameChar(b byte) bool { + return (b == '-' || b == '_' || + ('0' <= b && b <= '9') || + ('a' <= b && b <= 'z') || + ('A' <= b && b <= 'Z')) +} + +// parseIdent parses an unquoted proto identifier and returns size. +// If allowNeg is true, it allows '-' to be the first character in the +// identifier. This is used when parsing literal values like -infinity, etc. +// Regular expression matches an identifier: `^[_a-zA-Z][_a-zA-Z0-9]*` +func parseIdent(input []byte, allowNeg bool) int { + var size int + + s := input + if len(s) == 0 { + return 0 + } + + if allowNeg && s[0] == '-' { + s = s[1:] + size++ + if len(s) == 0 { + return 0 + } + } + + switch { + case s[0] == '_', + 'a' <= s[0] && s[0] <= 'z', + 'A' <= s[0] && s[0] <= 'Z': + s = s[1:] + size++ + default: + return 0 + } + + for len(s) > 0 && (s[0] == '_' || + 'a' <= s[0] && s[0] <= 'z' || + 'A' <= s[0] && s[0] <= 'Z' || + '0' <= s[0] && s[0] <= '9') { + s = s[1:] + size++ + } + + if len(s) > 0 && !isDelim(s[0]) { + return 0 + } + + return size +} + +// parseScalar parses for a string, literal or number value. +func (d *Decoder) parseScalar() (Token, error) { + if d.in[0] == '"' || d.in[0] == '\'' { + return d.parseStringValue() + } + + if tok, ok := d.parseLiteralValue(); ok { + return tok, nil + } + + if tok, ok := d.parseNumberValue(); ok { + return tok, nil + } + + return Token{}, d.newSyntaxError("invalid scalar value: %s", errId(d.in)) +} + +// parseLiteralValue parses a literal value. A literal value is used for +// bools, special floats and enums. This function simply identifies that the +// field value is a literal. +func (d *Decoder) parseLiteralValue() (Token, bool) { + size := parseIdent(d.in, true) + if size == 0 { + return Token{}, false + } + return d.consumeToken(Scalar, size, literalValue), true +} + +// consumeToken constructs a Token for given Kind from d.in and consumes given +// size-length from it. +func (d *Decoder) consumeToken(kind Kind, size int, attrs uint8) Token { + // Important to compute raw and pos before consuming. + tok := Token{ + kind: kind, + attrs: attrs, + pos: len(d.orig) - len(d.in), + raw: d.in[:size], + } + d.consume(size) + return tok +} + +// newSyntaxError returns a syntax error with line and column information for +// current position. +func (d *Decoder) newSyntaxError(f string, x ...any) error { + e := fmt.Errorf(f, x...) + line, column := d.Position(len(d.orig) - len(d.in)) + return fmt.Errorf("syntax error (line %d:%d): %w", line, column, e) +} + +// Position returns line and column number of given index of the original input. +// It will panic if index is out of range. +func (d *Decoder) Position(idx int) (line int, column int) { + b := d.orig[:idx] + line = bytes.Count(b, []byte("\n")) + 1 + if i := bytes.LastIndexByte(b, '\n'); i >= 0 { + b = b[i+1:] + } + column = utf8.RuneCount(b) + 1 // ignore multi-rune characters + return line, column +} + +func (d *Decoder) tryConsumeChar(c byte) bool { + if len(d.in) > 0 && d.in[0] == c { + d.consume(1) + return true + } + return false +} + +// consume consumes n bytes of input and any subsequent whitespace or comments. +func (d *Decoder) consume(n int) { + d.in = consume(d.in, n) +} + +// consume consumes n bytes of input and any subsequent whitespace or comments. +func consume(b []byte, n int) []byte { + b = b[n:] + for len(b) > 0 { + switch b[0] { + case ' ', '\n', '\r', '\t': + b = b[1:] + case '#': + if i := bytes.IndexByte(b, '\n'); i >= 0 { + b = b[i+len("\n"):] + } else { + b = nil + } + default: + return b + } + } + return b +} + +// errId extracts a byte sequence that looks like an invalid ID +// (for the purposes of error reporting). +func errId(seq []byte) []byte { + const maxLen = 32 + for i := 0; i < len(seq); { + if i > maxLen { + return append(seq[:i:i], "…"...) + } + r, size := utf8.DecodeRune(seq[i:]) + if r > utf8.RuneSelf || (r != '/' && isDelim(byte(r))) { + if i == 0 { + // Either the first byte is invalid UTF-8 or a + // delimiter, or the first rune is non-ASCII. + // Return it as-is. + i = size + } + return seq[:i:i] + } + i += size + } + // No delimiter found. + return seq +} + +// isDelim returns true if given byte is a delimiter character. +func isDelim(c byte) bool { + return !(c == '-' || c == '+' || c == '.' || c == '_' || + ('a' <= c && c <= 'z') || + ('A' <= c && c <= 'Z') || + ('0' <= c && c <= '9')) +} diff --git a/prutalgen/internal/protobuf/text/decode_number.go b/prutalgen/internal/protobuf/text/decode_number.go new file mode 100644 index 0000000..579312c --- /dev/null +++ b/prutalgen/internal/protobuf/text/decode_number.go @@ -0,0 +1,221 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package text + +// parseNumberValue parses a number from the input and returns a Token object. +func (d *Decoder) parseNumberValue() (Token, bool) { + in := d.in + num := parseNumber(in) + if num.size == 0 { + return Token{}, false + } + numAttrs := num.kind + if num.neg { + numAttrs |= isNegative + } + tok := Token{ + kind: Scalar, + attrs: numberValue, + pos: len(d.orig) - len(d.in), + raw: d.in[:num.size], + str: num.string(d.in), + numAttrs: numAttrs, + } + d.consume(num.size) + return tok, true +} + +const ( + numDec uint8 = (1 << iota) / 2 + numHex + numOct + numFloat +) + +// number is the result of parsing out a valid number from parseNumber. It +// contains data for doing float or integer conversion via the strconv package +// in conjunction with the input bytes. +type number struct { + kind uint8 + neg bool + size int + // if neg, this is the length of whitespace and comments between + // the minus sign and the rest fo the number literal + sep int +} + +func (num number) string(data []byte) string { + strSize := num.size + last := num.size - 1 + if num.kind == numFloat && (data[last] == 'f' || data[last] == 'F') { + strSize = last + } + if num.neg && num.sep > 0 { + // strip whitespace/comments between negative sign and the rest + strLen := strSize - num.sep + str := make([]byte, strLen) + str[0] = data[0] + copy(str[1:], data[num.sep+1:strSize]) + return string(str) + } + return string(data[:strSize]) + +} + +// parseNumber constructs a number object from given input. It allows for the +// following patterns: +// +// integer: ^-?([1-9][0-9]*|0[xX][0-9a-fA-F]+|0[0-7]*) +// float: ^-?((0|[1-9][0-9]*)?([.][0-9]*)?([eE][+-]?[0-9]+)?[fF]?) +// +// It also returns the number of parsed bytes for the given number, 0 if it is +// not a number. +func parseNumber(input []byte) number { + kind := numDec + var size int + var neg bool + + s := input + if len(s) == 0 { + return number{} + } + + // Optional - + var sep int + if s[0] == '-' { + neg = true + s = s[1:] + size++ + // Consume any whitespace or comments between the + // negative sign and the rest of the number + lenBefore := len(s) + s = consume(s, 0) + sep = lenBefore - len(s) + size += sep + if len(s) == 0 { + return number{} + } + } + + switch { + case s[0] == '0': + if len(s) > 1 { + switch { + case s[1] == 'x' || s[1] == 'X': + // Parse as hex number. + kind = numHex + n := 2 + s = s[2:] + for len(s) > 0 && (('0' <= s[0] && s[0] <= '9') || + ('a' <= s[0] && s[0] <= 'f') || + ('A' <= s[0] && s[0] <= 'F')) { + s = s[1:] + n++ + } + if n == 2 { + return number{} + } + size += n + + case '0' <= s[1] && s[1] <= '7': + // Parse as octal number. + kind = numOct + n := 2 + s = s[2:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '7' { + s = s[1:] + n++ + } + size += n + } + + if kind&(numHex|numOct) > 0 { + if len(s) > 0 && !isDelim(s[0]) { + return number{} + } + return number{kind: kind, neg: neg, size: size, sep: sep} + } + } + s = s[1:] + size++ + + case '1' <= s[0] && s[0] <= '9': + n := 1 + s = s[1:] + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + n++ + } + size += n + + case s[0] == '.': + // Set kind to numFloat to signify the intent to parse as float. And + // that it needs to have other digits after '.'. + kind = numFloat + + default: + return number{} + } + + // . followed by 0 or more digits. + if len(s) > 0 && s[0] == '.' { + n := 1 + s = s[1:] + // If decimal point was before any digits, it should be followed by + // other digits. + if len(s) == 0 && kind == numFloat { + return number{} + } + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + n++ + } + size += n + kind = numFloat + } + + // e or E followed by an optional - or + and 1 or more digits. + if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { + kind = numFloat + s = s[1:] + n := 1 + if s[0] == '+' || s[0] == '-' { + s = s[1:] + n++ + if len(s) == 0 { + return number{} + } + } + for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { + s = s[1:] + n++ + } + size += n + } + + // Optional suffix f or F for floats. + if len(s) > 0 && (s[0] == 'f' || s[0] == 'F') { + kind = numFloat + s = s[1:] + size++ + } + + // Check that next byte is a delimiter or it is at the end. + if len(s) > 0 && !isDelim(s[0]) { + return number{} + } + + return number{kind: kind, neg: neg, size: size, sep: sep} +} + +// UnmarshalI32 returns an i32 number forr given a textproto intlit value. +func UnmarshalI32(s string) (int32, bool) { + d := NewDecoder([]byte(s)) + t, ok := d.parseNumberValue() + if ok { + return t.Int32() + } + return 0, false +} diff --git a/prutalgen/internal/protobuf/text/decode_string.go b/prutalgen/internal/protobuf/text/decode_string.go new file mode 100644 index 0000000..d60e44d --- /dev/null +++ b/prutalgen/internal/protobuf/text/decode_string.go @@ -0,0 +1,170 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package text + +import ( + "bytes" + "strconv" + "strings" + "unicode" + "unicode/utf16" + "unicode/utf8" +) + +// parseStringValue parses string field token. +// This differs from parseString since the text format allows +// multiple back-to-back string literals where they are semantically treated +// as a single large string with all values concatenated. +// +// E.g., `"foo" "bar" "baz"` => "foobarbaz" +func (d *Decoder) parseStringValue() (Token, error) { + // Note that the ending quote is sufficient to unambiguously mark the end + // of a string. Thus, the text grammar does not require intervening + // whitespace or control characters in-between strings. + // Thus, the following is valid: + // `"foo"'bar'"baz"` => "foobarbaz" + in0 := d.in + var ss []string + for len(d.in) > 0 && (d.in[0] == '"' || d.in[0] == '\'') { + s, err := d.parseString() + if err != nil { + return Token{}, err + } + ss = append(ss, s) + } + // d.in already points to the end of the value at this point. + return Token{ + kind: Scalar, + attrs: stringValue, + pos: len(d.orig) - len(in0), + raw: in0[:len(in0)-len(d.in)], + str: strings.Join(ss, ""), + }, nil +} + +// parseString parses a string value enclosed in " or '. +func (d *Decoder) parseString() (string, error) { + in := d.in + if len(in) == 0 { + return "", ErrUnexpectedEOF + } + quote := in[0] + in = in[1:] + i := indexNeedEscapeInBytes(in) + in, out := in[i:], in[:i:i] // set cap to prevent mutations + for len(in) > 0 { + switch r, n := utf8.DecodeRune(in); { + case r == utf8.RuneError && n == 1: + return "", d.newSyntaxError("invalid UTF-8 detected") + case r == 0 || r == '\n': + return "", d.newSyntaxError("invalid character %q in string", r) + case r == rune(quote): + in = in[1:] + d.consume(len(d.in) - len(in)) + return string(out), nil + case r == '\\': + if len(in) < 2 { + return "", ErrUnexpectedEOF + } + switch r := in[1]; r { + case '"', '\'', '\\', '?': + in, out = in[2:], append(out, r) + case 'a': + in, out = in[2:], append(out, '\a') + case 'b': + in, out = in[2:], append(out, '\b') + case 'n': + in, out = in[2:], append(out, '\n') + case 'r': + in, out = in[2:], append(out, '\r') + case 't': + in, out = in[2:], append(out, '\t') + case 'v': + in, out = in[2:], append(out, '\v') + case 'f': + in, out = in[2:], append(out, '\f') + case '0', '1', '2', '3', '4', '5', '6', '7': + // One, two, or three octal characters. + n := len(in[1:]) - len(bytes.TrimLeft(in[1:], "01234567")) + if n > 3 { + n = 3 + } + v, err := strconv.ParseUint(string(in[1:1+n]), 8, 8) + if err != nil { + return "", d.newSyntaxError("invalid octal escape code %q in string", in[:1+n]) + } + in, out = in[1+n:], append(out, byte(v)) + case 'x': + // One or two hexadecimal characters. + n := len(in[2:]) - len(bytes.TrimLeft(in[2:], "0123456789abcdefABCDEF")) + if n > 2 { + n = 2 + } + v, err := strconv.ParseUint(string(in[2:2+n]), 16, 8) + if err != nil { + return "", d.newSyntaxError("invalid hex escape code %q in string", in[:2+n]) + } + in, out = in[2+n:], append(out, byte(v)) + case 'u', 'U': + // Four or eight hexadecimal characters + n := 6 + if r == 'U' { + n = 10 + } + if len(in) < n { + return "", ErrUnexpectedEOF + } + v, err := strconv.ParseUint(string(in[2:n]), 16, 32) + if utf8.MaxRune < v || err != nil { + return "", d.newSyntaxError("invalid Unicode escape code %q in string", in[:n]) + } + in = in[n:] + + r := rune(v) + if utf16.IsSurrogate(r) { + if len(in) < 6 { + return "", ErrUnexpectedEOF + } + v, err := strconv.ParseUint(string(in[2:6]), 16, 16) + r = utf16.DecodeRune(r, rune(v)) + if in[0] != '\\' || in[1] != 'u' || r == unicode.ReplacementChar || err != nil { + return "", d.newSyntaxError("invalid Unicode escape code %q in string", in[:6]) + } + in = in[6:] + } + out = append(out, string(r)...) + default: + return "", d.newSyntaxError("invalid escape code %q in string", in[:2]) + } + default: + i := indexNeedEscapeInBytes(in[n:]) + in, out = in[n+i:], append(out, in[:n+i]...) + } + } + return "", ErrUnexpectedEOF +} + +// indexNeedEscapeInBytes returns the index of the character that needs +// escaping. If no characters need escaping, this returns the input length. +func indexNeedEscapeInBytes(b []byte) int { return indexNeedEscapeInString(string(b)) } + +// indexNeedEscapeInString returns the index of the character that needs +// escaping. If no characters need escaping, this returns the input length. +func indexNeedEscapeInString(s string) int { + for i := 0; i < len(s); i++ { + if c := s[i]; c < ' ' || c == '"' || c == '\'' || c == '\\' || c >= 0x7f { + return i + } + } + return len(s) +} + +// UnmarshalString returns an unescaped string given a textproto string value. +// String value needs to contain single or double quotes. This is only used by +// internal/encoding/defval package for unmarshaling bytes. +func UnmarshalString(s string) (string, error) { + d := NewDecoder([]byte(s)) + return d.parseString() +} diff --git a/prutalgen/internal/protobuf/text/decode_test.go b/prutalgen/internal/protobuf/text/decode_test.go new file mode 100644 index 0000000..766d55e --- /dev/null +++ b/prutalgen/internal/protobuf/text/decode_test.go @@ -0,0 +1,1944 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package text_test + +import ( + "fmt" + "math" + "strings" + "testing" + "unicode/utf8" + + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/text" +) + +var eofErr = text.ErrUnexpectedEOF.Error() + +type R struct { + // K is expected Kind of the returned Token object from calling Decoder.Read. + K text.Kind + // E is expected error substring from calling Decoder.Read if set. + E string + // T contains NT (if K is Name) or ST (if K is Scalar) or nil (others) + T any + // P is expected Token.Pos if set > 0. + P int + // RS is expected result from Token.RawString() if not empty. + RS string +} + +// NT contains data for checking against a name token. +type NT struct { + K text.NameKind + // Sep is true if name token should have separator character, else false. + Sep bool + // If K is IdentName or TypeName, invoke corresponding getter and compare against this field. + S string + // If K is FieldNumber, invoke getter and compare against this field. + N int32 +} + +// ST contains data for checking against a scalar token. +type ST struct { + // checker that is expected to return OK. + ok checker + // checker that is expected to return not OK. + nok checker +} + +// checker provides API for the token wrapper API call types Str, Enum, Bool, +// Uint64, Uint32, Int64, Int32, Float64, Float32. +type checker interface { + // checkOk checks and expects for token API call to return ok and compare + // against implementation-stored value. Returns empty string if success, + // else returns error message describing the error. + checkOk(text.Token) string + // checkNok checks and expects for token API call to return not ok. Returns + // empty string if success, else returns error message describing the error. + checkNok(text.Token) string +} + +type Str struct { + val string +} + +func (s Str) checkOk(tok text.Token) string { + got, ok := tok.String() + if !ok { + return fmt.Sprintf("Token.String() returned not OK for token: %v", tok.RawString()) + } + if got != s.val { + return fmt.Sprintf("Token.String() got %q want %q for token: %v", got, s.val, tok.RawString()) + } + return "" +} + +func (s Str) checkNok(tok text.Token) string { + if _, ok := tok.String(); ok { + return fmt.Sprintf("Token.String() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Enum struct { + val string +} + +func (e Enum) checkOk(tok text.Token) string { + got, ok := tok.Enum() + if !ok { + return fmt.Sprintf("Token.Enum() returned not OK for token: %v", tok.RawString()) + } + if got != e.val { + return fmt.Sprintf("Token.Enum() got %q want %q for token: %v", got, e.val, tok.RawString()) + } + return "" +} + +func (e Enum) checkNok(tok text.Token) string { + if _, ok := tok.Enum(); ok { + return fmt.Sprintf("Token.Enum() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Bool struct { + val bool +} + +func (b Bool) checkOk(tok text.Token) string { + got, ok := tok.Bool() + if !ok { + return fmt.Sprintf("Token.Bool() returned not OK for token: %v", tok.RawString()) + } + if got != b.val { + return fmt.Sprintf("Token.Bool() got %v want %v for token: %v", got, b.val, tok.RawString()) + } + return "" +} + +func (b Bool) checkNok(tok text.Token) string { + if _, ok := tok.Bool(); ok { + return fmt.Sprintf("Token.Bool() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Uint64 struct { + val uint64 +} + +func (n Uint64) checkOk(tok text.Token) string { + got, ok := tok.Uint64() + if !ok { + return fmt.Sprintf("Token.Uint64() returned not OK for token: %v", tok.RawString()) + } + if got != n.val { + return fmt.Sprintf("Token.Uint64() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Uint64) checkNok(tok text.Token) string { + if _, ok := tok.Uint64(); ok { + return fmt.Sprintf("Token.Uint64() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Uint32 struct { + val uint32 +} + +func (n Uint32) checkOk(tok text.Token) string { + got, ok := tok.Uint32() + if !ok { + return fmt.Sprintf("Token.Uint32() returned not OK for token: %v", tok.RawString()) + } + if got != n.val { + return fmt.Sprintf("Token.Uint32() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Uint32) checkNok(tok text.Token) string { + if _, ok := tok.Uint32(); ok { + return fmt.Sprintf("Token.Uint32() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Int64 struct { + val int64 +} + +func (n Int64) checkOk(tok text.Token) string { + got, ok := tok.Int64() + if !ok { + return fmt.Sprintf("Token.Int64() returned not OK for token: %v", tok.RawString()) + } + if got != n.val { + return fmt.Sprintf("Token.Int64() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Int64) checkNok(tok text.Token) string { + if _, ok := tok.Int64(); ok { + return fmt.Sprintf("Token.Int64() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Int32 struct { + val int32 +} + +func (n Int32) checkOk(tok text.Token) string { + got, ok := tok.Int32() + if !ok { + return fmt.Sprintf("Token.Int32() returned not OK for token: %v", tok.RawString()) + } + if got != n.val { + return fmt.Sprintf("Token.Int32() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Int32) checkNok(tok text.Token) string { + if _, ok := tok.Int32(); ok { + return fmt.Sprintf("Token.Int32() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Float64 struct { + val float64 +} + +func (n Float64) checkOk(tok text.Token) string { + got, ok := tok.Float64() + if !ok { + return fmt.Sprintf("Token.Float64() returned not OK for token: %v", tok.RawString()) + } + if math.Float64bits(got) != math.Float64bits(n.val) { + return fmt.Sprintf("Token.Float64() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Float64) checkNok(tok text.Token) string { + if _, ok := tok.Float64(); ok { + return fmt.Sprintf("Token.Float64() returned OK for token: %v", tok.RawString()) + } + return "" +} + +type Float32 struct { + val float32 +} + +func (n Float32) checkOk(tok text.Token) string { + got, ok := tok.Float32() + if !ok { + return fmt.Sprintf("Token.Float32() returned not OK for token: %v", tok.RawString()) + } + if math.Float32bits(got) != math.Float32bits(n.val) { + return fmt.Sprintf("Token.Float32() got %v want %v for token: %v", got, n.val, tok.RawString()) + } + return "" +} + +func (n Float32) checkNok(tok text.Token) string { + if _, ok := tok.Float32(); ok { + return fmt.Sprintf("Token.Float32() returned OK for token: %v", tok.RawString()) + } + return "" +} + +func TestDecoder(t *testing.T) { + const space = " \n\r\t" + tests := []struct { + in string + // want is a list of expected Tokens returned from calling Decoder.Read. + // An item makes the test code invoke Decoder.Read and compare against + // R.K and R.E. If R.K is Name, it compares + want []R + }{ + { + in: "", + want: []R{{K: text.EOF}}, + }, + { + in: "# comment", + want: []R{{K: text.EOF}}, + }, + { + in: space + "# comment" + space, + want: []R{{K: text.EOF}}, + }, + { + in: space, + want: []R{{K: text.EOF, P: len(space)}}, + }, + { + // Calling Read after EOF will keep returning EOF for + // succeeding Read calls. + in: space, + want: []R{ + {K: text.EOF}, + {K: text.EOF}, + {K: text.EOF}, + }, + }, + { + // NUL is an invalid whitespace since C++ uses C-strings. + in: "\x00", + want: []R{{E: "invalid field name: \x00"}}, + }, + + // Field names. + { + in: "name", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "name"}, RS: "name"}, + {E: eofErr}, + }, + }, + { + in: space + "name:" + space, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + {E: eofErr}, + }, + }, + { + in: space + "name" + space + ":" + space, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + {E: eofErr}, + }, + }, + { + in: "name # comment", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "name"}}, + {E: eofErr}, + }, + }, + { + // Comments only extend until the newline. + in: "# comment \nname", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "name"}, P: 11}, + }, + }, + { + in: "name # comment \n:", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + }, + }, + { + in: "name123", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "name123"}}, + }, + }, + { + in: "name_123", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "name_123"}}, + }, + }, + { + in: "_123", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, S: "_123"}}, + }, + }, + { + in: ":", + want: []R{{E: "syntax error (line 1:1): invalid field name: :"}}, + }, + { + in: "\n\n\n {", + want: []R{{E: "syntax error (line 4:2): invalid field name: {"}}, + }, + { + in: "123name", + want: []R{{E: "invalid field name: 123name"}}, + }, + { + in: `/`, + want: []R{{E: `invalid field name: /`}}, + }, + { + in: `世界`, + want: []R{{E: `invalid field name: 世`}}, + }, + { + in: `1a/b`, + want: []R{{E: `invalid field name: 1a`}}, + }, + { + in: `1c\d`, + want: []R{{E: `invalid field name: 1c`}}, + }, + { + in: "\x84f", + want: []R{{E: "invalid field name: \x84"}}, + }, + { + in: "\uFFFDxxx", + want: []R{{E: "invalid field name: \uFFFD"}}, + }, + { + in: "-a234567890123456789012345678901234567890abc", + want: []R{{E: "invalid field name: -a2345678901234567890123456789012…"}}, + }, + { + in: "[type]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "type"}, RS: "[type]"}, + }, + }, + { + // V1 allows this syntax. C++ does not, however, C++ also fails if + // field is Any and does not contain '/'. + in: "[/type]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "/type"}}, + }, + }, + { + in: "[.type]", + want: []R{{E: "invalid type URL/extension field name: [.type]"}}, + }, + { + in: "[pkg.Foo.extension_field]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "pkg.Foo.extension_field"}}, + }, + }, + { + in: "[domain.com/type]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "domain.com/type"}}, + }, + }, + { + in: "[domain.com/pkg.type]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "domain.com/pkg.type"}}, + }, + }, + { + in: "[sub.domain.com\x2fpath\x2fto\x2fproto.package.name]", + want: []R{ + { + K: text.Name, + T: NT{ + K: text.TypeName, + S: "sub.domain.com/path/to/proto.package.name", + }, + RS: "[sub.domain.com\x2fpath\x2fto\x2fproto.package.name]", + }, + }, + }, + { + // V2 no longer allows a quoted string for the Any type URL. + in: `["domain.com/pkg.type"]`, + want: []R{{E: `invalid type URL/extension field name: ["`}}, + }, + { + // V2 no longer allows a quoted string for the Any type URL. + in: `['domain.com/pkg.type']`, + want: []R{{E: `invalid type URL/extension field name: ['`}}, + }, + { + in: "[pkg.Foo.extension_field:", + want: []R{{E: "invalid type URL/extension field name: [pkg.Foo.extension_field:"}}, + }, + { + // V2 no longer allows whitespace within identifier "word". + in: "[proto.packa ge.field]", + want: []R{{E: "invalid type URL/extension field name: [proto.packa g"}}, + }, + { + // V2 no longer allows comments within identifier "word". + in: "[proto.packa # comment\n ge.field]", + want: []R{{E: "invalid type URL/extension field name: [proto.packa # comment\n g"}}, + }, + { + in: "[proto.package.]", + want: []R{{E: "invalid type URL/extension field name: [proto.package."}}, + }, + { + in: "[proto.package/]", + want: []R{{E: "invalid type URL/extension field name: [proto.package/"}}, + }, + { + in: `message_field{[bad@]`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {E: `invalid type URL/extension field name: [bad@`}, + }, + }, + { + in: `message_field{[invalid//type]`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {E: `invalid type URL/extension field name: [invalid//`}, + }, + }, + { + in: `message_field{[proto.package.]`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {E: `invalid type URL/extension field name: [proto.package.`}, + }, + }, + { + in: "[proto.package", + want: []R{{E: eofErr}}, + }, + { + in: "[" + space + "type" + space + "]" + space + ":", + want: []R{ + { + K: text.Name, + T: NT{ + K: text.TypeName, + Sep: true, + S: "type", + }, + RS: "[" + space + "type" + space + "]", + }, + }, + }, + { + // Whitespaces/comments are only allowed betweeb + in: "[" + space + "domain" + space + "." + space + "com # comment\n" + + "/" + "pkg" + space + "." + space + "type" + space + "]", + want: []R{ + {K: text.Name, T: NT{K: text.TypeName, S: "domain.com/pkg.type"}}, + }, + }, + { + in: "42", + want: []R{ + {K: text.Name, T: NT{K: text.FieldNumber, N: 42}}, + }, + }, + { + in: "0x42:", + want: []R{{E: "invalid field number: 0x42"}}, + }, + { + in: "042:", + want: []R{{E: "invalid field number: 042"}}, + }, + { + in: "123.456:", + want: []R{{E: "invalid field number: 123.456"}}, + }, + { + in: "-123", + want: []R{{E: "invalid field number: -123"}}, + }, + { + in: "- \t 123.321e6", + want: []R{{E: "invalid field number: -123.321e6"}}, + }, + { + in: "-", + want: []R{{E: "invalid field name: -"}}, + }, + { + in: "- ", + want: []R{{E: "invalid field name: -"}}, + }, + { + in: "- # negative\n 123", + want: []R{{E: "invalid field number: -123"}}, + }, + { + // Field number > math.MaxInt32. + in: "2147483648:", + want: []R{{E: "invalid field number: 2147483648"}}, + }, + + // String field value. More string parsing specific testing in + // TestUnmarshalString. + { + in: `name: "hello world"`, + want: []R{ + {K: text.Name}, + { + K: text.Scalar, + T: ST{ok: Str{"hello world"}, nok: Enum{}}, + RS: `"hello world"`, + }, + {K: text.EOF}, + }, + }, + { + in: `name: 'hello'`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + }, + }, + { + in: `name: "hello'`, + want: []R{ + {K: text.Name}, + {E: eofErr}, + }, + }, + { + in: `name: 'hello`, + want: []R{ + {K: text.Name}, + {E: eofErr}, + }, + }, + { + // Field name without separator is ok. prototext package will need + // to determine that this is not valid for scalar values. + in: space + `name` + space + `"hello"` + space, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + }, + }, + { + in: `name'hello'`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + }, + }, + { + in: `name: ` + space + `"hello"` + space + `,`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.EOF}, + }, + }, + { + in: `name` + space + `:` + `"hello"` + space + `;` + space, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"hello" , ,`, + want: []R{ + {K: text.Name}, + {K: text.Scalar}, + {E: "(line 1:16): invalid field name: ,"}, + }, + }, + { + in: `name:"hello" , ;`, + want: []R{ + {K: text.Name}, + {K: text.Scalar}, + {E: "(line 1:16): invalid field name: ;"}, + }, + }, + { + in: `name:"hello" name:'world'`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"hello", name:"world"`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"hello"; name:"world",`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.EOF}, + }, + }, + { + in: `foo:"hello"bar:"world"`, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "foo"}}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "bar"}}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.EOF}, + }, + }, + { + in: `foo:"hello"[bar]:"world"`, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "foo"}}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.Name, T: NT{K: text.TypeName, Sep: true, S: "bar"}}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"foo"` + space + `"bar"` + space + `'qux'`, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + {K: text.Scalar, T: ST{ok: Str{"foobarqux"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"foo"'bar'"qux"`, + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + {K: text.Scalar, T: ST{ok: Str{"foobarqux"}}}, + {K: text.EOF}, + }, + }, + { + in: `name:"foo"` + space + `"bar" # comment` + "\n'qux' # comment", + want: []R{ + {K: text.Name, T: NT{K: text.IdentName, Sep: true, S: "name"}}, + {K: text.Scalar, T: ST{ok: Str{"foobarqux"}}}, + {K: text.EOF}, + }, + }, + + // Lists. + { + in: `name: [`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {E: eofErr}, + }, + }, + { + in: `name: []`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + { + in: `name []`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + { + in: `name: [,`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {E: `(line 1:8): invalid scalar value: ,`}, + }, + }, + { + in: `name: [0`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar}, + {E: eofErr}, + }, + }, + { + in: `name: [` + space + `"hello"` + space + `]` + space, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}, P: len(space) + 7}, + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + { + in: `name: ["hello",]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {E: `invalid scalar value: ]`}, + }, + }, + { + in: `name: ["foo"` + space + `'bar' "qux"]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"foobarqux"}}}, + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + { + in: `name:` + space + `["foo",` + space + "'bar', # comment\n\n" + `"qux"]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"foo"}}}, + {K: text.Scalar, T: ST{ok: Str{"bar"}}}, + {K: text.Scalar, T: ST{ok: Str{"qux"}}}, + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + + { + // List within list is not allowed. + in: `name: [[]]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {E: `syntax error (line 1:8): invalid scalar value: [`}, + }, + }, + { + // List items need to be separated by ,. + in: `name: ["foo" true]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"foo"}}}, + {E: `syntax error (line 1:14): unexpected character 't'`}, + }, + }, + { + in: `name: ["foo"; "bar"]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"foo"}}}, + {E: `syntax error (line 1:13): unexpected character ';'`}, + }, + }, + { + in: `name: ["foo", true, ENUM, 1.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"foo"}}}, + {K: text.Scalar, T: ST{ok: Enum{"true"}}}, + {K: text.Scalar, T: ST{ok: Enum{"ENUM"}}}, + {K: text.Scalar, T: ST{ok: Float32{1.0}}}, + {K: text.ListClose}, + }, + }, + + // Boolean literal values. + { + in: `name: True`, + want: []R{ + {K: text.Name}, + { + K: text.Scalar, + T: ST{ok: Bool{true}}, + }, + {K: text.EOF}, + }, + }, + { + in: `name false`, + want: []R{ + {K: text.Name}, + { + K: text.Scalar, + T: ST{ok: Bool{false}}, + }, + {K: text.EOF}, + }, + }, + { + in: `name: [t, f, True, False, true, false, 1, 0, 0x01, 0x00, 01, 00]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + {K: text.ListClose}, + }, + }, + { + // Looks like boolean but not. + in: `name: [tRUe, falSE, -1, -0, -0x01, -0x00, -01, -00, 0.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.Scalar, T: ST{nok: Bool{}}}, + {K: text.ListClose}, + }, + }, + { + in: `foo: true[bar] false`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Bool{false}}}, + }, + }, + + // Enum field values. + { + in: space + `name: ENUM`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Enum{"ENUM"}}}, + }, + }, + { + in: space + `name:[TRUE, FALSE, T, F, t, f]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Enum{"TRUE"}}}, + {K: text.Scalar, T: ST{ok: Enum{"FALSE"}}}, + {K: text.Scalar, T: ST{ok: Enum{"T"}}}, + {K: text.Scalar, T: ST{ok: Enum{"F"}}}, + {K: text.Scalar, T: ST{ok: Enum{"t"}}}, + {K: text.Scalar, T: ST{ok: Enum{"f"}}}, + {K: text.ListClose}, + }, + }, + { + in: `foo: Enum1[bar]:Enum2`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Enum{"Enum1"}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Enum{"Enum2"}}}, + }, + }, + { + // Invalid enum values. + in: `name: [-inf, -foo, "string", 42, 1.0, 0x47]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.Scalar, T: ST{nok: Enum{}}}, + {K: text.ListClose}, + }, + }, + { + in: `name: true.`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: true.`}, + }, + }, + + // Numeric values. + { + in: `nums:42 nums:0x2A nums:052`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Uint64{42}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Uint64{42}}}, + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Uint64{42}}}, + }, + }, + { + in: `nums:[-42, -0x2a, -052]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums:[-42, -0x2a, -052]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Int64{-42}}}, + {K: text.Scalar, T: ST{ok: Int64{-42}}}, + {K: text.Scalar, T: ST{ok: Int64{-42}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0,0x0,00,-9876543210,9876543210,0x0123456789abcdef,-0x0123456789abcdef,01234567,-01234567]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Uint64{0}}}, + {K: text.Scalar, T: ST{ok: Int64{0}}}, + {K: text.Scalar, T: ST{ok: Uint64{0}}}, + {K: text.Scalar, T: ST{ok: Int64{-9876543210}}}, + {K: text.Scalar, T: ST{ok: Uint64{9876543210}}}, + {K: text.Scalar, T: ST{ok: Uint64{0x0123456789abcdef}}}, + {K: text.Scalar, T: ST{ok: Int64{-0x0123456789abcdef}}}, + {K: text.Scalar, T: ST{ok: Uint64{01234567}}}, + {K: text.Scalar, T: ST{ok: Int64{-01234567}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0,0x0,00,-876543210,876543210,0x01234,-0x01234,01234567,-01234567]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Uint32{0}}}, + {K: text.Scalar, T: ST{ok: Int32{0}}}, + {K: text.Scalar, T: ST{ok: Uint32{0}}}, + {K: text.Scalar, T: ST{ok: Int32{-876543210}}}, + {K: text.Scalar, T: ST{ok: Uint32{876543210}}}, + {K: text.Scalar, T: ST{ok: Uint32{0x01234}}}, + {K: text.Scalar, T: ST{ok: Int32{-0x01234}}}, + {K: text.Scalar, T: ST{ok: Uint32{01234567}}}, + {K: text.Scalar, T: ST{ok: Int32{-01234567}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [` + + fmt.Sprintf("%d", uint64(math.MaxUint64)) + `,` + + fmt.Sprintf("%d", uint32(math.MaxUint32)) + `,` + + fmt.Sprintf("%d", int64(math.MaxInt64)) + `,` + + fmt.Sprintf("%d", int64(math.MinInt64)) + `,` + + fmt.Sprintf("%d", int32(math.MaxInt32)) + `,` + + fmt.Sprintf("%d", int32(math.MinInt32)) + + `]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Uint64{math.MaxUint64}}}, + {K: text.Scalar, T: ST{ok: Uint32{math.MaxUint32}}}, + {K: text.Scalar, T: ST{ok: Int64{math.MaxInt64}}}, + {K: text.Scalar, T: ST{ok: Int64{math.MinInt64}}}, + {K: text.Scalar, T: ST{ok: Int32{math.MaxInt32}}}, + {K: text.Scalar, T: ST{ok: Int32{math.MinInt32}}}, + {K: text.ListClose}, + }, + }, + { + // Integer exceeds range. + in: `nums: [` + + `18446744073709551616,` + // max uint64 + 1 + fmt.Sprintf("%d", uint64(math.MaxUint32+1)) + `,` + + fmt.Sprintf("%d", uint64(math.MaxInt64+1)) + `,` + + `-9223372036854775809,` + // min int64 - 1 + fmt.Sprintf("%d", uint64(math.MaxInt32+1)) + `,` + + fmt.Sprintf("%d", int64(math.MinInt32-1)) + `` + + `]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0xbeefbeef, 0xbeefbeefbeefbeef]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + { + K: text.Scalar, + T: func() ST { + return ST{nok: Int32{}} + }(), + }, + { + K: text.Scalar, + T: func() ST { + return ST{nok: Int64{}} + }(), + }, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,0f,1f,10f,-0f,-1f,-10f,1.0,0.1e-3,1.5e+5,1e10,.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{0.0}}}, + {K: text.Scalar, T: ST{ok: Float64{0.0}}}, + {K: text.Scalar, T: ST{ok: Float64{1.0}}}, + {K: text.Scalar, T: ST{ok: Float64{10.0}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Copysign(0, -1)}}}, + {K: text.Scalar, T: ST{ok: Float64{-1.0}}}, + {K: text.Scalar, T: ST{ok: Float64{-10.0}}}, + {K: text.Scalar, T: ST{ok: Float64{1.0}}}, + {K: text.Scalar, T: ST{ok: Float64{0.1e-3}}}, + {K: text.Scalar, T: ST{ok: Float64{1.5e+5}}}, + {K: text.Scalar, T: ST{ok: Float64{1.0e+10}}}, + {K: text.Scalar, T: ST{ok: Float64{0.0}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,0f,1f,10f,-0f,-1f,-10f,1.0,0.1e-3,1.5e+5,1e10,.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{0.0}}}, + {K: text.Scalar, T: ST{ok: Float32{0.0}}}, + {K: text.Scalar, T: ST{ok: Float32{1.0}}}, + {K: text.Scalar, T: ST{ok: Float32{10.0}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Copysign(0, -1))}}}, + {K: text.Scalar, T: ST{ok: Float32{-1.0}}}, + {K: text.Scalar, T: ST{ok: Float32{-10.0}}}, + {K: text.Scalar, T: ST{ok: Float32{1.0}}}, + {K: text.Scalar, T: ST{ok: Float32{0.1e-3}}}, + {K: text.Scalar, T: ST{ok: Float32{1.5e+5}}}, + {K: text.Scalar, T: ST{ok: Float32{1.0e+10}}}, + {K: text.Scalar, T: ST{ok: Float32{0.0}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,1f,10F,1e1,1.10]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.Scalar, T: ST{nok: Int64{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,1f,10F,1e1,1.10]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.Scalar, T: ST{nok: Int32{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,1f,10F,1e1,1.10]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [0.,1f,10F,1e1,1.10]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [` + + fmt.Sprintf("%g", math.MaxFloat32) + `,` + + fmt.Sprintf("%g", -math.MaxFloat32) + `,` + + fmt.Sprintf("%g", math.MaxFloat32*2) + `,` + + fmt.Sprintf("%g", -math.MaxFloat32*2) + `,` + + `3.59539e+308,` + // math.MaxFloat64 * 2 + `-3.59539e+308,` + // -math.MaxFloat64 * 2 + fmt.Sprintf("%d000", uint64(math.MaxUint64)) + + `]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.MaxFloat32)}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(-math.MaxFloat32)}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.MaxUint64) * 1000}}}, + {K: text.ListClose}, + }, + }, + { + in: `nums: [` + + fmt.Sprintf("%g", math.MaxFloat64) + `,` + + fmt.Sprintf("%g", -math.MaxFloat64) + `,` + + `3.59539e+308,` + // math.MaxFloat64 * 2 + `-3.59539e+308,` + // -math.MaxFloat64 * 2 + fmt.Sprintf("%d000", uint64(math.MaxUint64)) + + `]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{math.MaxFloat64}}}, + {K: text.Scalar, T: ST{ok: Float64{-math.MaxFloat64}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.Scalar, T: ST{ok: Float64{float64(math.MaxUint64) * 1000}}}, + {K: text.ListClose}, + }, + }, + { + // -0 is only valid for signed types. It is not valid for unsigned types. + in: `num: [-0, -0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{nok: Uint32{}}}, + {K: text.Scalar, T: ST{nok: Uint64{}}}, + {K: text.ListClose}, + }, + }, + { + // -0 is only valid for signed types. It is not valid for unsigned types. + in: `num: [-0, -0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Int32{0}}}, + {K: text.Scalar, T: ST{ok: Int64{0}}}, + {K: text.ListClose}, + }, + }, + { + // Negative zeros on float64 should preserve sign bit. + in: `num: [-0, -.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{math.Copysign(0, -1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Copysign(0, -1)}}}, + {K: text.ListClose}, + }, + }, + { + // Negative zeros on float32 should preserve sign bit. + in: `num: [-0, -.0]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Copysign(0, -1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Copysign(0, -1))}}}, + {K: text.ListClose}, + }, + }, + { + in: `num: +0`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: +`}, + }, + }, + { + in: `num: 01.1234`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: 01.1234`}, + }, + }, + { + in: `num: 0x`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: 0x`}, + }, + }, + { + in: `num: 0xX`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: 0xX`}, + }, + }, + { + in: `num: 0800`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: 0800`}, + }, + }, + { + in: `num: 1.`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{ok: Float32{1.0}}}, + }, + }, + { + in: `num: -.`, + want: []R{ + {K: text.Name}, + {E: `invalid scalar value: -.`}, + }, + }, + + // Float special literal values, case-insensitive match. + { + in: `name:[nan, NaN, Nan, NAN]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{math.NaN()}}}, + {K: text.Scalar, T: ST{ok: Float64{math.NaN()}}}, + {K: text.Scalar, T: ST{ok: Float64{math.NaN()}}}, + {K: text.Scalar, T: ST{ok: Float64{math.NaN()}}}, + {K: text.ListClose}, + }, + }, + { + in: `name:[inf, INF, infinity, Infinity, INFinity]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(1)}}}, + {K: text.ListClose}, + }, + }, + { + in: `name:[-inf, -INF, -infinity, -Infinity, -INFinity]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.Scalar, T: ST{ok: Float64{math.Inf(-1)}}}, + {K: text.ListClose}, + }, + }, + { + in: `name:[nan, NaN, Nan, NAN]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.NaN())}}}, + {K: text.ListClose}, + }, + }, + { + in: `name:[inf, INF, infinity, Infinity, INFinity]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(1))}}}, + {K: text.ListClose}, + }, + }, + { + in: `name:[-inf, -INF, -infinity, -Infinity, -INFinity]`, + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.Scalar, T: ST{ok: Float32{float32(math.Inf(-1))}}}, + {K: text.ListClose}, + }, + }, + { + // C++ permits this, but we currently reject this. It is easy to add + // if needed. + in: `name: -nan`, + want: []R{ + {K: text.Name}, + {K: text.Scalar, T: ST{nok: Float64{}}}, + }, + }, + // Messages. + { + in: `m: {}`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {K: text.MessageClose}, + {K: text.EOF}, + }, + }, + { + in: `m: <>`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {K: text.MessageClose}, + {K: text.EOF}, + }, + }, + { + in: space + `m {` + space + "\n# comment\n" + `}` + space, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {K: text.MessageClose}, + }, + }, + { + in: `m { foo: < bar: "hello" > }`, + want: []R{ + {K: text.Name, RS: "m"}, + {K: text.MessageOpen}, + + {K: text.Name, RS: "foo"}, + {K: text.MessageOpen}, + + {K: text.Name, RS: "bar"}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + + {K: text.MessageClose}, + + {K: text.MessageClose}, + }, + }, + { + in: `list [ , {s:"world"} ]`, + want: []R{ + {K: text.Name, RS: "list"}, + {K: text.ListOpen}, + + {K: text.MessageOpen}, + {K: text.Name, RS: "s"}, + {K: text.Scalar, T: ST{ok: Str{"hello"}}}, + {K: text.MessageClose}, + + {K: text.MessageOpen}, + {K: text.Name, RS: "s"}, + {K: text.Scalar, T: ST{ok: Str{"world"}}}, + {K: text.MessageClose}, + + {K: text.ListClose}, + {K: text.EOF}, + }, + }, + { + in: `m: { >`, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + {E: `mismatched close character '>'`}, + }, + }, + { + in: `m: , { } ] ; + } + [qux]: "end" +} + `, + want: []R{ + {K: text.Name}, + {K: text.MessageOpen}, + + {K: text.Name, RS: "foo"}, + {K: text.Scalar, T: ST{ok: Bool{true}}}, + + {K: text.Name, RS: "bar"}, + {K: text.MessageOpen}, + + {K: text.Name, RS: "enum"}, + {K: text.Scalar, T: ST{ok: Enum{"ENUM"}}}, + + {K: text.Name, RS: "list"}, + {K: text.ListOpen}, + {K: text.MessageOpen}, + {K: text.MessageClose}, + {K: text.MessageOpen}, + {K: text.MessageClose}, + {K: text.ListClose}, + + {K: text.MessageClose}, + + {K: text.Name, RS: "[qux]"}, + {K: text.Scalar, T: ST{ok: Str{"end"}}}, + + {K: text.MessageClose}, + {K: text.EOF}, + }, + }, + + // Other syntax errors. + { + in: "x: -", + want: []R{ + {K: text.Name}, + {E: `syntax error (line 1:4): invalid scalar value: -`}, + }, + }, + { + in: "x:[\"💩\"x", + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"💩"}}, P: 3}, + {E: `syntax error (line 1:7)`}, + }, + }, + { + in: "x:\n\n[\"🔥🔥🔥\"x", + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"🔥🔥🔥"}}, P: 5}, + {E: `syntax error (line 3:7)`}, + }, + }, + { + // multi-rune emojis; could be column:8 + in: "x:[\"👍🏻👍🏿\"x", + want: []R{ + {K: text.Name}, + {K: text.ListOpen}, + {K: text.Scalar, T: ST{ok: Str{"👍🏻👍🏿"}}, P: 3}, + {E: `syntax error (line 1:10)`}, + }, + }, + } + + for _, tc := range tests { + t.Run("", func(t *testing.T) { + tc := tc + in := []byte(tc.in) + dec := text.NewDecoder(in[:len(in):len(in)]) + for i, want := range tc.want { + peekTok, peekErr := dec.Peek() + tok, err := dec.Read() + if err != nil { + if want.E == "" { + errorf(t, tc.in, "Read() got unexpected error: %v", err) + } else if !strings.Contains(err.Error(), want.E) { + errorf(t, tc.in, "Read() got %q, want %q", err, want.E) + } + return + } + if want.E != "" { + errorf(t, tc.in, "Read() got nil error, want %q", want.E) + return + } + gotK := tok.Kind() + if gotK != want.K { + errorf(t, tc.in, "Read() got %v, want %v", gotK, want.K) + return + } + checkToken(t, tok, i, want, tc.in) + if !text.TokenEquals(tok, peekTok) { + errorf(t, tc.in, "Peek() %+v != Read() token %+v", peekTok, tok) + } + if err != peekErr { + errorf(t, tc.in, "Peek() error %v != Read() error %v", err, peekErr) + } + } + }) + } +} + +func checkToken(t *testing.T, tok text.Token, idx int, r R, in string) { + // Validate Token.Pos() if R.P is set. + if r.P > 0 { + got := tok.Pos() + if got != r.P { + errorf(t, in, "want#%d: Token.Pos() got %v want %v", idx, got, r.P) + } + } + + // Validate Token.RawString if R.RS is set. + if len(r.RS) > 0 { + got := tok.RawString() + if got != r.RS { + errorf(t, in, "want#%d: Token.RawString() got %v want %v", idx, got, r.P) + } + } + + // Skip checking for Token details if r.T is not set. + if r.T == nil { + return + } + + switch tok.Kind() { + case text.Name: + want := r.T.(NT) + kind := tok.NameKind() + if kind != want.K { + errorf(t, in, "want#%d: Token.NameKind() got %v want %v", idx, kind, want.K) + return + } + switch kind { + case text.IdentName: + got := tok.IdentName() + if got != want.S { + errorf(t, in, "want#%d: Token.IdentName() got %v want %v", idx, got, want.S) + } + case text.TypeName: + got := tok.TypeName() + if got != want.S { + errorf(t, in, "want#%d: Token.TypeName() got %v want %v", idx, got, want.S) + } + case text.FieldNumber: + got := tok.FieldNumber() + if got != want.N { + errorf(t, in, "want#%d: Token.FieldNumber() got %v want %v", idx, got, want.N) + } + } + + case text.Scalar: + want := r.T.(ST) + if ok := want.ok; ok != nil { + if err := ok.checkOk(tok); err != "" { + errorf(t, in, "want#%d: %s", idx, err) + } + } + if nok := want.nok; nok != nil { + if err := nok.checkNok(tok); err != "" { + errorf(t, in, "want#%d: %s", idx, err) + } + } + } +} + +func errorf(t *testing.T, in string, fmtStr string, args ...any) { + t.Helper() + vargs := []any{in} + vargs = append(vargs, args...) + t.Errorf("input:\n%s\n~end~\n"+fmtStr, vargs...) +} + +func TestUnmarshalString(t *testing.T) { + tests := []struct { + in string + // want is expected string result. + want string + // err is expected error substring from calling DecodeString if set. + err string + }{ + { + in: func() string { + var b []byte + for i := 0; i < utf8.RuneSelf; i++ { + switch i { + case 0, '\\', '\n', '\'': // these must be escaped, so ignore them + default: + b = append(b, byte(i)) + } + } + return "'" + string(b) + "'" + }(), + want: "\x01\x02\x03\x04\x05\x06\a\b\t\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~\u007f", + }, + { + in: "'\xde\xad\xbe\xef'", + err: `invalid UTF-8 detected`, + }, + { + // Valid UTF-8 wire encoding, but sub-optimal encoding. + in: "'\xc0\x80'", + err: "invalid UTF-8 detected", + }, + { + // Valid UTF-8 wire encoding, but invalid rune (surrogate pair). + in: "'\xed\xa0\x80'", + err: "invalid UTF-8 detected", + }, + { + // Valid UTF-8 wire encoding, but invalid rune (above max rune). + in: "'\xf7\xbf\xbf\xbf'", + err: "invalid UTF-8 detected", + }, + { + // Valid UTF-8 wire encoding of the RuneError rune. + in: "'\xef\xbf\xbd'", + want: string(utf8.RuneError), + }, + { + in: "'hello\u1234world'", + want: "hello\u1234world", + }, + { + in: `'\"\'\\\?\a\b\n\r\t\v\f\1\12\123\xA\xaB\x12\uAb8f\U0010FFFF'`, + want: "\"'\\?\a\b\n\r\t\v\f\x01\nS\n\xab\x12\uab8f\U0010ffff", + }, + { + in: `str: '\8'`, + err: `invalid escape code "\\8" in string`, + }, + { + in: `'\1x'`, + want: "\001x", + }, + { + in: `'\12x'`, + want: "\012x", + }, + { + in: `'\123x'`, + want: "\123x", + }, + { + in: `'\1234x'`, + want: "\1234x", + }, + { + in: `'\1'`, + want: "\001", + }, + { + in: `'\12'`, + want: "\012", + }, + { + in: `'\123'`, + want: "\123", + }, + { + in: `'\1234'`, + want: "\1234", + }, + { + in: `'\377'`, + want: "\377", + }, + { + // Overflow octal escape. + in: `'\400'`, + err: `invalid octal escape code "\\400" in string`, + }, + { + in: `'\xfx'`, + want: "\x0fx", + }, + { + in: `'\xffx'`, + want: "\xffx", + }, + { + in: `'\xfffx'`, + want: "\xfffx", + }, + { + in: `'\xf'`, + want: "\x0f", + }, + { + in: `'\xff'`, + want: "\xff", + }, + { + in: `'\xfff'`, + want: "\xfff", + }, + { + in: `'\xz'`, + err: `invalid hex escape code "\\x" in string`, + }, + { + in: `'\uPo'`, + err: eofErr, + }, + { + in: `'\uPoo'`, + err: `invalid Unicode escape code "\\uPoo'" in string`, + }, + { + in: `str: '\uPoop'`, + err: `invalid Unicode escape code "\\uPoop" in string`, + }, + { + // Unmatched surrogate pair. + in: `str: '\uDEAD'`, + err: `unexpected EOF`, // trying to reader other half + }, + { + // Surrogate pair with invalid other half. + in: `str: '\uDEAD\u0000'`, + err: `invalid Unicode escape code "\\u0000" in string`, + }, + { + // Properly matched surrogate pair. + in: `'\uD800\uDEAD'`, + want: "𐊭", + }, + { + // Overflow on Unicode rune. + in: `'\U00110000'`, + err: `invalid Unicode escape code "\\U00110000" in string`, + }, + { + in: `'\z'`, + err: `invalid escape code "\\z" in string`, + }, + { + // Strings cannot have NUL literal since C-style strings forbid them. + in: "'\x00'", + err: `invalid character '\x00' in string`, + }, + { + // Strings cannot have newline literal. The C++ permits them if an + // option is specified to allow them. In Go, we always forbid them. + in: "'\n'", + err: `invalid character '\n' in string`, + }, + } + + for _, tc := range tests { + t.Run("", func(t *testing.T) { + got, err := text.UnmarshalString(tc.in) + if err != nil { + if tc.err == "" { + errorf(t, tc.in, "UnmarshalString() got unexpected error: %q", err) + } else if !strings.Contains(err.Error(), tc.err) { + errorf(t, tc.in, "UnmarshalString() error got %q, want %q", err, tc.err) + } + return + } + if tc.err != "" { + errorf(t, tc.in, "UnmarshalString() got nil error, want %q", tc.err) + return + } + if got != tc.want { + errorf(t, tc.in, "UnmarshalString()\n[got]\n%s\n[want]\n%s", got, tc.want) + } + }) + } +} + +// Tests line and column number produced by Decoder.Position. +func TestPosition(t *testing.T) { + dec := text.NewDecoder([]byte("0123456789\n12345\n789")) + + tests := []struct { + pos int + row int + col int + }{ + { + pos: 0, + row: 1, + col: 1, + }, + { + pos: 10, + row: 1, + col: 11, + }, + { + pos: 11, + row: 2, + col: 1, + }, + { + pos: 18, + row: 3, + col: 2, + }, + } + + for _, tc := range tests { + t.Run("", func(t *testing.T) { + row, col := dec.Position(tc.pos) + if row != tc.row || col != tc.col { + t.Errorf("Position(%d) got (%d,%d) want (%d,%d)", tc.pos, row, col, tc.row, tc.col) + } + }) + } +} diff --git a/prutalgen/internal/protobuf/text/decode_token.go b/prutalgen/internal/protobuf/text/decode_token.go new file mode 100644 index 0000000..7fa771b --- /dev/null +++ b/prutalgen/internal/protobuf/text/decode_token.go @@ -0,0 +1,357 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package text + +import ( + "bytes" + "fmt" + "math" + "strconv" + "strings" +) + +// Kind represents a token kind expressible in the textproto format. +type Kind uint8 + +// Kind values. +const ( + Invalid Kind = iota + EOF + Name // Name indicates the field name. + Scalar // Scalar are scalar values, e.g. "string", 47, ENUM_LITERAL, true. + MessageOpen + MessageClose + ListOpen + ListClose + + // comma and semi-colon are only for parsing in between values and should not be exposed. + comma + semicolon + + // bof indicates beginning of file, which is the default token + // kind at the beginning of parsing. + bof = Invalid +) + +func (t Kind) String() string { + switch t { + case Invalid: + return "" + case EOF: + return "eof" + case Scalar: + return "scalar" + case Name: + return "name" + case MessageOpen: + return "{" + case MessageClose: + return "}" + case ListOpen: + return "[" + case ListClose: + return "]" + case comma: + return "," + case semicolon: + return ";" + default: + return fmt.Sprintf("", uint8(t)) + } +} + +// NameKind represents different types of field names. +type NameKind uint8 + +// NameKind values. +const ( + IdentName NameKind = iota + 1 + TypeName + FieldNumber +) + +func (t NameKind) String() string { + switch t { + case IdentName: + return "IdentName" + case TypeName: + return "TypeName" + case FieldNumber: + return "FieldNumber" + default: + return fmt.Sprintf("", uint8(t)) + } +} + +// Bit mask in Token.attrs to indicate if a Name token is followed by the +// separator char ':'. The field name separator char is optional for message +// field or repeated message field, but required for all other types. Decoder +// simply indicates whether a Name token is followed by separator or not. It is +// up to the prototext package to validate. +const hasSeparator = 1 << 7 + +// Scalar value types. +const ( + numberValue = iota + 1 + stringValue + literalValue +) + +// Bit mask in Token.numAttrs to indicate that the number is a negative. +const isNegative = 1 << 7 + +// Token provides a parsed token kind and value. Values are provided by the +// different accessor methods. +type Token struct { + // Kind of the Token object. + kind Kind + // attrs contains metadata for the following Kinds: + // Name: hasSeparator bit and one of NameKind. + // Scalar: one of numberValue, stringValue, literalValue. + attrs uint8 + // numAttrs contains metadata for numberValue: + // - highest bit is whether negative or positive. + // - lower bits indicate one of numDec, numHex, numOct, numFloat. + numAttrs uint8 + // pos provides the position of the token in the original input. + pos int + // raw bytes of the serialized token. + // This is a subslice into the original input. + raw []byte + // str contains parsed string for the following: + // - stringValue of Scalar kind + // - numberValue of Scalar kind + // - TypeName of Name kind + str string +} + +// Kind returns the token kind. +func (t Token) Kind() Kind { + return t.kind +} + +// RawString returns the read value in string. +func (t Token) RawString() string { + return string(t.raw) +} + +// Pos returns the token position from the input. +func (t Token) Pos() int { + return t.pos +} + +// NameKind returns IdentName, TypeName or FieldNumber. +// It panics if type is not Name. +func (t Token) NameKind() NameKind { + if t.kind == Name { + return NameKind(t.attrs &^ hasSeparator) + } + panic(fmt.Sprintf("Token is not a Name type: %s", t.kind)) +} + +// HasSeparator returns true if the field name is followed by the separator char +// ':', else false. It panics if type is not Name. +func (t Token) HasSeparator() bool { + if t.kind == Name { + return t.attrs&hasSeparator != 0 + } + panic(fmt.Sprintf("Token is not a Name type: %s", t.kind)) +} + +// IdentName returns the value for IdentName type. +func (t Token) IdentName() string { + if t.kind == Name && t.attrs&uint8(IdentName) != 0 { + return string(t.raw) + } + panic(fmt.Sprintf("Token is not an IdentName: %s:%s", t.kind, NameKind(t.attrs&^hasSeparator))) +} + +// TypeName returns the value for TypeName type. +func (t Token) TypeName() string { + if t.kind == Name && t.attrs&uint8(TypeName) != 0 { + return t.str + } + panic(fmt.Sprintf("Token is not a TypeName: %s:%s", t.kind, NameKind(t.attrs&^hasSeparator))) +} + +// FieldNumber returns the value for FieldNumber type. It returns a +// non-negative int32 value. Caller will still need to validate for the correct +// field number range. +func (t Token) FieldNumber() int32 { + if t.kind != Name || t.attrs&uint8(FieldNumber) == 0 { + panic(fmt.Sprintf("Token is not a FieldNumber: %s:%s", t.kind, NameKind(t.attrs&^hasSeparator))) + } + // Following should not return an error as it had already been called right + // before this Token was constructed. + num, _ := strconv.ParseInt(string(t.raw), 10, 32) + return int32(num) +} + +// String returns the string value for a Scalar type. +func (t Token) String() (string, bool) { + if t.kind != Scalar || t.attrs != stringValue { + return "", false + } + return t.str, true +} + +// Enum returns the literal value for a Scalar type for use as enum literals. +func (t Token) Enum() (string, bool) { + if t.kind != Scalar || t.attrs != literalValue || (len(t.raw) > 0 && t.raw[0] == '-') { + return "", false + } + return string(t.raw), true +} + +// Bool returns the bool value for a Scalar type. +func (t Token) Bool() (bool, bool) { + if t.kind != Scalar { + return false, false + } + switch t.attrs { + case literalValue: + if b, ok := boolLits[string(t.raw)]; ok { + return b, true + } + case numberValue: + // Unsigned integer representation of 0 or 1 is permitted: 00, 0x0, 01, + // 0x1, etc. + n, err := strconv.ParseUint(t.str, 0, 64) + if err == nil { + switch n { + case 0: + return false, true + case 1: + return true, true + } + } + } + return false, false +} + +// These exact boolean literals are the ones supported in C++. +var boolLits = map[string]bool{ + "t": true, + "true": true, + "True": true, + "f": false, + "false": false, + "False": false, +} + +// Uint64 returns the uint64 value for a Scalar type. +func (t Token) Uint64() (uint64, bool) { + if t.kind != Scalar || t.attrs != numberValue || + t.numAttrs&isNegative > 0 || t.numAttrs&numFloat > 0 { + return 0, false + } + n, err := strconv.ParseUint(t.str, 0, 64) + if err != nil { + return 0, false + } + return n, true +} + +// Uint32 returns the uint32 value for a Scalar type. +func (t Token) Uint32() (uint32, bool) { + if t.kind != Scalar || t.attrs != numberValue || + t.numAttrs&isNegative > 0 || t.numAttrs&numFloat > 0 { + return 0, false + } + n, err := strconv.ParseUint(t.str, 0, 32) + if err != nil { + return 0, false + } + return uint32(n), true +} + +// Int64 returns the int64 value for a Scalar type. +func (t Token) Int64() (int64, bool) { + if t.kind != Scalar || t.attrs != numberValue || t.numAttrs&numFloat > 0 { + return 0, false + } + if n, err := strconv.ParseInt(t.str, 0, 64); err == nil { + return n, true + } + return 0, false +} + +// Int32 returns the int32 value for a Scalar type. +func (t Token) Int32() (int32, bool) { + if t.kind != Scalar || t.attrs != numberValue || t.numAttrs&numFloat > 0 { + return 0, false + } + if n, err := strconv.ParseInt(t.str, 0, 32); err == nil { + return int32(n), true + } + return 0, false +} + +// Float64 returns the float64 value for a Scalar type. +func (t Token) Float64() (float64, bool) { + if t.kind != Scalar { + return 0, false + } + switch t.attrs { + case literalValue: + if f, ok := floatLits[strings.ToLower(string(t.raw))]; ok { + return f, true + } + case numberValue: + n, err := strconv.ParseFloat(t.str, 64) + if err == nil { + return n, true + } + nerr := err.(*strconv.NumError) + if nerr.Err == strconv.ErrRange { + return n, true + } + } + return 0, false +} + +// Float32 returns the float32 value for a Scalar type. +func (t Token) Float32() (float32, bool) { + if t.kind != Scalar { + return 0, false + } + switch t.attrs { + case literalValue: + if f, ok := floatLits[strings.ToLower(string(t.raw))]; ok { + return float32(f), true + } + case numberValue: + n, err := strconv.ParseFloat(t.str, 64) + if err == nil { + // Overflows are treated as (-)infinity. + return float32(n), true + } + nerr := err.(*strconv.NumError) + if nerr.Err == strconv.ErrRange { + return float32(n), true + } + } + return 0, false +} + +// These are the supported float literals which C++ permits case-insensitive +// variants of these. +var floatLits = map[string]float64{ + "nan": math.NaN(), + "inf": math.Inf(1), + "infinity": math.Inf(1), + "-inf": math.Inf(-1), + "-infinity": math.Inf(-1), +} + +// TokenEquals returns true if given Tokens are equal, else false. +func TokenEquals(x, y Token) bool { + return x.kind == y.kind && + x.attrs == y.attrs && + x.numAttrs == y.numAttrs && + x.pos == y.pos && + bytes.Equal(x.raw, y.raw) && + x.str == y.str +} diff --git a/prutalgen/internal/update_parser.sh b/prutalgen/internal/update_parser.sh new file mode 100755 index 0000000..85922f3 --- /dev/null +++ b/prutalgen/internal/update_parser.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -x +set -e + +echo "generating parser code ... " +# https://www.antlr.org/download.html +# or brew install antlr on mac +antlr -Dlanguage=Go -o parser ./Protobuf.g4 + +# we need to support old Go versions +# see: https://github.com/antlr/antlr4/pull/4754 +echo "replacing antlr to internal ... " +GITHUB_PKG="github.com/antlr4-go/antlr/v4" +INTERNAL_PKG="github.com/cloudwego/prutal/prutalgen/internal/antlr" +sed -i.bak "s:$GITHUB_PKG:$INTERNAL_PKG:g" ./parser/*.go && rm ./parser/*.bak + +echo "all done" diff --git a/prutalgen/main.go b/prutalgen/main.go new file mode 100644 index 0000000..35352ce --- /dev/null +++ b/prutalgen/main.go @@ -0,0 +1,76 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "flag" + "os" + "path/filepath" + "strings" + + "github.com/cloudwego/prutal/prutalgen/pkg/prutalgen" + "github.com/cloudwego/prutal/prutalgen/pkg/utils/args" +) + +func main() { + var ( + protoPath sliceArg + out string + opts args.GoOpts + + GenGetter bool + ) + flags := flag.NewFlagSet("prutalgen", flag.ExitOnError) + flags.Var(&protoPath, "proto_path", "") + flags.Var(&protoPath, "I", "") + flags.StringVar(&out, "go_out", "", "") + flags.Var(&opts, "go_opt", "") + flags.BoolVar(&GenGetter, "gen_getter", false, "") + _ = flags.Parse(os.Args[1:]) + + if len(protoPath) == 0 { + protoPath = append(protoPath, ".") + } + if out == "" { + out = "." + } + + x := prutalgen.NewLoader([]string(protoPath), opts.Proto2pkg()) + g := prutalgen.NewGoCodeGen() + g.Getter = GenGetter + args := flags.Args() + if len(args) == 0 { + println("WARN: no proto file provided") + } + for _, a := range args { + p := x.LoadProto(filepath.Clean(a))[0] + if err := g.Gen(p, opts.GenPathType(), out); err != nil { + p.Fatalf("generate code err: %s", err) + } + } +} + +type sliceArg []string + +func (a *sliceArg) String() string { + return strings.Join(*a, ",") +} + +func (a *sliceArg) Set(v string) error { + *a = append(*a, v) + return nil +} diff --git a/prutalgen/pkg/prutalgen/code.go b/prutalgen/pkg/prutalgen/code.go new file mode 100644 index 0000000..0cb9e3e --- /dev/null +++ b/prutalgen/pkg/prutalgen/code.go @@ -0,0 +1,363 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "go/format" + "os" + "path" + "path/filepath" + "regexp" + "strconv" + "strings" +) + +type GoCodeGen struct { + Format bool + + // Generates GetXXX funcs + Getter bool +} + +func NewGoCodeGen() *GoCodeGen { + return &GoCodeGen{ + Format: true, + Getter: false, + } +} + +var shellsafeRE = regexp.MustCompile(`^[a-zA-Z0-9\-_.=/]+$`) + +func argsQuote(args []string) string { + args = append([]string(nil), args...) + for i, a := range args { + if !shellsafeRE.MatchString(a) { + args[i] = "'" + strings.ReplaceAll(a, `'`, `\'`) + "'" + } + } + return strings.Join(args, " ") +} + +func (g *GoCodeGen) SourcePath(p *Proto, gt GenPathType, out string, suffix string) string { + fn := "" + switch gt { + case GenByImport: + // same name as proto file + fn = strings.TrimSuffix(path.Base(p.ProtoFile), ".proto") + suffix + // file path uses import path + fn = filepath.Join(out, fullFilename(p.GoImport, fn)) + + case GenBySourceRelative: + // same path as proto file + fn = strings.TrimSuffix(p.ProtoFile, ".proto") + suffix + + default: + panic("unknown GenPathType: " + gt) + } + return fn +} + +func (g *GoCodeGen) Gen(p *Proto, gt GenPathType, out string) error { + fn := g.SourcePath(p, gt, out, ".pb.go") + p.Infof("generating %s", refPath(fn)) + + header := "// Code generated by prutalgen. DO NOT EDIT.\n" + + "// " + argsQuote(os.Args) + w := NewCodeWriter(header, p.GoPackage) + g.ProtoGen(p, w) + b := w.Bytes() + err := os.MkdirAll(filepath.Dir(fn), 0755) + if err != nil { + return err + } + err = os.WriteFile(fn, b, 0644) + if err != nil { + return err + } + if g.Format { + newb, err := format.Source(b) + if err != nil { + p.Warnf("code format err: %s. Possibly a BUG and please report to author.", err) + } else { + err := os.WriteFile(fn, newb, 0644) + if err != nil { + return err + } + } + } + p.Infof("generated %s", refPath(fn)) + return nil +} + +func (g *GoCodeGen) ProtoGen(p *Proto, w *CodeWriter) { + for _, e := range p.Enums { + w.F("") + g.EnumGen(e, w) + } + + // always generate enums before messages + // same order as protobuf + for _, m := range p.Messages { + for _, e := range m.Enums { + w.F("") + g.EnumGen(e, w) + } + } + + for _, m := range p.Messages { + w.F("") + g.MessageGen(m, w) + } +} + +func (g *GoCodeGen) EnumGen(e *Enum, w *CodeWriter) { + if e.HeadComment != "" { + w.F("%s", e.HeadComment) + } + w.F("type %s int32"+e.InlineComment, e.GoName) + w.F("") + if len(e.Fields) > 0 { + w.F("const (") + for i, f := range e.Fields { + if f.HeadComment != "" { + if i != 0 { + w.F("") // empty line between last field and the comment of new field + } + w.F("%s", f.HeadComment) + } + w.F("%s %s = %d %s", f.GoName, e.GoName, f.Value, f.InlineComment) + } + w.F(")") + } + + if e.OptionGenNameMapping() { + w.F("\n// Enum value maps for %s.", e.GoName) + w.F("var %s_name = map[int32]string {", e.GoName) + for _, f := range e.Fields { + w.F("%d: %q,", f.Value, f.Name) + } + w.F("}") + w.F("") + w.F("var %s_value = map[string]int32 {", e.GoName) + for _, f := range e.Fields { + w.F("%q: %d,", f.Name, f.Value) + } + w.F("}") + + // String func + w.UsePkg("strconv", "") + w.F("func (x %s) String() string {", e.GoName) + w.F("s, ok := %s_name[int32(x)]", e.GoName) + w.F("if ok { return s }") + w.F("return strconv.Itoa(int(x))") + w.F("}") + } +} + +func (g *GoCodeGen) MessageGen(m *Message, w *CodeWriter) { + if m.HeadComment != "" { + w.F("%s", m.HeadComment) + } + generatedOneOfField := map[*Oneof]bool{} + + w.F("type %s struct {", m.GoName) + for i, f := range m.Fields { + if f.Oneof != nil { + if generatedOneOfField[f.Oneof] { + continue + } + // The Oneof fields will be generated in m.Oneofs loop + // Only need placeholder here for Oneof fields + o := f.Oneof + w.F("// Types that are assignable to %s:", o.FieldName()) + w.F("//") + for _, f := range o.Fields { + w.F("//\t*%s_%s", m.GoName, f.GoName) + } + // field and protobuf_oneof tag + w.F("%s %s `protobuf_oneof:\"%s\"`", o.FieldName(), o.FieldType(), o.Name) + generatedOneOfField[f.Oneof] = true + continue + } + if i != 0 && f.HeadComment != "" { + w.F("") // empty line between last field and the comment of new field + } + g.FieldGen(f, w) + } + if m.OptionUnknownFields() { + w.F("unknownFields []byte `json:\"-\"`") + } + w.F("} %s", m.InlineComment) + + // func Reset + w.F("\nfunc (x *%s) Reset() { *x = %s{} }", m.GoName, m.GoName) + + for k := range generatedOneOfField { + delete(generatedOneOfField, k) + } + + if !g.Getter { + goto SkipGetter + } + // GetXXX + for _, f := range m.Fields { + w.F("") + if f.Oneof != nil { + o := f.Oneof + if !generatedOneOfField[f.Oneof] { + w.F("func (x *%s) Get%s() %s {", m.GoName, o.FieldName(), o.FieldType()) + w.F("if x != nil { return x.%s }", o.FieldName()) + w.F("return nil") + w.F("}") + } + generatedOneOfField[f.Oneof] = true + + w.F("func (x *%s) Get%s() %s {", m.GoName, f.GoName, f.GoTypeName()) + w.F("if p, ok := x.Get%s().(*%s); ok {", o.FieldName(), f.OneofStructName()) + w.F("return p.%s", f.GoName) + w.F("}") + w.F("return %s", f.GoZero()) + w.F("}") + continue + } + + typename := f.GoTypeName() + if !f.IsMessage() && typename[0] == '*' { // optional field with pointer + w.F("func (x *%s) Get%s() %s {", m.GoName, f.GoName, typename[1:]) + w.F("if x != nil && x.%s != nil { return *x.%s }", f.GoName, f.GoName) + } else { + w.F("func (x *%s) Get%s() %s {", m.GoName, f.GoName, typename) + w.F("if x != nil { return x.%s }", f.GoName) + } + w.F("return %s", f.GoZero()) + w.F("}") + } +SkipGetter: + + if len(m.Oneofs) > 0 { + w.F("// XXX_OneofWrappers is for the internal use of the prutal package.") + w.F("func (*%s) XXX_OneofWrappers() []interface{} {", m.GoName) + w.F("return []interface{}{") + for _, o := range m.Oneofs { + for _, f := range o.Fields { + w.F("(*%s)(nil),", f.OneofStructName()) + } + } + w.F("}}") + } + + for _, o := range m.Oneofs { + w.F("") + g.OneofGen(o, w) + } + + // always generate embedded messasges after main message + // same order as protobuf + for _, x := range m.Messages { + w.F("") + g.MessageGen(x, w) + } +} + +func (g *GoCodeGen) FieldStructTag(f *Field) []byte { + b := make([]byte, 0, 100) + + // protobuf + b = append(b, `protobuf:"`...) + + // wiretype + if f.IsMap() { + b = append(b, "bytes"...) + } else { + b = append(b, f.Type.EncodingType()...) + } + + // field number + b = append(b, ',') + b = strconv.AppendInt(b, int64(f.FieldNumber), 10) + + // field label + b = append(b, ',') + if f.Repeated || f.IsMap() { + b = append(b, "rep"...) + if f.IsPackedEncoding() { + b = append(b, ",packed"...) + } + } else if f.Required { + b = append(b, "req"...) + } else { + b = append(b, "opt"...) + } + + // field name + b = append(b, ',') + b = append(b, "name="...) + b = append(b, f.Name...) + b = append(b, '"') // end of protobuf tag + + // json + b = append(b, ` json:"`...) + b = append(b, f.Name...) + b = append(b, ",omitempty"...) + b = append(b, '"') + + if f.IsMap() { + // protobuf_key + b = append(b, ` protobuf_key:"`...) + b = append(b, f.Key.EncodingType()...) + b = append(b, ",1,opt,name=key"...) + b = append(b, '"') + + // protobuf_val + b = append(b, ` protobuf_val:"`...) + b = append(b, f.Type.EncodingType()...) + b = append(b, ",2,opt,name=value"...) + b = append(b, '"') + } + return b +} + +func (g *GoCodeGen) FieldGen(f *Field, w *CodeWriter) { + if f.HeadComment != "" { + w.F("%s", f.HeadComment) + } + w.F("%s %s `%s` %s", f.GoName, f.GoTypeName(), g.FieldStructTag(f), f.InlineComment) + w.UsePkg(f.Type.GoImport, "") + if f.Key != nil { + w.UsePkg(f.Key.GoImport, "") + } +} + +func (g *GoCodeGen) OneofGen(o *Oneof, w *CodeWriter) { + // all oneof fields share the same private interface + typename := o.FieldType() + w.F("type %s interface {", typename) + w.F("%s()", typename) + w.F("}") + + for _, f := range o.Fields { + structname := f.OneofStructName() + w.F("") + w.F("type %s struct {", structname) + g.FieldGen(f, w) + w.F("}") + w.F("") + + // implements the oneof private interface + w.F("func (*%s) %s() {}", structname, typename) + } +} diff --git a/prutalgen/pkg/prutalgen/code_test.go b/prutalgen/pkg/prutalgen/code_test.go new file mode 100644 index 0000000..3918d33 --- /dev/null +++ b/prutalgen/pkg/prutalgen/code_test.go @@ -0,0 +1,120 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestGenCode(t *testing.T) { + g := NewGoCodeGen() + g.Getter = true + p := &Proto{ProtoFile: filepath.Join(t.TempDir(), "test.proto"), l: testLogger{t}} + p.GoPackage = "test" + + p.Enums = []*Enum{{ + GoName: "Enum1", + Fields: []*EnumField{ + {GoName: "Enum1_Zero", Value: 0}, + {GoName: "Enum1_N", Value: 10001}, + }, + Proto: p, + }} + + m := &Message{ + GoName: "Message1", + Fields: []*Field{ + {GoName: "Field1", Type: &Type{Name: "string"}, FieldNumber: 20001}, + {GoName: "Field2", Type: &Type{Name: "uint64"}, Repeated: true, FieldNumber: 20002}, + {GoName: "Field3", Type: &Type{Name: "string"}, Optional: true, FieldNumber: 20003}, + {GoName: "Field4", Key: &Type{Name: "string"}, Type: &Type{Name: "string"}, FieldNumber: 20004}, + }, + Proto: p, + } + p.Messages = []*Message{m} + + m.Oneofs = []*Oneof{{ + Name: "Oneof1", + Fields: []*Field{ + {GoName: "OneofA", Type: &Type{Name: "string"}, FieldNumber: 20101}, + {GoName: "OneofB", Type: &Type{Name: "string"}, FieldNumber: 20102}, + }, + Msg: m, + }} + oneof := m.Oneofs[0] + oneof.Fields[0].Oneof = oneof + oneof.Fields[1].Oneof = oneof + m.Fields = append(m.Fields, oneof.Fields...) + for _, f := range m.Fields { + f.Msg = m + } + + m.Enums = []*Enum{{ + GoName: "NestedEnum", + Fields: []*EnumField{ + {GoName: "NestedEnum_Zero", Value: 0}, + {GoName: "NestedEnum_N", Value: 30001}, + }, + Proto: p, + }} + + m.Messages = []*Message{{ + GoName: "NestedMsg", + Fields: []*Field{ + {GoName: "NField1", Type: &Type{Name: "string"}, FieldNumber: 40001}, + }, + Proto: p, + }} + for _, f := range m.Messages[0].Fields { + f.Msg = m.Messages[0] + } + + _ = g.Gen(p, GenBySourceRelative, "") + + outfn := filepath.Join(filepath.Dir(p.ProtoFile), "test.pb.go") + b, err := os.ReadFile(outfn) + assert.NoError(t, err) + + src := string(b) + lines := strings.Split(src, "\n") + assertLine := func(s string) { + t.Helper() + for _, l := range lines { + if strings.Contains(l, s) { + return + } + } + t.Fatal("not match", s) + } + t.Log(src) + assertLine("Enum1 = 0") + assertLine("Enum1 = 10001") + assertLine("NestedEnum = 0") + assertLine("NestedEnum = 30001") + assertLine("Field1 string") + assertLine("Field2 []uint64") + assertLine("Field3 *string") + assertLine("Field4 map[string]string") + assertLine("OneofA string") + assertLine("OneofB string") + assertLine("NField1 string") +} diff --git a/prutalgen/pkg/prutalgen/codewriter.go b/prutalgen/pkg/prutalgen/codewriter.go new file mode 100644 index 0000000..ea88bc5 --- /dev/null +++ b/prutalgen/pkg/prutalgen/codewriter.go @@ -0,0 +1,118 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "bytes" + "fmt" + "path" + "strings" +) + +const importPlaceHolder = "{PRUTALGEN_GO_IMPORTS}" + +type CodeWriter struct { + buf *bytes.Buffer + pkgs map[string]string // import -> alias +} + +func NewCodeWriter(header, pkg string) *CodeWriter { + w := &CodeWriter{ + buf: &bytes.Buffer{}, + pkgs: make(map[string]string), + } + w.F("%s", header) + w.F("") + w.F("package %s", pkg) + w.F("") + w.F(importPlaceHolder) + return w +} + +func (w *CodeWriter) UsePkg(p, a string) { + if p == "" { + return + } + if path.Base(p) == a { + w.pkgs[p] = "" + } else { + w.pkgs[p] = a + } +} + +func (w *CodeWriter) genImports() []byte { + const ( + cloudwegoRepoPrefix = "github.com/cloudwego/" + ) + pp0 := make([]string, 0, len(w.pkgs)) + pp1 := make([]string, 0, len(w.pkgs)) // for cloudwego + for pkg := range w.pkgs { // grouping + if strings.HasPrefix(pkg, cloudwegoRepoPrefix) { + pp1 = append(pp1, pkg) + } else { + pp0 = append(pp0, pkg) + } + } + + // check if need an empty line between groups + if len(pp0) != 0 && len(pp1) > 0 { + pp0 = append(pp0, "") + } + + // no imports? + pp0 = append(pp0, pp1...) + if len(pp0) == 0 { + return nil + } + + s := &bytes.Buffer{} + if len(pp0) == 1 { // only imports one pkg? + fmt.Fprintf(s, "import %s %q", w.pkgs[pp0[0]], pp0[0]) + } else { // more than one imports + fmt.Fprintln(s, "import (") + for _, p := range pp0 { + if p == "" { + fmt.Fprintln(s, "") + } else { + fmt.Fprintf(s, "%s %q\n", w.pkgs[p], p) + } + } + fmt.Fprint(s, ")") + } + return s.Bytes() +} + +func (w *CodeWriter) F(format string, a ...interface{}) { + if len(a) == 0 { + w.buf.WriteString(format) + } else { + fmt.Fprintf(w.buf, format, a...) + } + + // always newline for each call + if len(format) == 0 { + w.buf.WriteByte('\n') + } else if buf := w.buf.Bytes(); len(buf) == 0 || buf[len(buf)-1] != '\n' { + w.buf.WriteByte('\n') + } +} + +func (w *CodeWriter) Bytes() []byte { + b := w.buf.Bytes() + b = bytes.Replace(b, []byte(importPlaceHolder), w.genImports(), 1) + return b +} diff --git a/prutalgen/pkg/prutalgen/codewriter_test.go b/prutalgen/pkg/prutalgen/codewriter_test.go new file mode 100644 index 0000000..48253c3 --- /dev/null +++ b/prutalgen/pkg/prutalgen/codewriter_test.go @@ -0,0 +1,53 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "go/format" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func sourceEqual(t *testing.T, a, b []byte) { + t.Helper() + if v, err := format.Source(a); err == nil { + a = v + } + if v, err := format.Source(b); err == nil { + b = v + } + assert.Equal(t, string(a), string(b)) +} + +func TestCodeWriter(t *testing.T) { + w := NewCodeWriter("", "main") + w.UsePkg("fmt", "") + w.UsePkg("time", "") + w.UsePkg("github.com/cloudwego/gopkg", "") + w.F("func main() {}") + + sourceEqual(t, []byte(` +package main +import ( + "fmt" + "time" + + "github.com/cloudwego/gopkg" +) +func main() {}`), w.Bytes()) +} diff --git a/prutalgen/pkg/prutalgen/comment.go b/prutalgen/pkg/prutalgen/comment.go new file mode 100644 index 0000000..fb5fec7 --- /dev/null +++ b/prutalgen/pkg/prutalgen/comment.go @@ -0,0 +1,122 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" +) + +type streamContext struct { + s *antlr.CommonTokenStream + + // Token index -> true + // a comment should be returned in + // consumeHeadComment or consumeInlineComment, NOT both. + commentConsumed map[int]bool +} + +func newStreamContext(s *antlr.CommonTokenStream) *streamContext { + return &streamContext{s: s, commentConsumed: map[int]bool{}} +} + +func filterCommentOrWhitespace(tt []antlr.Token) (ret []antlr.Token, hasComment bool) { + ret = tt[:0] + for _, t := range tt { + switch t.GetTokenType() { + case parser.ProtobufParserLINE_COMMENT, + parser.ProtobufParserCOMMENT: + hasComment = true + ret = append(ret, t) + case parser.ProtobufParserWS: + ret = append(ret, t) + } + } + if !hasComment { + return nil, hasComment + } + return ret, true +} + +func (x *streamContext) consumeHeadComment(c antlr.ParserRuleContext) string { + tt, ok := filterCommentOrWhitespace( + x.s.GetHiddenTokensToLeft(c.GetStart().GetTokenIndex(), -1), + ) + if !ok { + return "" + } + ss := make([]string, 0, len(tt)) + for i := len(tt) - 1; i >= 0; i-- { + t := tt[i] + ti := t.GetTokenIndex() + tp := t.GetTokenType() + if x.commentConsumed[ti] { + break + } + s := t.GetText() + if tp == parser.ProtobufParserWS { + if strings.Count(s, "\n") > 1 { + // normally only one new line between 2 comment tokens are expected + // if we got more than one \n, likely it's an empty line to seperate two definitions + break + } + } + x.commentConsumed[ti] = true + s = strings.TrimSpace(s) + if s != "" { + ss = append(ss, s) + } + } + if len(ss) == 0 { + return "" + } + // reverse ss coz we scan backward above + for i, j := 0, len(ss)-1; i < j; i, j = i+1, j-1 { + ss[i], ss[j] = ss[j], ss[i] + } + return strings.Join(ss, "\n") +} + +func (x *streamContext) consumeInlineComment(c antlr.ParserRuleContext) string { + tt, ok := filterCommentOrWhitespace( + x.s.GetHiddenTokensToRight(c.GetStop().GetTokenIndex(), -1), + ) + if !ok { + return "" + } + for _, t := range tt { + s := t.GetText() + tp := t.GetTokenType() + ti := t.GetTokenIndex() + if x.commentConsumed[ti] { + return "" + } + if tp == parser.ProtobufParserWS { + if strings.Contains(s, "\n") { + return "" // newline? the commment may not belong to the given rule + } + continue // skip parser.ProtobufParserWS + } + // parser.ProtobufParserLINE_COMMENT or parser.ProtobufParserCOMMENT + x.commentConsumed[t.GetTokenIndex()] = true + return strings.TrimSpace(t.GetText()) + } + return "" +} diff --git a/prutalgen/pkg/prutalgen/comment_test.go b/prutalgen/pkg/prutalgen/comment_test.go new file mode 100644 index 0000000..2dda0ee --- /dev/null +++ b/prutalgen/pkg/prutalgen/comment_test.go @@ -0,0 +1,63 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestConsumeComment(t *testing.T) { + + f := loadTestProto(t, `option go_package = "testcomment"; + +// comment line1 +message Hello { +// comment line2 +string a = 1; // comment line3 +// comment line4 +string b = 2; /* +comment line5 +line5+ +line5++ +*/ +// comment line6 +// line7 +// line8 +string c = 3; +// comment line9 + +/* +comment line10 +line11 +line12 +*/ +string d = 4; +} + `) + + m := f.Messages[0] + assert.Equal(t, "// comment line1", m.HeadComment) + assert.Equal(t, "// comment line2", m.Fields[0].HeadComment) + assert.Equal(t, "// comment line3", m.Fields[0].InlineComment) + assert.Equal(t, "// comment line4", m.Fields[1].HeadComment) + assert.Equal(t, "/*\ncomment line5\nline5+\nline5++\n*/", m.Fields[1].InlineComment) + assert.Equal(t, "// comment line6\n// line7\n// line8", m.Fields[2].HeadComment) + assert.Equal(t, "", m.Fields[2].InlineComment) // should not contain line9 + assert.Equal(t, "/*\ncomment line10\nline11\nline12\n*/", m.Fields[3].HeadComment) +} diff --git a/prutalgen/pkg/prutalgen/consts.go b/prutalgen/pkg/prutalgen/consts.go new file mode 100644 index 0000000..efca5c6 --- /dev/null +++ b/prutalgen/pkg/prutalgen/consts.go @@ -0,0 +1,114 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +const ( + editionProto2 = "proto2" + editionProto3 = "proto3" + edition2023 = "2023" +) + +type GenPathType string + +const ( + GenByImport GenPathType = "import" + GenBySourceRelative GenPathType = "source_relative" +) + +const ( // implemented options of github.com/gogo/protobuf + gogoproto_nullable = "(gogoproto.nullable)" + + gogoproto_enum_prefix = "(gogoproto.goproto_enum_prefix)" + gogoproto_enum_prefix_all = "(gogoproto.goproto_enum_prefix_all)" + + gogoproto_goproto_unrecognized = "(gogoproto.goproto_unrecognized)" + gogoproto_goproto_unrecognized_all = "(gogoproto.goproto_unrecognized_all)" +) + +const ( // prutal options + optionNoPointer = "(prutal.gen_no_pointer)" + optionNoEnumPrefix = "(prutal.gen_no_enum_prefix)" + optionEnumNameMapping = "(prutal.gen_enum_name_mapping)" + optionGetter = "(prutal.gen_getter)" + optionUnknownFields = "(prutal.gen_unknown_fields)" +) + +const ( + // editions features + // see: https://protobuf.dev/editions/features/ + + f_repeated_field_encoding = "features.repeated_field_encoding" + f_field_presence = "features.field_presence" +) + +const ( // proto2 options + option_packed = "packed" +) + +// https://protobuf.dev/programming-guides/proto3/#scalar +var scalar2GoTypes = map[string]string{ + "double": "float64", + "float": "float32", + "int32": "int32", + "int64": "int64", + "uint32": "uint32", + "uint64": "uint64", + "sint32": "int32", + "sint64": "int64", + "fixed32": "uint32", + "fixed64": "uint64", + "sfixed32": "int32", + "sfixed64": "int64", + "bool": "bool", + "string": "string", + "bytes": "[]byte", +} + +var scalar2encodingType = map[string]string{ + "double": "fixed64", + "float": "fixed32", + "int32": "varint", + "int64": "varint", + "uint32": "varint", + "uint64": "varint", + "sint32": "zigzag32", + "sint64": "zigzag64", + "fixed32": "fixed32", + "fixed64": "fixed64", + "sfixed32": "fixed32", + "sfixed64": "fixed64", + "bool": "varint", + "string": "bytes", + "bytes": "bytes", +} + +// `packed` for any scalar type that is not string or bytes +var scalarPackedTypes = map[string]bool{ + "double": true, + "float": true, + "int32": true, + "int64": true, + "uint32": true, + "uint64": true, + "sint32": true, + "sint64": true, + "fixed32": true, + "fixed64": true, + "sfixed32": true, + "sfixed64": true, + "bool": true, +} diff --git a/prutalgen/pkg/prutalgen/embed.go b/prutalgen/pkg/prutalgen/embed.go new file mode 100644 index 0000000..21b557f --- /dev/null +++ b/prutalgen/pkg/prutalgen/embed.go @@ -0,0 +1,83 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import _ "embed" + +var ( + //go:embed wellknowns/any.proto + any_proto []byte + + //go:embed wellknowns/api.proto + api_proto []byte + + //go:embed wellknowns/descriptor.proto + descriptor_proto []byte + + //go:embed wellknowns/duration.proto + duration_proto []byte + + //go:embed wellknowns/empty.proto + empty_proto []byte + + //go:embed wellknowns/field_mask.proto + field_mask_proto []byte + + //go:embed wellknowns/source_context.proto + source_context_proto []byte + + //go:embed wellknowns/struct.proto + struct_proto []byte + + //go:embed wellknowns/timestamp.proto + timestamp_proto []byte + + //go:embed wellknowns/type.proto + type_proto []byte + + //go:embed wellknowns/wrappers.proto + wrappers_proto []byte +) + +var embeddedProtos = map[string][]byte{} + +func RegisterEmbeddedProto(proto string, b []byte) { + embeddedProtos[proto] = b +} + +func init() { + type protoFile struct { + Name string + Data []byte + } + wellknowns := []protoFile{ + {"google/protobuf/any.proto", any_proto}, + {"google/protobuf/api.proto", api_proto}, + {"google/protobuf/descriptor.proto", descriptor_proto}, + {"google/protobuf/duration.proto", duration_proto}, + {"google/protobuf/empty.proto", empty_proto}, + {"google/protobuf/field_mask.proto", field_mask_proto}, + {"google/protobuf/source_context.proto", source_context_proto}, + {"google/protobuf/struct.proto", struct_proto}, + {"google/protobuf/timestamp.proto", timestamp_proto}, + {"google/protobuf/type.proto", type_proto}, + {"google/protobuf/wrappers.proto", wrappers_proto}, + } + for _, f := range wellknowns { + RegisterEmbeddedProto(f.Name, f.Data) + } +} diff --git a/prutalgen/pkg/prutalgen/embed_test.go b/prutalgen/pkg/prutalgen/embed_test.go new file mode 100644 index 0000000..a07c802 --- /dev/null +++ b/prutalgen/pkg/prutalgen/embed_test.go @@ -0,0 +1,26 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import "testing" + +func TestEmbeddedProtos(t *testing.T) { + x := NewLoader(nil, nil) + for k := range embeddedProtos { + _ = x.LoadProto(k) + } +} diff --git a/prutalgen/pkg/prutalgen/enum.go b/prutalgen/pkg/prutalgen/enum.go new file mode 100644 index 0000000..fc04726 --- /dev/null +++ b/prutalgen/pkg/prutalgen/enum.go @@ -0,0 +1,201 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/strs" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/text" +) + +type Enum struct { + HeadComment string + InlineComment string + + Name string // orignial name in proto + + GoName string + Fields []*EnumField + Options Options + + reserved reservedRanges + + Msg *Message // for embedded enum only + Proto *Proto +} + +func (e *Enum) String() string { + b := &strings.Builder{} + fmt.Fprintf(b, "Enum %s {\n", e.FullName()) + for _, f := range e.Fields { + fmt.Fprintf(b, " %s = %d", f.GoName, f.Value) + if len(f.Options) > 0 { + fmt.Fprintf(b, " %+v\n", f.Options) + } else { + fmt.Fprintf(b, "\n") + } + } + fmt.Fprintf(b, "}\n") + return b.String() +} + +func (e *Enum) FullName() string { + ss := make([]string, 0, 2) + if e.Msg != nil { + ss = append(ss, e.Msg.FullName()) + } else if e.Proto.Package != "" { + ss = append(ss, e.Proto.Package) + } + ss = append(ss, e.Name) + return strings.Join(ss, ".") +} + +func (e *Enum) IsReservedField(v int32) bool { + return e.reserved.In(v) +} + +func (e *Enum) OptionGenNoPrefix() bool { + if v, ok := e.Options.Get(optionNoEnumPrefix); ok { + return istrue(v) + } else if v, ok := e.Proto.Options.Get(optionNoEnumPrefix); ok { + return istrue(v) + } else if v, ok := e.Options.Get(gogoproto_enum_prefix); ok { + return isfalse(v) + } else if v, ok := e.Proto.Options.Get(gogoproto_enum_prefix_all); ok { + return isfalse(v) + } + return false +} + +func (e *Enum) OptionGenNameMapping() bool { + if v, ok := e.Options.Get(optionEnumNameMapping); ok { + return istrue(v) + } else if v, ok := e.Proto.Options.Get(optionEnumNameMapping); ok { + return istrue(v) + } + return true +} + +func (e *Enum) resolve() { + p := e.Proto + e.GoName = strs.GoCamelCase(strings.TrimPrefix(e.Name, p.Package+".")) + if e.Msg != nil { + e.GoName = e.Msg.GoName + "_" + e.GoName + } + for _, f := range e.Fields { + if f.OptionGenNoPrefix() { + f.GoName = strs.GoCamelCase(f.Name) + } else if e.Msg != nil { + f.GoName = e.Msg.GoName + "_" + f.Name + } else { + f.GoName = e.GoName + "_" + f.Name + } + } +} + +func (e *Enum) verify() error { + errs := []error{} + m := map[int32]bool{} + for _, f := range e.Fields { + if e.IsReservedField(f.Value) { + errs = append(errs, fmt.Errorf("%q = %d is reserved", f.Name, f.Value)) + } else if m[f.Value] { + errs = append(errs, fmt.Errorf("%q = %d is duplicated", f.Name, f.Value)) + } + m[f.Value] = true + } + return joinErrs(errs...) +} + +type EnumField struct { + HeadComment string + InlineComment string + + Name string // orignial name in proto + + GoName string + Value int32 + Options Options + + Enum *Enum +} + +func (x *EnumField) OptionGenNoPrefix() bool { + if v, ok := x.Options.Get(optionNoEnumPrefix); ok { + return istrue(v) + } + return x.Enum.OptionGenNoPrefix() +} + +func (x *protoLoader) EnterEnumDef(c *parser.EnumDefContext) { + // ENUM enumName enumBody + e := &Enum{ + HeadComment: x.consumeHeadComment(c), InlineComment: x.consumeInlineComment(c), + Name: c.EnumName().GetText()} + switch getRuleIndex(c.GetParent()) { + case parser.ProtobufParserRULE_topLevelDef: // top level message + p := x.currentProto() + p.Enums = append(p.Enums, e) + e.Proto = p + case parser.ProtobufParserRULE_messageElement: // embedded message + m := x.currentMsg() + m.Enums = append(m.Enums, e) + e.Msg = m + e.Proto = x.currentProto() + default: + panic("unknown parent rule") + } + x.enum = e // for options, see ExitOptionStatement +} + +func (x *protoLoader) ExitEnumDef(c *parser.EnumDefContext) { + x.enum = nil +} + +func (x *protoLoader) ExitEnumField(c *parser.EnumFieldContext) { + // ident EQ (MINUS)? intLit enumValueOptions? SEMI + f := &EnumField{ + HeadComment: x.consumeHeadComment(c), InlineComment: x.consumeInlineComment(c), + Name: c.Ident().GetText()} + + // (MINUS)? intLit + if num, ok := text.UnmarshalI32(c.IntLit().GetText()); !ok { + t := c.IntLit() + x.Fatalf("%s - parse enum %q err", getTokenPos(t), t.GetText()) + } else { + f.Value = num + } + if t := c.MINUS(); t != nil { + f.Value = -f.Value + } + + // enumValueOptions + if oo := c.EnumValueOptions(); oo != nil { + for _, o := range oo.AllEnumValueOption() { + v, err := unmarshalConst(o.Constant().GetText()) + if err != nil { + x.Fatalf("%s - enum field option syntax err: %s", getTokenPos(o), err) + } + f.Options = append(f.Options, &Option{Name: o.OptionName().GetText(), Value: v}) + } + } + f.Enum = x.enum + f.Enum.Fields = append(f.Enum.Fields, f) +} diff --git a/prutalgen/pkg/prutalgen/enum_test.go b/prutalgen/pkg/prutalgen/enum_test.go new file mode 100644 index 0000000..34b5cd9 --- /dev/null +++ b/prutalgen/pkg/prutalgen/enum_test.go @@ -0,0 +1,160 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestEnum(t *testing.T) { + e := &Enum{Proto: &Proto{}} + assert.False(t, e.OptionGenNoPrefix()) + assert.True(t, e.OptionGenNameMapping()) + + oo := Options{ + {Name: optionNoEnumPrefix, Value: ""}, + {Name: gogoproto_enum_prefix, Value: "false"}, // lower higher priority + {Name: gogoproto_enum_prefix_all, Value: "false"}, // lower higher priority + } + e.Options = oo + oo[0].Value = "true" + assert.True(t, e.OptionGenNoPrefix()) + oo[0].Value = "false" + assert.False(t, e.OptionGenNoPrefix()) + e.Options = oo[1:] + assert.True(t, e.OptionGenNoPrefix()) + + e.Options = nil + e.Proto.Options = oo + oo[0].Value = "true" + assert.True(t, e.OptionGenNoPrefix()) + oo[0].Value = "false" + assert.False(t, e.OptionGenNoPrefix()) + e.Proto.Options = oo[1:] + assert.True(t, e.OptionGenNoPrefix()) + + oo = Options{ + {Name: optionEnumNameMapping, Value: ""}, + } + + e.Options = oo + oo[0].Value = "true" + assert.True(t, e.OptionGenNameMapping()) + oo[0].Value = "false" + assert.False(t, e.OptionGenNameMapping()) + e.Options = nil + e.Proto.Options = oo + oo[0].Value = "true" + assert.True(t, e.OptionGenNameMapping()) + oo[0].Value = "false" + assert.False(t, e.OptionGenNameMapping()) +} + +func TestEnum_Verify(t *testing.T) { + p := &Proto{Package: "test.enum.verify"} + e := &Enum{ + Name: "e", + Proto: p, + Fields: []*EnumField{ + {Name: "ev1", Value: 1}, + }, + } + p.Enums = []*Enum{e} + + // reserved + e.reserved = append(e.reserved, reservedRange{from: 1, to: 1}) + assert.ErrorContains(t, p.verify(), "1 is reserved") + e.reserved = nil + assert.NoError(t, p.verify()) + + // duplicated + e.Fields = append(e.Fields, e.Fields[0]) + assert.ErrorContains(t, p.verify(), "1 is duplicated") + e.Fields = e.Fields[:1] + assert.NoError(t, p.verify()) +} + +func TestLoader_Enum(t *testing.T) { + f := loadTestProto(t, ` +option go_package = "test"; +enum myEnum0 { + ENUM0 = 0; + ENUM1 = 1; +} + + +message myMsg { +enum eEnum { + ENUM0 = 0; + ENUM2 = 2; +} +} + + +enum myEnum1 { + option (prutal.gen_no_enum_prefix) = true; + A = 0; + B = 1; + C = 2 [(prutal.gen_no_enum_prefix) = false]; +} +`) + t.Log(f.String()) + + ee := f.Enums + assert.Equal(t, 2, len(ee)) + + e := ee[0] + assert.Equal(t, "myEnum0", e.Name) + assert.Equal(t, "MyEnum0", e.GoName) + assert.Equal(t, 2, len(e.Fields)) + assert.Equal(t, "ENUM0", e.Fields[0].Name) + assert.Equal(t, "MyEnum0_ENUM0", e.Fields[0].GoName) + assert.Equal(t, int32(0), e.Fields[0].Value) + assert.Equal(t, "ENUM1", e.Fields[1].Name) + assert.Equal(t, "MyEnum0_ENUM1", e.Fields[1].GoName) + assert.Equal(t, int32(1), e.Fields[1].Value) + + e = ee[1] + assert.Equal(t, "myEnum1", e.Name) + assert.Equal(t, "MyEnum1", e.GoName) + assert.Equal(t, 3, len(e.Fields)) + assert.Equal(t, "A", e.Fields[0].Name) // prutal.gen_no_enum_prefix=true + assert.Equal(t, "A", e.Fields[0].GoName) + assert.Equal(t, int32(0), e.Fields[0].Value) + assert.Equal(t, "B", e.Fields[1].Name) // prutal.gen_no_enum_prefix=true + assert.Equal(t, "B", e.Fields[1].GoName) + assert.Equal(t, int32(1), e.Fields[1].Value) + assert.Equal(t, "C", e.Fields[2].Name) // prutal.gen_no_enum_prefix=true + assert.Equal(t, "MyEnum1_C", e.Fields[2].GoName) + assert.Equal(t, int32(2), e.Fields[2].Value) + + m := f.Messages[0] + ee = m.Enums + assert.Equal(t, 1, len(ee)) + e = ee[0] + assert.Equal(t, "eEnum", e.Name) + assert.Equal(t, "MyMsg_EEnum", e.GoName) + assert.Equal(t, 2, len(e.Fields)) + assert.Equal(t, "ENUM0", e.Fields[0].Name) + assert.Equal(t, "MyMsg_ENUM0", e.Fields[0].GoName) + assert.Equal(t, int32(0), e.Fields[0].Value) + assert.Equal(t, "ENUM2", e.Fields[1].Name) + assert.Equal(t, "MyMsg_ENUM2", e.Fields[1].GoName) + assert.Equal(t, int32(2), e.Fields[1].Value) +} diff --git a/prutalgen/pkg/prutalgen/field.go b/prutalgen/pkg/prutalgen/field.go new file mode 100644 index 0000000..051d7d6 --- /dev/null +++ b/prutalgen/pkg/prutalgen/field.go @@ -0,0 +1,346 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "path" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/strs" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/text" +) + +type Field struct { + HeadComment string + InlineComment string + + // names in original IDL + Name string + Key *Type // for map only + Type *Type + + GoName string + + FieldNumber int32 + Repeated bool + Required bool + Optional bool + + Options Options + + Oneof *Oneof // if not nil, it's from oneof + Msg *Message + rule antlr.ParserRuleContext +} + +func (f *Field) String() string { + b := &strings.Builder{} + if f.IsMap() { + fmt.Fprintf(b, "%d - %s <%s, %s>", f.FieldNumber, f.GoName, f.Key, f.Type) + } else { + fmt.Fprintf(b, "%d - %s %s", f.FieldNumber, f.GoName, f.Type) + } + return b.String() +} + +func (f *Field) IsMap() bool { + return f.Key != nil +} + +func (f *Field) IsEnum() bool { + return !f.IsMap() && f.Type.IsEnum() +} + +func (f *Field) IsMessage() bool { + return !f.IsMap() && f.Type.IsMessage() +} + +func (f *Field) IsPackedEncoding() bool { + if f.IsMap() { + return false + } + if !(scalarPackedTypes[f.Type.Name] || f.Type.IsEnum()) { + return false + } + + p := f.Msg.Proto + if p.IsProto2() { + return f.Options.Is(option_packed, "true") + } + + if p.IsEdition2023() { + v := "PACKED" + if s, ok := f.Options.Get(f_repeated_field_encoding); ok { + v = s + } else if s, ok = p.Options.Get(f_repeated_field_encoding); ok { + v = s + } + return v == "PACKED" + } + return true // proto3 +} + +func (f *Field) resolve() { + f.GoName = strs.GoCamelCase(f.Name) + f.Type.resolve(true) + // f.IsMap() + // no need to call resolve, map is alaways scalar type +} + +func (f *Field) OptionGenNoPointer() bool { + if v, ok := f.Options.Get(optionNoPointer); ok { + return istrue(v) + } + if v, ok := f.Options.Get(gogoproto_nullable); ok { + return isfalse(v) + } + return false +} + +func goTypeName(t *Type, noptr bool) string { + if !noptr && t.IsMessage() { + // message type is pointer by default + return "*" + t.GoName() + } + return t.GoName() +} + +func (f *Field) GoTypeName() string { + noptr := f.OptionGenNoPointer() + if f.IsMap() { + kt := f.Key.GoName() + vt := goTypeName(f.Type, noptr) + return fmt.Sprintf("map[%s]%s", kt, vt) + } + if f.Repeated { + return "[]" + goTypeName(f.Type, noptr) + } + if f.IsPointer() { + return "*" + f.Type.GoName() + } + return f.Type.GoName() +} + +func (f *Field) IsPointer() bool { + if f.OptionGenNoPointer() { + return false + } + if f.IsMap() || f.Repeated || f.Type.GoName() == "[]byte" { + // map or slice can be nil, so it's always NOT pointer + // the "[]byte" case is same as f.Repeated + return false + } + + if f.IsMessage() { + // message type is pointer by default + return true + } + if f.Optional { + // if optional it's pointer + return true + } + if f.Oneof != nil { + // no need pointer for oneof fields + // we already have an interface for it. + return false + } + + p := f.Msg.Proto + if p.IsProto2() { + return true // proto2 is pointer by default + } + if p.IsEdition2023() { + s, ok := f.Options.Get(f_field_presence) + if ok { + return s == "EXPLICIT" + } + s, ok = p.Options.Get(f_field_presence) + if ok { + return s == "EXPLICIT" + } + return true // Default: EXPLICIT, which is same as proto2 + } + return false // proto3? +} + +func (f *Field) GoZero() string { + if f.IsMap() || f.Repeated { + return "nil" + } + if f.IsEnum() { // search for the const var + tp := f.Type + e := tp.Enum() + for _, f := range e.Fields { + if f.Value != 0 { + continue + } + zero := f.GoName + if tp.GoImport == "" { + return zero + } + return path.Base(tp.GoImport) + "." + zero + } + return "0" + } + ft := f.Type.GoName() + switch ft { // scalar types + case "float32", "float64", + "int32", "int64", + "uint32", "uint64": + return "0" + case "bool": + return "false" + case "string": + return `""` + case "[]byte": + return "nil" + } + if f.IsMessage() && f.OptionGenNoPointer() { + return ft + "{}" // zero struct + } + return "nil" +} + +// OneofStructName returns the struct name of each field in oneof +// +// For each field in oneof, it will be defined as a struct. +// like for +// +// message Example { +// oneof contact_info { +// string email = 3; +// string phone = 4; +// } +// +// for email, there will be a struct: +// +// type Example_Email struct { +// Email string +// } +// +// for message Example, the field will be: +// +// type Example_Email struct { +// ContactInfo isExample_ContactInfo +// } +func (f *Field) OneofStructName() string { + if f.Oneof == nil { + panic("not oneof") + } + return f.Msg.GoName + "_" + f.GoName +} + +type fieldContext interface { + antlr.ParserRuleContext + + FieldType() parser.IFieldTypeContext + FieldName() parser.IFieldNameContext + FieldNumber() parser.IFieldNumberContext + FieldOptions() parser.IFieldOptionsContext +} + +type fieldWithKeyTypeContext interface { + fieldContext + + KeyType() parser.IKeyTypeContext +} + +type noKeyTypeContext struct{ fieldContext } + +func (_ noKeyTypeContext) KeyType() parser.IKeyTypeContext { return nil } + +func (x *protoLoader) newField(c fieldWithKeyTypeContext) *Field { + ft := c.FieldType() + f := &Field{ + HeadComment: x.consumeHeadComment(c), InlineComment: x.consumeInlineComment(c), + Name: c.FieldName().GetText(), + Type: &Type{Name: ft.GetText(), rule: ft}, + rule: c, + } + f.Type.f = f + + if kt := c.KeyType(); kt != nil { + f.Key = &Type{Name: kt.GetText(), rule: kt} + } + fieldn := c.FieldNumber() + num, ok := text.UnmarshalI32(fieldn.GetText()) + if !ok { + x.Fatalf("%s - parse field number %q err", getTokenPos(fieldn), fieldn.GetText()) + } + f.FieldNumber = num + if oo := c.FieldOptions(); oo != nil { + for _, o := range oo.AllFieldOption() { + v, err := unmarshalConst(o.Constant().GetText()) + if err != nil { + x.Fatalf("%s - field option syntax err: %s", getTokenPos(o), err) + } + f.Options = append(f.Options, &Option{Name: o.OptionName().GetText(), Value: v}) + } + } + return f +} + +func (x *protoLoader) ExitField(c *parser.FieldContext) { + switch getRuleIndex(c.GetParent()) { + case parser.ProtobufParserRULE_extendDef: // only for protoc + return + } + // fieldLabel? type_ fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + f := x.newField(noKeyTypeContext{c}) + if l := c.FieldLabel(); l != nil { + switch l.GetText() { + case "repeated": + f.Repeated = true + + case "required": + if x.currentProto().Edition != editionProto2 { + x.Fatalf("%s - `required` keyword only available for proto2", getTokenPos(l)) + } + f.Required = true + + case "optional": + f.Optional = true + } + } + m := x.currentMsg() + m.Fields = append(m.Fields, f) + f.Msg = m +} + +func (x *protoLoader) ExitOneofField(c *parser.OneofFieldContext) { + // type_ fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + f := x.newField(noKeyTypeContext{c}) + m := x.currentMsg() + + // oneof fields are normal fields with oneof define. + f.Oneof = last(m.Oneofs) + f.Oneof.Fields = append(f.Oneof.Fields, f) + f.Msg = m + m.Fields = append(m.Fields, f) +} + +func (x *protoLoader) ExitMapField(c *parser.MapFieldContext) { + // MAP LT keyType COMMA type_ GT fieldName EQ fieldNumber (LB fieldOptions RB)? SEMI + f := x.newField(c) + m := x.currentMsg() + m.Fields = append(m.Fields, f) + f.Msg = m +} diff --git a/prutalgen/pkg/prutalgen/field_test.go b/prutalgen/pkg/prutalgen/field_test.go new file mode 100644 index 0000000..53cb40a --- /dev/null +++ b/prutalgen/pkg/prutalgen/field_test.go @@ -0,0 +1,192 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestField(t *testing.T) { + f := &Field{} + + // OptionGenNoPointer + f.Options = nil + assert.False(t, f.OptionGenNoPointer()) + + f.Options = Options{{Name: optionNoPointer, Value: "true"}} + assert.True(t, f.OptionGenNoPointer()) + + f.Options = Options{{Name: gogoproto_nullable, Value: "false"}} + assert.True(t, f.OptionGenNoPointer()) + + reset := func(f *Field) { + f.Options = nil + f.Key, f.Type = nil, nil + f.Repeated, f.Optional, f.Required = false, false, false + f.Msg = &Message{Proto: &Proto{}} + } + + // GoTypeName:map + reset(f) + f.Key = &Type{Name: "float"} + f.Type = &Type{Name: "double"} + assert.Equal(t, "map[float32]float64", f.GoTypeName()) + + // GoTypeName:slice + reset(f) + f.Type = &Type{Name: "double"} + assert.Equal(t, "float64", f.GoTypeName()) + f.Repeated = true + assert.Equal(t, "[]float64", f.GoTypeName()) + + // GoTypeName:optional or proto2 + reset(f) + f.Type = &Type{Name: "double"} + assert.Equal(t, "float64", f.GoTypeName()) + f.Optional = true + assert.Equal(t, "*float64", f.GoTypeName()) + f.Optional = false + assert.Equal(t, "float64", f.GoTypeName()) + f.Msg = &Message{Proto: &Proto{Edition: editionProto2}} + assert.Equal(t, "*float64", f.GoTypeName()) + + // GoTypeName: bytes + reset(f) + f.Optional = true + f.Type = &Type{Name: "bytes"} // no pointer even f.Optional=true + assert.Equal(t, "[]byte", f.GoTypeName()) + + // GoTypeName:message + reset(f) + nopointerOpt := Options{{Name: optionNoPointer, Value: "true"}} + f.Type = &Type{Name: "Msg", typ: &Message{GoName: "Msg"}} + assert.Equal(t, "*Msg", f.GoTypeName()) + f.Options = nopointerOpt + assert.Equal(t, "Msg", f.GoTypeName()) + f.Options = nil + f.Repeated = true + assert.Equal(t, "[]*Msg", f.GoTypeName()) + f.Options = nopointerOpt + assert.Equal(t, "[]Msg", f.GoTypeName()) + + // GoZero: map + f.Key = &Type{Name: "float"} + f.Type = &Type{Name: "double"} + assert.Equal(t, "nil", f.GoZero()) + f.Key = nil + + // GoZero: slice + f.Repeated = true + f.Type = &Type{Name: "double"} + assert.Equal(t, "nil", f.GoZero()) + f.Repeated = false + + // GoZero: scalars + f.Type = &Type{Name: "double"} + assert.Equal(t, "0", f.GoZero()) + f.Type = &Type{Name: "bool"} + assert.Equal(t, "false", f.GoZero()) + f.Type = &Type{Name: "string"} + assert.Equal(t, `""`, f.GoZero()) + f.Type = &Type{Name: "bytes"} + assert.Equal(t, "nil", f.GoZero()) + + // GoZero: enum + e := &Enum{ + Name: "my_enum", + GoName: "MyEnum", + Fields: []*EnumField{{Name: "Zero", GoName: "MyEnum_Zero"}}, + } + f.Type = &Type{Name: "my_enum", + typ: e, + GoImport: "base", + } + assert.Equal(t, "base.MyEnum_Zero", f.GoZero()) + f.Type.GoImport = "" + assert.Equal(t, "MyEnum_Zero", f.GoZero()) + e.Fields = nil + assert.Equal(t, "0", f.GoZero()) + + // GOZero: struct + f.Options = nil + f.Type = &Type{Name: "my_struct", + typ: &Message{GoName: "MyStruct"}, + } + assert.Equal(t, "nil", f.GoZero()) + f.Options = nopointerOpt + assert.Equal(t, "MyStruct{}", f.GoZero()) + +} + +func TestLoader_Field(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "testfield"; +message M { +required string email = 1; +repeated string names = 2; +optional string tel = 3; +map extra = 4; + +message N { +} +N m = 5; + +enum E { + V = 0; +} + +E e = 6 [(prutal.test) = true]; +}`, + ) + + m := p.Messages[0] + assert.Equal(t, 6, len(m.Fields)) + + f1 := m.Fields[0] + t.Log(f1.String()) + assert.Equal(t, int32(1), f1.FieldNumber) + assert.True(t, f1.Required) + + f2 := m.Fields[1] + t.Log(f2.String()) + assert.Equal(t, int32(2), f2.FieldNumber) + assert.True(t, f2.Repeated) + + f3 := m.Fields[2] + t.Log(f3.String()) + assert.Equal(t, int32(3), f3.FieldNumber) + assert.True(t, f3.Optional) + + f4 := m.Fields[3] + t.Log(f4.String()) + assert.Equal(t, int32(4), f4.FieldNumber) + assert.True(t, f4.IsMap()) + + f5 := m.Fields[4] + t.Log(f5.String()) + assert.Equal(t, int32(5), f5.FieldNumber) + assert.True(t, f5.IsMessage()) + + f6 := m.Fields[5] + t.Log(f6.String()) + assert.Equal(t, int32(6), f6.FieldNumber) + assert.True(t, f6.IsEnum()) + o, _ := f6.Options.Get("(prutal.test)") + assert.Equal(t, "true", o) +} diff --git a/prutalgen/pkg/prutalgen/logger.go b/prutalgen/pkg/prutalgen/logger.go new file mode 100644 index 0000000..60b5963 --- /dev/null +++ b/prutalgen/pkg/prutalgen/logger.go @@ -0,0 +1,41 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "log" + "os" +) + +type LoggerIface interface { + Fatalf(format string, v ...any) // must call Exit + Printf(format string, v ...any) +} + +var defaultLogger LoggerIface = log.New(os.Stderr, "", 0) + +// LoggerFunc converts a func to LoggerIface +type LoggerFunc func(format string, v ...any) + +func (f LoggerFunc) Printf(format string, v ...any) { + f(format, v...) +} + +func (f LoggerFunc) Fatalf(format string, v ...any) { + f(format, v...) + os.Exit(1) +} diff --git a/prutalgen/pkg/prutalgen/message.go b/prutalgen/pkg/prutalgen/message.go new file mode 100644 index 0000000..88e221f --- /dev/null +++ b/prutalgen/pkg/prutalgen/message.go @@ -0,0 +1,184 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/strs" +) + +type Message struct { + HeadComment string + InlineComment string + + Name string // original name in IDL + + GoName string // name used by generated code + + // embedded declarations + Enums []*Enum + Messages []*Message + + // Fields and Options + Fields []*Field + Oneofs []*Oneof + Options Options + + reserved reservedRanges + + Msg *Message // for embedded message only + Proto *Proto +} + +func (m *Message) String() string { + b := &strings.Builder{} + fmt.Fprintf(b, "Message %s {\n", m.FullName()) + fmt.Fprintf(b, "Options:%v\n", m.Options) + for _, x := range m.Enums { + fmt.Fprintf(b, "-> %s\n", x.String()) + } + for _, x := range m.Messages { + fmt.Fprintf(b, "-> %s\n", x.String()) + } + for _, x := range m.Fields { + fmt.Fprintf(b, "-> %s\n", x.String()) + } + fmt.Fprintf(b, "}\n") + return b.String() +} + +func (m *Message) FullName() string { + ss := make([]string, 0, 2) + if m.Msg != nil { + ss = append(ss, m.Msg.FullName()) + } else if m.Proto.Package != "" { + ss = append(ss, m.Proto.Package) + } + ss = append(ss, m.Name) + return strings.Join(ss, ".") +} + +func (m *Message) IsReservedField(v int32) bool { + return m.reserved.In(v) +} + +func (m *Message) OptionUnknownFields() bool { + if v, ok := m.Options.Get(optionUnknownFields); ok { + return istrue(v) + } else if v, ok := m.Proto.Options.Get(optionUnknownFields); ok { + return istrue(v) + } else if v, ok := m.Options.Get(gogoproto_goproto_unrecognized); ok { + return istrue(v) + } else if v, ok := m.Proto.Options.Get(gogoproto_goproto_unrecognized_all); ok { + return istrue(v) + } + return false +} + +func (m *Message) getType(name string) any { + if m.Name == name { + return m + } + if name, ok := trimPathPrefix(name, m.Name); ok { + for _, x := range m.Enums { + if x.Name == name { + return x + } + } + for _, x := range m.Messages { + if v := x.getType(name); v != nil { + return v + } + } + } + return nil +} + +func (m *Message) resolve() { + p := m.Proto + m.GoName = strs.GoCamelCase(strings.TrimPrefix(m.Name, p.Package+".")) + if m.Msg != nil { + m.GoName = m.Msg.GoName + "_" + m.GoName // check duplicates? + } + + // resolve declarations before fields, + // coz fields may use these declarations + for _, x := range m.Enums { + x.resolve() + } + + for _, x := range m.Messages { + x.resolve() + } + + for _, x := range m.Fields { + x.resolve() + } +} + +func (m *Message) verify() error { + var errs []error + for _, x := range m.Enums { + if err := x.verify(); err != nil { + errs = append(errs, fmt.Errorf("enum %s verify err: %w", x.FullName(), err)) + } + } + for _, x := range m.Messages { + if err := x.verify(); err != nil { + errs = append(errs, fmt.Errorf("message %s verify err: %w", x.FullName(), err)) + } + } + exists := map[int32]bool{} + for _, x := range m.Fields { + if m.IsReservedField(x.FieldNumber) { + errs = append(errs, fmt.Errorf("field %q field number = %d is reserved", x.Name, x.FieldNumber)) + } else if exists[x.FieldNumber] { + errs = append(errs, fmt.Errorf("field %q field number = %d is duplicated", x.Name, x.FieldNumber)) + } + exists[x.FieldNumber] = true + } + return joinErrs(errs...) +} + +func (x *protoLoader) EnterMessageDef(c *parser.MessageDefContext) { + m := &Message{} + m.HeadComment = x.consumeHeadComment(c) + m.InlineComment = x.consumeInlineComment(c) + m.Name = c.MessageName().GetText() + switch getRuleIndex(c.GetParent()) { + case parser.ProtobufParserRULE_topLevelDef: // top level message + p := x.currentProto() + p.Messages = append(p.Messages, m) + m.Proto = p + + case parser.ProtobufParserRULE_messageElement: // embedded message + m0 := x.currentMsg() + m0.Messages = append(m0.Messages, m) + m.Msg = m0 + m.Proto = x.currentProto() + default: + panic("unknown parent rule") + } + push(&x.msgstack, m) +} + +func (x *protoLoader) ExitMessageDef(c *parser.MessageDefContext) { + pop(&x.msgstack) +} diff --git a/prutalgen/pkg/prutalgen/message_test.go b/prutalgen/pkg/prutalgen/message_test.go new file mode 100644 index 0000000..cd80717 --- /dev/null +++ b/prutalgen/pkg/prutalgen/message_test.go @@ -0,0 +1,134 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestMessage(t *testing.T) { + m := &Message{Name: "m"} + m.Proto = &Proto{Package: "test.message"} + p := m.Proto + + // getType + m.Enums = []*Enum{{Name: "e", Proto: p}} + assert.Same(t, m.Enums[0], m.getType("m.e")) + + m.Messages = []*Message{{Name: "m", Proto: p}} + assert.Same(t, m.Messages[0], m.getType("m.m")) + + // OptionUnknownFields + assert.False(t, m.OptionUnknownFields()) + + m.Options = Options{{Name: optionUnknownFields, Value: "true"}} + assert.True(t, m.OptionUnknownFields()) + + m.Options = Options{{Name: gogoproto_goproto_unrecognized, Value: "true"}} + assert.True(t, m.OptionUnknownFields()) + + m.Options = nil + + p.Options = Options{{Name: optionUnknownFields, Value: "true"}} + assert.True(t, m.OptionUnknownFields()) + + p.Options = Options{{Name: gogoproto_goproto_unrecognized_all, Value: "true"}} + assert.True(t, m.OptionUnknownFields()) + + // String + m.Fields = []*Field{{Name: "f"}} + t.Log(m.String()) +} + +func TestMessage_Verify(t *testing.T) { + p := &Proto{Package: "test.message.verify"} + m := &Message{Name: "m"} + m.Proto = p + p.Messages = []*Message{m} + + // reserved + m.reserved = append(m.reserved, reservedRange{from: 1, to: 1}) + m.Fields = append(m.Fields, &Field{Name: "testfield", FieldNumber: 1}) + assert.ErrorContains(t, p.verify(), "field number = 1 is reserved") + m.reserved = nil + assert.NoError(t, p.verify()) + + // duplicated + m.Fields = append(m.Fields, &Field{Name: "testfield2", FieldNumber: 1}) + assert.ErrorContains(t, p.verify(), "field number = 1 is duplicated") + m.Fields = nil + assert.NoError(t, p.verify()) + + // nested msg case + mm := &Message{ + Name: "mm", + Fields: []*Field{ + {Name: "testfield1", FieldNumber: 1}, + {Name: "testfield2", FieldNumber: 1}, + }, + Msg: m, + Proto: m.Proto, + } + m.Messages = []*Message{mm} + assert.ErrorContains(t, p.verify(), "field number = 1 is duplicated") + m.Messages = nil + assert.NoError(t, p.verify()) + + // nested enum case + m.Enums = []*Enum{{ + Name: "e", + Proto: m.Proto, + Msg: m, + Fields: []*EnumField{ + {Name: "ev1", Value: 2}, + {Name: "ev2", Value: 2}, // duplicated + }, + }} + assert.ErrorContains(t, p.verify(), "2 is duplicated") + m.Enums = nil + assert.NoError(t, p.verify()) +} + +func TestLoader_Message(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "testmessage"; +message M { + message m { + } + + enum e { + v = 0; + } + + string f = 1; +} +`) + + m := p.Messages[0] + assert.Equal(t, "M", m.Name) + assert.Equal(t, 1, len(m.Messages)) + assert.Equal(t, "m", m.Messages[0].Name) + assert.Equal(t, "M_M", m.Messages[0].GoName) + assert.Equal(t, 1, len(m.Enums)) + assert.Equal(t, "e", m.Enums[0].Name) + assert.Equal(t, "M_E", m.Enums[0].GoName) + assert.Equal(t, 1, len(m.Fields)) + assert.Equal(t, "f", m.Fields[0].Name) + assert.Equal(t, "F", m.Fields[0].GoName) +} diff --git a/prutalgen/pkg/prutalgen/oneof.go b/prutalgen/pkg/prutalgen/oneof.go new file mode 100644 index 0000000..64678c2 --- /dev/null +++ b/prutalgen/pkg/prutalgen/oneof.go @@ -0,0 +1,46 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/strs" +) + +type Oneof struct { + Name string + + Options Options + + Msg *Message + Fields []*Field +} + +func (o *Oneof) FieldName() string { + return strs.GoCamelCase(o.Name) +} + +func (o *Oneof) FieldType() string { + return "is" + o.Msg.GoName + "_" + strs.GoCamelCase(o.Name) +} + +func (x *protoLoader) EnterOneof(c *parser.OneofContext) { + o := &Oneof{} + o.Name = c.OneofName().GetText() + o.Msg = x.currentMsg() + o.Msg.Oneofs = append(o.Msg.Oneofs, o) +} diff --git a/prutalgen/pkg/prutalgen/oneof_test.go b/prutalgen/pkg/prutalgen/oneof_test.go new file mode 100644 index 0000000..f3e60aa --- /dev/null +++ b/prutalgen/pkg/prutalgen/oneof_test.go @@ -0,0 +1,40 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestLoader_Oneof(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "testoneof"; +message M { + oneof test_oneof { + string name = 1; + string nick = 2; + } +} +`) + + o := p.Messages[0].Oneofs[0] + assert.Equal(t, "TestOneof", o.FieldName()) + assert.Equal(t, "isM_TestOneof", o.FieldType()) + assert.Equal(t, 2, len(o.Fields)) +} diff --git a/prutalgen/pkg/prutalgen/option.go b/prutalgen/pkg/prutalgen/option.go new file mode 100644 index 0000000..7affbae --- /dev/null +++ b/prutalgen/pkg/prutalgen/option.go @@ -0,0 +1,129 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" +) + +type Option struct { + Name string + Value string +} + +type Options []*Option + +func (oo Options) String() string { + b := &strings.Builder{} + fmt.Fprintf(b, "[") + for i, o := range oo { + if i != 0 { + fmt.Fprintf(b, ", ") + } + fmt.Fprintf(b, "%q: %q", o.Name, o.Value) + } + fmt.Fprintf(b, "]") + return b.String() +} + +func (oo Options) Get(name string) (string, bool) { + // return the last one in case we have multiple values + for i := len(oo) - 1; i >= 0; i-- { + o := oo[i] + if o.Name == name { + return o.Value, true + } + } + return "", false +} + +func (oo Options) Is(name string, value string) bool { + s, ok := oo.Get(name) + return ok && s == value +} + +func (x *protoLoader) ExitOptionStatement(c *parser.OptionStatementContext) { + v, err := unmarshalConst(c.Constant().GetText()) + if err != nil { + x.Fatalf("%s - option syntax err: %s", getTokenPos(c), err) + } + + // name may include extensions like (gogoproto.goproto_unrecognized_all) + // we ignore parsing it, coz it's only used by protoc. + // but we will keep it for optimization cases like `[(gogoproto.nullable) = false];` + o := &Option{Name: c.OptionName().GetText(), Value: v} + + if !verifyOption(o.Name, o.Value) { + x.Fatalf("%s - option %q unsupported value %q", getTokenPos(c), o.Name, o.Value) + } + + switch getRuleIndex(c.GetParent()) { + case parser.ProtobufParserRULE_proto: + p := x.currentProto() + p.Options = append(p.Options, o) + + case parser.ProtobufParserRULE_messageElement: + m := x.currentMsg() + m.Options = append(m.Options, o) + + case parser.ProtobufParserRULE_oneof: + of := x.currentOneof() + of.Options = append(of.Options, o) + + case parser.ProtobufParserRULE_enumElement: + x.enum.Options = append(x.enum.Options, o) + + case parser.ProtobufParserRULE_serviceElement: + s := x.currentService() + s.Options = append(s.Options, o) + + case parser.ProtobufParserRULE_rpc: + s := x.currentService() + rpc := last(s.Methods) + rpc.Options = append(rpc.Options, o) + + default: + return + } +} + +func verifyOption(name, v string) bool { + switch name { + case f_repeated_field_encoding: + return v == "EXPANDED" || v == "PACKED" + + case f_field_presence: + return v == "EXPLICIT" || v == "IMPLICIT" + + case option_packed: + return verifyTrueOrFalse(v) + + default: + return true + } +} + +func verifyTrueOrFalse(v string) bool { + switch v { + case "true", "false": + return true + } + return false +} diff --git a/prutalgen/pkg/prutalgen/option_test.go b/prutalgen/pkg/prutalgen/option_test.go new file mode 100644 index 0000000..f11d7f8 --- /dev/null +++ b/prutalgen/pkg/prutalgen/option_test.go @@ -0,0 +1,101 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestOption(t *testing.T) { + p := Options{ + {Name: "k", Value: "v1"}, + {Name: "k", Value: "v2"}, + } + s, ok := p.Get("k") + assert.True(t, ok) + assert.Equal(t, "v2", s) + assert.True(t, p.Is("k", "v2")) + assert.False(t, p.Is("k", "v1")) + s, ok = p.Get("x") + assert.False(t, ok) + assert.Equal(t, "", s) + assert.False(t, p.Is("x", "")) + t.Log(p.String()) +} + +func TestLoader_Option(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "testoption"; + +option (prutal.test.proto) = "o1"; + +message M { + option (prutal.test.message) = "o2"; + + oneof test_oneof { + option (prutal.test.oneof) = "o3"; + string name = 2; + string nick = 3; + } +} + +enum TestEnum { + option (prutal.test.enum) = "o4"; + Z = 0; +} + +service echo_service { + option (prutal.test.service) = "o5"; + rpc echo (M) returns (M) { + option (prutal.test.rpc) = "o6"; + }; +} + +`) + + v, ok := p.Options.Get("(prutal.test.proto)") + assert.True(t, ok) + assert.Equal(t, "o1", v) + + m := p.Messages[0] + v, ok = m.Options.Get("(prutal.test.message)") + assert.True(t, ok) + assert.Equal(t, "o2", v) + + o := m.Oneofs[0] + v, ok = o.Options.Get("(prutal.test.oneof)") + assert.True(t, ok) + assert.Equal(t, "o3", v) + + e := p.Enums[0] + v, ok = e.Options.Get("(prutal.test.enum)") + assert.True(t, ok) + assert.Equal(t, "o4", v) + + s := p.Services[0] + v, ok = s.Options.Get("(prutal.test.service)") + assert.True(t, ok) + assert.Equal(t, "o5", v) + + r := s.Methods[0] + v, ok = r.Options.Get("(prutal.test.rpc)") + assert.True(t, ok) + assert.Equal(t, "o6", v) + +} diff --git a/prutalgen/pkg/prutalgen/proto.go b/prutalgen/pkg/prutalgen/proto.go new file mode 100644 index 0000000..f1567b6 --- /dev/null +++ b/prutalgen/pkg/prutalgen/proto.go @@ -0,0 +1,212 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "path" + "path/filepath" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/text" +) + +// Import ... +// https://protobuf.com/docs/language-spec#imports +type Import struct { + *Proto + + Public bool +} + +// Proto represents a proto file +type Proto struct { + ProtoFile string + Edition string + Package string + + GoImport string // the full import path + GoPackage string // package name without path + + Imports []*Import + Options Options + + Enums []*Enum + Messages []*Message + + Services []*Service + + l LoggerIface +} + +func (p *Proto) String() string { + b := &strings.Builder{} + fmt.Fprintf(b, "Proto %s Edition %s Package %s\n", p.ProtoFile, p.Edition, p.Package) + fmt.Fprintf(b, "Options: %v\n", p.Options) + for _, e := range p.Enums { + fmt.Fprintf(b, "- %s\n", e.String()) + } + for _, m := range p.Messages { + fmt.Fprintf(b, "- %s\n", m.String()) + } + return b.String() +} + +func (p *Proto) setGoPackage(s string) { + imp, pkg, _ := strings.Cut(s, ";") + imp = strings.TrimSpace(imp) + pkg = strings.TrimSpace(pkg) + p.GoImport = imp + if pkg != "" { + p.GoPackage = pkg + } else if p.GoImport != "" { + p.GoPackage = path.Base(p.GoImport) + } +} + +func (p *Proto) getType(name string) any { + for _, m := range p.Messages { + if v := m.getType(name); v != nil { + return v + } + } + for _, e := range p.Enums { + if e.Name == name { + return e + } + } + for _, x := range p.Imports { + if !x.Public { + continue + } + if t := x.Proto.getType(name); t != nil { + return t + } + } + if name1, ok := trimPathPrefix(name, p.Package); ok { + return p.getType(name1) // try again without package prefix + } + return nil +} + +func (p *Proto) IsProto2() bool { + return p.Edition == editionProto2 +} + +func (p *Proto) IsProto3() bool { + return p.Edition == editionProto3 +} + +func (p *Proto) IsEdition2023() bool { + return p.Edition == edition2023 +} + +func (p *Proto) refFile() string { + return refPath(p.ProtoFile) +} + +func (p *Proto) Fatalf(fm string, aa ...any) { + p.l.Fatalf("[FATAL] "+p.refFile()+": "+fm, aa...) +} + +func (p *Proto) Warnf(fm string, aa ...any) { + p.l.Printf("[WARN ] "+p.refFile()+": "+fm, aa...) +} + +func (p *Proto) Infof(fm string, aa ...any) { + p.l.Printf("[INFO ] "+p.refFile()+": "+fm, aa...) +} + +func (p *Proto) resolve() { + for _, e := range p.Enums { + e.resolve() + } + for _, m := range p.Messages { + m.resolve() + } + for _, s := range p.Services { + s.resolve() + } +} + +func (p *Proto) verify() error { + var errs []error + for _, e := range p.Enums { + if err := e.verify(); err != nil { + errs = append(errs, fmt.Errorf("enum %s verify err: %w", e.FullName(), err)) + } + } + for _, m := range p.Messages { + if err := m.verify(); err != nil { + errs = append(errs, fmt.Errorf("message %s verify err: %w", m.FullName(), err)) + } + } + for _, s := range p.Services { + if err := s.verify(); err != nil { + errs = append(errs, fmt.Errorf("service %q verify err: %w", s.Name, err)) + } + } + return joinErrs(errs...) +} + +// listeners + +func (x *protoLoader) ExitEdition(c *parser.EditionContext) { + p := x.currentProto() + s := c.StrLit() + v, err := text.UnmarshalString(s.GetText()) + if err != nil { + x.Fatalf("%s : %s", getTokenPos(s), err) + } + switch v { + case editionProto2, editionProto3, edition2023: + p.Edition = v + default: + x.Fatalf("%s : unknown syntax/edition %q", getTokenPos(s), v) + } +} + +func (x *protoLoader) ExitPackageStatement(c *parser.PackageStatementContext) { + p := x.currentProto() + if p.Package != "" { + x.Fatalf("%s - Multiple package definitions.", getTokenPos(c)) + } + p.Package = c.FullIdent().GetText() +} + +func (x *protoLoader) ExitImportStatement(c *parser.ImportStatementContext) { + // IMPORT (WEAK | PUBLIC)? strLit SEMI + p := x.currentProto() + if len(getText(c.WEAK())) > 0 { + x.Warnf("%s - weak import is not supported", getTokenPos(c)) + } + imp := &Import{} + if len(getText(c.PUBLIC())) > 0 { + imp.Public = true + } + s := c.StrLit().GetText() + importpath, err := text.UnmarshalString(s) + if err != nil { + x.Fatalf("%s - import syntax err: %s", getTokenPos(c), err) + } + // protoc internal proto + if filepath.Base(importpath) != "prutal.proto" { + imp.Proto = x.loadProto(importpath) + p.Imports = append(p.Imports, imp) + } +} diff --git a/prutalgen/pkg/prutalgen/proto_test.go b/prutalgen/pkg/prutalgen/proto_test.go new file mode 100644 index 0000000..c3a6944 --- /dev/null +++ b/prutalgen/pkg/prutalgen/proto_test.go @@ -0,0 +1,67 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestLoader_Proto(t *testing.T) { + x := NewLoader([]string{"."}, nil) + x.SetLogger(&testLogger{t}) + empty := writeFile(t, "empty.proto", []byte(`option go_package = "empty";`)) + fn := writeFile(t, "test.proto", []byte(fmt.Sprintf(` +syntax = "proto3"; +package prutal_test; +import public "%s"; +option go_package = "hello/prutal_test; prutal";`, empty))) + ff := x.LoadProto(fn) + assert.Equal(t, 2, len(ff)) + f := ff[0] + assert.Equal(t, fn, f.ProtoFile) + assert.Equal(t, "prutal", f.GoPackage) // from `go_package` + assert.Equal(t, "prutal_test", f.Package) // from `package` + assert.True(t, f.IsProto3()) + assert.True(t, f.Imports[0].Public) + assert.Same(t, f.Imports[0].Proto, ff[1]) + assert.Equal(t, empty, ff[1].ProtoFile) + t.Log(f.String()) +} + +func TestProto(t *testing.T) { + p0 := &Proto{Package: "p0"} + p := &Proto{Package: "p"} + p.Imports = []*Import{{ + Proto: p0, + }} + + p.Messages = []*Message{{Name: "m"}} + p.Enums = []*Enum{{Name: "e"}} + p.Imports = []*Import{ + {Public: false, Proto: &Proto{Messages: []*Message{{Name: "m0"}}}}, + {Public: true, Proto: &Proto{Messages: []*Message{{Name: "m0"}}}}, + } + + assert.Same(t, p.Messages[0], p.getType("m")) + assert.Same(t, p.Enums[0], p.getType("e")) + assert.Same(t, p.Imports[1].Messages[0], p.getType("m0")) + assert.True(t, p.getType("x") == nil) + +} diff --git a/prutalgen/pkg/prutalgen/prutalgen.go b/prutalgen/pkg/prutalgen/prutalgen.go new file mode 100644 index 0000000..71e830b --- /dev/null +++ b/prutalgen/pkg/prutalgen/prutalgen.go @@ -0,0 +1,268 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "bytes" + "os" + "path" + "path/filepath" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" +) + +type protoLoader struct { + *parser.BaseProtobufListener + + includes []string + proto2gopkg map[string]string + + protos []*Proto // all proto files appended by order + + // state vars + streams []*streamContext // mainly for comment + protostack []*Proto + msgstack []*Message + enum *Enum // current enum + + l LoggerIface +} + +type Loader interface { + SetLogger(LoggerIface) + LoadProto(file string) []*Proto +} + +func NewLoader(includes []string, proto2gopkg map[string]string) Loader { + // add empty element to check filepath without include in searchProtoFile + includes = append(includes, "") + return &protoLoader{ + includes: includes, + proto2gopkg: proto2gopkg, + + l: defaultLogger, + } +} + +func (x *protoLoader) SetLogger(l LoggerIface) { + if l == nil { + x.l = defaultLogger + } else { + x.l = l + } +} + +func fullFilename(incl string, file string) string { + // file path in proto would be in the form of unix style + // need `filepath.FromSlash` for converting it on windows + return filepath.FromSlash(path.Join(incl, file)) +} + +func (x *protoLoader) searchProtoFile(file string) string { + for _, incl := range x.includes { + fn := fullFilename(incl, file) + fn, err := filepath.Abs(fn) + if err != nil { + continue + } + if _, err := os.Stat(fn); err == nil { + return fn + } + } + x.Fatalf("proto file %q not found in includes %v", file, x.includes) + return "" // never goes here +} + +func (x *protoLoader) LoadProto(file string) []*Proto { + x.reset() + _ = x.loadProto(file) + for i := len(x.protos) - 1; i >= 0; i-- { // bottom up processing + p := x.protos[i] + p.resolve() + if err := p.verify(); err != nil { + x.Fatalf("proto %s verify err: %s", p.ProtoFile, err) + } + } + return x.protos +} + +func (x *protoLoader) reset() { + x.protos = nil + x.streams = nil + x.protostack = nil + x.msgstack = nil + x.enum = nil +} + +func (x *protoLoader) Fatalf(fm string, aa ...any) { + if len(x.protostack) > 0 { + x.currentProto().Fatalf(fm, aa...) + } else { + x.l.Fatalf("[FATAL] "+fm, aa...) + } +} + +func (x *protoLoader) Warnf(fm string, aa ...any) { + if len(x.protostack) > 0 { + x.currentProto().Warnf(fm, aa...) + } else { + x.l.Printf("[WARN ] "+fm, aa...) + } +} + +func (x *protoLoader) Infof(fm string, aa ...any) { + if len(x.protostack) > 0 { + x.currentProto().Infof(fm, aa...) + } else { + x.l.Printf("[INFO ] "+fm, aa...) + } +} + +func (x *protoLoader) currentStream() *streamContext { + return last(x.streams) +} + +func (x *protoLoader) currentProto() *Proto { + return last(x.protostack) +} + +func (x *protoLoader) currentMsg() *Message { + return last(x.msgstack) +} + +func (x *protoLoader) currentOneof() *Oneof { + m := x.currentMsg() + return last(m.Oneofs) +} + +func (x *protoLoader) currentService() *Service { + p := x.currentProto() + return last(p.Services) +} + +func (x *protoLoader) getProtoByFile(fn string) *Proto { + for _, p := range x.protos { + if p.ProtoFile == fn { + return p + } + } + return nil +} + +func (x *protoLoader) loadProto(file string) *Proto { + if embeddedProtos[file] != nil { + return x.loadEmbeddedProto(file) + } + p := &Proto{ + ProtoFile: x.searchProtoFile(file), + Edition: editionProto2, + + l: x.l, + } + p.setGoPackage(x.proto2gopkg[file]) + push(&x.protostack, p) + defer pop(&x.protostack) + + if proto := x.getProtoByFile(p.ProtoFile); proto != nil { + files := make([]string, 0, len(x.protostack)) + for _, p := range x.protostack { + files = append(files, p.ProtoFile) + } + x.l.Fatalf("cyclic import is NOT allowed: %s", strings.Join(files, " \n\t-> ")) + return p + } + x.protos = append(x.protos, p) + + x.Infof("parsing") + is, err := antlr.NewFileStream(p.ProtoFile) + if err != nil { + x.Fatalf("open file err: %s", err) + } + x.parseInput(is) + return p +} + +func (x *protoLoader) loadEmbeddedProto(file string) *Proto { + data := embeddedProtos[file] + p := &Proto{ + ProtoFile: file, + Edition: editionProto2, + + l: x.l, + } + push(&x.protostack, p) + defer pop(&x.protostack) + x.protos = append(x.protos, p) + is := antlr.NewIoStream(bytes.NewReader(data)) + x.parseInput(is) + return p +} + +func (x *protoLoader) parseInput(in antlr.CharStream) { + p := x.currentProto() + + lexer := parser.NewProtobufLexer(in) + s := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel) + push(&x.streams, newStreamContext(s)) + defer pop(&x.streams) + + e := &errorListener{l: x.l} + ps := parser.NewProtobufParser(s) + ps.RemoveErrorListeners() + ps.AddErrorListener(e) + proto := ps.Proto() + if e.HasError() { + x.Fatalf("error occurred during parsing proto file") + } + antlr.ParseTreeWalkerDefault.Walk(x, proto) + + gopkg, ok := p.Options.Get("go_package") + if ok { + p.setGoPackage(gopkg) + } + if p.GoPackage == "" { + x.Fatalf(`option "go_package" not set`) + } +} + +func (x *protoLoader) consumeHeadComment(c antlr.ParserRuleContext) string { + s := x.currentStream() + return s.consumeHeadComment(c) +} + +func (x *protoLoader) consumeInlineComment(c antlr.ParserRuleContext) string { + s := x.currentStream() + return s.consumeInlineComment(c) +} + +type errorListener struct { + *antlr.DefaultErrorListener + hasError bool + + l LoggerIface // from protoLoader +} + +func (x *errorListener) SyntaxError(_ antlr.Recognizer, _ any, + line, column int, msg string, _ antlr.RecognitionException) { + x.hasError = true + x.l.Printf("[ERROR] syntax error at line %d column %d - %s\n", line, column, msg) +} + +func (x *errorListener) HasError() bool { return x.hasError } diff --git a/prutalgen/pkg/prutalgen/prutalgen_test.go b/prutalgen/pkg/prutalgen/prutalgen_test.go new file mode 100644 index 0000000..dd0cf7d --- /dev/null +++ b/prutalgen/pkg/prutalgen/prutalgen_test.go @@ -0,0 +1,136 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +type testLogger struct { + *testing.T +} + +func (l testLogger) Printf(format string, v ...any) { + l.Helper() + l.Logf(format, v...) +} + +func writeFile(t *testing.T, fn string, b []byte) string { + dir := t.TempDir() + fn = filepath.Join(dir, fn) + if err := os.WriteFile(fn, b, 0644); err != nil { + t.Helper() + t.Fatal(err) + } + return fn +} + +type expectLogger struct { + t *testing.T + + PrintContains []string + FatalContains string +} + +func (l *expectLogger) Printf(format string, v ...any) { + s := fmt.Sprintf(format, v...) + l.t.Log(s) + if len(l.PrintContains) > 0 { + l.t.Helper() + expect := l.PrintContains[0] + l.PrintContains = l.PrintContains[1:] + assert.StringContains(l.t, s, expect) + } +} + +func (l *expectLogger) Fatalf(format string, v ...any) { + s := fmt.Sprintf(format, v...) + l.t.Log(s) + if l.FatalContains == "" { + l.t.FailNow() + } else { + l.t.Helper() + assert.StringContains(l.t, s, l.FatalContains) + } + l.t.SkipNow() // must skip coz it should NOT continue to run +} + +func loadTestProto(t *testing.T, payload string) *Proto { + x := NewLoader([]string{"."}, nil) + x.SetLogger(&testLogger{t}) + fn := writeFile(t, "test.proto", []byte(payload)) + ff := x.LoadProto(fn) + assert.Equal(t, 1, len(ff)) + assert.Equal(t, fn, ff[0].ProtoFile) + return ff[0] +} + +func expectFail(t *testing.T, payload string, l LoggerIface) { + x := NewLoader([]string{"."}, nil) + x.SetLogger(l) + fn := writeFile(t, "test.proto", []byte(payload)) + _ = x.LoadProto(fn) + t.Helper() + t.Fatal("didn't call Fatal") +} + +func TestLoader(t *testing.T) { + f := loadTestProto(t, `option go_package = "hello/prutal_test";`) + assert.Equal(t, "prutal_test", f.GoPackage) // base path of go_package + + f = loadTestProto(t, `option go_package = "hello/prutal_test; prutal";`) + assert.Equal(t, "prutal", f.GoPackage) // go_package with package name +} + +func TestLoader_SyntaxError(t *testing.T) { + expectFail(t, `import "blabla"`, &expectLogger{t: t, + PrintContains: []string{`parsing`, `missing ';'`}, + FatalContains: `error occurred`, + }) +} + +func TestLoader_NoGoPackage(t *testing.T) { + expectFail(t, ``, &expectLogger{t: t, + FatalContains: `option "go_package" not set`, + }) +} + +func TestLoader_FileNotFound(t *testing.T) { + x := NewLoader([]string{"."}, nil) + x.SetLogger(&expectLogger{t: t, + FatalContains: `proto file "XXX" not found`, + }) + _ = x.LoadProto("XXX") + t.Fatal("never goes here. logger Fatalf in LoadProto") +} + +func TestLoader_CyclicImport(t *testing.T) { + fn := writeFile(t, "test.proto", []byte( + `import "test.proto";`, + )) + x := NewLoader([]string{filepath.Dir(fn)}, nil) + x.SetLogger(&expectLogger{t: t, + FatalContains: `cyclic import`, + }) + _ = x.LoadProto("test.proto") + t.Fatal("never goes here. logger Fatalf in LoadProto") +} diff --git a/prutalgen/pkg/prutalgen/reserved.go b/prutalgen/pkg/prutalgen/reserved.go new file mode 100644 index 0000000..f3661af --- /dev/null +++ b/prutalgen/pkg/prutalgen/reserved.go @@ -0,0 +1,86 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "math" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" +) + +const ( + // reserved by protobuf implementation + protobufReservedMin = 19000 + protobufReservedMax = 19999 +) + +type reservedRange struct { + from int32 + to int32 +} + +type reservedRanges []reservedRange + +func (rr reservedRanges) In(v int32) bool { + if v >= protobufReservedMin && v <= protobufReservedMax { + return true + } + for _, r := range rr { + if v >= r.from && v <= r.to { + return true + } + } + return false +} + +func (x *protoLoader) ExitReserved(c *parser.ReservedContext) { + rc := c.Ranges() + if rc == nil { + return + } + rr := rc.AllOneRange() + ranges := make([]reservedRange, 0, len(rr)) + for _, r := range rr { + i, err := parseI32(r.IntLit(0)) + if err != nil { + x.Fatalf("%s", err) + } + v := reservedRange{from: int32(i)} + if r.TO() == nil { + v.to = v.from + } else if r.MAX() != nil { + v.to = math.MaxInt32 + } else { + i, err = parseI32(r.IntLit(1)) + if err != nil { + x.Fatalf("%s", err) + } + v.to = i + } + ranges = append(ranges, v) + } + + switch getRuleIndex(c.GetParent()) { + case parser.ProtobufParserRULE_messageElement: + m := x.currentMsg() + m.reserved = append(m.reserved, ranges...) + + case parser.ProtobufParserRULE_enumElement: + e := x.enum + e.reserved = append(e.reserved, ranges...) + } +} diff --git a/prutalgen/pkg/prutalgen/reserved_test.go b/prutalgen/pkg/prutalgen/reserved_test.go new file mode 100644 index 0000000..306b7c0 --- /dev/null +++ b/prutalgen/pkg/prutalgen/reserved_test.go @@ -0,0 +1,107 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestReservedRange(t *testing.T) { + rr := reservedRanges{} + rr = append(rr, reservedRange{1, 2}, reservedRange{5, 6}) + assert.False(t, rr.In(0)) + assert.True(t, rr.In(1)) + assert.True(t, rr.In(2)) + assert.False(t, rr.In(3)) + assert.False(t, rr.In(4)) + assert.True(t, rr.In(5)) + assert.True(t, rr.In(6)) + assert.False(t, rr.In(7)) + assert.True(t, rr.In(19000)) + assert.True(t, rr.In(19999)) +} + +func TestLoader_Reserved(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "testmessage"; +message M { + string f = 1; + + reserved 3,5; + reserved 7 to 10; + reserved 100 to max; + + enum e { + reserved 30,50; + reserved 70 to 100; + reserved 1000 to max; + } +} +`) + + m := p.Messages[0] + type testcase struct { + f int32 + v bool + } + + { // (*Message) IsReservedField + cases := []testcase{ + {2, false}, + {3, true}, + {4, false}, + {5, true}, + {6, false}, + {7, true}, + {8, true}, + {10, true}, + {11, false}, + {99, false}, + {100, true}, + {101, true}, + {1000000, true}, + } + for _, c := range cases { + assert.Equal(t, c.v, m.IsReservedField(c.f), c.f) + } + } + + { // (*Enum) IsReservedField + e := m.Enums[0] + cases := []testcase{ + {20, false}, + {30, true}, + {40, false}, + {50, true}, + {60, false}, + {70, true}, + {80, true}, + {100, true}, + {110, false}, + {990, false}, + {1000, true}, + {1001, true}, + {10000000, true}, + } + for _, c := range cases { + assert.Equal(t, c.v, e.IsReservedField(c.f), c.f) + } + } + +} diff --git a/prutalgen/pkg/prutalgen/service.go b/prutalgen/pkg/prutalgen/service.go new file mode 100644 index 0000000..ef54de8 --- /dev/null +++ b/prutalgen/pkg/prutalgen/service.go @@ -0,0 +1,116 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/parser" + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/strs" +) + +type Service struct { + HeadComment string + InlineComment string + + Name string + GoName string + + Methods []*Method + Options Options + + Proto *Proto +} + +func (s *Service) resolve() { + p := s.Proto + s.GoName = strs.GoCamelCase(strings.TrimPrefix(s.Name, p.Package+".")) + for _, m := range s.Methods { + m.resolve() + } +} + +func (s *Service) verify() error { return nil } + +type Method struct { + HeadComment string + InlineComment string + + Name string // orignal name in IDL + GoName string + + Request *Type + Return *Type + + RequestStream bool + ReturnStream bool + + Options Options + + Service *Service +} + +func (r *Method) resolve() { + r.GoName = strs.GoCamelCase(r.Name) + r.Request.resolve(false) + r.Return.resolve(false) +} + +func (x *protoLoader) EnterServiceDef(c *parser.ServiceDefContext) { + // SERVICE serviceName LC serviceElement* RC + s := &Service{ + HeadComment: x.consumeHeadComment(c), InlineComment: x.consumeInlineComment(c), + Name: c.ServiceName().GetText(), + Proto: x.currentProto(), + } + + p := x.currentProto() + p.Services = append(p.Services, s) +} + +func (x *protoLoader) EnterRpc(c *parser.RpcContext) { + // rpc = "rpc" rpcName "(" [ "stream" ] messageType ")" "returns" "(" [ "stream" ] + // messageType ")" (( "{" {option | emptyStatement } "}" ) | ";") + s := x.currentService() + m := &Method{ + Name: c.RpcName().GetText(), + Service: s, + } + s.Methods = append(s.Methods, m) +} + +func (x *protoLoader) ExitRpc(c *parser.RpcContext) { + m := last(x.currentService().Methods) + tt := c.AllMessageType() // 0 for request, 1 for return + for _, s := range c.AllSTREAM() { + if pos := s.GetSymbol().GetStart(); pos < tt[0].GetStart().GetStart() { + m.RequestStream = true + } else if pos < tt[1].GetStart().GetStart() { + m.ReturnStream = true + } + } + m.Request = &Type{ + Name: tt[0].GetText(), + rule: tt[0], + m: m, + } + m.Return = &Type{ + Name: tt[1].GetText(), + rule: tt[1], + m: m, + } +} diff --git a/prutalgen/pkg/prutalgen/service_test.go b/prutalgen/pkg/prutalgen/service_test.go new file mode 100644 index 0000000..6b6e375 --- /dev/null +++ b/prutalgen/pkg/prutalgen/service_test.go @@ -0,0 +1,61 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestLoader_Service(t *testing.T) { + p := loadTestProto(t, ` +option go_package = "echo"; +message M { + string Msg = 1; +} + +service echo_service { + rpc echo (M) returns (M); +}`) + s := p.Services[0] + assert.Equal(t, "echo_service", s.Name) + assert.Equal(t, "EchoService", s.GoName) + + rpc := s.Methods[0] + assert.Equal(t, "echo", rpc.Name) + assert.Equal(t, "Echo", rpc.GoName) + assert.Same(t, p.Messages[0], rpc.Request.Message()) + assert.Same(t, p.Messages[0], rpc.Return.Message()) + assert.False(t, rpc.RequestStream) + assert.False(t, rpc.ReturnStream) + + p = loadTestProto(t, ` +option go_package = "echo"; +message M { + string Msg = 1; +} + +service echo_service { + rpc echo (stream M) returns (stream M); +}`) + + rpc = p.Services[0].Methods[0] + assert.True(t, rpc.RequestStream) + assert.True(t, rpc.ReturnStream) + +} diff --git a/prutalgen/pkg/prutalgen/type.go b/prutalgen/pkg/prutalgen/type.go new file mode 100644 index 0000000..9a71208 --- /dev/null +++ b/prutalgen/pkg/prutalgen/type.go @@ -0,0 +1,184 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "path" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" +) + +type Type struct { + Name string + GoImport string + + // *Enum or *Message + typ any + + // the type belongs to Field or RPC + f *Field + m *Method + + // for logging when resolve + rule antlr.ParserRuleContext +} + +// GoName returns the GoName of underlying type +// +// Coz when calling resolve(), the underlying type may remains unresolved, +// that's why GoName is a method instead of a property. +func (t *Type) GoName() string { + if t.typ == nil { + return scalar2GoTypes[t.Name] + } + ret := "" + switch ft := t.typ.(type) { + case *Enum: + ret = ft.GoName + case *Message: + ret = ft.GoName + default: + panic("[BUG] unknown type") + } + if len(t.GoImport) == 0 { + return ret + } + return path.Base(t.GoImport) + "." + ret +} + +func (t *Type) String() string { + return t.GoName() +} + +func (t *Type) EncodingType() string { + if t.typ != nil { + switch t.typ.(type) { + case *Message: + return "bytes" + case *Enum: + return "varint" + default: + panic("[BUG] unknown type") + } + } + ret, ok := scalar2encodingType[t.Name] + if !ok { + panic("[BUG] unknown type name") + } + return ret +} + +func (t *Type) Message() *Message { + m, _ := t.typ.(*Message) + return m +} + +func (t *Type) IsMessage() bool { + return t.Message() != nil +} + +func (t *Type) Enum() *Enum { + e, _ := t.typ.(*Enum) + return e +} + +func (t *Type) IsEnum() bool { + return t.Enum() != nil +} + +func (t *Type) resolve(allowScalar bool) { + var p *Proto + var m *Message // for checking embedded types + if t.f != nil { + m = t.f.Msg + p = m.Proto + } else if t.m != nil { + p = t.m.Service.Proto + } + + t.GoImport = "" + t.typ = nil + + if allowScalar { + if _, ok := scalar2GoTypes[t.Name]; ok { + return + } + } + + if m != nil { + if v := m.getType(t.Name); v != nil { + t.typ = v + return + } + } + + // (DOT)? (ident DOT)* ident + + // search Message + if m != nil { + for x := m; x != nil; x = x.Msg { + t.typ = x.getType(t.Name) + if t.typ != nil { + return + } + for _, e := range x.Enums { + if e.Name == t.Name { + t.typ = e + return + } + } + for _, m := range x.Messages { + if v := m.getType(t.Name); v != nil { + t.typ = v + return + } + } + } + } + + // search proto and imports + protos := make([]*Proto, 0, len(p.Imports)+1) + protos = append(protos, p) // always check p + for _, x := range p.Imports { + if p.Package == x.Package { // same package + protos = append(protos, x.Proto) + } + } + + // if name starts with "." it means the type is in local package + if !strings.HasPrefix(t.Name, ".") { + for _, x := range p.Imports { + if hasPathPrefix(t.Name, x.Package) { + protos = append(protos, x.Proto) + } + } + } else { + // trim "." for searching type + t.Name = strings.TrimLeft(t.Name, ".") + } + for _, x := range protos { + t.typ = x.getType(t.Name) + if t.typ != nil { + if x.GoImport != p.GoImport { + t.GoImport = x.GoImport + } + return + } + } + p.Fatalf("line %d: type %q not found.", t.rule.GetStart().GetLine(), t.Name) +} diff --git a/prutalgen/pkg/prutalgen/type_test.go b/prutalgen/pkg/prutalgen/type_test.go new file mode 100644 index 0000000..37121e8 --- /dev/null +++ b/prutalgen/pkg/prutalgen/type_test.go @@ -0,0 +1,117 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestType(t *testing.T) { + p := &Type{} + + m := &Message{} + m.GoName = "MessageType" + + e := &Enum{} + m.GoName = "EnumType" + + // test GoName + p.Name = "sint64" + assert.Equal(t, "int64", p.GoName()) + assert.Equal(t, "int64", p.String()) // same + + p.typ = m + assert.Equal(t, m.GoName, p.GoName()) + + p.typ = e + assert.Equal(t, e.GoName, p.GoName()) + p.GoImport = "prutal/base" + assert.Equal(t, "base."+e.GoName, p.GoName()) + + // test EncodingType + p.typ = nil + p.Name = "sint64" + assert.Equal(t, "zigzag64", p.EncodingType()) + p.typ = m + assert.Equal(t, "bytes", p.EncodingType()) + p.typ = e + assert.Equal(t, "varint", p.EncodingType()) + + // Message + p.typ = nil + assert.False(t, p.IsMessage()) + p.typ = m + assert.True(t, p.IsMessage()) + assert.Same(t, m, p.Message()) + + // Enum + p.typ = nil + assert.False(t, p.IsEnum()) + p.typ = e + assert.True(t, p.IsEnum()) + assert.Same(t, e, p.Enum()) + + // resolve:scalar + p.typ, p.f, p.m = nil, nil, nil + p.Name = "sint64" + p.resolve(true) + assert.Equal(t, "int64", p.GoName()) + + // resolve: field type (nested type) of a message + // NestedType in m's parent + m = &Message{Msg: &Message{Messages: []*Message{{Name: "nested_type", GoName: "NestedType"}}}} + p.typ, p.f, p.m = nil, nil, nil + p.f = &Field{Msg: m} + p.Name = m.Msg.Messages[0].Name + p.resolve(false) + assert.Equal(t, m.Msg.Messages[0].GoName, p.GoName()) + + // resolve: field type of a message + m = &Message{Proto: &Proto{Messages: []*Message{{Name: "message_type", GoName: "MessageType"}}}} + p.typ, p.f, p.m = nil, nil, nil + p.f = &Field{Msg: m} + p.Name = m.Proto.Messages[0].Name + p.resolve(false) + assert.Equal(t, m.Proto.Messages[0].GoName, p.GoName()) + + // resolve: args / returns type of a service + method := &Method{Service: &Service{Proto: m.Proto}} + p.typ, p.f, p.m = nil, nil, nil + p.m = method + p.Name = method.Service.Proto.Messages[0].Name + p.resolve(false) + assert.Equal(t, method.Service.Proto.Messages[0].GoName, p.GoName()) + + // resolve: not in same package + m = &Message{Proto: &Proto{ + Imports: []*Import{{Proto: &Proto{ + Package: "base", + GoImport: "gobase", + Messages: []*Message{{Name: "response", GoName: "Response"}}, + }}}, + }} + p.typ, p.f, p.m = nil, nil, nil + p.f = &Field{Msg: m} + p.Name = m.Proto.Imports[0].Package + "." + m.Proto.Imports[0].Messages[0].Name + p.resolve(false) + assert.Equal(t, + m.Proto.Imports[0].GoImport+"."+m.Proto.Imports[0].Messages[0].GoName, + p.GoName()) + +} diff --git a/prutalgen/pkg/prutalgen/utils.go b/prutalgen/pkg/prutalgen/utils.go new file mode 100644 index 0000000..2b91142 --- /dev/null +++ b/prutalgen/pkg/prutalgen/utils.go @@ -0,0 +1,121 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + "github.com/cloudwego/prutal/prutalgen/internal/antlr" + + "github.com/cloudwego/prutal/prutalgen/internal/protobuf/text" +) + +func push[T any](vv *[]T, v T) { + *vv = append(*vv, v) +} + +func pop[T any](vv *[]*T) { + (*vv)[len(*vv)-1] = nil + *vv = (*vv)[:len(*vv)-1] +} + +func last[T any](vv []T) T { + return vv[len(vv)-1] +} + +func unmarshalConst(s string) (string, error) { + if len(s) > 0 && (s[0] == '\'' || s[0] == '"') { + return text.UnmarshalString(s) + } + return s, nil +} + +func getText(n antlr.TerminalNode) string { + if n == nil { + return "" + } + return n.GetText() +} + +func getRuleIndex(v antlr.Tree) int { + type RuleContext interface { + GetRuleIndex() int + } + return v.(RuleContext).GetRuleIndex() +} + +func getTokenPos(v antlr.ParserRuleContext) string { + t := v.GetStart() + return fmt.Sprintf("line %d column %d", t.GetLine(), t.GetColumn()) +} + +func parseI32(c antlr.ParserRuleContext) (int32, error) { + v, err := strconv.ParseInt(c.GetText(), 10, 32) + if err != nil { + return 0, fmt.Errorf("%s - %w", getTokenPos(c), err) + } + return int32(v), nil +} + +func isfalse(v string) bool { return v == "false" } +func istrue(v string) bool { return v == "true" } + +func hasPathPrefix(a, b string) bool { + // same as strings.HasPrefix(a, b + ".") + return len(a) >= len(b)+1 && + a[:len(b)] == b && a[len(b)] == '.' +} + +func trimPathPrefix(a, b string) (string, bool) { + if hasPathPrefix(a, b) { + return a[len(b)+1:], true + } + return a, false +} + +func refPath(abs string) string { + cwd, err := os.Getwd() + if err != nil { + return abs + } + ret, err := filepath.Rel(cwd, abs) + if err != nil { + return abs + } + return ret +} + +// NOTE: This implemenation doesn't works with errors.Is/As. +// Use errors.Join if >=go1.20 +func joinErrs(errs ...error) error { + if len(errs) == 0 { + return nil + } + if len(errs) == 1 { + return errs[0] + } + ss := make([]string, 0, len(errs)) + for _, err := range errs { + ss = append(ss, err.Error()) + } + return errors.New(strings.Join(ss, "\n")) +} diff --git a/prutalgen/pkg/prutalgen/utils_test.go b/prutalgen/pkg/prutalgen/utils_test.go new file mode 100644 index 0000000..15054b6 --- /dev/null +++ b/prutalgen/pkg/prutalgen/utils_test.go @@ -0,0 +1,39 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package prutalgen + +import ( + "io" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" +) + +func TestHasPathPrefix(t *testing.T) { + assert.True(t, hasPathPrefix("a.b.c", "a.b")) + assert.False(t, hasPathPrefix("a.b", "a.b")) + assert.False(t, hasPathPrefix("a.bc", "a.b")) + assert.False(t, hasPathPrefix("a", "b")) +} + +func TestJoinErrs(t *testing.T) { + assert.NoError(t, joinErrs()) + assert.Same(t, io.EOF, joinErrs(io.EOF)) + assert.Equal(t, + io.EOF.Error()+"\n"+io.ErrUnexpectedEOF.Error(), + joinErrs(io.EOF, io.ErrUnexpectedEOF).Error()) +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/any.proto b/prutalgen/pkg/prutalgen/wellknowns/any.proto new file mode 100644 index 0000000..eff44e5 --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/any.proto @@ -0,0 +1,162 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/anypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "AnyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Any` contains an arbitrary serialized protocol buffer message along with a +// URL that describes the type of the serialized message. +// +// Protobuf library provides support to pack/unpack Any values in the form +// of utility functions or additional generated methods of the Any type. +// +// Example 1: Pack and unpack a message in C++. +// +// Foo foo = ...; +// Any any; +// any.PackFrom(foo); +// ... +// if (any.UnpackTo(&foo)) { +// ... +// } +// +// Example 2: Pack and unpack a message in Java. +// +// Foo foo = ...; +// Any any = Any.pack(foo); +// ... +// if (any.is(Foo.class)) { +// foo = any.unpack(Foo.class); +// } +// // or ... +// if (any.isSameTypeAs(Foo.getDefaultInstance())) { +// foo = any.unpack(Foo.getDefaultInstance()); +// } +// +// Example 3: Pack and unpack a message in Python. +// +// foo = Foo(...) +// any = Any() +// any.Pack(foo) +// ... +// if any.Is(Foo.DESCRIPTOR): +// any.Unpack(foo) +// ... +// +// Example 4: Pack and unpack a message in Go +// +// foo := &pb.Foo{...} +// any, err := anypb.New(foo) +// if err != nil { +// ... +// } +// ... +// foo := &pb.Foo{} +// if err := any.UnmarshalTo(foo); err != nil { +// ... +// } +// +// The pack methods provided by protobuf library will by default use +// 'type.googleapis.com/full.type.name' as the type URL and the unpack +// methods only use the fully qualified type name after the last '/' +// in the type URL, for example "foo.bar.com/x/y.z" will yield type +// name "y.z". +// +// JSON +// ==== +// The JSON representation of an `Any` value uses the regular +// representation of the deserialized, embedded message, with an +// additional field `@type` which contains the type URL. Example: +// +// package google.profile; +// message Person { +// string first_name = 1; +// string last_name = 2; +// } +// +// { +// "@type": "type.googleapis.com/google.profile.Person", +// "firstName": , +// "lastName": +// } +// +// If the embedded message type is well-known and has a custom JSON +// representation, that representation will be embedded adding a field +// `value` which holds the custom JSON in addition to the `@type` +// field. Example (for message [google.protobuf.Duration][]): +// +// { +// "@type": "type.googleapis.com/google.protobuf.Duration", +// "value": "1.212s" +// } +// +message Any { + // A URL/resource name that uniquely identifies the type of the serialized + // protocol buffer message. This string must contain at least + // one "/" character. The last segment of the URL's path must represent + // the fully qualified name of the type (as in + // `path/google.protobuf.Duration`). The name should be in a canonical form + // (e.g., leading "." is not accepted). + // + // In practice, teams usually precompile into the binary all types that they + // expect it to use in the context of Any. However, for URLs which use the + // scheme `http`, `https`, or no scheme, one can optionally set up a type + // server that maps type URLs to message definitions as follows: + // + // * If no scheme is provided, `https` is assumed. + // * An HTTP GET on the URL must yield a [google.protobuf.Type][] + // value in binary format, or produce an error. + // * Applications are allowed to cache lookup results based on the + // URL, or have them precompiled into a binary to avoid any + // lookup. Therefore, binary compatibility needs to be preserved + // on changes to types. (Use versioned type names to manage + // breaking changes.) + // + // Note: this functionality is not currently available in the official + // protobuf release, and it is not used for type URLs beginning with + // type.googleapis.com. As of May 2023, there are no widely used type server + // implementations and no plans to implement one. + // + // Schemes other than `http`, `https` (or the empty scheme) might be + // used with implementation specific semantics. + // + string type_url = 1; + + // Must be a valid serialized protocol buffer of the above specified type. + bytes value = 2; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/api.proto b/prutalgen/pkg/prutalgen/wellknowns/api.proto new file mode 100644 index 0000000..afc9cc1 --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/api.proto @@ -0,0 +1,207 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/source_context.proto"; +import "google/protobuf/type.proto"; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "ApiProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/apipb"; + +// Api is a light-weight descriptor for an API Interface. +// +// Interfaces are also described as "protocol buffer services" in some contexts, +// such as by the "service" keyword in a .proto file, but they are different +// from API Services, which represent a concrete implementation of an interface +// as opposed to simply a description of methods and bindings. They are also +// sometimes simply referred to as "APIs" in other contexts, such as the name of +// this message itself. See https://cloud.google.com/apis/design/glossary for +// detailed terminology. +message Api { + // The fully qualified name of this interface, including package name + // followed by the interface's simple name. + string name = 1; + + // The methods of this interface, in unspecified order. + repeated Method methods = 2; + + // Any metadata attached to the interface. + repeated Option options = 3; + + // A version string for this interface. If specified, must have the form + // `major-version.minor-version`, as in `1.10`. If the minor version is + // omitted, it defaults to zero. If the entire version field is empty, the + // major version is derived from the package name, as outlined below. If the + // field is not empty, the version in the package name will be verified to be + // consistent with what is provided here. + // + // The versioning schema uses [semantic + // versioning](http://semver.org) where the major version number + // indicates a breaking change and the minor version an additive, + // non-breaking change. Both version numbers are signals to users + // what to expect from different versions, and should be carefully + // chosen based on the product plan. + // + // The major version is also reflected in the package name of the + // interface, which must end in `v`, as in + // `google.feature.v1`. For major versions 0 and 1, the suffix can + // be omitted. Zero major versions must only be used for + // experimental, non-GA interfaces. + // + string version = 4; + + // Source context for the protocol buffer service represented by this + // message. + SourceContext source_context = 5; + + // Included interfaces. See [Mixin][]. + repeated Mixin mixins = 6; + + // The source syntax of the service. + Syntax syntax = 7; +} + +// Method represents a method of an API interface. +message Method { + // The simple name of this method. + string name = 1; + + // A URL of the input message type. + string request_type_url = 2; + + // If true, the request is streamed. + bool request_streaming = 3; + + // The URL of the output message type. + string response_type_url = 4; + + // If true, the response is streamed. + bool response_streaming = 5; + + // Any metadata attached to the method. + repeated Option options = 6; + + // The source syntax of this method. + Syntax syntax = 7; +} + +// Declares an API Interface to be included in this interface. The including +// interface must redeclare all the methods from the included interface, but +// documentation and options are inherited as follows: +// +// - If after comment and whitespace stripping, the documentation +// string of the redeclared method is empty, it will be inherited +// from the original method. +// +// - Each annotation belonging to the service config (http, +// visibility) which is not set in the redeclared method will be +// inherited. +// +// - If an http annotation is inherited, the path pattern will be +// modified as follows. Any version prefix will be replaced by the +// version of the including interface plus the [root][] path if +// specified. +// +// Example of a simple mixin: +// +// package google.acl.v1; +// service AccessControl { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v1/{resource=**}:getAcl"; +// } +// } +// +// package google.storage.v2; +// service Storage { +// rpc GetAcl(GetAclRequest) returns (Acl); +// +// // Get a data record. +// rpc GetData(GetDataRequest) returns (Data) { +// option (google.api.http).get = "/v2/{resource=**}"; +// } +// } +// +// Example of a mixin configuration: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// +// The mixin construct implies that all methods in `AccessControl` are +// also declared with same name and request/response types in +// `Storage`. A documentation generator or annotation processor will +// see the effective `Storage.GetAcl` method after inheriting +// documentation and annotations as follows: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/{resource=**}:getAcl"; +// } +// ... +// } +// +// Note how the version in the path pattern changed from `v1` to `v2`. +// +// If the `root` field in the mixin is specified, it should be a +// relative path under which inherited HTTP paths are placed. Example: +// +// apis: +// - name: google.storage.v2.Storage +// mixins: +// - name: google.acl.v1.AccessControl +// root: acls +// +// This implies the following inherited HTTP annotation: +// +// service Storage { +// // Get the underlying ACL object. +// rpc GetAcl(GetAclRequest) returns (Acl) { +// option (google.api.http).get = "/v2/acls/{resource=**}:getAcl"; +// } +// ... +// } +message Mixin { + // The fully qualified name of the interface which is included. + string name = 1; + + // If non-empty specifies a path under which inherited HTTP paths + // are rooted. + string root = 2; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/descriptor.proto b/prutalgen/pkg/prutalgen/wellknowns/descriptor.proto new file mode 100644 index 0000000..dd2d0fb --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/descriptor.proto @@ -0,0 +1,1337 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Author: kenton@google.com (Kenton Varda) +// Based on original Protocol Buffers design by +// Sanjay Ghemawat, Jeff Dean, and others. +// +// The messages in this file describe the definitions found in .proto files. +// A valid .proto file can be translated directly to a FileDescriptorProto +// without any other information (e.g. without reading its imports). + +syntax = "proto2"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/descriptorpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DescriptorProtos"; +option csharp_namespace = "Google.Protobuf.Reflection"; +option objc_class_prefix = "GPB"; +option cc_enable_arenas = true; + +// descriptor.proto must be optimized for speed because reflection-based +// algorithms don't work during bootstrapping. +option optimize_for = SPEED; + +// The protocol compiler can output a FileDescriptorSet containing the .proto +// files it parses. +message FileDescriptorSet { + repeated FileDescriptorProto file = 1; + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.FileDescriptorSetExtension" + full_name: ".buf.descriptor.v1.buf_file_descriptor_set_extension" + }]; +} + +// The full set of known editions. +enum Edition { + // A placeholder for an unknown edition value. + EDITION_UNKNOWN = 0; + + // A placeholder edition for specifying default behaviors *before* a feature + // was first introduced. This is effectively an "infinite past". + EDITION_LEGACY = 900; + + // Legacy syntax "editions". These pre-date editions, but behave much like + // distinct editions. These can't be used to specify the edition of proto + // files, but feature definitions must supply proto2/proto3 defaults for + // backwards compatibility. + EDITION_PROTO2 = 998; + EDITION_PROTO3 = 999; + + // Editions that have been released. The specific values are arbitrary and + // should not be depended on, but they will always be time-ordered for easy + // comparison. + EDITION_2023 = 1000; + EDITION_2024 = 1001; + + // Placeholder editions for testing feature resolution. These should not be + // used or relied on outside of tests. + EDITION_1_TEST_ONLY = 1; + EDITION_2_TEST_ONLY = 2; + EDITION_99997_TEST_ONLY = 99997; + EDITION_99998_TEST_ONLY = 99998; + EDITION_99999_TEST_ONLY = 99999; + + // Placeholder for specifying unbounded edition support. This should only + // ever be used by plugins that can expect to never require any changes to + // support a new edition. + EDITION_MAX = 0x7FFFFFFF; +} + +// Describes a complete .proto file. +message FileDescriptorProto { + optional string name = 1; // file name, relative to root of source tree + optional string package = 2; // e.g. "foo", "foo.bar", etc. + + // Names of files imported by this file. + repeated string dependency = 3; + // Indexes of the public imported files in the dependency list above. + repeated int32 public_dependency = 10; + // Indexes of the weak imported files in the dependency list. + // For Google-internal migration only. Do not use. + repeated int32 weak_dependency = 11; + + // All top-level definitions in this file. + repeated DescriptorProto message_type = 4; + repeated EnumDescriptorProto enum_type = 5; + repeated ServiceDescriptorProto service = 6; + repeated FieldDescriptorProto extension = 7; + + optional FileOptions options = 8; + + // This field contains optional information about the original source code. + // You may safely remove this entire field without harming runtime + // functionality of the descriptors -- the information is needed only by + // development tools. + optional SourceCodeInfo source_code_info = 9; + + // The syntax of the proto file. + // The supported values are "proto2", "proto3", and "editions". + // + // If `edition` is present, this value must be "editions". + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional string syntax = 12; + + // The edition of the proto file. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional Edition edition = 14; +} + +// Describes a message type. +message DescriptorProto { + optional string name = 1; + + repeated FieldDescriptorProto field = 2; + repeated FieldDescriptorProto extension = 6; + + repeated DescriptorProto nested_type = 3; + repeated EnumDescriptorProto enum_type = 4; + + message ExtensionRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + + optional ExtensionRangeOptions options = 3; + } + repeated ExtensionRange extension_range = 5; + + repeated OneofDescriptorProto oneof_decl = 8; + + optional MessageOptions options = 7; + + // Range of reserved tag numbers. Reserved tag numbers may not be used by + // fields or extension ranges in the same message. Reserved ranges may + // not overlap. + message ReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Exclusive. + } + repeated ReservedRange reserved_range = 9; + // Reserved field names, which may not be used by fields in the same message. + // A given name may only be reserved once. + repeated string reserved_name = 10; +} + +message ExtensionRangeOptions { + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + message Declaration { + // The extension number declared within the extension range. + optional int32 number = 1; + + // The fully-qualified name of the extension field. There must be a leading + // dot in front of the full name. + optional string full_name = 2; + + // The fully-qualified type name of the extension field. Unlike + // Metadata.type, Declaration.type must have a leading dot for messages + // and enums. + optional string type = 3; + + // If true, indicates that the number is reserved in the extension range, + // and any extension field with the number will fail to compile. Set this + // when a declared extension field is deleted. + optional bool reserved = 5; + + // If true, indicates that the extension must be defined as repeated. + // Otherwise the extension must be defined as optional. + optional bool repeated = 6; + + reserved 4; // removed is_repeated + } + + // For external users: DO NOT USE. We are in the process of open sourcing + // extension declaration and executing internal cleanups before it can be + // used externally. + repeated Declaration declaration = 2 [retention = RETENTION_SOURCE]; + + // Any features defined in the specific edition. + optional FeatureSet features = 50; + + // The verification state of the extension range. + enum VerificationState { + // All the extensions of the range must be declared. + DECLARATION = 0; + UNVERIFIED = 1; + } + + // The verification state of the range. + // TODO: flip the default to DECLARATION once all empty ranges + // are marked as UNVERIFIED. + optional VerificationState verification = 3 + [default = UNVERIFIED, retention = RETENTION_SOURCE]; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// Describes a field within a message. +message FieldDescriptorProto { + enum Type { + // 0 is reserved for errors. + // Order is weird for historical reasons. + TYPE_DOUBLE = 1; + TYPE_FLOAT = 2; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + TYPE_INT64 = 3; + TYPE_UINT64 = 4; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + TYPE_INT32 = 5; + TYPE_FIXED64 = 6; + TYPE_FIXED32 = 7; + TYPE_BOOL = 8; + TYPE_STRING = 9; + // Tag-delimited aggregate. + // Group type is deprecated and not supported after google.protobuf. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. In Editions, the group wire format + // can be enabled via the `message_encoding` feature. + TYPE_GROUP = 10; + TYPE_MESSAGE = 11; // Length-delimited aggregate. + + // New in version 2. + TYPE_BYTES = 12; + TYPE_UINT32 = 13; + TYPE_ENUM = 14; + TYPE_SFIXED32 = 15; + TYPE_SFIXED64 = 16; + TYPE_SINT32 = 17; // Uses ZigZag encoding. + TYPE_SINT64 = 18; // Uses ZigZag encoding. + } + + enum Label { + // 0 is reserved for errors + LABEL_OPTIONAL = 1; + LABEL_REPEATED = 3; + // The required label is only allowed in google.protobuf. In proto3 and Editions + // it's explicitly prohibited. In Editions, the `field_presence` feature + // can be used to get this behavior. + LABEL_REQUIRED = 2; + } + + optional string name = 1; + optional int32 number = 3; + optional Label label = 4; + + // If type_name is set, this need not be set. If both this and type_name + // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP. + optional Type type = 5; + + // For message and enum types, this is the name of the type. If the name + // starts with a '.', it is fully-qualified. Otherwise, C++-like scoping + // rules are used to find the type (i.e. first the nested types within this + // message are searched, then within the parent, on up to the root + // namespace). + optional string type_name = 6; + + // For extensions, this is the name of the type being extended. It is + // resolved in the same manner as type_name. + optional string extendee = 2; + + // For numeric types, contains the original text representation of the value. + // For booleans, "true" or "false". + // For strings, contains the default text contents (not escaped in any way). + // For bytes, contains the C escaped value. All bytes >= 128 are escaped. + optional string default_value = 7; + + // If set, gives the index of a oneof in the containing type's oneof_decl + // list. This field is a member of that oneof. + optional int32 oneof_index = 9; + + // JSON name of this field. The value is set by protocol compiler. If the + // user has set a "json_name" option on this field, that option's value + // will be used. Otherwise, it's deduced from the field's name by converting + // it to camelCase. + optional string json_name = 10; + + optional FieldOptions options = 8; + + // If true, this is a proto3 "optional". When a proto3 field is optional, it + // tracks presence regardless of field type. + // + // When proto3_optional is true, this field must belong to a oneof to signal + // to old proto3 clients that presence is tracked for this field. This oneof + // is known as a "synthetic" oneof, and this field must be its sole member + // (each proto3 optional field gets its own synthetic oneof). Synthetic oneofs + // exist in the descriptor only, and do not generate any API. Synthetic oneofs + // must be ordered after all "real" oneofs. + // + // For message fields, proto3_optional doesn't create any semantic change, + // since non-repeated message fields always track presence. However it still + // indicates the semantic detail of whether the user wrote "optional" or not. + // This can be useful for round-tripping the .proto file. For consistency we + // give message fields a synthetic oneof also, even though it is not required + // to track presence. This is especially important because the parser can't + // tell if a field is a message or an enum, so it must always create a + // synthetic oneof. + // + // Proto2 optional fields do not set this flag, because they already indicate + // optional with `LABEL_OPTIONAL`. + optional bool proto3_optional = 17; +} + +// Describes a oneof. +message OneofDescriptorProto { + optional string name = 1; + optional OneofOptions options = 2; +} + +// Describes an enum type. +message EnumDescriptorProto { + optional string name = 1; + + repeated EnumValueDescriptorProto value = 2; + + optional EnumOptions options = 3; + + // Range of reserved numeric values. Reserved values may not be used by + // entries in the same enum. Reserved ranges may not overlap. + // + // Note that this is distinct from DescriptorProto.ReservedRange in that it + // is inclusive such that it can appropriately represent the entire int32 + // domain. + message EnumReservedRange { + optional int32 start = 1; // Inclusive. + optional int32 end = 2; // Inclusive. + } + + // Range of reserved numeric values. Reserved numeric values may not be used + // by enum values in the same enum declaration. Reserved ranges may not + // overlap. + repeated EnumReservedRange reserved_range = 4; + + // Reserved enum value names, which may not be reused. A given name may only + // be reserved once. + repeated string reserved_name = 5; +} + +// Describes a value within an enum. +message EnumValueDescriptorProto { + optional string name = 1; + optional int32 number = 2; + + optional EnumValueOptions options = 3; +} + +// Describes a service. +message ServiceDescriptorProto { + optional string name = 1; + repeated MethodDescriptorProto method = 2; + + optional ServiceOptions options = 3; +} + +// Describes a method of a service. +message MethodDescriptorProto { + optional string name = 1; + + // Input and output type names. These are resolved in the same way as + // FieldDescriptorProto.type_name, but must refer to a message type. + optional string input_type = 2; + optional string output_type = 3; + + optional MethodOptions options = 4; + + // Identifies if client streams multiple client messages + optional bool client_streaming = 5 [default = false]; + // Identifies if server streams multiple server messages + optional bool server_streaming = 6 [default = false]; +} + +// =================================================================== +// Options + +// Each of the definitions above may have "options" attached. These are +// just annotations which may cause code to be generated slightly differently +// or may contain hints for code that manipulates protocol messages. +// +// Clients may define custom options as extensions of the *Options messages. +// These extensions may not yet be known at parsing time, so the parser cannot +// store the values in them. Instead it stores them in a field in the *Options +// message called uninterpreted_option. This field must have the same name +// across all *Options messages. We then use this field to populate the +// extensions when we build a descriptor, at which point all protos have been +// parsed and so all extensions are known. +// +// Extension numbers for custom options may be chosen as follows: +// * For options which will only be used within a single application or +// organization, or for experimental options, use field numbers 50000 +// through 99999. It is up to you to ensure that you do not use the +// same number for multiple options. +// * For options which will be published and used publicly by multiple +// independent entities, e-mail protobuf-global-extension-registry@google.com +// to reserve extension numbers. Simply provide your project name (e.g. +// Objective-C plugin) and your project website (if available) -- there's no +// need to explain how you intend to use them. Usually you only need one +// extension number. You can declare multiple options with only one extension +// number by putting them in a sub-message. See the Custom Options section of +// the docs for examples: +// https://developers.google.com/protocol-buffers/docs/proto#options +// If this turns out to be popular, a web service will be set up +// to automatically assign option numbers. + +message FileOptions { + + // Sets the Java package where classes generated from this .proto will be + // placed. By default, the proto package is used, but this is often + // inappropriate because proto packages do not normally start with backwards + // domain names. + optional string java_package = 1; + + // Controls the name of the wrapper Java class generated for the .proto file. + // That class will always contain the .proto file's getDescriptor() method as + // well as any top-level extensions defined in the .proto file. + // If java_multiple_files is disabled, then all the other classes from the + // .proto file will be nested inside the single wrapper outer class. + optional string java_outer_classname = 8; + + // If enabled, then the Java code generator will generate a separate .java + // file for each top-level message, enum, and service defined in the .proto + // file. Thus, these types will *not* be nested inside the wrapper class + // named by java_outer_classname. However, the wrapper class will still be + // generated to contain the file's getDescriptor() method as well as any + // top-level extensions defined in the file. + optional bool java_multiple_files = 10 [default = false]; + + // This option does nothing. + optional bool java_generate_equals_and_hash = 20 [deprecated=true]; + + // A proto2 file can set this to true to opt in to UTF-8 checking for Java, + // which will throw an exception if invalid UTF-8 is parsed from the wire or + // assigned to a string field. + // + // TODO: clarify exactly what kinds of field types this option + // applies to, and update these docs accordingly. + // + // Proto3 files already perform these checks. Setting the option explicitly to + // false has no effect: it cannot be used to opt proto3 files out of UTF-8 + // checks. + optional bool java_string_check_utf8 = 27 [default = false]; + + // Generated classes can be optimized for speed or code size. + enum OptimizeMode { + SPEED = 1; // Generate complete code for parsing, serialization, + // etc. + CODE_SIZE = 2; // Use ReflectionOps to implement these methods. + LITE_RUNTIME = 3; // Generate code using MessageLite and the lite runtime. + } + optional OptimizeMode optimize_for = 9 [default = SPEED]; + + // Sets the Go package where structs generated from this .proto will be + // placed. If omitted, the Go package will be derived from the following: + // - The basename of the package import path, if provided. + // - Otherwise, the package statement in the .proto file, if present. + // - Otherwise, the basename of the .proto file, without extension. + optional string go_package = 11; + + // Should generic services be generated in each language? "Generic" services + // are not specific to any particular RPC system. They are generated by the + // main code generators in each language (without additional plugins). + // Generic services were the only kind of service generation supported by + // early versions of google.protobuf. + // + // Generic services are now considered deprecated in favor of using plugins + // that generate code specific to your particular RPC system. Therefore, + // these default to false. Old code which depends on generic services should + // explicitly set them to true. + optional bool cc_generic_services = 16 [default = false]; + optional bool java_generic_services = 17 [default = false]; + optional bool py_generic_services = 18 [default = false]; + reserved 42; // removed php_generic_services + reserved "php_generic_services"; + + // Is this file deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for everything in the file, or it will be completely ignored; in the very + // least, this is a formalization for deprecating files. + optional bool deprecated = 23 [default = false]; + + // Enables the use of arenas for the proto messages in this file. This applies + // only to generated classes for C++. + optional bool cc_enable_arenas = 31 [default = true]; + + // Sets the objective c class prefix which is prepended to all objective c + // generated classes from this .proto. There is no default. + optional string objc_class_prefix = 36; + + // Namespace for generated classes; defaults to the package. + optional string csharp_namespace = 37; + + // By default Swift generators will take the proto package and CamelCase it + // replacing '.' with underscore and use that to prefix the types/symbols + // defined. When this options is provided, they will use this value instead + // to prefix the types/symbols defined. + optional string swift_prefix = 39; + + // Sets the php class prefix which is prepended to all php generated classes + // from this .proto. Default is empty. + optional string php_class_prefix = 40; + + // Use this option to change the namespace of php generated classes. Default + // is empty. When this option is empty, the package name will be used for + // determining the namespace. + optional string php_namespace = 41; + + // Use this option to change the namespace of php generated metadata classes. + // Default is empty. When this option is empty, the proto file name will be + // used for determining the namespace. + optional string php_metadata_namespace = 44; + + // Use this option to change the package of ruby generated classes. Default + // is empty. When this option is not set, the package name will be used for + // determining the ruby package. + optional string ruby_package = 45; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 50; + + // The parser stores options it doesn't recognize here. + // See the documentation for the "Options" section above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. + // See the documentation for the "Options" section above. + extensions 1000 to max; + + reserved 38; +} + +message MessageOptions { + // Set true to use the old proto1 MessageSet wire format for extensions. + // This is provided for backwards-compatibility with the MessageSet wire + // format. You should not use this for any other reason: It's less + // efficient, has fewer features, and is more complicated. + // + // The message must be defined exactly as follows: + // message Foo { + // option message_set_wire_format = true; + // extensions 4 to max; + // } + // Note that the message cannot have any defined fields; MessageSets only + // have extensions. + // + // All extensions of your type must be singular messages; e.g. they cannot + // be int32s, enums, or repeated messages. + // + // Because this is an option, the above two restrictions are not enforced by + // the protocol compiler. + optional bool message_set_wire_format = 1 [default = false]; + + // Disables the generation of the standard "descriptor()" accessor, which can + // conflict with a field of the same name. This is meant to make migration + // from proto1 easier; new code should avoid fields named "descriptor". + optional bool no_standard_descriptor_accessor = 2 [default = false]; + + // Is this message deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the message, or it will be completely ignored; in the very least, + // this is a formalization for deprecating messages. + optional bool deprecated = 3 [default = false]; + + reserved 4, 5, 6; + + // Whether the message is an automatically generated map entry type for the + // maps field. + // + // For maps fields: + // map map_field = 1; + // The parsed descriptor looks like: + // message MapFieldEntry { + // option map_entry = true; + // optional KeyType key = 1; + // optional ValueType value = 2; + // } + // repeated MapFieldEntry map_field = 1; + // + // Implementations may choose not to generate the map_entry=true message, but + // use a native map in the target language to hold the keys and values. + // The reflection APIs in such implementations still need to work as + // if the field is a repeated message field. + // + // NOTE: Do not set the option in .proto files. Always use the maps syntax + // instead. The option should only be implicitly set by the proto compiler + // parser. + optional bool map_entry = 7; + + reserved 8; // javalite_serializable + reserved 9; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // + // This should only be used as a temporary measure against broken builds due + // to the change in behavior for JSON field name conflicts. + // + // TODO This is legacy behavior we plan to remove once downstream + // teams have had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 11 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 12; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message FieldOptions { + // NOTE: ctype is deprecated. Use `features.(pb.cpp).string_type` instead. + // The ctype option instructs the C++ code generator to use a different + // representation of the field than it normally would. See the specific + // options below. This option is only implemented to support use of + // [ctype=CORD] and [ctype=STRING] (the default) on non-repeated fields of + // type "bytes" in the open source release. + // TODO: make ctype actually deprecated. + optional CType ctype = 1 [/*deprecated = true,*/ default = STRING]; + enum CType { + // Default mode. + STRING = 0; + + // The option [ctype=CORD] may be applied to a non-repeated field of type + // "bytes". It indicates that in C++, the data should be stored in a Cord + // instead of a string. For very large strings, this may reduce memory + // fragmentation. It may also allow better performance when parsing from a + // Cord, or when parsing with aliasing enabled, as the parsed Cord may then + // alias the original buffer. + CORD = 1; + + STRING_PIECE = 2; + } + // The packed option can be enabled for repeated primitive fields to enable + // a more efficient representation on the wire. Rather than repeatedly + // writing the tag and type for each element, the entire array is encoded as + // a single length-delimited blob. In proto3, only explicit setting it to + // false will avoid using packed encoding. This option is prohibited in + // Editions, but the `repeated_field_encoding` feature can be used to control + // the behavior. + optional bool packed = 2; + + // The jstype option determines the JavaScript type used for values of the + // field. The option is permitted only for 64 bit integral and fixed types + // (int64, uint64, sint64, fixed64, sfixed64). A field with jstype JS_STRING + // is represented as JavaScript string, which avoids loss of precision that + // can happen when a large value is converted to a floating point JavaScript. + // Specifying JS_NUMBER for the jstype causes the generated JavaScript code to + // use the JavaScript "number" type. The behavior of the default option + // JS_NORMAL is implementation dependent. + // + // This option is an enum to permit additional types to be added, e.g. + // goog.math.Integer. + optional JSType jstype = 6 [default = JS_NORMAL]; + enum JSType { + // Use the default type. + JS_NORMAL = 0; + + // Use JavaScript strings. + JS_STRING = 1; + + // Use JavaScript numbers. + JS_NUMBER = 2; + } + + // Should this field be parsed lazily? Lazy applies only to message-type + // fields. It means that when the outer message is initially parsed, the + // inner message's contents will not be parsed but instead stored in encoded + // form. The inner message will actually be parsed when it is first accessed. + // + // This is only a hint. Implementations are free to choose whether to use + // eager or lazy parsing regardless of the value of this option. However, + // setting this option true suggests that the protocol author believes that + // using lazy parsing on this field is worth the additional bookkeeping + // overhead typically needed to implement it. + // + // This option does not affect the public interface of any generated code; + // all method signatures remain the same. Furthermore, thread-safety of the + // interface is not affected by this option; const methods remain safe to + // call from multiple threads concurrently, while non-const methods continue + // to require exclusive access. + // + // Note that lazy message fields are still eagerly verified to check + // ill-formed wireformat or missing required fields. Calling IsInitialized() + // on the outer message would fail if the inner message has missing required + // fields. Failed verification would result in parsing failure (except when + // uninitialized messages are acceptable). + optional bool lazy = 5 [default = false]; + + // unverified_lazy does no correctness checks on the byte stream. This should + // only be used where lazy with verification is prohibitive for performance + // reasons. + optional bool unverified_lazy = 15 [default = false]; + + // Is this field deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for accessors, or it will be completely ignored; in the very least, this + // is a formalization for deprecating fields. + optional bool deprecated = 3 [default = false]; + + // For Google-internal migration only. Do not use. + optional bool weak = 10 [default = false]; + + // Indicate that the field value should not be printed out when using debug + // formats, e.g. when the field contains sensitive credentials. + optional bool debug_redact = 16 [default = false]; + + // If set to RETENTION_SOURCE, the option will be omitted from the binary. + enum OptionRetention { + RETENTION_UNKNOWN = 0; + RETENTION_RUNTIME = 1; + RETENTION_SOURCE = 2; + } + + optional OptionRetention retention = 17; + + // This indicates the types of entities that the field may apply to when used + // as an option. If it is unset, then the field may be freely used as an + // option on any kind of entity. + enum OptionTargetType { + TARGET_TYPE_UNKNOWN = 0; + TARGET_TYPE_FILE = 1; + TARGET_TYPE_EXTENSION_RANGE = 2; + TARGET_TYPE_MESSAGE = 3; + TARGET_TYPE_FIELD = 4; + TARGET_TYPE_ONEOF = 5; + TARGET_TYPE_ENUM = 6; + TARGET_TYPE_ENUM_ENTRY = 7; + TARGET_TYPE_SERVICE = 8; + TARGET_TYPE_METHOD = 9; + } + + repeated OptionTargetType targets = 19; + + message EditionDefault { + optional Edition edition = 3; + optional string value = 2; // Textproto value. + } + repeated EditionDefault edition_defaults = 20; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 21; + + // Information about the support window of a feature. + message FeatureSupport { + // The edition that this feature was first available in. In editions + // earlier than this one, the default assigned to EDITION_LEGACY will be + // used, and proto files will not be able to override it. + optional Edition edition_introduced = 1; + + // The edition this feature becomes deprecated in. Using this after this + // edition may trigger warnings. + optional Edition edition_deprecated = 2; + + // The deprecation warning text if this feature is used after the edition it + // was marked deprecated in. + optional string deprecation_warning = 3; + + // The edition this feature is no longer available in. In editions after + // this one, the last default assigned will be used, and proto files will + // not be able to override it. + optional Edition edition_removed = 4; + } + optional FeatureSupport feature_support = 22; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; + + reserved 4; // removed jtype + reserved 18; // reserve target, target_obsolete_do_not_use +} + +message OneofOptions { + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 1; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumOptions { + + // Set this option to true to allow mapping different tag names to the same + // value. + optional bool allow_alias = 2; + + // Is this enum deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum, or it will be completely ignored; in the very least, this + // is a formalization for deprecating enums. + optional bool deprecated = 3 [default = false]; + + reserved 5; // javanano_as_lite + + // Enable the legacy handling of JSON field name conflicts. This lowercases + // and strips underscored from the fields before comparison in proto3 only. + // The new behavior takes `json_name` into account and applies to proto2 as + // well. + // TODO Remove this legacy behavior once downstream teams have + // had time to migrate. + optional bool deprecated_legacy_json_field_conflicts = 6 [deprecated = true]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 7; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message EnumValueOptions { + // Is this enum value deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the enum value, or it will be completely ignored; in the very least, + // this is a formalization for deprecating enum values. + optional bool deprecated = 1 [default = false]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 2; + + // Indicate that fields annotated with this enum value should not be printed + // out when using debug formats, e.g. when the field contains sensitive + // credentials. + optional bool debug_redact = 3 [default = false]; + + // Information about the support window of a feature value. + optional FieldOptions.FeatureSupport feature_support = 4; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message ServiceOptions { + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 34; + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this service deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the service, or it will be completely ignored; in the very least, + // this is a formalization for deprecating services. + optional bool deprecated = 33 [default = false]; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +message MethodOptions { + + // Note: Field numbers 1 through 32 are reserved for Google's internal RPC + // framework. We apologize for hoarding these numbers to ourselves, but + // we were already using them long before we decided to release Protocol + // Buffers. + + // Is this method deprecated? + // Depending on the target platform, this can emit Deprecated annotations + // for the method, or it will be completely ignored; in the very least, + // this is a formalization for deprecating methods. + optional bool deprecated = 33 [default = false]; + + // Is this method side-effect-free (or safe in HTTP parlance), or idempotent, + // or neither? HTTP based RPC implementation may choose GET verb for safe + // methods, and PUT verb for idempotent methods instead of the default POST. + enum IdempotencyLevel { + IDEMPOTENCY_UNKNOWN = 0; + NO_SIDE_EFFECTS = 1; // implies idempotent + IDEMPOTENT = 2; // idempotent, but may have side effects + } + optional IdempotencyLevel idempotency_level = 34 + [default = IDEMPOTENCY_UNKNOWN]; + + // Any features defined in the specific edition. + // WARNING: This field should only be used by protobuf plugins or special + // cases like the proto compiler. Other uses are discouraged and + // developers should rely on the protoreflect APIs for their client language. + optional FeatureSet features = 35; + + // The parser stores options it doesn't recognize here. See above. + repeated UninterpretedOption uninterpreted_option = 999; + + // Clients can define custom options in extensions of this message. See above. + extensions 1000 to max; +} + +// A message representing a option the parser does not recognize. This only +// appears in options protos created by the compiler::Parser class. +// DescriptorPool resolves these when building Descriptor objects. Therefore, +// options protos in descriptor objects (e.g. returned by Descriptor::options(), +// or produced by Descriptor::CopyTo()) will never have UninterpretedOptions +// in them. +message UninterpretedOption { + // The name of the uninterpreted option. Each string represents a segment in + // a dot-separated name. is_extension is true iff a segment represents an + // extension (denoted with parentheses in options specs in .proto files). + // E.g.,{ ["foo", false], ["bar.baz", true], ["moo", false] } represents + // "foo.(bar.baz).moo". + message NamePart { + required string name_part = 1; + required bool is_extension = 2; + } + repeated NamePart name = 2; + + // The value of the uninterpreted option, in whatever type the tokenizer + // identified it as during parsing. Exactly one of these should be set. + optional string identifier_value = 3; + optional uint64 positive_int_value = 4; + optional int64 negative_int_value = 5; + optional double double_value = 6; + optional bytes string_value = 7; + optional string aggregate_value = 8; +} + +// =================================================================== +// Features + +// TODO Enums in C++ gencode (and potentially other languages) are +// not well scoped. This means that each of the feature enums below can clash +// with each other. The short names we've chosen maximize call-site +// readability, but leave us very open to this scenario. A future feature will +// be designed and implemented to handle this, hopefully before we ever hit a +// conflict here. +message FeatureSet { + enum FieldPresence { + FIELD_PRESENCE_UNKNOWN = 0; + EXPLICIT = 1; + IMPLICIT = 2; + LEGACY_REQUIRED = 3; + } + optional FieldPresence field_presence = 1 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPLICIT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "IMPLICIT" }, + edition_defaults = { edition: EDITION_2023, value: "EXPLICIT" } + ]; + + enum EnumType { + ENUM_TYPE_UNKNOWN = 0; + OPEN = 1; + CLOSED = 2; + } + optional EnumType enum_type = 2 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "CLOSED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "OPEN" } + ]; + + enum RepeatedFieldEncoding { + REPEATED_FIELD_ENCODING_UNKNOWN = 0; + PACKED = 1; + EXPANDED = 2; + } + optional RepeatedFieldEncoding repeated_field_encoding = 3 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "EXPANDED" }, + edition_defaults = { edition: EDITION_PROTO3, value: "PACKED" } + ]; + + enum Utf8Validation { + UTF8_VALIDATION_UNKNOWN = 0; + VERIFY = 2; + NONE = 3; + reserved 1; + } + optional Utf8Validation utf8_validation = 4 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "NONE" }, + edition_defaults = { edition: EDITION_PROTO3, value: "VERIFY" } + ]; + + enum MessageEncoding { + MESSAGE_ENCODING_UNKNOWN = 0; + LENGTH_PREFIXED = 1; + DELIMITED = 2; + } + optional MessageEncoding message_encoding = 5 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_FIELD, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LENGTH_PREFIXED" } + ]; + + enum JsonFormat { + JSON_FORMAT_UNKNOWN = 0; + ALLOW = 1; + LEGACY_BEST_EFFORT = 2; + } + optional JsonFormat json_format = 6 [ + retention = RETENTION_RUNTIME, + targets = TARGET_TYPE_MESSAGE, + targets = TARGET_TYPE_ENUM, + targets = TARGET_TYPE_FILE, + feature_support = { + edition_introduced: EDITION_2023, + }, + edition_defaults = { edition: EDITION_LEGACY, value: "LEGACY_BEST_EFFORT" }, + edition_defaults = { edition: EDITION_PROTO3, value: "ALLOW" } + ]; + + reserved 999; + + extensions 1000 to 9994 [ + declaration = { + number: 1000, + full_name: ".pb.cpp", + type: ".pb.CppFeatures" + }, + declaration = { + number: 1001, + full_name: ".pb.java", + type: ".pb.JavaFeatures" + }, + declaration = { number: 1002, full_name: ".pb.go", type: ".pb.GoFeatures" }, + declaration = { + number: 9990, + full_name: ".pb.proto1", + type: ".pb.Proto1Features" + } + ]; + + extensions 9995 to 9999; // For internal testing + extensions 10000; // for https://github.com/bufbuild/protobuf-es +} + +// A compiled specification for the defaults of a set of features. These +// messages are generated from FeatureSet extensions and can be used to seed +// feature resolution. The resolution with this object becomes a simple search +// for the closest matching edition, followed by proto merges. +message FeatureSetDefaults { + // A map from every known edition with a unique set of defaults to its + // defaults. Not all editions may be contained here. For a given edition, + // the defaults at the closest matching edition ordered at or before it should + // be used. This field must be in strict ascending order by edition. + message FeatureSetEditionDefault { + optional Edition edition = 3; + + // Defaults of features that can be overridden in this edition. + optional FeatureSet overridable_features = 4; + + // Defaults of features that can't be overridden in this edition. + optional FeatureSet fixed_features = 5; + + reserved 1, 2; + reserved "features"; + } + repeated FeatureSetEditionDefault defaults = 1; + + // The minimum supported edition (inclusive) when this was constructed. + // Editions before this will not have defaults. + optional Edition minimum_edition = 4; + + // The maximum known edition (inclusive) when this was constructed. Editions + // after this will not have reliable defaults. + optional Edition maximum_edition = 5; +} + +// =================================================================== +// Optional source code info + +// Encapsulates information about the original source file from which a +// FileDescriptorProto was generated. +message SourceCodeInfo { + // A Location identifies a piece of source code in a .proto file which + // corresponds to a particular definition. This information is intended + // to be useful to IDEs, code indexers, documentation generators, and similar + // tools. + // + // For example, say we have a file like: + // message Foo { + // optional string foo = 1; + // } + // Let's look at just the field definition: + // optional string foo = 1; + // ^ ^^ ^^ ^ ^^^ + // a bc de f ghi + // We have the following locations: + // span path represents + // [a,i) [ 4, 0, 2, 0 ] The whole field definition. + // [a,b) [ 4, 0, 2, 0, 4 ] The label (optional). + // [c,d) [ 4, 0, 2, 0, 5 ] The type (string). + // [e,f) [ 4, 0, 2, 0, 1 ] The name (foo). + // [g,h) [ 4, 0, 2, 0, 3 ] The number (1). + // + // Notes: + // - A location may refer to a repeated field itself (i.e. not to any + // particular index within it). This is used whenever a set of elements are + // logically enclosed in a single code segment. For example, an entire + // extend block (possibly containing multiple extension definitions) will + // have an outer location whose path refers to the "extensions" repeated + // field without an index. + // - Multiple locations may have the same path. This happens when a single + // logical declaration is spread out across multiple places. The most + // obvious example is the "extend" block again -- there may be multiple + // extend blocks in the same scope, each of which will have the same path. + // - A location's span is not always a subset of its parent's span. For + // example, the "extendee" of an extension declaration appears at the + // beginning of the "extend" block and is shared by all extensions within + // the block. + // - Just because a location's span is a subset of some other location's span + // does not mean that it is a descendant. For example, a "group" defines + // both a type and a field in a single declaration. Thus, the locations + // corresponding to the type and field and their components will overlap. + // - Code which tries to interpret locations should probably be designed to + // ignore those that it doesn't understand, as more types of locations could + // be recorded in the future. + repeated Location location = 1; + message Location { + // Identifies which part of the FileDescriptorProto was defined at this + // location. + // + // Each element is a field number or an index. They form a path from + // the root FileDescriptorProto to the place where the definition appears. + // For example, this path: + // [ 4, 3, 2, 7, 1 ] + // refers to: + // file.message_type(3) // 4, 3 + // .field(7) // 2, 7 + // .name() // 1 + // This is because FileDescriptorProto.message_type has field number 4: + // repeated DescriptorProto message_type = 4; + // and DescriptorProto.field has field number 2: + // repeated FieldDescriptorProto field = 2; + // and FieldDescriptorProto.name has field number 1: + // optional string name = 1; + // + // Thus, the above path gives the location of a field name. If we removed + // the last element: + // [ 4, 3, 2, 7 ] + // this path refers to the whole field declaration (from the beginning + // of the label to the terminating semicolon). + repeated int32 path = 1 [packed = true]; + + // Always has exactly three or four elements: start line, start column, + // end line (optional, otherwise assumed same as start line), end column. + // These are packed into a single field for efficiency. Note that line + // and column numbers are zero-based -- typically you will want to add + // 1 to each before displaying to a user. + repeated int32 span = 2 [packed = true]; + + // If this SourceCodeInfo represents a complete declaration, these are any + // comments appearing before and after the declaration which appear to be + // attached to the declaration. + // + // A series of line comments appearing on consecutive lines, with no other + // tokens appearing on those lines, will be treated as a single comment. + // + // leading_detached_comments will keep paragraphs of comments that appear + // before (but not connected to) the current element. Each paragraph, + // separated by empty lines, will be one comment element in the repeated + // field. + // + // Only the comment content is provided; comment markers (e.g. //) are + // stripped out. For block comments, leading whitespace and an asterisk + // will be stripped from the beginning of each line other than the first. + // Newlines are included in the output. + // + // Examples: + // + // optional int32 foo = 1; // Comment attached to foo. + // // Comment attached to bar. + // optional int32 bar = 2; + // + // optional string baz = 3; + // // Comment attached to baz. + // // Another line attached to baz. + // + // // Comment attached to moo. + // // + // // Another line attached to moo. + // optional double moo = 4; + // + // // Detached comment for corge. This is not leading or trailing comments + // // to moo or corge because there are blank lines separating it from + // // both. + // + // // Detached comment for corge paragraph 2. + // + // optional string corge = 5; + // /* Block comment attached + // * to corge. Leading asterisks + // * will be removed. */ + // /* Block comment attached to + // * grault. */ + // optional int32 grault = 6; + // + // // ignored detached comments. + optional string leading_comments = 3; + optional string trailing_comments = 4; + repeated string leading_detached_comments = 6; + } + + // Extensions for tooling. + extensions 536000000 [declaration = { + number: 536000000 + type: ".buf.descriptor.v1.SourceCodeInfoExtension" + full_name: ".buf.descriptor.v1.buf_source_code_info_extension" + }]; +} + +// Describes the relationship between generated code and its original source +// file. A GeneratedCodeInfo message is associated with only one generated +// source file, but may contain references to different source .proto files. +message GeneratedCodeInfo { + // An Annotation connects some span of text in generated code to an element + // of its generating .proto file. + repeated Annotation annotation = 1; + message Annotation { + // Identifies the element in the original source .proto file. This field + // is formatted the same as SourceCodeInfo.Location.path. + repeated int32 path = 1 [packed = true]; + + // Identifies the filesystem path to the original source .proto. + optional string source_file = 2; + + // Identifies the starting offset in bytes in the generated code + // that relates to the identified object. + optional int32 begin = 3; + + // Identifies the ending offset in bytes in the generated code that + // relates to the identified object. The end offset should be one past + // the last relevant byte (so the length of the text = end - begin). + optional int32 end = 4; + + // Represents the identified object's effect on the element in the original + // .proto file. + enum Semantic { + // There is no effect or the effect is indescribable. + NONE = 0; + // The element is set or otherwise mutated. + SET = 1; + // An alias to the element is returned. + ALIAS = 2; + } + optional Semantic semantic = 5; + } +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/duration.proto b/prutalgen/pkg/prutalgen/wellknowns/duration.proto new file mode 100644 index 0000000..41f40c2 --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/duration.proto @@ -0,0 +1,115 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/durationpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "DurationProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Duration represents a signed, fixed-length span of time represented +// as a count of seconds and fractions of seconds at nanosecond +// resolution. It is independent of any calendar and concepts like "day" +// or "month". It is related to Timestamp in that the difference between +// two Timestamp values is a Duration and it can be added or subtracted +// from a Timestamp. Range is approximately +-10,000 years. +// +// # Examples +// +// Example 1: Compute Duration from two Timestamps in pseudo code. +// +// Timestamp start = ...; +// Timestamp end = ...; +// Duration duration = ...; +// +// duration.seconds = end.seconds - start.seconds; +// duration.nanos = end.nanos - start.nanos; +// +// if (duration.seconds < 0 && duration.nanos > 0) { +// duration.seconds += 1; +// duration.nanos -= 1000000000; +// } else if (duration.seconds > 0 && duration.nanos < 0) { +// duration.seconds -= 1; +// duration.nanos += 1000000000; +// } +// +// Example 2: Compute Timestamp from Timestamp + Duration in pseudo code. +// +// Timestamp start = ...; +// Duration duration = ...; +// Timestamp end = ...; +// +// end.seconds = start.seconds + duration.seconds; +// end.nanos = start.nanos + duration.nanos; +// +// if (end.nanos < 0) { +// end.seconds -= 1; +// end.nanos += 1000000000; +// } else if (end.nanos >= 1000000000) { +// end.seconds += 1; +// end.nanos -= 1000000000; +// } +// +// Example 3: Compute Duration from datetime.timedelta in Python. +// +// td = datetime.timedelta(days=3, minutes=10) +// duration = Duration() +// duration.FromTimedelta(td) +// +// # JSON Mapping +// +// In JSON format, the Duration type is encoded as a string rather than an +// object, where the string ends in the suffix "s" (indicating seconds) and +// is preceded by the number of seconds, with nanoseconds expressed as +// fractional seconds. For example, 3 seconds with 0 nanoseconds should be +// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should +// be expressed in JSON format as "3.000000001s", and 3 seconds and 1 +// microsecond should be expressed in JSON format as "3.000001s". +// +message Duration { + // Signed seconds of the span of time. Must be from -315,576,000,000 + // to +315,576,000,000 inclusive. Note: these bounds are computed from: + // 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years + int64 seconds = 1; + + // Signed fractions of a second at nanosecond resolution of the span + // of time. Durations less than one second are represented with a 0 + // `seconds` field and a positive or negative `nanos` field. For durations + // of one second or more, a non-zero value for the `nanos` field must be + // of the same sign as the `seconds` field. Must be from -999,999,999 + // to +999,999,999 inclusive. + int32 nanos = 2; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/empty.proto b/prutalgen/pkg/prutalgen/wellknowns/empty.proto new file mode 100644 index 0000000..b87c89d --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/empty.proto @@ -0,0 +1,51 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option go_package = "google.golang.org/protobuf/types/known/emptypb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "EmptyProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option cc_enable_arenas = true; + +// A generic empty message that you can re-use to avoid defining duplicated +// empty messages in your APIs. A typical example is to use it as the request +// or the response type of an API method. For instance: +// +// service Foo { +// rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); +// } +// +message Empty {} diff --git a/prutalgen/pkg/prutalgen/wellknowns/field_mask.proto b/prutalgen/pkg/prutalgen/wellknowns/field_mask.proto new file mode 100644 index 0000000..b28334b --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/field_mask.proto @@ -0,0 +1,245 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "FieldMaskProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/fieldmaskpb"; +option cc_enable_arenas = true; + +// `FieldMask` represents a set of symbolic field paths, for example: +// +// paths: "f.a" +// paths: "f.b.d" +// +// Here `f` represents a field in some root message, `a` and `b` +// fields in the message found in `f`, and `d` a field found in the +// message in `f.b`. +// +// Field masks are used to specify a subset of fields that should be +// returned by a get operation or modified by an update operation. +// Field masks also have a custom JSON encoding (see below). +// +// # Field Masks in Projections +// +// When used in the context of a projection, a response message or +// sub-message is filtered by the API to only contain those fields as +// specified in the mask. For example, if the mask in the previous +// example is applied to a response message as follows: +// +// f { +// a : 22 +// b { +// d : 1 +// x : 2 +// } +// y : 13 +// } +// z: 8 +// +// The result will not contain specific values for fields x,y and z +// (their value will be set to the default, and omitted in proto text +// output): +// +// +// f { +// a : 22 +// b { +// d : 1 +// } +// } +// +// A repeated field is not allowed except at the last position of a +// paths string. +// +// If a FieldMask object is not present in a get operation, the +// operation applies to all fields (as if a FieldMask of all fields +// had been specified). +// +// Note that a field mask does not necessarily apply to the +// top-level response message. In case of a REST get operation, the +// field mask applies directly to the response, but in case of a REST +// list operation, the mask instead applies to each individual message +// in the returned resource list. In case of a REST custom method, +// other definitions may be used. Where the mask applies will be +// clearly documented together with its declaration in the API. In +// any case, the effect on the returned resource/resources is required +// behavior for APIs. +// +// # Field Masks in Update Operations +// +// A field mask in update operations specifies which fields of the +// targeted resource are going to be updated. The API is required +// to only change the values of the fields as specified in the mask +// and leave the others untouched. If a resource is passed in to +// describe the updated values, the API ignores the values of all +// fields not covered by the mask. +// +// If a repeated field is specified for an update operation, new values will +// be appended to the existing repeated field in the target resource. Note that +// a repeated field is only allowed in the last position of a `paths` string. +// +// If a sub-message is specified in the last position of the field mask for an +// update operation, then new value will be merged into the existing sub-message +// in the target resource. +// +// For example, given the target message: +// +// f { +// b { +// d: 1 +// x: 2 +// } +// c: [1] +// } +// +// And an update message: +// +// f { +// b { +// d: 10 +// } +// c: [2] +// } +// +// then if the field mask is: +// +// paths: ["f.b", "f.c"] +// +// then the result will be: +// +// f { +// b { +// d: 10 +// x: 2 +// } +// c: [1, 2] +// } +// +// An implementation may provide options to override this default behavior for +// repeated and message fields. +// +// In order to reset a field's value to the default, the field must +// be in the mask and set to the default value in the provided resource. +// Hence, in order to reset all fields of a resource, provide a default +// instance of the resource and set all fields in the mask, or do +// not provide a mask as described below. +// +// If a field mask is not present on update, the operation applies to +// all fields (as if a field mask of all fields has been specified). +// Note that in the presence of schema evolution, this may mean that +// fields the client does not know and has therefore not filled into +// the request will be reset to their default. If this is unwanted +// behavior, a specific service may require a client to always specify +// a field mask, producing an error if not. +// +// As with get operations, the location of the resource which +// describes the updated values in the request message depends on the +// operation kind. In any case, the effect of the field mask is +// required to be honored by the API. +// +// ## Considerations for HTTP REST +// +// The HTTP kind of an update operation which uses a field mask must +// be set to PATCH instead of PUT in order to satisfy HTTP semantics +// (PUT must only be used for full updates). +// +// # JSON Encoding of Field Masks +// +// In JSON, a field mask is encoded as a single string where paths are +// separated by a comma. Fields name in each path are converted +// to/from lower-camel naming conventions. +// +// As an example, consider the following message declarations: +// +// message Profile { +// User user = 1; +// Photo photo = 2; +// } +// message User { +// string display_name = 1; +// string address = 2; +// } +// +// In proto a field mask for `Profile` may look as such: +// +// mask { +// paths: "user.display_name" +// paths: "photo" +// } +// +// In JSON, the same mask is represented as below: +// +// { +// mask: "user.displayName,photo" +// } +// +// # Field Masks and Oneof Fields +// +// Field masks treat fields in oneofs just as regular fields. Consider the +// following message: +// +// message SampleMessage { +// oneof test_oneof { +// string name = 4; +// SubMessage sub_message = 9; +// } +// } +// +// The field mask can be: +// +// mask { +// paths: "name" +// } +// +// Or: +// +// mask { +// paths: "sub_message" +// } +// +// Note that oneof type names ("test_oneof" in this case) cannot be used in +// paths. +// +// ## Field Mask Verification +// +// The implementation of any API method which has a FieldMask type field in the +// request should verify the included field paths, and return an +// `INVALID_ARGUMENT` error if any path is unmappable. +message FieldMask { + // The set of field mask paths. + repeated string paths = 1; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/source_context.proto b/prutalgen/pkg/prutalgen/wellknowns/source_context.proto new file mode 100644 index 0000000..135f50f --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/source_context.proto @@ -0,0 +1,48 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option java_package = "com.google.protobuf"; +option java_outer_classname = "SourceContextProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/sourcecontextpb"; + +// `SourceContext` represents information about the source of a +// protobuf element, like the file in which it is defined. +message SourceContext { + // The path-qualified name of the .proto file that contained the associated + // protobuf element. For example: `"google/protobuf/source_context.proto"`. + string file_name = 1; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/struct.proto b/prutalgen/pkg/prutalgen/wellknowns/struct.proto new file mode 100644 index 0000000..1bf0c1a --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/struct.proto @@ -0,0 +1,95 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/structpb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "StructProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// `Struct` represents a structured data value, consisting of fields +// which map to dynamically typed values. In some languages, `Struct` +// might be supported by a native representation. For example, in +// scripting languages like JS a struct is represented as an +// object. The details of that representation are described together +// with the proto support for the language. +// +// The JSON representation for `Struct` is JSON object. +message Struct { + // Unordered map of dynamically typed values. + map fields = 1; +} + +// `Value` represents a dynamically typed value which can be either +// null, a number, a string, a boolean, a recursive struct value, or a +// list of values. A producer of value is expected to set one of these +// variants. Absence of any variant indicates an error. +// +// The JSON representation for `Value` is JSON value. +message Value { + // The kind of value. + oneof kind { + // Represents a null value. + NullValue null_value = 1; + // Represents a double value. + double number_value = 2; + // Represents a string value. + string string_value = 3; + // Represents a boolean value. + bool bool_value = 4; + // Represents a structured value. + Struct struct_value = 5; + // Represents a repeated `Value`. + ListValue list_value = 6; + } +} + +// `NullValue` is a singleton enumeration to represent the null value for the +// `Value` type union. +// +// The JSON representation for `NullValue` is JSON `null`. +enum NullValue { + // Null value. + NULL_VALUE = 0; +} + +// `ListValue` is a wrapper around a repeated field of values. +// +// The JSON representation for `ListValue` is JSON array. +message ListValue { + // Repeated field of dynamically typed values. + repeated Value values = 1; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/timestamp.proto b/prutalgen/pkg/prutalgen/wellknowns/timestamp.proto new file mode 100644 index 0000000..fd0bc07 --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/timestamp.proto @@ -0,0 +1,144 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/timestamppb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TimestampProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// A Timestamp represents a point in time independent of any time zone or local +// calendar, encoded as a count of seconds and fractions of seconds at +// nanosecond resolution. The count is relative to an epoch at UTC midnight on +// January 1, 1970, in the proleptic Gregorian calendar which extends the +// Gregorian calendar backwards to year one. +// +// All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap +// second table is needed for interpretation, using a [24-hour linear +// smear](https://developers.google.com/time/smear). +// +// The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By +// restricting to that range, we ensure that we can convert to and from [RFC +// 3339](https://www.ietf.org/rfc/rfc3339.txt) date strings. +// +// # Examples +// +// Example 1: Compute Timestamp from POSIX `time()`. +// +// Timestamp timestamp; +// timestamp.set_seconds(time(NULL)); +// timestamp.set_nanos(0); +// +// Example 2: Compute Timestamp from POSIX `gettimeofday()`. +// +// struct timeval tv; +// gettimeofday(&tv, NULL); +// +// Timestamp timestamp; +// timestamp.set_seconds(tv.tv_sec); +// timestamp.set_nanos(tv.tv_usec * 1000); +// +// Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`. +// +// FILETIME ft; +// GetSystemTimeAsFileTime(&ft); +// UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime; +// +// // A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z +// // is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z. +// Timestamp timestamp; +// timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL)); +// timestamp.set_nanos((INT32) ((ticks % 10000000) * 100)); +// +// Example 4: Compute Timestamp from Java `System.currentTimeMillis()`. +// +// long millis = System.currentTimeMillis(); +// +// Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000) +// .setNanos((int) ((millis % 1000) * 1000000)).build(); +// +// Example 5: Compute Timestamp from Java `Instant.now()`. +// +// Instant now = Instant.now(); +// +// Timestamp timestamp = +// Timestamp.newBuilder().setSeconds(now.getEpochSecond()) +// .setNanos(now.getNano()).build(); +// +// Example 6: Compute Timestamp from current time in Python. +// +// timestamp = Timestamp() +// timestamp.GetCurrentTime() +// +// # JSON Mapping +// +// In JSON format, the Timestamp type is encoded as a string in the +// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the +// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z" +// where {year} is always expressed using four digits while {month}, {day}, +// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional +// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution), +// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone +// is required. A proto3 JSON serializer should always use UTC (as indicated by +// "Z") when printing the Timestamp type and a proto3 JSON parser should be +// able to accept both UTC and other timezones (as indicated by an offset). +// +// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past +// 01:30 UTC on January 15, 2017. +// +// In JavaScript, one can convert a Date object to this format using the +// standard +// [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) +// method. In Python, a standard `datetime.datetime` object can be converted +// to this format using +// [`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with +// the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use +// the Joda Time's [`ISODateTimeFormat.dateTime()`]( +// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime() +// ) to obtain a formatter capable of generating timestamps in this format. +// +message Timestamp { + // Represents seconds of UTC time since Unix epoch + // 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to + // 9999-12-31T23:59:59Z inclusive. + int64 seconds = 1; + + // Non-negative fractions of a second at nanosecond resolution. Negative + // second values with fractions must still have non-negative nanos values + // that count forward in time. Must be from 0 to 999,999,999 + // inclusive. + int32 nanos = 2; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/type.proto b/prutalgen/pkg/prutalgen/wellknowns/type.proto new file mode 100644 index 0000000..48cb11e --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/type.proto @@ -0,0 +1,193 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +syntax = "proto3"; + +package google.protobuf; + +import "google/protobuf/any.proto"; +import "google/protobuf/source_context.proto"; + +option cc_enable_arenas = true; +option java_package = "com.google.protobuf"; +option java_outer_classname = "TypeProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; +option go_package = "google.golang.org/protobuf/types/known/typepb"; + +// A protocol buffer message type. +message Type { + // The fully qualified message name. + string name = 1; + // The list of fields. + repeated Field fields = 2; + // The list of types appearing in `oneof` definitions in this type. + repeated string oneofs = 3; + // The protocol buffer options. + repeated Option options = 4; + // The source context. + SourceContext source_context = 5; + // The source syntax. + Syntax syntax = 6; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 7; +} + +// A single field of a message type. +message Field { + // Basic field types. + enum Kind { + // Field type unknown. + TYPE_UNKNOWN = 0; + // Field type double. + TYPE_DOUBLE = 1; + // Field type float. + TYPE_FLOAT = 2; + // Field type int64. + TYPE_INT64 = 3; + // Field type uint64. + TYPE_UINT64 = 4; + // Field type int32. + TYPE_INT32 = 5; + // Field type fixed64. + TYPE_FIXED64 = 6; + // Field type fixed32. + TYPE_FIXED32 = 7; + // Field type bool. + TYPE_BOOL = 8; + // Field type string. + TYPE_STRING = 9; + // Field type group. Proto2 syntax only, and deprecated. + TYPE_GROUP = 10; + // Field type message. + TYPE_MESSAGE = 11; + // Field type bytes. + TYPE_BYTES = 12; + // Field type uint32. + TYPE_UINT32 = 13; + // Field type enum. + TYPE_ENUM = 14; + // Field type sfixed32. + TYPE_SFIXED32 = 15; + // Field type sfixed64. + TYPE_SFIXED64 = 16; + // Field type sint32. + TYPE_SINT32 = 17; + // Field type sint64. + TYPE_SINT64 = 18; + } + + // Whether a field is optional, required, or repeated. + enum Cardinality { + // For fields with unknown cardinality. + CARDINALITY_UNKNOWN = 0; + // For optional fields. + CARDINALITY_OPTIONAL = 1; + // For required fields. Proto2 syntax only. + CARDINALITY_REQUIRED = 2; + // For repeated fields. + CARDINALITY_REPEATED = 3; + } + + // The field type. + Kind kind = 1; + // The field cardinality. + Cardinality cardinality = 2; + // The field number. + int32 number = 3; + // The field name. + string name = 4; + // The field type URL, without the scheme, for message or enumeration + // types. Example: `"type.googleapis.com/google.protobuf.Timestamp"`. + string type_url = 6; + // The index of the field type in `Type.oneofs`, for message or enumeration + // types. The first type has index 1; zero means the type is not in the list. + int32 oneof_index = 7; + // Whether to use alternative packed wire representation. + bool packed = 8; + // The protocol buffer options. + repeated Option options = 9; + // The field JSON name. + string json_name = 10; + // The string value of the default value of this field. Proto2 syntax only. + string default_value = 11; +} + +// Enum type definition. +message Enum { + // Enum type name. + string name = 1; + // Enum value definitions. + repeated EnumValue enumvalue = 2; + // Protocol buffer options. + repeated Option options = 3; + // The source context. + SourceContext source_context = 4; + // The source syntax. + Syntax syntax = 5; + // The source edition string, only valid when syntax is SYNTAX_EDITIONS. + string edition = 6; +} + +// Enum value definition. +message EnumValue { + // Enum value name. + string name = 1; + // Enum value number. + int32 number = 2; + // Protocol buffer options. + repeated Option options = 3; +} + +// A protocol buffer option, which can be attached to a message, field, +// enumeration, etc. +message Option { + // The option's name. For protobuf built-in options (options defined in + // descriptor.proto), this is the short name. For example, `"map_entry"`. + // For custom options, it should be the fully-qualified name. For example, + // `"google.api.http"`. + string name = 1; + // The option's value packed in an Any message. If the value is a primitive, + // the corresponding wrapper type defined in google/protobuf/wrappers.proto + // should be used. If the value is an enum, it should be stored as an int32 + // value using the google.protobuf.Int32Value type. + Any value = 2; +} + +// The syntax in which a protocol buffer element is defined. +enum Syntax { + // Syntax `proto2`. + SYNTAX_PROTO2 = 0; + // Syntax `proto3`. + SYNTAX_PROTO3 = 1; + // Syntax `editions`. + SYNTAX_EDITIONS = 2; +} diff --git a/prutalgen/pkg/prutalgen/wellknowns/update_proto.sh b/prutalgen/pkg/prutalgen/wellknowns/update_proto.sh new file mode 100755 index 0000000..22bc41e --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/update_proto.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + + +if [[ -z $PROTOBUF_REPO ]]; then + echo "PROTOBUF_REPO not set" + exit -1 +fi + + +protos=( +"any.proto" +"api.proto" +"descriptor.proto" +"duration.proto" +"empty.proto" +"field_mask.proto" +"source_context.proto" +"struct.proto" +"timestamp.proto" +"type.proto" +"wrappers.proto" +) + +for proto in "${protos[@]}"; +do + cp -v $PROTOBUF_REPO/src/google/protobuf/$proto ./ +done diff --git a/prutalgen/pkg/prutalgen/wellknowns/wrappers.proto b/prutalgen/pkg/prutalgen/wellknowns/wrappers.proto new file mode 100644 index 0000000..1959fa5 --- /dev/null +++ b/prutalgen/pkg/prutalgen/wellknowns/wrappers.proto @@ -0,0 +1,123 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Wrappers for primitive (non-message) types. These types are useful +// for embedding primitives in the `google.protobuf.Any` type and for places +// where we need to distinguish between the absence of a primitive +// typed field and its default value. +// +// These wrappers have no meaningful use within repeated fields as they lack +// the ability to detect presence on individual elements. +// These wrappers have no meaningful use within a map or a oneof since +// individual entries of a map or fields of a oneof can already detect presence. + +syntax = "proto3"; + +package google.protobuf; + +option cc_enable_arenas = true; +option go_package = "google.golang.org/protobuf/types/known/wrapperspb"; +option java_package = "com.google.protobuf"; +option java_outer_classname = "WrappersProto"; +option java_multiple_files = true; +option objc_class_prefix = "GPB"; +option csharp_namespace = "Google.Protobuf.WellKnownTypes"; + +// Wrapper message for `double`. +// +// The JSON representation for `DoubleValue` is JSON number. +message DoubleValue { + // The double value. + double value = 1; +} + +// Wrapper message for `float`. +// +// The JSON representation for `FloatValue` is JSON number. +message FloatValue { + // The float value. + float value = 1; +} + +// Wrapper message for `int64`. +// +// The JSON representation for `Int64Value` is JSON string. +message Int64Value { + // The int64 value. + int64 value = 1; +} + +// Wrapper message for `uint64`. +// +// The JSON representation for `UInt64Value` is JSON string. +message UInt64Value { + // The uint64 value. + uint64 value = 1; +} + +// Wrapper message for `int32`. +// +// The JSON representation for `Int32Value` is JSON number. +message Int32Value { + // The int32 value. + int32 value = 1; +} + +// Wrapper message for `uint32`. +// +// The JSON representation for `UInt32Value` is JSON number. +message UInt32Value { + // The uint32 value. + uint32 value = 1; +} + +// Wrapper message for `bool`. +// +// The JSON representation for `BoolValue` is JSON `true` and `false`. +message BoolValue { + // The bool value. + bool value = 1; +} + +// Wrapper message for `string`. +// +// The JSON representation for `StringValue` is JSON string. +message StringValue { + // The string value. + string value = 1; +} + +// Wrapper message for `bytes`. +// +// The JSON representation for `BytesValue` is JSON string. +message BytesValue { + // The bytes value. + bytes value = 1; +} diff --git a/prutalgen/pkg/utils/args/args.go b/prutalgen/pkg/utils/args/args.go new file mode 100644 index 0000000..94d2942 --- /dev/null +++ b/prutalgen/pkg/utils/args/args.go @@ -0,0 +1,33 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package args + +import "strings" + +// StringArgs impelements https://pkg.go.dev/flag#Value for []string +type StringArgs []string + +// String ... +func (a *StringArgs) String() string { + return strings.Join(*a, ",") +} + +// Set ... +func (a *StringArgs) Set(v string) error { + *a = append(*a, v) + return nil +} diff --git a/prutalgen/pkg/utils/args/go_opts.go b/prutalgen/pkg/utils/args/go_opts.go new file mode 100644 index 0000000..4b16270 --- /dev/null +++ b/prutalgen/pkg/utils/args/go_opts.go @@ -0,0 +1,59 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package args + +import ( + "path/filepath" + "strings" + + "github.com/cloudwego/prutal/prutalgen/pkg/prutalgen" +) + +// GoOpts represents args of go_opt for protobuf go compatibility purposes +type GoOpts struct { + StringArgs +} + +// GenPathType ... prutalgen.GenByImport or prutalgen.GenBySourceRelative +func (o *GoOpts) GenPathType() prutalgen.GenPathType { + ret := prutalgen.GenByImport // default value + for _, s := range o.StringArgs { + if s == "paths=import" { + ret = prutalgen.GenByImport + } else if s == "paths=source_relative" { + ret = prutalgen.GenBySourceRelative + } + } + return ret +} + +// Proto2pkg ... for the M opt +// see: https://protobuf.dev/reference/go/go-generated/#package +func (o *GoOpts) Proto2pkg() map[string]string { + ret := make(map[string]string) + for _, s := range o.StringArgs { + if s == "" || s[0] != 'M' { + continue + } + s = s[1:] + a, b, ok := strings.Cut(s, "=") + if ok { + ret[filepath.Clean(a)] = b + } + } + return ret +} diff --git a/prutalgen/pkg/utils/args/go_opts_test.go b/prutalgen/pkg/utils/args/go_opts_test.go new file mode 100644 index 0000000..02f52bf --- /dev/null +++ b/prutalgen/pkg/utils/args/go_opts_test.go @@ -0,0 +1,44 @@ +/* + * Copyright 2025 CloudWeGo Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package args + +import ( + "flag" + "testing" + + "github.com/cloudwego/prutal/internal/testutils/assert" + "github.com/cloudwego/prutal/prutalgen/pkg/prutalgen" +) + +func TestGoOpts(t *testing.T) { + o := GoOpts{} + f := flag.NewFlagSet(t.Name(), flag.PanicOnError) + f.Var(&o, "go_opt", "") + inputs := []string{ + "--go_opt=paths=import", + "--go_opt=paths=source_relative", + "--go_opt=Mprotos/buzz.proto=example.com/project/protos/fizz", + "--go_opt=Mprotos/bar.proto=example.com/project/protos/foo", + } + assert.NoError(t, f.Parse(inputs)) + assert.Equal(t, prutalgen.GenBySourceRelative, o.GenPathType()) + m := o.Proto2pkg() + assert.MapEqual(t, map[string]string{ + "protos/buzz.proto": "example.com/project/protos/fizz", + "protos/bar.proto": "example.com/project/protos/foo", + }, m) +} diff --git a/prutalgen/proto/prutal.proto b/prutalgen/proto/prutal.proto new file mode 100644 index 0000000..c4d6df7 --- /dev/null +++ b/prutalgen/proto/prutal.proto @@ -0,0 +1,23 @@ +// This file is only used when user need to generate code by using `protoc` +// prutalgen will ignore imports which named prutal.proto + +syntax = "proto2"; + +package prutal + +import "google/protobuf/descriptor.proto"; + +option go_package = "github.com/cloudwego/prutal/prutalgen"; + + +extend google.protobuf.FileOptions { +} + +extend google.protobuf.EnumOptions { +} + +extend google.protobuf.EnumValueOptions { +} + +extend google.protobuf.MessageOptions { +} diff --git a/tests/cases/edition2023/Makefile b/tests/cases/edition2023/Makefile new file mode 100644 index 0000000..d717f2f --- /dev/null +++ b/tests/cases/edition2023/Makefile @@ -0,0 +1,8 @@ +test_messages_edition2023.pb.go: test_messages_edition2023.proto + prutalgen --proto_path=. --go_out=. \ + --go_opt=paths=source_relative --go_opt=Mtest_messages_edition2023.proto=./edition2023 \ + --gen_getter=true \ + ./test_messages_edition2023.proto + +test: test_messages_edition2023.pb.go + go build diff --git a/tests/cases/edition2023/test_messages_edition2023.pb.go b/tests/cases/edition2023/test_messages_edition2023.pb.go new file mode 100644 index 0000000..25a02ba --- /dev/null +++ b/tests/cases/edition2023/test_messages_edition2023.pb.go @@ -0,0 +1,1053 @@ +// Code generated by prutalgen. DO NOT EDIT. +// prutalgen --proto_path=. --go_out=. --go_opt=paths=source_relative --go_opt=Mtest_messages_edition2023.proto=./edition2023 --gen_getter=true ./test_messages_edition2023.proto + +package edition2023 + +import "strconv" + +type ForeignEnumEdition2023 int32 + +const ( + ForeignEnumEdition2023_FOREIGN_FOO ForeignEnumEdition2023 = 0 + ForeignEnumEdition2023_FOREIGN_BAR ForeignEnumEdition2023 = 1 + ForeignEnumEdition2023_FOREIGN_BAZ ForeignEnumEdition2023 = 2 +) + +// Enum value maps for ForeignEnumEdition2023. +var ForeignEnumEdition2023_name = map[int32]string{ + 0: "FOREIGN_FOO", + 1: "FOREIGN_BAR", + 2: "FOREIGN_BAZ", +} + +var ForeignEnumEdition2023_value = map[string]int32{ + "FOREIGN_FOO": 0, + "FOREIGN_BAR": 1, + "FOREIGN_BAZ": 2, +} + +func (x ForeignEnumEdition2023) String() string { + s, ok := ForeignEnumEdition2023_name[int32(x)] + if ok { + return s + } + return strconv.Itoa(int(x)) +} + +type TestAllTypesEdition2023_NestedEnum int32 + +const ( + TestAllTypesEdition2023_FOO TestAllTypesEdition2023_NestedEnum = 0 + TestAllTypesEdition2023_BAR TestAllTypesEdition2023_NestedEnum = 1 + TestAllTypesEdition2023_BAZ TestAllTypesEdition2023_NestedEnum = 2 + TestAllTypesEdition2023_NEG TestAllTypesEdition2023_NestedEnum = -1 // Intentionally negative. +) + +// Enum value maps for TestAllTypesEdition2023_NestedEnum. +var TestAllTypesEdition2023_NestedEnum_name = map[int32]string{ + 0: "FOO", + 1: "BAR", + 2: "BAZ", + -1: "NEG", +} + +var TestAllTypesEdition2023_NestedEnum_value = map[string]int32{ + "FOO": 0, + "BAR": 1, + "BAZ": 2, + "NEG": -1, +} + +func (x TestAllTypesEdition2023_NestedEnum) String() string { + s, ok := TestAllTypesEdition2023_NestedEnum_name[int32(x)] + if ok { + return s + } + return strconv.Itoa(int(x)) +} + +type ComplexMessage struct { + D *int32 `protobuf:"varint,1,opt,name=d" json:"d,omitempty"` +} + +func (x *ComplexMessage) Reset() { *x = ComplexMessage{} } + +func (x *ComplexMessage) GetD() int32 { + if x != nil && x.D != nil { + return *x.D + } + return 0 +} + +type TestAllTypesEdition2023 struct { + // Singular + OptionalInt32 *int32 `protobuf:"varint,1,opt,name=optional_int32" json:"optional_int32,omitempty"` + OptionalInt64 *int64 `protobuf:"varint,2,opt,name=optional_int64" json:"optional_int64,omitempty"` + OptionalUint32 *uint32 `protobuf:"varint,3,opt,name=optional_uint32" json:"optional_uint32,omitempty"` + OptionalUint64 *uint64 `protobuf:"varint,4,opt,name=optional_uint64" json:"optional_uint64,omitempty"` + OptionalSint32 *int32 `protobuf:"zigzag32,5,opt,name=optional_sint32" json:"optional_sint32,omitempty"` + OptionalSint64 *int64 `protobuf:"zigzag64,6,opt,name=optional_sint64" json:"optional_sint64,omitempty"` + OptionalFixed32 *uint32 `protobuf:"fixed32,7,opt,name=optional_fixed32" json:"optional_fixed32,omitempty"` + OptionalFixed64 *uint64 `protobuf:"fixed64,8,opt,name=optional_fixed64" json:"optional_fixed64,omitempty"` + OptionalSfixed32 *int32 `protobuf:"fixed32,9,opt,name=optional_sfixed32" json:"optional_sfixed32,omitempty"` + OptionalSfixed64 *int64 `protobuf:"fixed64,10,opt,name=optional_sfixed64" json:"optional_sfixed64,omitempty"` + OptionalFloat *float32 `protobuf:"fixed32,11,opt,name=optional_float" json:"optional_float,omitempty"` + OptionalDouble *float64 `protobuf:"fixed64,12,opt,name=optional_double" json:"optional_double,omitempty"` + OptionalBool *bool `protobuf:"varint,13,opt,name=optional_bool" json:"optional_bool,omitempty"` + OptionalString *string `protobuf:"bytes,14,opt,name=optional_string" json:"optional_string,omitempty"` + OptionalBytes []byte `protobuf:"bytes,15,opt,name=optional_bytes" json:"optional_bytes,omitempty"` + OptionalNestedMessage *TestAllTypesEdition2023_NestedMessage `protobuf:"bytes,18,opt,name=optional_nested_message" json:"optional_nested_message,omitempty"` + OptionalForeignMessage *ForeignMessageEdition2023 `protobuf:"bytes,19,opt,name=optional_foreign_message" json:"optional_foreign_message,omitempty"` + OptionalNestedEnum *TestAllTypesEdition2023_NestedEnum `protobuf:"varint,21,opt,name=optional_nested_enum" json:"optional_nested_enum,omitempty"` + OptionalForeignEnum *ForeignEnumEdition2023 `protobuf:"varint,22,opt,name=optional_foreign_enum" json:"optional_foreign_enum,omitempty"` + OptionalStringPiece *string `protobuf:"bytes,24,opt,name=optional_string_piece" json:"optional_string_piece,omitempty"` + OptionalCord *string `protobuf:"bytes,25,opt,name=optional_cord" json:"optional_cord,omitempty"` + RecursiveMessage *TestAllTypesEdition2023 `protobuf:"bytes,27,opt,name=recursive_message" json:"recursive_message,omitempty"` + + // Repeated + RepeatedInt32 []int32 `protobuf:"varint,31,rep,packed,name=repeated_int32" json:"repeated_int32,omitempty"` + RepeatedInt64 []int64 `protobuf:"varint,32,rep,packed,name=repeated_int64" json:"repeated_int64,omitempty"` + RepeatedUint32 []uint32 `protobuf:"varint,33,rep,packed,name=repeated_uint32" json:"repeated_uint32,omitempty"` + RepeatedUint64 []uint64 `protobuf:"varint,34,rep,packed,name=repeated_uint64" json:"repeated_uint64,omitempty"` + RepeatedSint32 []int32 `protobuf:"zigzag32,35,rep,packed,name=repeated_sint32" json:"repeated_sint32,omitempty"` + RepeatedSint64 []int64 `protobuf:"zigzag64,36,rep,packed,name=repeated_sint64" json:"repeated_sint64,omitempty"` + RepeatedFixed32 []uint32 `protobuf:"fixed32,37,rep,packed,name=repeated_fixed32" json:"repeated_fixed32,omitempty"` + RepeatedFixed64 []uint64 `protobuf:"fixed64,38,rep,packed,name=repeated_fixed64" json:"repeated_fixed64,omitempty"` + RepeatedSfixed32 []int32 `protobuf:"fixed32,39,rep,packed,name=repeated_sfixed32" json:"repeated_sfixed32,omitempty"` + RepeatedSfixed64 []int64 `protobuf:"fixed64,40,rep,packed,name=repeated_sfixed64" json:"repeated_sfixed64,omitempty"` + RepeatedFloat []float32 `protobuf:"fixed32,41,rep,packed,name=repeated_float" json:"repeated_float,omitempty"` + RepeatedDouble []float64 `protobuf:"fixed64,42,rep,packed,name=repeated_double" json:"repeated_double,omitempty"` + RepeatedBool []bool `protobuf:"varint,43,rep,packed,name=repeated_bool" json:"repeated_bool,omitempty"` + RepeatedString []string `protobuf:"bytes,44,rep,name=repeated_string" json:"repeated_string,omitempty"` + RepeatedBytes [][]byte `protobuf:"bytes,45,rep,name=repeated_bytes" json:"repeated_bytes,omitempty"` + RepeatedNestedMessage []*TestAllTypesEdition2023_NestedMessage `protobuf:"bytes,48,rep,name=repeated_nested_message" json:"repeated_nested_message,omitempty"` + RepeatedForeignMessage []*ForeignMessageEdition2023 `protobuf:"bytes,49,rep,name=repeated_foreign_message" json:"repeated_foreign_message,omitempty"` + RepeatedNestedEnum []TestAllTypesEdition2023_NestedEnum `protobuf:"varint,51,rep,packed,name=repeated_nested_enum" json:"repeated_nested_enum,omitempty"` + RepeatedForeignEnum []ForeignEnumEdition2023 `protobuf:"varint,52,rep,packed,name=repeated_foreign_enum" json:"repeated_foreign_enum,omitempty"` + RepeatedStringPiece []string `protobuf:"bytes,54,rep,name=repeated_string_piece" json:"repeated_string_piece,omitempty"` + RepeatedCord []string `protobuf:"bytes,55,rep,name=repeated_cord" json:"repeated_cord,omitempty"` + + // Packed + PackedInt32 []int32 `protobuf:"varint,75,rep,packed,name=packed_int32" json:"packed_int32,omitempty"` + PackedInt64 []int64 `protobuf:"varint,76,rep,packed,name=packed_int64" json:"packed_int64,omitempty"` + PackedUint32 []uint32 `protobuf:"varint,77,rep,packed,name=packed_uint32" json:"packed_uint32,omitempty"` + PackedUint64 []uint64 `protobuf:"varint,78,rep,packed,name=packed_uint64" json:"packed_uint64,omitempty"` + PackedSint32 []int32 `protobuf:"zigzag32,79,rep,packed,name=packed_sint32" json:"packed_sint32,omitempty"` + PackedSint64 []int64 `protobuf:"zigzag64,80,rep,packed,name=packed_sint64" json:"packed_sint64,omitempty"` + PackedFixed32 []uint32 `protobuf:"fixed32,81,rep,packed,name=packed_fixed32" json:"packed_fixed32,omitempty"` + PackedFixed64 []uint64 `protobuf:"fixed64,82,rep,packed,name=packed_fixed64" json:"packed_fixed64,omitempty"` + PackedSfixed32 []int32 `protobuf:"fixed32,83,rep,packed,name=packed_sfixed32" json:"packed_sfixed32,omitempty"` + PackedSfixed64 []int64 `protobuf:"fixed64,84,rep,packed,name=packed_sfixed64" json:"packed_sfixed64,omitempty"` + PackedFloat []float32 `protobuf:"fixed32,85,rep,packed,name=packed_float" json:"packed_float,omitempty"` + PackedDouble []float64 `protobuf:"fixed64,86,rep,packed,name=packed_double" json:"packed_double,omitempty"` + PackedBool []bool `protobuf:"varint,87,rep,packed,name=packed_bool" json:"packed_bool,omitempty"` + PackedNestedEnum []TestAllTypesEdition2023_NestedEnum `protobuf:"varint,88,rep,packed,name=packed_nested_enum" json:"packed_nested_enum,omitempty"` + + // Unpacked + UnpackedInt32 []int32 `protobuf:"varint,89,rep,name=unpacked_int32" json:"unpacked_int32,omitempty"` + UnpackedInt64 []int64 `protobuf:"varint,90,rep,name=unpacked_int64" json:"unpacked_int64,omitempty"` + UnpackedUint32 []uint32 `protobuf:"varint,91,rep,name=unpacked_uint32" json:"unpacked_uint32,omitempty"` + UnpackedUint64 []uint64 `protobuf:"varint,92,rep,name=unpacked_uint64" json:"unpacked_uint64,omitempty"` + UnpackedSint32 []int32 `protobuf:"zigzag32,93,rep,name=unpacked_sint32" json:"unpacked_sint32,omitempty"` + UnpackedSint64 []int64 `protobuf:"zigzag64,94,rep,name=unpacked_sint64" json:"unpacked_sint64,omitempty"` + UnpackedFixed32 []uint32 `protobuf:"fixed32,95,rep,name=unpacked_fixed32" json:"unpacked_fixed32,omitempty"` + UnpackedFixed64 []uint64 `protobuf:"fixed64,96,rep,name=unpacked_fixed64" json:"unpacked_fixed64,omitempty"` + UnpackedSfixed32 []int32 `protobuf:"fixed32,97,rep,name=unpacked_sfixed32" json:"unpacked_sfixed32,omitempty"` + UnpackedSfixed64 []int64 `protobuf:"fixed64,98,rep,name=unpacked_sfixed64" json:"unpacked_sfixed64,omitempty"` + UnpackedFloat []float32 `protobuf:"fixed32,99,rep,name=unpacked_float" json:"unpacked_float,omitempty"` + UnpackedDouble []float64 `protobuf:"fixed64,100,rep,name=unpacked_double" json:"unpacked_double,omitempty"` + UnpackedBool []bool `protobuf:"varint,101,rep,name=unpacked_bool" json:"unpacked_bool,omitempty"` + UnpackedNestedEnum []TestAllTypesEdition2023_NestedEnum `protobuf:"varint,102,rep,name=unpacked_nested_enum" json:"unpacked_nested_enum,omitempty"` + + // Map + MapInt32Int32 map[int32]int32 `protobuf:"bytes,56,rep,name=map_int32_int32" json:"map_int32_int32,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapInt64Int64 map[int64]int64 `protobuf:"bytes,57,rep,name=map_int64_int64" json:"map_int64_int64,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapUint32Uint32 map[uint32]uint32 `protobuf:"bytes,58,rep,name=map_uint32_uint32" json:"map_uint32_uint32,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapUint64Uint64 map[uint64]uint64 `protobuf:"bytes,59,rep,name=map_uint64_uint64" json:"map_uint64_uint64,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapSint32Sint32 map[int32]int32 `protobuf:"bytes,60,rep,name=map_sint32_sint32" json:"map_sint32_sint32,omitempty" protobuf_key:"zigzag32,1,opt,name=key" protobuf_val:"zigzag32,2,opt,name=value"` + MapSint64Sint64 map[int64]int64 `protobuf:"bytes,61,rep,name=map_sint64_sint64" json:"map_sint64_sint64,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"zigzag64,2,opt,name=value"` + MapFixed32Fixed32 map[uint32]uint32 `protobuf:"bytes,62,rep,name=map_fixed32_fixed32" json:"map_fixed32_fixed32,omitempty" protobuf_key:"fixed32,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapFixed64Fixed64 map[uint64]uint64 `protobuf:"bytes,63,rep,name=map_fixed64_fixed64" json:"map_fixed64_fixed64,omitempty" protobuf_key:"fixed64,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapSfixed32Sfixed32 map[int32]int32 `protobuf:"bytes,64,rep,name=map_sfixed32_sfixed32" json:"map_sfixed32_sfixed32,omitempty" protobuf_key:"fixed32,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapSfixed64Sfixed64 map[int64]int64 `protobuf:"bytes,65,rep,name=map_sfixed64_sfixed64" json:"map_sfixed64_sfixed64,omitempty" protobuf_key:"fixed64,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapInt32Float map[int32]float32 `protobuf:"bytes,66,rep,name=map_int32_float" json:"map_int32_float,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapInt32Double map[int32]float64 `protobuf:"bytes,67,rep,name=map_int32_double" json:"map_int32_double,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapBoolBool map[bool]bool `protobuf:"bytes,68,rep,name=map_bool_bool" json:"map_bool_bool,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapStringString map[string]string `protobuf:"bytes,69,rep,name=map_string_string" json:"map_string_string,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringBytes map[string][]byte `protobuf:"bytes,70,rep,name=map_string_bytes" json:"map_string_bytes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringNestedMessage map[string]*TestAllTypesEdition2023_NestedMessage `protobuf:"bytes,71,rep,name=map_string_nested_message" json:"map_string_nested_message,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringForeignMessage map[string]*ForeignMessageEdition2023 `protobuf:"bytes,72,rep,name=map_string_foreign_message" json:"map_string_foreign_message,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringNestedEnum map[string]TestAllTypesEdition2023_NestedEnum `protobuf:"bytes,73,rep,name=map_string_nested_enum" json:"map_string_nested_enum,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapStringForeignEnum map[string]ForeignEnumEdition2023 `protobuf:"bytes,74,rep,name=map_string_foreign_enum" json:"map_string_foreign_enum,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + // Types that are assignable to OneofField: + // + // *TestAllTypesEdition2023_OneofUint32 + // *TestAllTypesEdition2023_OneofNestedMessage + // *TestAllTypesEdition2023_OneofString + // *TestAllTypesEdition2023_OneofBytes + // *TestAllTypesEdition2023_OneofBool + // *TestAllTypesEdition2023_OneofUint64 + // *TestAllTypesEdition2023_OneofFloat + // *TestAllTypesEdition2023_OneofDouble + // *TestAllTypesEdition2023_OneofEnum + OneofField isTestAllTypesEdition2023_OneofField `protobuf_oneof:"oneof_field"` + Groupliketype *TestAllTypesEdition2023_GroupLikeType `protobuf:"bytes,201,opt,name=groupliketype" json:"groupliketype,omitempty"` + DelimitedField *TestAllTypesEdition2023_GroupLikeType `protobuf:"bytes,202,opt,name=delimited_field" json:"delimited_field,omitempty"` +} + +func (x *TestAllTypesEdition2023) Reset() { *x = TestAllTypesEdition2023{} } + +func (x *TestAllTypesEdition2023) GetOptionalInt32() int32 { + if x != nil && x.OptionalInt32 != nil { + return *x.OptionalInt32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalInt64() int64 { + if x != nil && x.OptionalInt64 != nil { + return *x.OptionalInt64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalUint32() uint32 { + if x != nil && x.OptionalUint32 != nil { + return *x.OptionalUint32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalUint64() uint64 { + if x != nil && x.OptionalUint64 != nil { + return *x.OptionalUint64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalSint32() int32 { + if x != nil && x.OptionalSint32 != nil { + return *x.OptionalSint32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalSint64() int64 { + if x != nil && x.OptionalSint64 != nil { + return *x.OptionalSint64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalFixed32() uint32 { + if x != nil && x.OptionalFixed32 != nil { + return *x.OptionalFixed32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalFixed64() uint64 { + if x != nil && x.OptionalFixed64 != nil { + return *x.OptionalFixed64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalSfixed32() int32 { + if x != nil && x.OptionalSfixed32 != nil { + return *x.OptionalSfixed32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalSfixed64() int64 { + if x != nil && x.OptionalSfixed64 != nil { + return *x.OptionalSfixed64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalFloat() float32 { + if x != nil && x.OptionalFloat != nil { + return *x.OptionalFloat + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalDouble() float64 { + if x != nil && x.OptionalDouble != nil { + return *x.OptionalDouble + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOptionalBool() bool { + if x != nil && x.OptionalBool != nil { + return *x.OptionalBool + } + return false +} + +func (x *TestAllTypesEdition2023) GetOptionalString() string { + if x != nil && x.OptionalString != nil { + return *x.OptionalString + } + return "" +} + +func (x *TestAllTypesEdition2023) GetOptionalBytes() []byte { + if x != nil { + return x.OptionalBytes + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOptionalNestedMessage() *TestAllTypesEdition2023_NestedMessage { + if x != nil { + return x.OptionalNestedMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOptionalForeignMessage() *ForeignMessageEdition2023 { + if x != nil { + return x.OptionalForeignMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOptionalNestedEnum() TestAllTypesEdition2023_NestedEnum { + if x != nil && x.OptionalNestedEnum != nil { + return *x.OptionalNestedEnum + } + return TestAllTypesEdition2023_FOO +} + +func (x *TestAllTypesEdition2023) GetOptionalForeignEnum() ForeignEnumEdition2023 { + if x != nil && x.OptionalForeignEnum != nil { + return *x.OptionalForeignEnum + } + return ForeignEnumEdition2023_FOREIGN_FOO +} + +func (x *TestAllTypesEdition2023) GetOptionalStringPiece() string { + if x != nil && x.OptionalStringPiece != nil { + return *x.OptionalStringPiece + } + return "" +} + +func (x *TestAllTypesEdition2023) GetOptionalCord() string { + if x != nil && x.OptionalCord != nil { + return *x.OptionalCord + } + return "" +} + +func (x *TestAllTypesEdition2023) GetRecursiveMessage() *TestAllTypesEdition2023 { + if x != nil { + return x.RecursiveMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedInt32() []int32 { + if x != nil { + return x.RepeatedInt32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedInt64() []int64 { + if x != nil { + return x.RepeatedInt64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedUint32() []uint32 { + if x != nil { + return x.RepeatedUint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedUint64() []uint64 { + if x != nil { + return x.RepeatedUint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedSint32() []int32 { + if x != nil { + return x.RepeatedSint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedSint64() []int64 { + if x != nil { + return x.RepeatedSint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedFixed32() []uint32 { + if x != nil { + return x.RepeatedFixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedFixed64() []uint64 { + if x != nil { + return x.RepeatedFixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedSfixed32() []int32 { + if x != nil { + return x.RepeatedSfixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedSfixed64() []int64 { + if x != nil { + return x.RepeatedSfixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedFloat() []float32 { + if x != nil { + return x.RepeatedFloat + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedDouble() []float64 { + if x != nil { + return x.RepeatedDouble + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedBool() []bool { + if x != nil { + return x.RepeatedBool + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedString() []string { + if x != nil { + return x.RepeatedString + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedBytes() [][]byte { + if x != nil { + return x.RepeatedBytes + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedNestedMessage() []*TestAllTypesEdition2023_NestedMessage { + if x != nil { + return x.RepeatedNestedMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedForeignMessage() []*ForeignMessageEdition2023 { + if x != nil { + return x.RepeatedForeignMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedNestedEnum() []TestAllTypesEdition2023_NestedEnum { + if x != nil { + return x.RepeatedNestedEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedForeignEnum() []ForeignEnumEdition2023 { + if x != nil { + return x.RepeatedForeignEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedStringPiece() []string { + if x != nil { + return x.RepeatedStringPiece + } + return nil +} + +func (x *TestAllTypesEdition2023) GetRepeatedCord() []string { + if x != nil { + return x.RepeatedCord + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedInt32() []int32 { + if x != nil { + return x.PackedInt32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedInt64() []int64 { + if x != nil { + return x.PackedInt64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedUint32() []uint32 { + if x != nil { + return x.PackedUint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedUint64() []uint64 { + if x != nil { + return x.PackedUint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedSint32() []int32 { + if x != nil { + return x.PackedSint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedSint64() []int64 { + if x != nil { + return x.PackedSint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedFixed32() []uint32 { + if x != nil { + return x.PackedFixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedFixed64() []uint64 { + if x != nil { + return x.PackedFixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedSfixed32() []int32 { + if x != nil { + return x.PackedSfixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedSfixed64() []int64 { + if x != nil { + return x.PackedSfixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedFloat() []float32 { + if x != nil { + return x.PackedFloat + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedDouble() []float64 { + if x != nil { + return x.PackedDouble + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedBool() []bool { + if x != nil { + return x.PackedBool + } + return nil +} + +func (x *TestAllTypesEdition2023) GetPackedNestedEnum() []TestAllTypesEdition2023_NestedEnum { + if x != nil { + return x.PackedNestedEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedInt32() []int32 { + if x != nil { + return x.UnpackedInt32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedInt64() []int64 { + if x != nil { + return x.UnpackedInt64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedUint32() []uint32 { + if x != nil { + return x.UnpackedUint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedUint64() []uint64 { + if x != nil { + return x.UnpackedUint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedSint32() []int32 { + if x != nil { + return x.UnpackedSint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedSint64() []int64 { + if x != nil { + return x.UnpackedSint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedFixed32() []uint32 { + if x != nil { + return x.UnpackedFixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedFixed64() []uint64 { + if x != nil { + return x.UnpackedFixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedSfixed32() []int32 { + if x != nil { + return x.UnpackedSfixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedSfixed64() []int64 { + if x != nil { + return x.UnpackedSfixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedFloat() []float32 { + if x != nil { + return x.UnpackedFloat + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedDouble() []float64 { + if x != nil { + return x.UnpackedDouble + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedBool() []bool { + if x != nil { + return x.UnpackedBool + } + return nil +} + +func (x *TestAllTypesEdition2023) GetUnpackedNestedEnum() []TestAllTypesEdition2023_NestedEnum { + if x != nil { + return x.UnpackedNestedEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapInt32Int32() map[int32]int32 { + if x != nil { + return x.MapInt32Int32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapInt64Int64() map[int64]int64 { + if x != nil { + return x.MapInt64Int64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapUint32Uint32() map[uint32]uint32 { + if x != nil { + return x.MapUint32Uint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapUint64Uint64() map[uint64]uint64 { + if x != nil { + return x.MapUint64Uint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapSint32Sint32() map[int32]int32 { + if x != nil { + return x.MapSint32Sint32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapSint64Sint64() map[int64]int64 { + if x != nil { + return x.MapSint64Sint64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapFixed32Fixed32() map[uint32]uint32 { + if x != nil { + return x.MapFixed32Fixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapFixed64Fixed64() map[uint64]uint64 { + if x != nil { + return x.MapFixed64Fixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapSfixed32Sfixed32() map[int32]int32 { + if x != nil { + return x.MapSfixed32Sfixed32 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapSfixed64Sfixed64() map[int64]int64 { + if x != nil { + return x.MapSfixed64Sfixed64 + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapInt32Float() map[int32]float32 { + if x != nil { + return x.MapInt32Float + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapInt32Double() map[int32]float64 { + if x != nil { + return x.MapInt32Double + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapBoolBool() map[bool]bool { + if x != nil { + return x.MapBoolBool + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringString() map[string]string { + if x != nil { + return x.MapStringString + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringBytes() map[string][]byte { + if x != nil { + return x.MapStringBytes + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringNestedMessage() map[string]*TestAllTypesEdition2023_NestedMessage { + if x != nil { + return x.MapStringNestedMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringForeignMessage() map[string]*ForeignMessageEdition2023 { + if x != nil { + return x.MapStringForeignMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringNestedEnum() map[string]TestAllTypesEdition2023_NestedEnum { + if x != nil { + return x.MapStringNestedEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetMapStringForeignEnum() map[string]ForeignEnumEdition2023 { + if x != nil { + return x.MapStringForeignEnum + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOneofField() isTestAllTypesEdition2023_OneofField { + if x != nil { + return x.OneofField + } + return nil +} +func (x *TestAllTypesEdition2023) GetOneofUint32() uint32 { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofUint32); ok { + return p.OneofUint32 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOneofNestedMessage() *TestAllTypesEdition2023_NestedMessage { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofNestedMessage); ok { + return p.OneofNestedMessage + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOneofString() string { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofString); ok { + return p.OneofString + } + return "" +} + +func (x *TestAllTypesEdition2023) GetOneofBytes() []byte { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofBytes); ok { + return p.OneofBytes + } + return nil +} + +func (x *TestAllTypesEdition2023) GetOneofBool() bool { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofBool); ok { + return p.OneofBool + } + return false +} + +func (x *TestAllTypesEdition2023) GetOneofUint64() uint64 { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofUint64); ok { + return p.OneofUint64 + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOneofFloat() float32 { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofFloat); ok { + return p.OneofFloat + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOneofDouble() float64 { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofDouble); ok { + return p.OneofDouble + } + return 0 +} + +func (x *TestAllTypesEdition2023) GetOneofEnum() TestAllTypesEdition2023_NestedEnum { + if p, ok := x.GetOneofField().(*TestAllTypesEdition2023_OneofEnum); ok { + return p.OneofEnum + } + return TestAllTypesEdition2023_FOO +} + +func (x *TestAllTypesEdition2023) GetGroupliketype() *TestAllTypesEdition2023_GroupLikeType { + if x != nil { + return x.Groupliketype + } + return nil +} + +func (x *TestAllTypesEdition2023) GetDelimitedField() *TestAllTypesEdition2023_GroupLikeType { + if x != nil { + return x.DelimitedField + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the prutal package. +func (*TestAllTypesEdition2023) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*TestAllTypesEdition2023_OneofUint32)(nil), + (*TestAllTypesEdition2023_OneofNestedMessage)(nil), + (*TestAllTypesEdition2023_OneofString)(nil), + (*TestAllTypesEdition2023_OneofBytes)(nil), + (*TestAllTypesEdition2023_OneofBool)(nil), + (*TestAllTypesEdition2023_OneofUint64)(nil), + (*TestAllTypesEdition2023_OneofFloat)(nil), + (*TestAllTypesEdition2023_OneofDouble)(nil), + (*TestAllTypesEdition2023_OneofEnum)(nil), + } +} + +type isTestAllTypesEdition2023_OneofField interface { + isTestAllTypesEdition2023_OneofField() +} + +type TestAllTypesEdition2023_OneofUint32 struct { + OneofUint32 uint32 `protobuf:"varint,111,opt,name=oneof_uint32" json:"oneof_uint32,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofUint32) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofNestedMessage struct { + OneofNestedMessage *TestAllTypesEdition2023_NestedMessage `protobuf:"bytes,112,opt,name=oneof_nested_message" json:"oneof_nested_message,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofNestedMessage) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofString struct { + OneofString string `protobuf:"bytes,113,opt,name=oneof_string" json:"oneof_string,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofString) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofBytes struct { + OneofBytes []byte `protobuf:"bytes,114,opt,name=oneof_bytes" json:"oneof_bytes,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofBytes) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofBool struct { + OneofBool bool `protobuf:"varint,115,opt,name=oneof_bool" json:"oneof_bool,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofBool) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofUint64 struct { + OneofUint64 uint64 `protobuf:"varint,116,opt,name=oneof_uint64" json:"oneof_uint64,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofUint64) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofFloat struct { + OneofFloat float32 `protobuf:"fixed32,117,opt,name=oneof_float" json:"oneof_float,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofFloat) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofDouble struct { + OneofDouble float64 `protobuf:"fixed64,118,opt,name=oneof_double" json:"oneof_double,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofDouble) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_OneofEnum struct { + OneofEnum TestAllTypesEdition2023_NestedEnum `protobuf:"varint,119,opt,name=oneof_enum" json:"oneof_enum,omitempty"` +} + +func (*TestAllTypesEdition2023_OneofEnum) isTestAllTypesEdition2023_OneofField() {} + +type TestAllTypesEdition2023_NestedMessage struct { + A *int32 `protobuf:"varint,1,opt,name=a" json:"a,omitempty"` + Corecursive *TestAllTypesEdition2023 `protobuf:"bytes,2,opt,name=corecursive" json:"corecursive,omitempty"` +} + +func (x *TestAllTypesEdition2023_NestedMessage) Reset() { *x = TestAllTypesEdition2023_NestedMessage{} } + +func (x *TestAllTypesEdition2023_NestedMessage) GetA() int32 { + if x != nil && x.A != nil { + return *x.A + } + return 0 +} + +func (x *TestAllTypesEdition2023_NestedMessage) GetCorecursive() *TestAllTypesEdition2023 { + if x != nil { + return x.Corecursive + } + return nil +} + +// groups +type TestAllTypesEdition2023_GroupLikeType struct { + GroupInt32 *int32 `protobuf:"varint,202,opt,name=group_int32" json:"group_int32,omitempty"` + GroupUint32 *uint32 `protobuf:"varint,203,opt,name=group_uint32" json:"group_uint32,omitempty"` +} + +func (x *TestAllTypesEdition2023_GroupLikeType) Reset() { *x = TestAllTypesEdition2023_GroupLikeType{} } + +func (x *TestAllTypesEdition2023_GroupLikeType) GetGroupInt32() int32 { + if x != nil && x.GroupInt32 != nil { + return *x.GroupInt32 + } + return 0 +} + +func (x *TestAllTypesEdition2023_GroupLikeType) GetGroupUint32() uint32 { + if x != nil && x.GroupUint32 != nil { + return *x.GroupUint32 + } + return 0 +} + +type ForeignMessageEdition2023 struct { + C *int32 `protobuf:"varint,1,opt,name=c" json:"c,omitempty"` +} + +func (x *ForeignMessageEdition2023) Reset() { *x = ForeignMessageEdition2023{} } + +func (x *ForeignMessageEdition2023) GetC() int32 { + if x != nil && x.C != nil { + return *x.C + } + return 0 +} + +type GroupLikeType struct { + C *int32 `protobuf:"varint,1,opt,name=c" json:"c,omitempty"` +} + +func (x *GroupLikeType) Reset() { *x = GroupLikeType{} } + +func (x *GroupLikeType) GetC() int32 { + if x != nil && x.C != nil { + return *x.C + } + return 0 +} diff --git a/tests/cases/edition2023/test_messages_edition2023.proto b/tests/cases/edition2023/test_messages_edition2023.proto new file mode 100644 index 0000000..7affff6 --- /dev/null +++ b/tests/cases/edition2023/test_messages_edition2023.proto @@ -0,0 +1,217 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2024 Google Inc. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file or at +// https://developers.google.com/open-source/licenses/bsd + +edition = "2023"; + +package protobuf_test_messages.editions; + +option features.message_encoding = DELIMITED; +option java_package = "com.google.protobuf_test_messages.edition2023"; +option java_multiple_files = true; +option objc_class_prefix = "Editions"; + +message ComplexMessage { + int32 d = 1; +} + +message TestAllTypesEdition2023 { + message NestedMessage { + int32 a = 1; + TestAllTypesEdition2023 corecursive = 2 + [features.message_encoding = LENGTH_PREFIXED]; + } + + enum NestedEnum { + FOO = 0; + BAR = 1; + BAZ = 2; + NEG = -1; // Intentionally negative. + } + + // Singular + int32 optional_int32 = 1; + int64 optional_int64 = 2; + uint32 optional_uint32 = 3; + uint64 optional_uint64 = 4; + sint32 optional_sint32 = 5; + sint64 optional_sint64 = 6; + fixed32 optional_fixed32 = 7; + fixed64 optional_fixed64 = 8; + sfixed32 optional_sfixed32 = 9; + sfixed64 optional_sfixed64 = 10; + float optional_float = 11; + double optional_double = 12; + bool optional_bool = 13; + string optional_string = 14; + bytes optional_bytes = 15; + + NestedMessage optional_nested_message = 18 + [features.message_encoding = LENGTH_PREFIXED]; + ForeignMessageEdition2023 optional_foreign_message = 19 + [features.message_encoding = LENGTH_PREFIXED]; + + NestedEnum optional_nested_enum = 21; + ForeignEnumEdition2023 optional_foreign_enum = 22; + + string optional_string_piece = 24 [ctype = STRING_PIECE]; + string optional_cord = 25 [ctype = CORD]; + + TestAllTypesEdition2023 recursive_message = 27 + [features.message_encoding = LENGTH_PREFIXED]; + + // Repeated + repeated int32 repeated_int32 = 31; + repeated int64 repeated_int64 = 32; + repeated uint32 repeated_uint32 = 33; + repeated uint64 repeated_uint64 = 34; + repeated sint32 repeated_sint32 = 35; + repeated sint64 repeated_sint64 = 36; + repeated fixed32 repeated_fixed32 = 37; + repeated fixed64 repeated_fixed64 = 38; + repeated sfixed32 repeated_sfixed32 = 39; + repeated sfixed64 repeated_sfixed64 = 40; + repeated float repeated_float = 41; + repeated double repeated_double = 42; + repeated bool repeated_bool = 43; + repeated string repeated_string = 44; + repeated bytes repeated_bytes = 45; + + repeated NestedMessage repeated_nested_message = 48 + [features.message_encoding = LENGTH_PREFIXED]; + repeated ForeignMessageEdition2023 repeated_foreign_message = 49 + [features.message_encoding = LENGTH_PREFIXED]; + + repeated NestedEnum repeated_nested_enum = 51; + repeated ForeignEnumEdition2023 repeated_foreign_enum = 52; + + repeated string repeated_string_piece = 54 [ctype = STRING_PIECE]; + repeated string repeated_cord = 55 [ctype = CORD]; + + // Packed + repeated int32 packed_int32 = 75 [features.repeated_field_encoding = PACKED]; + repeated int64 packed_int64 = 76 [features.repeated_field_encoding = PACKED]; + repeated uint32 packed_uint32 = 77 + [features.repeated_field_encoding = PACKED]; + repeated uint64 packed_uint64 = 78 + [features.repeated_field_encoding = PACKED]; + repeated sint32 packed_sint32 = 79 + [features.repeated_field_encoding = PACKED]; + repeated sint64 packed_sint64 = 80 + [features.repeated_field_encoding = PACKED]; + repeated fixed32 packed_fixed32 = 81 + [features.repeated_field_encoding = PACKED]; + repeated fixed64 packed_fixed64 = 82 + [features.repeated_field_encoding = PACKED]; + repeated sfixed32 packed_sfixed32 = 83 + [features.repeated_field_encoding = PACKED]; + repeated sfixed64 packed_sfixed64 = 84 + [features.repeated_field_encoding = PACKED]; + repeated float packed_float = 85 [features.repeated_field_encoding = PACKED]; + repeated double packed_double = 86 + [features.repeated_field_encoding = PACKED]; + repeated bool packed_bool = 87 [features.repeated_field_encoding = PACKED]; + repeated NestedEnum packed_nested_enum = 88 + [features.repeated_field_encoding = PACKED]; + + // Unpacked + repeated int32 unpacked_int32 = 89 + [features.repeated_field_encoding = EXPANDED]; + repeated int64 unpacked_int64 = 90 + [features.repeated_field_encoding = EXPANDED]; + repeated uint32 unpacked_uint32 = 91 + [features.repeated_field_encoding = EXPANDED]; + repeated uint64 unpacked_uint64 = 92 + [features.repeated_field_encoding = EXPANDED]; + repeated sint32 unpacked_sint32 = 93 + [features.repeated_field_encoding = EXPANDED]; + repeated sint64 unpacked_sint64 = 94 + [features.repeated_field_encoding = EXPANDED]; + repeated fixed32 unpacked_fixed32 = 95 + [features.repeated_field_encoding = EXPANDED]; + repeated fixed64 unpacked_fixed64 = 96 + [features.repeated_field_encoding = EXPANDED]; + repeated sfixed32 unpacked_sfixed32 = 97 + [features.repeated_field_encoding = EXPANDED]; + repeated sfixed64 unpacked_sfixed64 = 98 + [features.repeated_field_encoding = EXPANDED]; + repeated float unpacked_float = 99 + [features.repeated_field_encoding = EXPANDED]; + repeated double unpacked_double = 100 + [features.repeated_field_encoding = EXPANDED]; + repeated bool unpacked_bool = 101 + [features.repeated_field_encoding = EXPANDED]; + repeated NestedEnum unpacked_nested_enum = 102 + [features.repeated_field_encoding = EXPANDED]; + + // Map + map map_int32_int32 = 56; + map map_int64_int64 = 57; + map map_uint32_uint32 = 58; + map map_uint64_uint64 = 59; + map map_sint32_sint32 = 60; + map map_sint64_sint64 = 61; + map map_fixed32_fixed32 = 62; + map map_fixed64_fixed64 = 63; + map map_sfixed32_sfixed32 = 64; + map map_sfixed64_sfixed64 = 65; + map map_int32_float = 66; + map map_int32_double = 67; + map map_bool_bool = 68; + map map_string_string = 69; + map map_string_bytes = 70; + map map_string_nested_message = 71; + map map_string_foreign_message = 72; + map map_string_nested_enum = 73; + map map_string_foreign_enum = 74; + + oneof oneof_field { + uint32 oneof_uint32 = 111; + NestedMessage oneof_nested_message = 112 + [features.message_encoding = LENGTH_PREFIXED]; + string oneof_string = 113; + bytes oneof_bytes = 114; + bool oneof_bool = 115; + uint64 oneof_uint64 = 116; + float oneof_float = 117; + double oneof_double = 118; + NestedEnum oneof_enum = 119; + } + + // extensions + extensions 120 to 200; + + // groups + message GroupLikeType { + int32 group_int32 = 202; + uint32 group_uint32 = 203; + } + GroupLikeType groupliketype = 201; + GroupLikeType delimited_field = 202; +} + +message ForeignMessageEdition2023 { + int32 c = 1; +} + +enum ForeignEnumEdition2023 { + FOREIGN_FOO = 0; + FOREIGN_BAR = 1; + FOREIGN_BAZ = 2; +} + +extend TestAllTypesEdition2023 { + int32 extension_int32 = 120; +} + +message GroupLikeType { + int32 c = 1; +} + +extend TestAllTypesEdition2023 { + GroupLikeType groupliketype = 121; + GroupLikeType delimited_ext = 122; +} diff --git a/tests/cases/oneof/Makefile b/tests/cases/oneof/Makefile new file mode 100644 index 0000000..48de6d4 --- /dev/null +++ b/tests/cases/oneof/Makefile @@ -0,0 +1,5 @@ +oneof.pb.go: oneof.proto + prutalgen --proto_path=. --go_out=. --go_opt=paths=source_relative ./oneof.proto + +test: oneof.pb.go + go build diff --git a/tests/cases/oneof/oneof.pb.go b/tests/cases/oneof/oneof.pb.go new file mode 100644 index 0000000..11fc098 --- /dev/null +++ b/tests/cases/oneof/oneof.pb.go @@ -0,0 +1,61 @@ +// Code generated by prutalgen. DO NOT EDIT. +// prutalgen --proto_path=. --go_out=. --go_opt=paths=source_relative ./oneof.proto + +package oneof + +type TestOneofMessage struct { + // Types that are assignable to OneOfField1: + // + // *TestOneofMessage_Field1 + // *TestOneofMessage_Field2 + OneOfField1 isTestOneofMessage_OneOfField1 `protobuf_oneof:"one_of_field1"` + // Types that are assignable to OneOfField2: + // + // *TestOneofMessage_Field3 + // *TestOneofMessage_Field4 + OneOfField2 isTestOneofMessage_OneOfField2 `protobuf_oneof:"one_of_field2"` +} + +func (x *TestOneofMessage) Reset() { *x = TestOneofMessage{} } + +// XXX_OneofWrappers is for the internal use of the prutal package. +func (*TestOneofMessage) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*TestOneofMessage_Field1)(nil), + (*TestOneofMessage_Field2)(nil), + (*TestOneofMessage_Field3)(nil), + (*TestOneofMessage_Field4)(nil), + } +} + +type isTestOneofMessage_OneOfField1 interface { + isTestOneofMessage_OneOfField1() +} + +type TestOneofMessage_Field1 struct { + Field1 bool `protobuf:"varint,1,opt,name=field1" json:"field1,omitempty"` +} + +func (*TestOneofMessage_Field1) isTestOneofMessage_OneOfField1() {} + +type TestOneofMessage_Field2 struct { + Field2 int64 `protobuf:"varint,2,opt,name=field2" json:"field2,omitempty"` +} + +func (*TestOneofMessage_Field2) isTestOneofMessage_OneOfField1() {} + +type isTestOneofMessage_OneOfField2 interface { + isTestOneofMessage_OneOfField2() +} + +type TestOneofMessage_Field3 struct { + Field3 int32 `protobuf:"varint,3,opt,name=field3" json:"field3,omitempty"` +} + +func (*TestOneofMessage_Field3) isTestOneofMessage_OneOfField2() {} + +type TestOneofMessage_Field4 struct { + Field4 *TestOneofMessage `protobuf:"bytes,4,opt,name=field4" json:"field4,omitempty"` +} + +func (*TestOneofMessage_Field4) isTestOneofMessage_OneOfField2() {} diff --git a/tests/cases/oneof/oneof.proto b/tests/cases/oneof/oneof.proto new file mode 100644 index 0000000..8a2e184 --- /dev/null +++ b/tests/cases/oneof/oneof.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; + +package test_oneof; + +option go_package = "./oneof"; + +message TestOneofMessage { + oneof one_of_field1 { + bool field1 = 1; + int64 field2 = 2; + } + + oneof one_of_field2 { + int32 field3 = 3; + TestOneofMessage field4 = 4; + } +} diff --git a/tests/cases/proto2/Makefile b/tests/cases/proto2/Makefile new file mode 100644 index 0000000..322f587 --- /dev/null +++ b/tests/cases/proto2/Makefile @@ -0,0 +1,5 @@ +proto2.pb.go: proto2.proto + prutalgen --proto_path=. --go_out=. --go_opt=paths=source_relative ./proto2.proto + +test: proto2.pb.go + go build diff --git a/tests/cases/proto2/proto2.pb.go b/tests/cases/proto2/proto2.pb.go new file mode 100644 index 0000000..3e75067 --- /dev/null +++ b/tests/cases/proto2/proto2.pb.go @@ -0,0 +1,135 @@ +// Code generated by prutalgen. DO NOT EDIT. +// prutalgen --proto_path=. --go_out=. --go_opt=paths=source_relative ./proto2.proto + +package proto2 + +import "strconv" + +type Message_NestedEnum int32 + +const ( + Message_ZERO Message_NestedEnum = 0 + Message_ONE Message_NestedEnum = 1 + Message_TWO Message_NestedEnum = 2 +) + +// Enum value maps for Message_NestedEnum. +var Message_NestedEnum_name = map[int32]string{ + 0: "ZERO", + 1: "ONE", + 2: "TWO", +} + +var Message_NestedEnum_value = map[string]int32{ + "ZERO": 0, + "ONE": 1, + "TWO": 2, +} + +func (x Message_NestedEnum) String() string { + s, ok := Message_NestedEnum_name[int32(x)] + if ok { + return s + } + return strconv.Itoa(int(x)) +} + +type Message struct { + // Singular - optional + OptionalInt32 *int32 `protobuf:"varint,1,opt,name=optional_int32" json:"optional_int32,omitempty"` + OptionalInt64 *int64 `protobuf:"varint,2,opt,name=optional_int64" json:"optional_int64,omitempty"` + OptionalUint32 *uint32 `protobuf:"varint,3,opt,name=optional_uint32" json:"optional_uint32,omitempty"` + OptionalUint64 *uint64 `protobuf:"varint,4,opt,name=optional_uint64" json:"optional_uint64,omitempty"` + OptionalSint32 *int32 `protobuf:"zigzag32,5,opt,name=optional_sint32" json:"optional_sint32,omitempty"` + OptionalSint64 *int64 `protobuf:"zigzag64,6,opt,name=optional_sint64" json:"optional_sint64,omitempty"` + OptionalFixed32 *uint32 `protobuf:"fixed32,7,opt,name=optional_fixed32" json:"optional_fixed32,omitempty"` + OptionalFixed64 *uint64 `protobuf:"fixed64,8,opt,name=optional_fixed64" json:"optional_fixed64,omitempty"` + OptionalSfixed32 *int32 `protobuf:"fixed32,9,opt,name=optional_sfixed32" json:"optional_sfixed32,omitempty"` + OptionalSfixed64 *int64 `protobuf:"fixed64,10,opt,name=optional_sfixed64" json:"optional_sfixed64,omitempty"` + OptionalFloat *float32 `protobuf:"fixed32,11,opt,name=optional_float" json:"optional_float,omitempty"` + OptionalDouble *float64 `protobuf:"fixed64,12,opt,name=optional_double" json:"optional_double,omitempty"` + OptionalBool *bool `protobuf:"varint,13,opt,name=optional_bool" json:"optional_bool,omitempty"` + OptionalString *string `protobuf:"bytes,14,opt,name=optional_string" json:"optional_string,omitempty"` + OptionalBytes []byte `protobuf:"bytes,15,opt,name=optional_bytes" json:"optional_bytes,omitempty"` + OptionalEnum *Message_NestedEnum `protobuf:"varint,16,opt,name=optional_enum" json:"optional_enum,omitempty"` + + // Singular - required + RequiredInt32 *int32 `protobuf:"varint,1001,req,name=required_int32" json:"required_int32,omitempty"` + RequiredInt64 *int64 `protobuf:"varint,1002,req,name=required_int64" json:"required_int64,omitempty"` + RequiredUint32 *uint32 `protobuf:"varint,1003,req,name=required_uint32" json:"required_uint32,omitempty"` + RequiredUint64 *uint64 `protobuf:"varint,1004,req,name=required_uint64" json:"required_uint64,omitempty"` + RequiredSint32 *int32 `protobuf:"zigzag32,1005,req,name=required_sint32" json:"required_sint32,omitempty"` + RequiredSint64 *int64 `protobuf:"zigzag64,1006,req,name=required_sint64" json:"required_sint64,omitempty"` + RequiredFixed32 *uint32 `protobuf:"fixed32,1007,req,name=required_fixed32" json:"required_fixed32,omitempty"` + RequiredFixed64 *uint64 `protobuf:"fixed64,1008,req,name=required_fixed64" json:"required_fixed64,omitempty"` + RequiredSfixed32 *int32 `protobuf:"fixed32,1009,req,name=required_sfixed32" json:"required_sfixed32,omitempty"` + RequiredSfixed64 *int64 `protobuf:"fixed64,1010,req,name=required_sfixed64" json:"required_sfixed64,omitempty"` + RequiredFloat *float32 `protobuf:"fixed32,1011,req,name=required_float" json:"required_float,omitempty"` + RequiredDouble *float64 `protobuf:"fixed64,1012,req,name=required_double" json:"required_double,omitempty"` + RequiredBool *bool `protobuf:"varint,1013,req,name=required_bool" json:"required_bool,omitempty"` + RequiredString *string `protobuf:"bytes,1014,req,name=required_string" json:"required_string,omitempty"` + RequiredBytes []byte `protobuf:"bytes,1015,req,name=required_bytes" json:"required_bytes,omitempty"` + RequiredEnum *Message_NestedEnum `protobuf:"varint,1016,req,name=required_enum" json:"required_enum,omitempty"` + + // Repeated + RepeatedInt32 []int32 `protobuf:"varint,2001,rep,name=repeated_int32" json:"repeated_int32,omitempty"` + RepeatedInt64 []int64 `protobuf:"varint,2002,rep,name=repeated_int64" json:"repeated_int64,omitempty"` + RepeatedUint32 []uint32 `protobuf:"varint,2003,rep,name=repeated_uint32" json:"repeated_uint32,omitempty"` + RepeatedUint64 []uint64 `protobuf:"varint,2004,rep,name=repeated_uint64" json:"repeated_uint64,omitempty"` + RepeatedSint32 []int32 `protobuf:"zigzag32,2005,rep,name=repeated_sint32" json:"repeated_sint32,omitempty"` + RepeatedSint64 []int64 `protobuf:"zigzag64,2006,rep,name=repeated_sint64" json:"repeated_sint64,omitempty"` + RepeatedFixed32 []uint32 `protobuf:"fixed32,2007,rep,name=repeated_fixed32" json:"repeated_fixed32,omitempty"` + RepeatedFixed64 []uint64 `protobuf:"fixed64,2008,rep,name=repeated_fixed64" json:"repeated_fixed64,omitempty"` + RepeatedSfixed32 []int32 `protobuf:"fixed32,2009,rep,name=repeated_sfixed32" json:"repeated_sfixed32,omitempty"` + RepeatedSfixed64 []int64 `protobuf:"fixed64,2010,rep,name=repeated_sfixed64" json:"repeated_sfixed64,omitempty"` + RepeatedFloat []float32 `protobuf:"fixed32,2011,rep,name=repeated_float" json:"repeated_float,omitempty"` + RepeatedDouble []float64 `protobuf:"fixed64,2012,rep,name=repeated_double" json:"repeated_double,omitempty"` + RepeatedBool []bool `protobuf:"varint,2013,rep,name=repeated_bool" json:"repeated_bool,omitempty"` + RepeatedString []string `protobuf:"bytes,2014,rep,name=repeated_string" json:"repeated_string,omitempty"` + RepeatedBytes [][]byte `protobuf:"bytes,2015,rep,name=repeated_bytes" json:"repeated_bytes,omitempty"` + RepeatedEnum []Message_NestedEnum `protobuf:"varint,2016,rep,name=repeated_enum" json:"repeated_enum,omitempty"` + RepeatedMsg []*Message_NestedMessage `protobuf:"bytes,2017,rep,name=repeated_msg" json:"repeated_msg,omitempty"` + + // Repeated - packed + PackedInt32 []int32 `protobuf:"varint,3001,rep,packed,name=packed_int32" json:"packed_int32,omitempty"` + PackedInt64 []int64 `protobuf:"varint,3002,rep,packed,name=packed_int64" json:"packed_int64,omitempty"` + PackedUint32 []uint32 `protobuf:"varint,3003,rep,packed,name=packed_uint32" json:"packed_uint32,omitempty"` + PackedUint64 []uint64 `protobuf:"varint,3004,rep,packed,name=packed_uint64" json:"packed_uint64,omitempty"` + PackedSint32 []int32 `protobuf:"zigzag32,3005,rep,packed,name=packed_sint32" json:"packed_sint32,omitempty"` + PackedSint64 []int64 `protobuf:"zigzag64,3006,rep,packed,name=packed_sint64" json:"packed_sint64,omitempty"` + PackedFixed32 []uint32 `protobuf:"fixed32,3007,rep,packed,name=packed_fixed32" json:"packed_fixed32,omitempty"` + PackedFixed64 []uint64 `protobuf:"fixed64,3008,rep,packed,name=packed_fixed64" json:"packed_fixed64,omitempty"` + PackedSfixed32 []int32 `protobuf:"fixed32,3009,rep,packed,name=packed_sfixed32" json:"packed_sfixed32,omitempty"` + PackedSfixed64 []int64 `protobuf:"fixed64,3010,rep,packed,name=packed_sfixed64" json:"packed_sfixed64,omitempty"` + PackedFloat []float32 `protobuf:"fixed32,3011,rep,packed,name=packed_float" json:"packed_float,omitempty"` + PackedDouble []float64 `protobuf:"fixed64,3012,rep,packed,name=packed_double" json:"packed_double,omitempty"` + PackedBool []bool `protobuf:"varint,3013,rep,packed,name=packed_bool" json:"packed_bool,omitempty"` + PackedEnum []Message_NestedEnum `protobuf:"varint,3016,rep,packed,name=packed_enum" json:"packed_enum,omitempty"` + + // Map + MapInt32Int32 map[int32]int32 `protobuf:"bytes,4001,rep,name=map_int32_int32" json:"map_int32_int32,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapInt64Int64 map[int64]int64 `protobuf:"bytes,4002,rep,name=map_int64_int64" json:"map_int64_int64,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapUint32Uint32 map[uint32]uint32 `protobuf:"bytes,4003,rep,name=map_uint32_uint32" json:"map_uint32_uint32,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapUint64Uint64 map[uint64]uint64 `protobuf:"bytes,4004,rep,name=map_uint64_uint64" json:"map_uint64_uint64,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapSint32Sint32 map[int32]int32 `protobuf:"bytes,4005,rep,name=map_sint32_sint32" json:"map_sint32_sint32,omitempty" protobuf_key:"zigzag32,1,opt,name=key" protobuf_val:"zigzag32,2,opt,name=value"` + MapSint64Sint64 map[int64]int64 `protobuf:"bytes,4006,rep,name=map_sint64_sint64" json:"map_sint64_sint64,omitempty" protobuf_key:"zigzag64,1,opt,name=key" protobuf_val:"zigzag64,2,opt,name=value"` + MapFixed32Fixed32 map[uint32]uint32 `protobuf:"bytes,4007,rep,name=map_fixed32_fixed32" json:"map_fixed32_fixed32,omitempty" protobuf_key:"fixed32,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapFixed64Fixed64 map[uint64]uint64 `protobuf:"bytes,4008,rep,name=map_fixed64_fixed64" json:"map_fixed64_fixed64,omitempty" protobuf_key:"fixed64,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapSfixed32Sfixed32 map[int32]int32 `protobuf:"bytes,4009,rep,name=map_sfixed32_sfixed32" json:"map_sfixed32_sfixed32,omitempty" protobuf_key:"fixed32,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapSfixed64Sfixed64 map[int64]int64 `protobuf:"bytes,4010,rep,name=map_sfixed64_sfixed64" json:"map_sfixed64_sfixed64,omitempty" protobuf_key:"fixed64,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapInt32Float map[int32]float32 `protobuf:"bytes,4011,rep,name=map_int32_float" json:"map_int32_float,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"fixed32,2,opt,name=value"` + MapInt32Double map[int32]float64 `protobuf:"bytes,4012,rep,name=map_int32_double" json:"map_int32_double,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"fixed64,2,opt,name=value"` + MapBoolBool map[bool]bool `protobuf:"bytes,4013,rep,name=map_bool_bool" json:"map_bool_bool,omitempty" protobuf_key:"varint,1,opt,name=key" protobuf_val:"varint,2,opt,name=value"` + MapStringString map[string]string `protobuf:"bytes,4014,rep,name=map_string_string" json:"map_string_string,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringBytes map[string][]byte `protobuf:"bytes,4015,rep,name=map_string_bytes" json:"map_string_bytes,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + MapStringNestedMessage map[string]*Message_NestedMessage `protobuf:"bytes,4016,rep,name=map_string_nested_message" json:"map_string_nested_message,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` +} + +func (x *Message) Reset() { *x = Message{} } + +type Message_NestedMessage struct { + I32 *int32 `protobuf:"varint,1,opt,name=i32" json:"i32,omitempty"` + M *Message `protobuf:"bytes,2,opt,name=M" json:"M,omitempty"` +} + +func (x *Message_NestedMessage) Reset() { *x = Message_NestedMessage{} } diff --git a/tests/cases/proto2/proto2.proto b/tests/cases/proto2/proto2.proto new file mode 100644 index 0000000..e66f194 --- /dev/null +++ b/tests/cases/proto2/proto2.proto @@ -0,0 +1,111 @@ +syntax = "proto2"; + +package test_proto2; + +option go_package = "./proto2"; + + +message Message { + enum NestedEnum { + ZERO = 0; + ONE = 1; + TWO = 2; + } + + message NestedMessage { + optional int32 i32 = 1; + optional Message M = 2; + } + + // Singular - optional + optional int32 optional_int32 = 1; + optional int64 optional_int64 = 2; + optional uint32 optional_uint32 = 3; + optional uint64 optional_uint64 = 4; + optional sint32 optional_sint32 = 5; + optional sint64 optional_sint64 = 6; + optional fixed32 optional_fixed32 = 7; + optional fixed64 optional_fixed64 = 8; + optional sfixed32 optional_sfixed32 = 9; + optional sfixed64 optional_sfixed64 = 10; + optional float optional_float = 11; + optional double optional_double = 12; + optional bool optional_bool = 13; + optional string optional_string = 14; + optional bytes optional_bytes = 15; + optional NestedEnum optional_enum = 16; + + // Singular - required + required int32 required_int32 = 1001; + required int64 required_int64 = 1002; + required uint32 required_uint32 = 1003; + required uint64 required_uint64 = 1004; + required sint32 required_sint32 = 1005; + required sint64 required_sint64 = 1006; + required fixed32 required_fixed32 = 1007; + required fixed64 required_fixed64 = 1008; + required sfixed32 required_sfixed32 = 1009; + required sfixed64 required_sfixed64 = 1010; + required float required_float = 1011; + required double required_double = 1012; + required bool required_bool = 1013; + required string required_string = 1014; + required bytes required_bytes = 1015; + required NestedEnum required_enum = 1016; + + + + // Repeated + repeated int32 repeated_int32 = 2001; + repeated int64 repeated_int64 = 2002; + repeated uint32 repeated_uint32 = 2003; + repeated uint64 repeated_uint64 = 2004; + repeated sint32 repeated_sint32 = 2005; + repeated sint64 repeated_sint64 = 2006; + repeated fixed32 repeated_fixed32 = 2007; + repeated fixed64 repeated_fixed64 = 2008; + repeated sfixed32 repeated_sfixed32 = 2009; + repeated sfixed64 repeated_sfixed64 = 2010; + repeated float repeated_float = 2011; + repeated double repeated_double = 2012; + repeated bool repeated_bool = 2013; + repeated string repeated_string = 2014; + repeated bytes repeated_bytes = 2015; + repeated NestedEnum repeated_enum = 2016; + repeated NestedMessage repeated_msg = 2017; + + // Repeated - packed + repeated int32 packed_int32 = 3001 [packed = true]; + repeated int64 packed_int64 = 3002 [packed = true]; + repeated uint32 packed_uint32 = 3003 [packed = true]; + repeated uint64 packed_uint64 = 3004 [packed = true]; + repeated sint32 packed_sint32 = 3005 [packed = true]; + repeated sint64 packed_sint64 = 3006 [packed = true]; + repeated fixed32 packed_fixed32 = 3007 [packed = true]; + repeated fixed64 packed_fixed64 = 3008 [packed = true]; + repeated sfixed32 packed_sfixed32 = 3009 [packed = true]; + repeated sfixed64 packed_sfixed64 = 3010 [packed = true]; + repeated float packed_float = 3011 [packed = true]; + repeated double packed_double = 3012 [packed = true]; + repeated bool packed_bool = 3013 [packed = true]; + repeated NestedEnum packed_enum = 3016 [packed = true]; + + + // Map + map map_int32_int32 = 4001; + map map_int64_int64 = 4002; + map map_uint32_uint32 = 4003; + map map_uint64_uint64 = 4004; + map map_sint32_sint32 = 4005; + map map_sint64_sint64 = 4006; + map map_fixed32_fixed32 = 4007; + map map_fixed64_fixed64 = 4008; + map map_sfixed32_sfixed32 = 4009; + map map_sfixed64_sfixed64 = 4010; + map map_int32_float = 4011; + map map_int32_double = 4012; + map map_bool_bool = 4013; + map map_string_string = 4014; + map map_string_bytes = 4015; + map map_string_nested_message = 4016; +} diff --git a/tests/run.sh b/tests/run.sh new file mode 100755 index 0000000..af412af --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,36 @@ +#!/bin/bash + +set -e + +# build prutalgen +echo "building prutalgen ..." +cd ../prutalgen +go build -v +cd - >/dev/null + +# copy to bin and add to PATH +mkdir -p bin +cp ../prutalgen/prutalgen bin/ +export PATH=$PWD/bin:$PATH +which prutalgen +echo "" + +# touch all proto files to ensure `make` will generate new ones +find . -type f -name "*.proto" -exec touch {} + + +for dir in ./cases/*/; do + if [ ! -d "$dir" ]; then + continue + fi + cd $dir + echo "running test under $dir ..." + if [ -f "Makefile" ] || [ -f "makefile" ]; then + make test + elif [ -f "run.sh" ]; then + ./run.sh + else + echo "no makefile or run.sh found" + fi + echo "" + cd - >/dev/null +done