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,298 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class View
|
|
5
|
+
# Makes the view class of a message class: Ruby source for its index
|
|
6
|
+
# builder and its readers, `class_eval`ed into a subclass of {View}, the
|
|
7
|
+
# way {Codec::DecodeGenerator} makes decoders.
|
|
8
|
+
#
|
|
9
|
+
# A view of a class with `n` fields keeps one Array, the index:
|
|
10
|
+
#
|
|
11
|
+
# [0, n) per field: an inline varint value, an offset, a flat Array
|
|
12
|
+
# of offsets and lengths (merged messages), or the entries of
|
|
13
|
+
# a repeated or map field; nil when the field is not set
|
|
14
|
+
# [n, 2n) the length belonging to an offset in the first part
|
|
15
|
+
# [2n, 3n) decoded values, kept after the first read
|
|
16
|
+
# 3n unknown fields as number, wire type, start, end
|
|
17
|
+
# 3n + 1 the UnknownField list, once asked for
|
|
18
|
+
# @api private
|
|
19
|
+
module Generator
|
|
20
|
+
F = Message::FieldInfo
|
|
21
|
+
|
|
22
|
+
module_function
|
|
23
|
+
|
|
24
|
+
# @param klass [Class] a message class
|
|
25
|
+
# @param fields [Array<Message::FieldInfo>]
|
|
26
|
+
# @param oneofs [Array<Array(Symbol, Array<Integer>)>]
|
|
27
|
+
# @return [Class] the view class
|
|
28
|
+
def install(klass, fields, oneofs)
|
|
29
|
+
view = Class.new(View)
|
|
30
|
+
view.__define(klass, fields)
|
|
31
|
+
view.class_eval(source(fields, oneofs, reserved), "(protostar view #{klass.descriptor.full_name})", 1)
|
|
32
|
+
klass.instance_variable_set(:@__view_class, view)
|
|
33
|
+
view
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Names a view must not lose to a field: its own methods and those a
|
|
37
|
+
# message keeps for itself, so that views and messages have the same readers
|
|
38
|
+
def reserved
|
|
39
|
+
(View.public_instance_methods + Message.public_instance_methods + %i[initialize]).to_h { |m| [m, true] }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def source(fields, oneofs, reserved)
|
|
43
|
+
n = fields.size
|
|
44
|
+
taken = reserved.dup
|
|
45
|
+
fields.each { |info| taken[:"__f#{info.index}"] = true }
|
|
46
|
+
src = +"# frozen_string_literal: true\n"
|
|
47
|
+
src << initializer(n)
|
|
48
|
+
src << scanner(fields, n)
|
|
49
|
+
fields.each { |info| src << reader(info, n) }
|
|
50
|
+
fields.each do |info|
|
|
51
|
+
name = info.name
|
|
52
|
+
i = info.index
|
|
53
|
+
[
|
|
54
|
+
[name.to_sym, "alias_method :#{name}, :__f#{i}\n"],
|
|
55
|
+
[:"#{name}=", "def #{name}=(_value); __frozen!; end\n"]
|
|
56
|
+
].each do |method, code|
|
|
57
|
+
next if taken[method]
|
|
58
|
+
taken[method] = true
|
|
59
|
+
src << code
|
|
60
|
+
end
|
|
61
|
+
next unless info.presence
|
|
62
|
+
[
|
|
63
|
+
[:"has_#{name}?", "def has_#{name}?; !@ix[#{i}].nil?; end\n"],
|
|
64
|
+
[:"clear_#{name}", "def clear_#{name}; __frozen!; end\n"]
|
|
65
|
+
].each do |method, code|
|
|
66
|
+
next if taken[method]
|
|
67
|
+
taken[method] = true
|
|
68
|
+
src << code
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
oneofs.each do |oneof_name, slots|
|
|
72
|
+
which = :"which_#{oneof_name}"
|
|
73
|
+
next if taken[which]
|
|
74
|
+
taken[which] = true
|
|
75
|
+
src << "def which_#{oneof_name}\nix = @ix\n"
|
|
76
|
+
slots.each { |slot| src << "return :#{fields[slot].name} unless ix[#{slot}].nil?\n" }
|
|
77
|
+
src << "nil\nend\n"
|
|
78
|
+
end
|
|
79
|
+
src
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def initializer(n)
|
|
83
|
+
<<~RUBY
|
|
84
|
+
def initialize(buf, off, len, segs, depth)
|
|
85
|
+
@buf = buf
|
|
86
|
+
@off = off
|
|
87
|
+
@len = len
|
|
88
|
+
@segs = segs
|
|
89
|
+
@depth = depth
|
|
90
|
+
ix = Array.new(#{3 * n + 2})
|
|
91
|
+
__scan(buf, off, off + len, ix, depth)
|
|
92
|
+
if segs
|
|
93
|
+
k = 2
|
|
94
|
+
while k < segs.size
|
|
95
|
+
__scan(buf, segs[k], segs[k] + segs[k + 1], ix, depth)
|
|
96
|
+
k += 2
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
@ix = ix
|
|
100
|
+
freeze
|
|
101
|
+
end
|
|
102
|
+
RUBY
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def scanner(fields, n)
|
|
106
|
+
branches = fields.map { |info| branch(info, n) }.join
|
|
107
|
+
<<~RUBY
|
|
108
|
+
def __scan(buf, pos, limit, ix, depth)
|
|
109
|
+
c = nil
|
|
110
|
+
while pos < limit
|
|
111
|
+
at = pos
|
|
112
|
+
if (tag = buf.getbyte(pos)) < 0x80
|
|
113
|
+
pos += 1
|
|
114
|
+
elsif pos + 1 < limit && (b = buf.getbyte(pos + 1)) < 0x80
|
|
115
|
+
tag = (tag & 0x7F) | (b << 7)
|
|
116
|
+
pos += 2
|
|
117
|
+
else
|
|
118
|
+
c ||= Protostar::Wire::Reader.new(buf, pos, limit)
|
|
119
|
+
c.pos = pos
|
|
120
|
+
tag = c.read_varint
|
|
121
|
+
pos = c.pos
|
|
122
|
+
end
|
|
123
|
+
#{branches.empty? ? "if false" : "case tag"}
|
|
124
|
+
#{branches}
|
|
125
|
+
else
|
|
126
|
+
c ||= Protostar::Wire::Reader.new(buf, pos, limit)
|
|
127
|
+
c.pos = pos
|
|
128
|
+
Protostar::View::Scan.other(c, tag, at, ix, #{3 * n}, depth)
|
|
129
|
+
pos = c.pos
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
RUBY
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def branch(info, n)
|
|
137
|
+
tag = (info.number << 3) | info.wire_type
|
|
138
|
+
out = "when #{tag} # #{info.name}\n"
|
|
139
|
+
out << if info.map
|
|
140
|
+
map_branch(info, n)
|
|
141
|
+
elsif info.kind == F::GROUP
|
|
142
|
+
group_branch(info, n)
|
|
143
|
+
elsif info.message? || info.kind == F::STRING || info.kind == F::BYTES
|
|
144
|
+
"#{length}#{store_segment(info, n, "pos")}pos += len\n"
|
|
145
|
+
elsif (fixed = F::FIXED[info.kind])
|
|
146
|
+
width = fixed[0]
|
|
147
|
+
"raise Protostar::DecodeError.new(\"truncated #{width * 8}-bit value\", offset: pos) if pos + #{width} > limit\n" \
|
|
148
|
+
"#{store(info, "pos")}pos += #{width}\n"
|
|
149
|
+
elsif info.kind == F::ENUM
|
|
150
|
+
enum_branch(info, n)
|
|
151
|
+
else
|
|
152
|
+
"#{varint("v")}#{store(info, "v")}"
|
|
153
|
+
end
|
|
154
|
+
out << packed_branch(info, n) if info.packable
|
|
155
|
+
out
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Reads a length prefix into `len`, checked against the limit
|
|
159
|
+
def length
|
|
160
|
+
"#{varint("len")}raise Protostar::DecodeError.new(\"length \#{len} runs past the end of the input\", offset: pos) if pos + len > limit\n"
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def varint(var)
|
|
164
|
+
<<~RUBY
|
|
165
|
+
if pos < limit && (#{var} = buf.getbyte(pos)) < 0x80
|
|
166
|
+
pos += 1
|
|
167
|
+
elsif pos + 1 < limit && (b = buf.getbyte(pos + 1)) < 0x80
|
|
168
|
+
#{var} = (#{var} & 0x7F) | (b << 7)
|
|
169
|
+
pos += 2
|
|
170
|
+
else
|
|
171
|
+
c ||= Protostar::Wire::Reader.new(buf, pos, limit)
|
|
172
|
+
c.pos = pos
|
|
173
|
+
#{var} = c.read_varint
|
|
174
|
+
pos = c.pos
|
|
175
|
+
end
|
|
176
|
+
RUBY
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# A singular scalar, or one unpacked element of a repeated one
|
|
180
|
+
def store(info, var)
|
|
181
|
+
i = info.index
|
|
182
|
+
return "(ix[#{i}] ||= []) << #{var} << -1\n" if info.repeated
|
|
183
|
+
"#{clear_siblings(info)}ix[#{i}] = #{var}\n"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def clear_siblings(info)
|
|
187
|
+
info.siblings ? info.siblings.map { |s| "ix[#{s}] = nil\n" }.join : ""
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# A length-delimited value or group contents at `start`, `len` bytes long
|
|
191
|
+
def store_segment(info, n, start)
|
|
192
|
+
i = info.index
|
|
193
|
+
return "(ix[#{i}] ||= []) << #{start} << len\n" if info.repeated
|
|
194
|
+
return "#{clear_siblings(info)}ix[#{i}] = #{start}\nix[#{n + i}] = len\n" unless info.message?
|
|
195
|
+
# a message that occurs again merges: its view reads every piece
|
|
196
|
+
<<~RUBY
|
|
197
|
+
if (o = ix[#{i}]).nil?
|
|
198
|
+
#{clear_siblings(info)}ix[#{i}] = #{start}
|
|
199
|
+
ix[#{n + i}] = len
|
|
200
|
+
elsif o.is_a?(Integer)
|
|
201
|
+
ix[#{i}] = [o, ix[#{n + i}], #{start}, len]
|
|
202
|
+
else
|
|
203
|
+
o << #{start} << len
|
|
204
|
+
end
|
|
205
|
+
RUBY
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def group_branch(info, n)
|
|
209
|
+
<<~RUBY
|
|
210
|
+
c ||= Protostar::Wire::Reader.new(buf, pos, limit)
|
|
211
|
+
c.pos = pos
|
|
212
|
+
len = Protostar::View::Scan.group_end(c, #{info.number}, depth) - pos
|
|
213
|
+
#{store_segment(info, n, "pos")}pos = c.pos
|
|
214
|
+
RUBY
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def enum_branch(info, n)
|
|
218
|
+
narrow = "v -= 0x1_0000_0000 if (v &= 0xFFFF_FFFF) >= 0x8000_0000\n"
|
|
219
|
+
return "#{varint("v")}#{narrow}#{store(info, "v")}" unless info.closed_enum
|
|
220
|
+
src = "start = pos\n#{varint("v")}#{narrow}"
|
|
221
|
+
src << "if self.class.__fields[#{info.index}].enum_names.key?(v)\n#{store(info, "v")}"
|
|
222
|
+
src << "else\n(ix[#{3 * n}] ||= []) << #{info.number} << 0 << start << pos\nend\n"
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def packed_branch(info, n)
|
|
226
|
+
i = info.index
|
|
227
|
+
out = "when #{(info.number << 3) | Wire::LENGTH_DELIMITED} # #{info.name}, packed\n#{length}"
|
|
228
|
+
if (fixed = F::FIXED[info.kind])
|
|
229
|
+
out << "raise Protostar::DecodeError.new(\"packed #{info.descriptor.type} length \#{len} is not a multiple of #{fixed[0]}\", offset: pos) " \
|
|
230
|
+
"unless (len % #{fixed[0]}).zero?\n"
|
|
231
|
+
elsif info.closed_enum
|
|
232
|
+
out << "c ||= Protostar::Wire::Reader.new(buf, pos, limit)\n"
|
|
233
|
+
out << "Protostar::View::Scan.packed_closed_enum(c, pos, pos + len, ix, #{3 * n}, #{info.number}, self.class.__fields[#{i}].enum_names)\n"
|
|
234
|
+
else
|
|
235
|
+
out << "Protostar::View::Scan.check_varints(buf, pos, pos + len)\n"
|
|
236
|
+
end
|
|
237
|
+
out << "(ix[#{i}] ||= []) << pos << len if len > 0\npos += len\n"
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def map_branch(info, n)
|
|
241
|
+
i = info.index
|
|
242
|
+
if info.value.closed_enum
|
|
243
|
+
out = "lp = pos\n#{length}"
|
|
244
|
+
out << "if Protostar::View::Scan.map_unknown_enum?(buf, pos, len, self.class.__fields[#{i}], depth)\n"
|
|
245
|
+
out << "(ix[#{3 * n}] ||= []) << #{info.number} << 2 << lp << pos + len\nelse\n"
|
|
246
|
+
out << "(ix[#{i}] ||= []) << pos << len\nend\n"
|
|
247
|
+
else
|
|
248
|
+
out = "#{length}(ix[#{i}] ||= []) << pos << len\n"
|
|
249
|
+
end
|
|
250
|
+
out << "pos += len\n"
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def reader(info, n)
|
|
254
|
+
i = info.index
|
|
255
|
+
m = 2 * n + i
|
|
256
|
+
field = "self.class.__fields[#{i}]"
|
|
257
|
+
body = if info.map
|
|
258
|
+
"@ix[#{m}] ||= ((e = @ix[#{i}]) ? Protostar::View::Map.new(@buf, #{field}, e, @depth) : Protostar::View::Map::EMPTY)"
|
|
259
|
+
elsif info.repeated
|
|
260
|
+
"@ix[#{m}] ||= ((e = @ix[#{i}]) ? Protostar::View::Repeated.new(@buf, #{field}, e, @depth) : Protostar::View::Repeated::EMPTY)"
|
|
261
|
+
elsif info.message?
|
|
262
|
+
"ix = @ix\n(v = ix[#{m}]) ? v : ((o = ix[#{i}]) ? (ix[#{m}] = __nested(#{i}, o, ix[#{n + i}])) : nil)"
|
|
263
|
+
elsif info.kind == F::STRING
|
|
264
|
+
memo(i, m, info, "Protostar::View::Scan.string(@buf, o, ix[#{n + i}], #{field})")
|
|
265
|
+
elsif info.kind == F::BYTES
|
|
266
|
+
memo(i, m, info, "Protostar::View::Scan.bytes(@buf, o, ix[#{n + i}])")
|
|
267
|
+
elsif (fixed = F::FIXED[info.kind])
|
|
268
|
+
memo(i, m, info, "@buf.unpack1(#{fixed[1].inspect}, offset: o)")
|
|
269
|
+
else
|
|
270
|
+
"v = @ix[#{i}]\nreturn #{default(info)} if v.nil?\n#{convert(info)}"
|
|
271
|
+
end
|
|
272
|
+
"def __f#{i}\n#{body}\nend\n"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def memo(i, m, info, decode)
|
|
276
|
+
"ix = @ix\n(v = ix[#{m}]).nil? ? ((o = ix[#{i}]) ? (ix[#{m}] = #{decode}) : #{default(info)}) : v"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def default(info)
|
|
280
|
+
Message::Accessors.default_literal(info)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Source turning the raw varint in `v` into the field's value
|
|
284
|
+
def convert(info)
|
|
285
|
+
case info.kind
|
|
286
|
+
when F::INT32 then "v -= 0x1_0000_0000 if (v &= 0xFFFF_FFFF) >= 0x8000_0000\nv"
|
|
287
|
+
when F::INT64 then "v >= 0x8000_0000_0000_0000 ? v - 0x1_0000_0000_0000_0000 : v"
|
|
288
|
+
when F::UINT32 then "v & 0xFFFF_FFFF"
|
|
289
|
+
when F::UINT64 then "v"
|
|
290
|
+
when F::SINT32 then "v &= 0xFFFF_FFFF\n(v >> 1) ^ -(v & 1)"
|
|
291
|
+
when F::SINT64 then "(v >> 1) ^ -(v & 1)"
|
|
292
|
+
when F::BOOL then "v != 0"
|
|
293
|
+
when F::ENUM then "self.class.__fields[#{info.index}].enum_names[v] || v"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class View
|
|
5
|
+
# The value of a map field read through a view. The entries are read on
|
|
6
|
+
# first use, keys and scalar values decoded; message values stay
|
|
7
|
+
# positions until read, then become nested views. A key that occurs
|
|
8
|
+
# twice keeps the last entry, as decoding does.
|
|
9
|
+
#
|
|
10
|
+
# view.attrs["colour"]
|
|
11
|
+
# view.attrs.key?("size")
|
|
12
|
+
# view.attrs.to_h
|
|
13
|
+
class Map
|
|
14
|
+
include Enumerable
|
|
15
|
+
|
|
16
|
+
F = Message::FieldInfo
|
|
17
|
+
private_constant :F
|
|
18
|
+
|
|
19
|
+
# Reads one map entry
|
|
20
|
+
# @return [Array(Object, Object, Boolean)] key, value and whether the
|
|
21
|
+
# value is a closed enum's unknown number. A message value is the
|
|
22
|
+
# flat Array of its segments' offsets and lengths.
|
|
23
|
+
# @api private
|
|
24
|
+
def self.entry(buf, offset, length, info, depth)
|
|
25
|
+
c = Wire::Reader.new(buf, offset, offset + length)
|
|
26
|
+
key_info = info.key
|
|
27
|
+
value_info = info.value
|
|
28
|
+
key = nil
|
|
29
|
+
value = nil
|
|
30
|
+
unknown_enum = false
|
|
31
|
+
while c.pos < c.limit
|
|
32
|
+
at = c.pos
|
|
33
|
+
tag = c.read_varint
|
|
34
|
+
Wire::Reader.check_tag(tag, at)
|
|
35
|
+
number = tag >> 3
|
|
36
|
+
wt = tag & 7
|
|
37
|
+
if number == 1 && wt == key_info.wire_type
|
|
38
|
+
key = Codec::Decoder.scalar(c, key_info)
|
|
39
|
+
elsif number == 2 && wt == value_info.wire_type
|
|
40
|
+
if value_info.message?
|
|
41
|
+
n = c.read_varint
|
|
42
|
+
start = c.pos
|
|
43
|
+
c.skip(n)
|
|
44
|
+
value = [] unless value.is_a?(Array)
|
|
45
|
+
value << start << n
|
|
46
|
+
elsif value_info.kind == F::ENUM
|
|
47
|
+
n = c.read_int32
|
|
48
|
+
value = value_info.enum_names[n]
|
|
49
|
+
if value.nil?
|
|
50
|
+
unknown_enum = value_info.closed_enum
|
|
51
|
+
value = n
|
|
52
|
+
else
|
|
53
|
+
unknown_enum = false
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
value = Codec::Decoder.scalar(c, value_info)
|
|
57
|
+
end
|
|
58
|
+
else
|
|
59
|
+
raise DecodeError.new("unmatched end of group #{number}", offset: at) if wt == Wire::END_GROUP
|
|
60
|
+
c.skip_field(number, wt, depth)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
key = key_info.default if key.nil?
|
|
64
|
+
if value.nil?
|
|
65
|
+
value = value_info.message? ? [] : value_info.default
|
|
66
|
+
end
|
|
67
|
+
[key, value, unknown_enum]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @api private
|
|
71
|
+
def initialize(buf, info, entries, depth)
|
|
72
|
+
@buf = buf
|
|
73
|
+
@info = info
|
|
74
|
+
@entries = entries
|
|
75
|
+
@depth = depth
|
|
76
|
+
@memo = [info ? nil : Message::EMPTY_HASH]
|
|
77
|
+
freeze
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @return [Object, nil] the value for `key`, nil when there is none
|
|
81
|
+
def [](key)
|
|
82
|
+
h = __hash
|
|
83
|
+
v = h[key]
|
|
84
|
+
v.is_a?(Array) ? (h[key] = Scan.nested(@info.value, @buf, v, @depth)) : v
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# @raise [KeyError] when there is no such key
|
|
88
|
+
def fetch(key, *default, &block)
|
|
89
|
+
return self[key] if __hash.key?(key)
|
|
90
|
+
__hash.fetch(key, *default, &block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def key?(key)
|
|
94
|
+
__hash.key?(key)
|
|
95
|
+
end
|
|
96
|
+
alias_method :has_key?, :key?
|
|
97
|
+
alias_method :include?, :key?
|
|
98
|
+
alias_method :member?, :key?
|
|
99
|
+
|
|
100
|
+
# @return [Array]
|
|
101
|
+
def keys
|
|
102
|
+
__hash.keys
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# @return [Array]
|
|
106
|
+
def values
|
|
107
|
+
to_h.values
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# @return [Integer]
|
|
111
|
+
def size
|
|
112
|
+
__hash.size
|
|
113
|
+
end
|
|
114
|
+
alias_method :length, :size
|
|
115
|
+
|
|
116
|
+
def empty?
|
|
117
|
+
__hash.empty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Yields each key and value, in the order the keys first occurred
|
|
121
|
+
def each(&block)
|
|
122
|
+
return enum_for(:each) { size } unless block
|
|
123
|
+
to_h.each(&block)
|
|
124
|
+
self
|
|
125
|
+
end
|
|
126
|
+
alias_method :each_pair, :each
|
|
127
|
+
|
|
128
|
+
# @return [Hash] every key and value, message values as nested views; frozen
|
|
129
|
+
def to_h
|
|
130
|
+
h = __hash
|
|
131
|
+
return h if h.frozen?
|
|
132
|
+
__resolve
|
|
133
|
+
h.dup.freeze
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Equal to another Map or a Hash with equal keys and values
|
|
137
|
+
def ==(other)
|
|
138
|
+
case other
|
|
139
|
+
when Map, Hash then to_h == other.to_h
|
|
140
|
+
else false
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def hash
|
|
145
|
+
to_h.hash
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def inspect
|
|
149
|
+
to_h.inspect
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def pretty_print(q)
|
|
153
|
+
q.pp(to_h)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Decodes every entry and freezes what it keeps, after which the
|
|
157
|
+
# container is Ractor-shareable when its buffer is
|
|
158
|
+
# @return [self]
|
|
159
|
+
def freeze_index
|
|
160
|
+
return self if @memo.frozen?
|
|
161
|
+
__resolve.each_value { |v| v.freeze_index if v.is_a?(View) }
|
|
162
|
+
@memo[0].freeze
|
|
163
|
+
@entries.freeze
|
|
164
|
+
@memo.freeze
|
|
165
|
+
self
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# @api private
|
|
169
|
+
def __hash
|
|
170
|
+
@memo[0] ||= begin
|
|
171
|
+
h = {}
|
|
172
|
+
entries = @entries
|
|
173
|
+
k = 0
|
|
174
|
+
while k < entries.size
|
|
175
|
+
key, value, unknown_enum = Map.entry(@buf, entries[k], entries[k + 1], @info, @depth)
|
|
176
|
+
h[key] = value unless unknown_enum
|
|
177
|
+
k += 2
|
|
178
|
+
end
|
|
179
|
+
h
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
private
|
|
184
|
+
|
|
185
|
+
def __resolve
|
|
186
|
+
h = __hash
|
|
187
|
+
h.each_key { |key| self[key] }
|
|
188
|
+
h
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
EMPTY = new(nil, nil, [].freeze, 0).freeze_index
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Protostar
|
|
4
|
+
class View
|
|
5
|
+
# The value of a repeated field read through a view. Elements of string,
|
|
6
|
+
# bytes and message fields are decoded one at a time when first read
|
|
7
|
+
# (messages as nested views over the same buffer); numbers, booleans and
|
|
8
|
+
# enums are decoded all together on first use. Every decoded element is
|
|
9
|
+
# kept, so reading it again is free.
|
|
10
|
+
#
|
|
11
|
+
# view.friends.size
|
|
12
|
+
# view.friends[2].name
|
|
13
|
+
# view.lucky_numbers.to_a # => [7, 13]
|
|
14
|
+
class Repeated
|
|
15
|
+
include Enumerable
|
|
16
|
+
|
|
17
|
+
FieldInfo = Message::FieldInfo
|
|
18
|
+
private_constant :FieldInfo
|
|
19
|
+
|
|
20
|
+
# @api private
|
|
21
|
+
def initialize(buf, info, entries, depth)
|
|
22
|
+
@buf = buf
|
|
23
|
+
@info = info
|
|
24
|
+
@entries = entries
|
|
25
|
+
@depth = depth
|
|
26
|
+
@lazy = info ? (info.message? || info.kind == FieldInfo::STRING || info.kind == FieldInfo::BYTES) : false
|
|
27
|
+
@memo = if @lazy
|
|
28
|
+
Array.new(entries.size / 2)
|
|
29
|
+
else
|
|
30
|
+
[info ? nil : Message::EMPTY_ARRAY]
|
|
31
|
+
end
|
|
32
|
+
freeze
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Integer]
|
|
36
|
+
def size
|
|
37
|
+
@lazy ? @memo.size : __values.size
|
|
38
|
+
end
|
|
39
|
+
alias_method :length, :size
|
|
40
|
+
|
|
41
|
+
def empty?
|
|
42
|
+
size.zero?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @overload [](index)
|
|
46
|
+
# @param index [Integer] negative counts from the end
|
|
47
|
+
# @return [Object, nil] the element, nil when out of range
|
|
48
|
+
# @overload [](start, length)
|
|
49
|
+
# @return [Array, nil]
|
|
50
|
+
# @overload [](range)
|
|
51
|
+
# @return [Array, nil]
|
|
52
|
+
def [](index, length = nil)
|
|
53
|
+
return to_a[index, length] if length
|
|
54
|
+
return to_a[index] unless index.is_a?(Integer)
|
|
55
|
+
return __values[index] unless @lazy
|
|
56
|
+
size = @memo.size
|
|
57
|
+
index += size if index < 0
|
|
58
|
+
return nil if index < 0 || index >= size
|
|
59
|
+
__element(index)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def each(&block)
|
|
63
|
+
return enum_for(:each) { size } unless block
|
|
64
|
+
if @lazy
|
|
65
|
+
@memo.size.times { |j| yield __element(j) }
|
|
66
|
+
else
|
|
67
|
+
__values.each(&block)
|
|
68
|
+
end
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Array] the elements; frozen
|
|
73
|
+
def to_a
|
|
74
|
+
return __values unless @lazy
|
|
75
|
+
Array.new(@memo.size) { |j| __element(j) }.freeze
|
|
76
|
+
end
|
|
77
|
+
alias_method :to_ary, :to_a
|
|
78
|
+
|
|
79
|
+
# Equal to another Repeated or an Array with equal elements
|
|
80
|
+
def ==(other)
|
|
81
|
+
case other
|
|
82
|
+
when Repeated, Array then to_a == other.to_a
|
|
83
|
+
else false
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def hash
|
|
88
|
+
to_a.hash
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inspect
|
|
92
|
+
to_a.inspect
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def pretty_print(q)
|
|
96
|
+
q.pp(to_a)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Decodes every element and freezes what it keeps, after which the
|
|
100
|
+
# container is Ractor-shareable when its buffer is
|
|
101
|
+
# @return [self]
|
|
102
|
+
def freeze_index
|
|
103
|
+
return self if @memo.frozen?
|
|
104
|
+
if @lazy
|
|
105
|
+
@memo.size.times do |j|
|
|
106
|
+
e = __element(j)
|
|
107
|
+
e.freeze_index if e.is_a?(View)
|
|
108
|
+
end
|
|
109
|
+
else
|
|
110
|
+
__values
|
|
111
|
+
end
|
|
112
|
+
@entries.freeze
|
|
113
|
+
@memo.freeze
|
|
114
|
+
self
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# @api private
|
|
118
|
+
def __values
|
|
119
|
+
@memo[0] ||= Scan.scalars(@buf, @info, @entries)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def __element(j)
|
|
125
|
+
memo = @memo
|
|
126
|
+
v = memo[j]
|
|
127
|
+
return v unless v.nil?
|
|
128
|
+
offset = @entries[2 * j]
|
|
129
|
+
length = @entries[2 * j + 1]
|
|
130
|
+
info = @info
|
|
131
|
+
memo[j] = case info.kind
|
|
132
|
+
when FieldInfo::STRING then Scan.string(@buf, offset, length, info)
|
|
133
|
+
when FieldInfo::BYTES then Scan.bytes(@buf, offset, length)
|
|
134
|
+
else
|
|
135
|
+
raise DecodeError.new("recursion limit exceeded", offset: offset) if @depth <= 0
|
|
136
|
+
info.message_class.__view_class.__new(@buf, offset, length, nil, @depth - 1)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
EMPTY = new(nil, nil, [].freeze, 0).freeze_index
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|