protostar 0.1.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +647 -0
- data/THIRD_PARTY_NOTICES.md +61 -0
- data/exe/protoc-gen-protostar +7 -0
- data/exe/protostar +7 -0
- data/lib/protostar/builder/generator.rb +71 -0
- data/lib/protostar/builder/sink.rb +35 -0
- data/lib/protostar/builder.rb +965 -0
- data/lib/protostar/cli.rb +267 -0
- data/lib/protostar/codec/decode_generator.rb +199 -0
- data/lib/protostar/codec/decoder.rb +296 -0
- data/lib/protostar/codec/encode_generator.rb +224 -0
- data/lib/protostar/codec/encoder.rb +121 -0
- data/lib/protostar/codec/sizer.rb +91 -0
- data/lib/protostar/codec.rb +15 -0
- data/lib/protostar/compiler/ast.rb +193 -0
- data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
- data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
- data/lib/protostar/compiler/emitters/grpc.rb +175 -0
- data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
- data/lib/protostar/compiler/emitters/rbs.rb +394 -0
- data/lib/protostar/compiler/emitters/ruby.rb +503 -0
- data/lib/protostar/compiler/emitters.rb +268 -0
- data/lib/protostar/compiler/include/LICENSE +32 -0
- data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
- data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
- data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
- data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
- data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
- data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
- data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
- data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
- data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
- data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
- data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
- data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
- data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
- data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
- data/lib/protostar/compiler/lexer.rb +659 -0
- data/lib/protostar/compiler/linker.rb +894 -0
- data/lib/protostar/compiler/option_interpreter.rb +363 -0
- data/lib/protostar/compiler/parser.rb +1696 -0
- data/lib/protostar/compiler/retention.rb +104 -0
- data/lib/protostar/compiler/run.rb +184 -0
- data/lib/protostar/compiler/source_info.rb +161 -0
- data/lib/protostar/compiler/symbol_table.rb +223 -0
- data/lib/protostar/compiler/text_format.rb +340 -0
- data/lib/protostar/compiler/validator.rb +944 -0
- data/lib/protostar/compiler.rb +304 -0
- data/lib/protostar/diagnostics/codes.rb +1307 -0
- data/lib/protostar/diagnostics/formats.rb +64 -0
- data/lib/protostar/diagnostics/renderer.rb +240 -0
- data/lib/protostar/diagnostics/source.rb +153 -0
- data/lib/protostar/diagnostics.rb +117 -0
- data/lib/protostar/dsl.rb +367 -0
- data/lib/protostar/enum.rb +70 -0
- data/lib/protostar/errors.rb +23 -0
- data/lib/protostar/google/protobuf/any_pb.rb +90 -0
- data/lib/protostar/google/protobuf/api_pb.rb +199 -0
- data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
- data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
- data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
- data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
- data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
- data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
- data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
- data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
- data/lib/protostar/google/protobuf/type_pb.rb +202 -0
- data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
- data/lib/protostar/google_protobuf_shim.rb +24 -0
- data/lib/protostar/json/encoder.rb +340 -0
- data/lib/protostar/json/errors.rb +34 -0
- data/lib/protostar/json/parser.rb +437 -0
- data/lib/protostar/json/path.rb +39 -0
- data/lib/protostar/json.rb +95 -0
- data/lib/protostar/lint/command.rb +197 -0
- data/lib/protostar/lint/config.rb +98 -0
- data/lib/protostar/lint/context.rb +126 -0
- data/lib/protostar/lint/directives.rb +56 -0
- data/lib/protostar/lint/fixer.rb +165 -0
- data/lib/protostar/lint/naming.rb +54 -0
- data/lib/protostar/lint/rule.rb +120 -0
- data/lib/protostar/lint/rules/comment_enum.rb +31 -0
- data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
- data/lib/protostar/lint/rules/comment_field.rb +33 -0
- data/lib/protostar/lint/rules/comment_message.rb +31 -0
- data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
- data/lib/protostar/lint/rules/comment_service.rb +31 -0
- data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
- data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
- data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
- data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
- data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
- data/lib/protostar/lint/rules/field_not_required.rb +39 -0
- data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
- data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
- data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/import_no_public.rb +29 -0
- data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
- data/lib/protostar/lint/rules/import_used.rb +33 -0
- data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
- data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
- data/lib/protostar/lint/rules/no_groups.rb +34 -0
- data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
- data/lib/protostar/lint/rules/package_defined.rb +28 -0
- data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
- data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
- data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
- data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
- data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
- data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
- data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
- data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
- data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
- data/lib/protostar/lint/rules/service_suffix.rb +28 -0
- data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
- data/lib/protostar/lint/runner.rb +135 -0
- data/lib/protostar/lint.rb +122 -0
- data/lib/protostar/message/accessors.rb +86 -0
- data/lib/protostar/message/coercion.rb +171 -0
- data/lib/protostar/message/field_info.rb +145 -0
- data/lib/protostar/message/reflection.rb +149 -0
- data/lib/protostar/message.rb +572 -0
- data/lib/protostar/plugin.rb +94 -0
- data/lib/protostar/schema/default_value.rb +113 -0
- data/lib/protostar/schema/descriptor.rb +41 -0
- data/lib/protostar/schema/draft/copy.rb +88 -0
- data/lib/protostar/schema/draft/resolution.rb +177 -0
- data/lib/protostar/schema/draft.rb +779 -0
- data/lib/protostar/schema/enum.rb +106 -0
- data/lib/protostar/schema/extension_range.rb +37 -0
- data/lib/protostar/schema/features.rb +153 -0
- data/lib/protostar/schema/field.rb +201 -0
- data/lib/protostar/schema/file.rb +217 -0
- data/lib/protostar/schema/freezer.rb +61 -0
- data/lib/protostar/schema/message.rb +104 -0
- data/lib/protostar/schema/oneof.rb +38 -0
- data/lib/protostar/schema/options.rb +367 -0
- data/lib/protostar/schema/pool.rb +179 -0
- data/lib/protostar/schema/proto.rb +576 -0
- data/lib/protostar/schema/resolver.rb +118 -0
- data/lib/protostar/schema/service.rb +89 -0
- data/lib/protostar/schema/source_info.rb +53 -0
- data/lib/protostar/schema.rb +65 -0
- data/lib/protostar/short_names/any.rb +7 -0
- data/lib/protostar/short_names/duration.rb +7 -0
- data/lib/protostar/short_names/empty.rb +7 -0
- data/lib/protostar/short_names/field_mask.rb +7 -0
- data/lib/protostar/short_names/struct.rb +9 -0
- data/lib/protostar/short_names/timestamp.rb +7 -0
- data/lib/protostar/short_names/wrappers.rb +15 -0
- data/lib/protostar/text/extensions.rb +114 -0
- data/lib/protostar/text/parser.rb +439 -0
- data/lib/protostar/text/printer.rb +304 -0
- data/lib/protostar/text/tokenizer.rb +116 -0
- data/lib/protostar/text.rb +67 -0
- data/lib/protostar/types.rb +152 -0
- data/lib/protostar/unknown_field.rb +58 -0
- data/lib/protostar/version.rb +5 -0
- data/lib/protostar/view/generator.rb +298 -0
- data/lib/protostar/view/map.rb +194 -0
- data/lib/protostar/view/repeated.rb +143 -0
- data/lib/protostar/view/scan.rb +183 -0
- data/lib/protostar/view.rb +283 -0
- data/lib/protostar/well_known/any.rb +52 -0
- data/lib/protostar/well_known/duration.rb +53 -0
- data/lib/protostar/well_known/list_value.rb +63 -0
- data/lib/protostar/well_known/struct.rb +55 -0
- data/lib/protostar/well_known/timestamp.rb +64 -0
- data/lib/protostar/well_known/value.rb +60 -0
- data/lib/protostar/well_known.rb +38 -0
- data/lib/protostar/wire/io_source.rb +40 -0
- data/lib/protostar/wire/reader.rb +254 -0
- data/lib/protostar/wire/writer.rb +201 -0
- data/lib/protostar/wire.rb +96 -0
- data/lib/protostar.rb +95 -0
- data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
- data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
- data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
- data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
- data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
- data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
- data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
- data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
- data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
- data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
- data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
- data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
- data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
- data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
- data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
- data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
- data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
- data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
- data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
- data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
- data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
- data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
- data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
- data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
- data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
- data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
- data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
- data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
- data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
- data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
- data/sig/protostar/lint.rbs +216 -0
- data/sig/protostar.rbs +326 -0
- metadata +366 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
module Codec
|
|
5
|
+
# The first encoding pass computes the encoded size of a message and of
|
|
6
|
+
# every message and packed run inside it, recorded in `sizes` (an
|
|
7
|
+
# identity Hash that lives for one encode) so the writer knows each
|
|
8
|
+
# length prefix before it writes the bytes it covers. The per-class
|
|
9
|
+
# `__size` methods ({EncodeGenerator}) do the work; this module has the
|
|
10
|
+
# parts they share.
|
|
11
|
+
# @api private
|
|
12
|
+
module Sizer
|
|
13
|
+
F = Message::FieldInfo
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# @param msg [Message]
|
|
18
|
+
# @param sizes [Hash] compare_by_identity
|
|
19
|
+
# @return [Integer]
|
|
20
|
+
def message(msg, sizes)
|
|
21
|
+
msg.__size(sizes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Size of one value, without its tag; a group's end tag is not included either
|
|
25
|
+
def value(info, v, sizes)
|
|
26
|
+
case info.kind
|
|
27
|
+
when F::INT32, F::INT64, F::UINT32, F::UINT64 then Wire.varint_size(v)
|
|
28
|
+
when F::ENUM then Wire.varint_size(Encoder.enum_number(info, v))
|
|
29
|
+
when F::SINT32, F::SINT64 then Wire.varint_size((v << 1) ^ (v >> 63))
|
|
30
|
+
when F::BOOL then 1
|
|
31
|
+
when F::STRING, F::BYTES
|
|
32
|
+
n = v.bytesize
|
|
33
|
+
Wire.varint_size(n) + n
|
|
34
|
+
when F::MESSAGE
|
|
35
|
+
n = v.__size(sizes)
|
|
36
|
+
Wire.varint_size(n) + n
|
|
37
|
+
when F::GROUP then v.__size(sizes)
|
|
38
|
+
when F::DOUBLE, F::FIXED64, F::SFIXED64 then 8
|
|
39
|
+
else 4
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Payload size of a packed run, without tag or length prefix
|
|
44
|
+
def packed(info, list)
|
|
45
|
+
list.each { |e| check(info, e) }
|
|
46
|
+
case info.kind
|
|
47
|
+
when F::DOUBLE, F::FIXED64, F::SFIXED64 then 8 * list.size
|
|
48
|
+
when F::FLOAT, F::FIXED32, F::SFIXED32 then 4 * list.size
|
|
49
|
+
when F::BOOL then list.size
|
|
50
|
+
when F::SINT32, F::SINT64 then list.sum { |v| Wire.varint_size((v << 1) ^ (v >> 63)) }
|
|
51
|
+
when F::ENUM then list.sum { |v| Wire.varint_size(Encoder.enum_number(info, v)) }
|
|
52
|
+
else list.sum { |v| Wire.varint_size(v) }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Size of every entry of a map field, tags and length prefixes included
|
|
57
|
+
def map(info, hash, sizes)
|
|
58
|
+
tag = info.tag.bytesize
|
|
59
|
+
total = 0
|
|
60
|
+
hash.each do |key, val|
|
|
61
|
+
n = entry(info, key, val, sizes)
|
|
62
|
+
total += tag + Wire.varint_size(n) + n
|
|
63
|
+
end
|
|
64
|
+
total
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Size of a map entry message: key field then value field, both always written
|
|
68
|
+
def entry(info, key, val, sizes)
|
|
69
|
+
k = info.key
|
|
70
|
+
v = info.value
|
|
71
|
+
check(k, key)
|
|
72
|
+
check(v, val)
|
|
73
|
+
k.tag.bytesize + value(k, key, sizes) + v.tag.bytesize + value(v, val, sizes)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# @param list [Array<UnknownField>]
|
|
77
|
+
def unknown(list)
|
|
78
|
+
list.sum { |u| Wire.varint_size((u.number << 3) | u.wire_type) + u.bytes.bytesize }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def check(info, value)
|
|
82
|
+
invalid(info, value) unless Message::Coercion.storable?(info, value)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @raise [EncodeError]
|
|
86
|
+
def invalid(info, value)
|
|
87
|
+
raise EncodeError, "#{info.descriptor.full_name} holds #{value.inspect[0, 64]}, which it cannot encode"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
# Binary encoding and decoding of {Message} objects. Use the methods on
|
|
5
|
+
# message classes and instances; these modules are their implementation.
|
|
6
|
+
module Codec
|
|
7
|
+
# Needed while a class is defined, never after
|
|
8
|
+
autoload :DecodeGenerator, ::File.expand_path("codec/decode_generator", __dir__)
|
|
9
|
+
autoload :EncodeGenerator, ::File.expand_path("codec/encode_generator", __dir__)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative "codec/decoder"
|
|
14
|
+
require_relative "codec/sizer"
|
|
15
|
+
require_relative "codec/encoder"
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class Compiler
|
|
5
|
+
# The parse tree of a `.proto` file. It holds what protoc's parser puts
|
|
6
|
+
# in a `FileDescriptorProto` before linking: names as written, options
|
|
7
|
+
# uninterpreted, map entries, groups and the synthetic oneofs of proto3
|
|
8
|
+
# `optional` fields already expanded, as protoc's parser expands them.
|
|
9
|
+
#
|
|
10
|
+
# Every node records where its parts start in {Node#positions}, keyed by
|
|
11
|
+
# the error locations protoc's DescriptorPool reports against (`:name`,
|
|
12
|
+
# `:number`, `:type`, `:extendee`, `:default_value`, `:option_name`,
|
|
13
|
+
# `:option_value`, `:input_type`, `:output_type`, `:editions`, `:other`),
|
|
14
|
+
# each a 0-based `[line, column]`.
|
|
15
|
+
module AST
|
|
16
|
+
# @api private
|
|
17
|
+
class Node
|
|
18
|
+
# @return [Hash{Symbol => Array(Integer, Integer)}]
|
|
19
|
+
attr_reader :positions
|
|
20
|
+
# Linker state: full name and source path
|
|
21
|
+
attr_accessor :full_name, :path
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
@positions = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Array(Integer, Integer), nil]
|
|
28
|
+
def position(kind)
|
|
29
|
+
@positions[kind]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A whole `.proto` file
|
|
34
|
+
class File < Node
|
|
35
|
+
attr_accessor :name, :package, :syntax, :edition, :options
|
|
36
|
+
attr_reader :dependency, :public_dependency, :weak_dependency, :option_dependency,
|
|
37
|
+
:message_type, :enum_type, :service, :extension, :import_positions
|
|
38
|
+
# @return [Array<SourceInfo::Location>] in protoc's order
|
|
39
|
+
attr_accessor :locations
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
super
|
|
43
|
+
@dependency = []
|
|
44
|
+
@public_dependency = []
|
|
45
|
+
@weak_dependency = []
|
|
46
|
+
@option_dependency = []
|
|
47
|
+
@message_type = []
|
|
48
|
+
@enum_type = []
|
|
49
|
+
@service = []
|
|
50
|
+
@extension = []
|
|
51
|
+
@import_positions = {}
|
|
52
|
+
@locations = []
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The options of one element: the uninterpreted `option` statements and
|
|
57
|
+
# the few options protoc's parser sets itself (`map_entry`)
|
|
58
|
+
class Options
|
|
59
|
+
# @return [Array<Option>]
|
|
60
|
+
attr_reader :uninterpreted
|
|
61
|
+
# @return [Hash] options set directly, e.g. `{map_entry: true}`
|
|
62
|
+
attr_reader :known
|
|
63
|
+
|
|
64
|
+
def initialize
|
|
65
|
+
@uninterpreted = []
|
|
66
|
+
@known = {}
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def empty?
|
|
70
|
+
@uninterpreted.empty? && @known.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A copy whose options carry no positions, as protoc's message copy
|
|
74
|
+
# leaves them out of its location table
|
|
75
|
+
def copy
|
|
76
|
+
other = Options.new
|
|
77
|
+
other.known.merge!(@known)
|
|
78
|
+
@uninterpreted.each { |o| other.uninterpreted << o.copy }
|
|
79
|
+
other
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# One uninterpreted option: `(my.ext).sub.field = value`
|
|
84
|
+
class Option < Node
|
|
85
|
+
# @return [Array<Array(String, Boolean)>] name parts and whether each is an extension
|
|
86
|
+
attr_reader :name
|
|
87
|
+
# @return [Symbol, nil] which value field is set: :identifier_value,
|
|
88
|
+
# :positive_int_value, :negative_int_value, :double_value,
|
|
89
|
+
# :string_value or :aggregate_value
|
|
90
|
+
attr_accessor :value_kind
|
|
91
|
+
attr_accessor :value
|
|
92
|
+
|
|
93
|
+
def initialize
|
|
94
|
+
super
|
|
95
|
+
@name = []
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def copy
|
|
99
|
+
other = Option.new
|
|
100
|
+
@name.each { |part| other.name << part }
|
|
101
|
+
other.value_kind = @value_kind
|
|
102
|
+
other.value = @value
|
|
103
|
+
other
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [String] the name as protoc prints it in errors: `(a.b).c`
|
|
107
|
+
def display_name
|
|
108
|
+
@name.map { |part, ext| ext ? "(#{part})" : part }.join(".")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# A message, including synthesized map entries and group bodies
|
|
113
|
+
class Message < Node
|
|
114
|
+
attr_accessor :name, :options, :visibility
|
|
115
|
+
attr_accessor :parent, :index
|
|
116
|
+
attr_reader :field, :nested_type, :enum_type, :extension_range, :extension, :oneof_decl,
|
|
117
|
+
:reserved_range, :reserved_name
|
|
118
|
+
|
|
119
|
+
def initialize
|
|
120
|
+
super
|
|
121
|
+
@field = []
|
|
122
|
+
@nested_type = []
|
|
123
|
+
@enum_type = []
|
|
124
|
+
@extension_range = []
|
|
125
|
+
@extension = []
|
|
126
|
+
@oneof_decl = []
|
|
127
|
+
@reserved_range = []
|
|
128
|
+
@reserved_name = []
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# A field or an extension
|
|
133
|
+
class Field < Node
|
|
134
|
+
attr_accessor :name, :number, :label, :type, :type_name, :extendee, :default_value, :json_name,
|
|
135
|
+
:oneof_index, :proto3_optional, :options
|
|
136
|
+
# Set by the linker: the declaring message (nil at file level), whether
|
|
137
|
+
# this is an extension, and its index
|
|
138
|
+
attr_accessor :parent, :extension, :index
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# A `oneof` declaration
|
|
142
|
+
class Oneof < Node
|
|
143
|
+
attr_accessor :name, :options
|
|
144
|
+
attr_accessor :parent, :index
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# An `extensions` range (end exclusive) or a `reserved` range
|
|
148
|
+
# (end exclusive for messages, inclusive for enums)
|
|
149
|
+
class Range < Node
|
|
150
|
+
attr_accessor :start, :end, :options
|
|
151
|
+
attr_accessor :parent, :index
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# An enum
|
|
155
|
+
class Enum < Node
|
|
156
|
+
attr_accessor :name, :options, :visibility
|
|
157
|
+
attr_accessor :parent, :index
|
|
158
|
+
attr_reader :value, :reserved_range, :reserved_name
|
|
159
|
+
|
|
160
|
+
def initialize
|
|
161
|
+
super
|
|
162
|
+
@value = []
|
|
163
|
+
@reserved_range = []
|
|
164
|
+
@reserved_name = []
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# An enum value
|
|
169
|
+
class EnumValue < Node
|
|
170
|
+
attr_accessor :name, :number, :options
|
|
171
|
+
attr_accessor :parent, :index
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# A service
|
|
175
|
+
class Service < Node
|
|
176
|
+
attr_accessor :name, :options
|
|
177
|
+
attr_accessor :index
|
|
178
|
+
attr_reader :rpcs
|
|
179
|
+
|
|
180
|
+
def initialize
|
|
181
|
+
super
|
|
182
|
+
@rpcs = []
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# An rpc
|
|
187
|
+
class Method < Node
|
|
188
|
+
attr_accessor :name, :input_type, :output_type, :options, :client_streaming, :server_streaming
|
|
189
|
+
attr_accessor :parent, :index
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class Compiler
|
|
5
|
+
module Emitters
|
|
6
|
+
# Writes a serialized `google.protobuf.FileDescriptorSet`, as protoc's
|
|
7
|
+
# `--descriptor_set_out` does
|
|
8
|
+
class DescriptorSet
|
|
9
|
+
# @param include_imports [Boolean] also write every file the given ones import,
|
|
10
|
+
# dependencies first (`import option` files included, as protoc does)
|
|
11
|
+
# @param include_source_info [Boolean] keep `source_code_info`
|
|
12
|
+
# @param pool [Schema::Pool, nil] where `import option` files and their extensions are found
|
|
13
|
+
def initialize(include_imports: false, include_source_info: false, pool: nil)
|
|
14
|
+
@include_imports = include_imports
|
|
15
|
+
@include_source_info = include_source_info
|
|
16
|
+
@pool = pool
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param files [Array<Schema::File>] the files named on the command line
|
|
20
|
+
# @param available [Array<Schema::File>] files `import option` names are looked up in
|
|
21
|
+
# before the pool, such as everything {Compiler#compile} returned
|
|
22
|
+
# @return [String] binary
|
|
23
|
+
def emit(files, available: [])
|
|
24
|
+
out = "".b
|
|
25
|
+
w = Wire::Writer.new(out)
|
|
26
|
+
@available = available.to_h { |f| [f.name, f] }
|
|
27
|
+
ordered(files).each do |file|
|
|
28
|
+
w.write_tag(1, Wire::LENGTH_DELIMITED)
|
|
29
|
+
w.write_bytes(file.serialize(source_info: @include_source_info))
|
|
30
|
+
end
|
|
31
|
+
out
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
# protoc's GetTransitiveDependencies: depth first, each file after its imports
|
|
37
|
+
def ordered(files)
|
|
38
|
+
return files unless @include_imports
|
|
39
|
+
seen = {}
|
|
40
|
+
out = []
|
|
41
|
+
visit = lambda do |file|
|
|
42
|
+
next if seen[file.name]
|
|
43
|
+
seen[file.name] = true
|
|
44
|
+
file.dependency_files.each { |dep| visit.call(dep) }
|
|
45
|
+
file.option_dependencies.each do |name|
|
|
46
|
+
dep = @available[name] || @pool&.file(name)
|
|
47
|
+
visit.call(dep) if dep
|
|
48
|
+
end
|
|
49
|
+
out << file
|
|
50
|
+
end
|
|
51
|
+
files.each { |f| visit.call(f) }
|
|
52
|
+
out
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class Compiler
|
|
5
|
+
module Emitters
|
|
6
|
+
# Writes the `_pb.rb` file protoc's own Ruby generator writes
|
|
7
|
+
# (ruby_generator.cc): the serialized descriptor added to
|
|
8
|
+
# google-protobuf's generated pool, and constants looked up from it.
|
|
9
|
+
class GoogleRuby
|
|
10
|
+
# @param pool [Schema::Pool, nil] where extensions used in options but defined in
|
|
11
|
+
# `import option` files are found
|
|
12
|
+
def initialize(pool: nil)
|
|
13
|
+
@pool = pool
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param files [Array<Schema::File>]
|
|
17
|
+
# @return [Hash{String => String}] output path to source
|
|
18
|
+
def emit(files)
|
|
19
|
+
files.to_h { |f| [Naming.require_name(f.name) + ".rb", file(f)] }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# @param file [Schema::File]
|
|
23
|
+
# @return [String]
|
|
24
|
+
# @raise [Emitters::Error] for a `ruby_package` protoc refuses
|
|
25
|
+
def file(file)
|
|
26
|
+
Naming.check_ruby_package(file)
|
|
27
|
+
p = Printer.new
|
|
28
|
+
p.print("# frozen_string_literal: true\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n" \
|
|
29
|
+
"# source: #{file.name}\n\n")
|
|
30
|
+
p.print("require 'google/protobuf'\n\n")
|
|
31
|
+
unless file.dependencies.empty?
|
|
32
|
+
file.dependencies.each { |dep| p.print("require '#{Naming.require_name(dep)}'\n") }
|
|
33
|
+
p.print("\n")
|
|
34
|
+
end
|
|
35
|
+
p.print("\ndescriptor_data = \"#{GoogleRuby.ruby_escape(GoogleRuby.serialized(file))}\"\n\n" \
|
|
36
|
+
"pool = ::Google::Protobuf::DescriptorPool.generated_pool\npool.add_serialized_file(descriptor_data)\n\n")
|
|
37
|
+
modules = Naming.module_path(file)
|
|
38
|
+
modules.each do |m|
|
|
39
|
+
p.print("module #{m}\n")
|
|
40
|
+
p.indent
|
|
41
|
+
end
|
|
42
|
+
file.messages.each { |m| message_assignment(p, "", m) }
|
|
43
|
+
file.enums.each { |e| enum_assignment(p, "", e) }
|
|
44
|
+
modules.size.times do
|
|
45
|
+
p.outdent
|
|
46
|
+
p.print("end\n")
|
|
47
|
+
end
|
|
48
|
+
p.to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# What protoc embeds: the file as `FileDescriptor::CopyTo` writes it,
|
|
52
|
+
# without source info and with `json_name` only where the .proto set
|
|
53
|
+
# one. Whether it was set is not kept, so a `json_name` equal to the
|
|
54
|
+
# derived one is taken as not set.
|
|
55
|
+
# @return [String] binary
|
|
56
|
+
def self.serialized(file)
|
|
57
|
+
file.serialize(source_info: false, default_json_names: false)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
PRINTABLE = (0x20..0x7E)
|
|
61
|
+
HEX = /[0-9A-Fa-f]/
|
|
62
|
+
C_ESCAPES = {"\n" => "\\n", "\r" => "\\r", "\t" => "\\t", "\"" => "\\\"", "'" => "\\'", "\\" => "\\\\"}.freeze
|
|
63
|
+
|
|
64
|
+
# absl::CHexEscape, then a backslash before every `#` that would
|
|
65
|
+
# start an interpolation: ruby_generator.cc's RubyEscape
|
|
66
|
+
def self.ruby_escape(bytes)
|
|
67
|
+
escaped = +""
|
|
68
|
+
last_hex = false
|
|
69
|
+
bytes.each_byte do |b|
|
|
70
|
+
c = b.chr
|
|
71
|
+
hex = false
|
|
72
|
+
if (e = C_ESCAPES[c])
|
|
73
|
+
escaped << e
|
|
74
|
+
elsif !PRINTABLE.cover?(b) || (last_hex && HEX.match?(c))
|
|
75
|
+
escaped << format("\\x%02x", b)
|
|
76
|
+
hex = true
|
|
77
|
+
else
|
|
78
|
+
escaped << c
|
|
79
|
+
end
|
|
80
|
+
last_hex = hex
|
|
81
|
+
end
|
|
82
|
+
escaped.gsub(/#(?=[{$@])/) { "\\#" }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def message_assignment(p, prefix, message)
|
|
88
|
+
return if message.map_entry?
|
|
89
|
+
name = Naming.rubify_constant(message.name)
|
|
90
|
+
p.print("#{prefix}#{name} = ::Google::Protobuf::DescriptorPool.generated_pool." \
|
|
91
|
+
"lookup(\"#{message.full_name}\").msgclass\n")
|
|
92
|
+
message.nested_messages.each { |m| message_assignment(p, "#{prefix}#{name}::", m) }
|
|
93
|
+
message.nested_enums.each { |e| enum_assignment(p, "#{prefix}#{name}::", e) }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def enum_assignment(p, prefix, enum)
|
|
97
|
+
p.print("#{prefix}#{Naming.rubify_constant(enum.name)} = ::Google::Protobuf::DescriptorPool.generated_pool." \
|
|
98
|
+
"lookup(\"#{enum.full_name}\").enummodule\n")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class Compiler
|
|
5
|
+
module Emitters
|
|
6
|
+
# Writes the `_services_pb.rb` stubs grpc-tools' Ruby plugin writes
|
|
7
|
+
# (grpc's src/compiler/ruby_generator.cc). Comments come from source
|
|
8
|
+
# info, so compile with it.
|
|
9
|
+
class Grpc
|
|
10
|
+
# @param files [Array<Schema::File>]
|
|
11
|
+
# @return [Hash{String => String}] output path to source, for files with services only
|
|
12
|
+
def emit(files)
|
|
13
|
+
files.each_with_object({}) do |f, out|
|
|
14
|
+
source = file(f)
|
|
15
|
+
out[Grpc.output_name(f.name)] = source unless source.empty?
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param name [String] "foo/bar.proto"
|
|
20
|
+
# @return [String] "foo/bar_services_pb.rb"
|
|
21
|
+
# @raise [Emitters::Error] for a name protoc's plugin refuses
|
|
22
|
+
def self.output_name(name)
|
|
23
|
+
unless name.size > 6 && ".proto".include?(name[-1])
|
|
24
|
+
raise Error, "Invalid proto file name: must end with .proto"
|
|
25
|
+
end
|
|
26
|
+
name[0, name.size - 6] + "_services_pb.rb"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param file [Schema::File]
|
|
30
|
+
# @return [String] empty when the file declares no services
|
|
31
|
+
def file(file)
|
|
32
|
+
return "" if file.services.empty?
|
|
33
|
+
@source = file.source_info
|
|
34
|
+
package = ruby_package(file)
|
|
35
|
+
p = Printer.new
|
|
36
|
+
p.print("# Generated by the protocol buffer compiler. DO NOT EDIT!\n")
|
|
37
|
+
p.print("# Source: #{file.name} for package '#{package}'\n")
|
|
38
|
+
file_comments = file_comments(file)
|
|
39
|
+
unless file_comments.empty?
|
|
40
|
+
p.print("# Original file comments:\n")
|
|
41
|
+
p.print(file_comments)
|
|
42
|
+
end
|
|
43
|
+
p.print("\nrequire 'grpc'\n")
|
|
44
|
+
p.print("require '#{file.name.sub(".proto", "_pb")}'\n\n")
|
|
45
|
+
modules = split(package)
|
|
46
|
+
modules.each do |m|
|
|
47
|
+
p.print("module #{Naming.package_to_module(m)}\n")
|
|
48
|
+
p.indent
|
|
49
|
+
end
|
|
50
|
+
file.services.each_with_index { |s, i| service(p, s, [6, i]) }
|
|
51
|
+
modules.size.times do
|
|
52
|
+
p.outdent
|
|
53
|
+
p.print("end\n")
|
|
54
|
+
end
|
|
55
|
+
p.to_s
|
|
56
|
+
ensure
|
|
57
|
+
@source = nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def service(p, service, path)
|
|
63
|
+
return if service.rpcs.empty?
|
|
64
|
+
p.print("module #{modularize(service.name)}\n")
|
|
65
|
+
p.indent
|
|
66
|
+
p.print(comments(path, true))
|
|
67
|
+
p.print("class Service\n")
|
|
68
|
+
p.indent
|
|
69
|
+
p.print("\ninclude ::GRPC::GenericService\n\n")
|
|
70
|
+
p.print("self.marshal_class_method = :encode\nself.unmarshal_class_method = :decode\n")
|
|
71
|
+
p.print("self.service_name = '#{service.full_name}'\n\n")
|
|
72
|
+
service.rpcs.each_with_index do |rpc, i|
|
|
73
|
+
input = type_of(rpc.input_type)
|
|
74
|
+
input = "stream(#{input})" if rpc.client_streaming?
|
|
75
|
+
output = type_of(rpc.output_type)
|
|
76
|
+
output = "stream(#{output})" if rpc.server_streaming?
|
|
77
|
+
p.print(comments(path + [2, i], true))
|
|
78
|
+
p.print("rpc :#{rpc.name}, #{input}, #{output}\n")
|
|
79
|
+
p.print(comments(path + [2, i], false))
|
|
80
|
+
end
|
|
81
|
+
p.outdent
|
|
82
|
+
p.print("end\n\nStub = Service.rpc_stub_class\n")
|
|
83
|
+
p.outdent
|
|
84
|
+
p.print("end\n")
|
|
85
|
+
p.print(comments(path, false))
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The package in dotted form, `ruby_package`'s `::` turned into dots
|
|
89
|
+
def ruby_package(file)
|
|
90
|
+
file.options.ruby_package&.gsub("::", ".") || file.package
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# std::getline splitting: no trailing empty piece
|
|
94
|
+
def split(text)
|
|
95
|
+
parts = text.split(".", -1)
|
|
96
|
+
parts.pop if parts.last == ""
|
|
97
|
+
parts
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def modularize(name)
|
|
101
|
+
return name if name.empty?
|
|
102
|
+
out = name[0].upcase(:ascii)
|
|
103
|
+
last_underscore = false
|
|
104
|
+
name[1..].each_char do |c|
|
|
105
|
+
if last_underscore && c != "_"
|
|
106
|
+
out << c.upcase(:ascii)
|
|
107
|
+
elsif c != "_"
|
|
108
|
+
out << c
|
|
109
|
+
end
|
|
110
|
+
last_underscore = c == "_"
|
|
111
|
+
end
|
|
112
|
+
out
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# grpc's RubyTypeOf: the message's Ruby constant path as grpc spells it
|
|
116
|
+
def type_of(message)
|
|
117
|
+
name = message.full_name
|
|
118
|
+
file = message.file
|
|
119
|
+
if file.options.ruby_package
|
|
120
|
+
name = name.delete_prefix(file.package) if name.start_with?(file.package)
|
|
121
|
+
name = name.delete_prefix(".")
|
|
122
|
+
name = "#{ruby_package(file)}.#{name}"
|
|
123
|
+
end
|
|
124
|
+
parts = split(".#{name}")
|
|
125
|
+
parts.each_with_index.map do |part, i|
|
|
126
|
+
(i < parts.size - 1) ? modularize(part) : Naming.rubify_constant(part)
|
|
127
|
+
end.join("::")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def lines(text)
|
|
131
|
+
return [] if text.nil?
|
|
132
|
+
parts = text.split("\n", -1)
|
|
133
|
+
parts.pop if parts.last == ""
|
|
134
|
+
parts
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# GetPrefixedComments: detached and leading comments, or trailing ones
|
|
138
|
+
def comments(path, leading)
|
|
139
|
+
location = @source&.location(path)
|
|
140
|
+
return "" unless location
|
|
141
|
+
out = []
|
|
142
|
+
if leading
|
|
143
|
+
location.leading_detached_comments.each { |c| out.concat(lines(c)) << "" }
|
|
144
|
+
out.concat(lines(location.leading_comments))
|
|
145
|
+
else
|
|
146
|
+
out.concat(lines(location.trailing_comments))
|
|
147
|
+
end
|
|
148
|
+
prefixed(out)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def prefixed(out)
|
|
152
|
+
out.map do |line|
|
|
153
|
+
if line.empty?
|
|
154
|
+
"#\n"
|
|
155
|
+
elsif line.start_with?(" ")
|
|
156
|
+
"##{line}\n"
|
|
157
|
+
else
|
|
158
|
+
"# #{line}\n"
|
|
159
|
+
end
|
|
160
|
+
end.join
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# The comments above the syntax line, printed raw: a `$` comes out doubled
|
|
164
|
+
def file_comments(file)
|
|
165
|
+
location = @source&.location([12])
|
|
166
|
+
return "" unless location
|
|
167
|
+
out = []
|
|
168
|
+
location.leading_detached_comments.each { |c| out.concat(lines(c)) << "" }
|
|
169
|
+
out.concat(lines(location.leading_comments))
|
|
170
|
+
prefixed(out).gsub("$", "$$")
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|