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.
Files changed (223) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +60 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +647 -0
  5. data/THIRD_PARTY_NOTICES.md +61 -0
  6. data/exe/protoc-gen-protostar +7 -0
  7. data/exe/protostar +7 -0
  8. data/lib/protostar/builder/generator.rb +71 -0
  9. data/lib/protostar/builder/sink.rb +35 -0
  10. data/lib/protostar/builder.rb +965 -0
  11. data/lib/protostar/cli.rb +267 -0
  12. data/lib/protostar/codec/decode_generator.rb +199 -0
  13. data/lib/protostar/codec/decoder.rb +296 -0
  14. data/lib/protostar/codec/encode_generator.rb +224 -0
  15. data/lib/protostar/codec/encoder.rb +121 -0
  16. data/lib/protostar/codec/sizer.rb +91 -0
  17. data/lib/protostar/codec.rb +15 -0
  18. data/lib/protostar/compiler/ast.rb +193 -0
  19. data/lib/protostar/compiler/emitters/descriptor_set.rb +57 -0
  20. data/lib/protostar/compiler/emitters/google_ruby.rb +103 -0
  21. data/lib/protostar/compiler/emitters/grpc.rb +175 -0
  22. data/lib/protostar/compiler/emitters/protostar_rbs.rb +252 -0
  23. data/lib/protostar/compiler/emitters/rbs.rb +394 -0
  24. data/lib/protostar/compiler/emitters/ruby.rb +503 -0
  25. data/lib/protostar/compiler/emitters.rb +268 -0
  26. data/lib/protostar/compiler/include/LICENSE +32 -0
  27. data/lib/protostar/compiler/include/google/protobuf/any.proto +106 -0
  28. data/lib/protostar/compiler/include/google/protobuf/api.proto +229 -0
  29. data/lib/protostar/compiler/include/google/protobuf/compiler/plugin.proto +180 -0
  30. data/lib/protostar/compiler/include/google/protobuf/cpp_features.proto +87 -0
  31. data/lib/protostar/compiler/include/google/protobuf/descriptor.proto +1532 -0
  32. data/lib/protostar/compiler/include/google/protobuf/duration.proto +115 -0
  33. data/lib/protostar/compiler/include/google/protobuf/empty.proto +51 -0
  34. data/lib/protostar/compiler/include/google/protobuf/field_mask.proto +243 -0
  35. data/lib/protostar/compiler/include/google/protobuf/java_features.proto +132 -0
  36. data/lib/protostar/compiler/include/google/protobuf/source_context.proto +48 -0
  37. data/lib/protostar/compiler/include/google/protobuf/struct.proto +111 -0
  38. data/lib/protostar/compiler/include/google/protobuf/timestamp.proto +145 -0
  39. data/lib/protostar/compiler/include/google/protobuf/type.proto +217 -0
  40. data/lib/protostar/compiler/include/google/protobuf/wrappers.proto +157 -0
  41. data/lib/protostar/compiler/lexer.rb +659 -0
  42. data/lib/protostar/compiler/linker.rb +894 -0
  43. data/lib/protostar/compiler/option_interpreter.rb +363 -0
  44. data/lib/protostar/compiler/parser.rb +1696 -0
  45. data/lib/protostar/compiler/retention.rb +104 -0
  46. data/lib/protostar/compiler/run.rb +184 -0
  47. data/lib/protostar/compiler/source_info.rb +161 -0
  48. data/lib/protostar/compiler/symbol_table.rb +223 -0
  49. data/lib/protostar/compiler/text_format.rb +340 -0
  50. data/lib/protostar/compiler/validator.rb +944 -0
  51. data/lib/protostar/compiler.rb +304 -0
  52. data/lib/protostar/diagnostics/codes.rb +1307 -0
  53. data/lib/protostar/diagnostics/formats.rb +64 -0
  54. data/lib/protostar/diagnostics/renderer.rb +240 -0
  55. data/lib/protostar/diagnostics/source.rb +153 -0
  56. data/lib/protostar/diagnostics.rb +117 -0
  57. data/lib/protostar/dsl.rb +367 -0
  58. data/lib/protostar/enum.rb +70 -0
  59. data/lib/protostar/errors.rb +23 -0
  60. data/lib/protostar/google/protobuf/any_pb.rb +90 -0
  61. data/lib/protostar/google/protobuf/api_pb.rb +199 -0
  62. data/lib/protostar/google/protobuf/compiler/plugin_pb.rb +168 -0
  63. data/lib/protostar/google/protobuf/descriptor_pb.rb +1239 -0
  64. data/lib/protostar/google/protobuf/duration_pb.rb +99 -0
  65. data/lib/protostar/google/protobuf/empty_pb.rb +36 -0
  66. data/lib/protostar/google/protobuf/field_mask_pb.rb +228 -0
  67. data/lib/protostar/google/protobuf/source_context_pb.rb +33 -0
  68. data/lib/protostar/google/protobuf/struct_pb.rb +91 -0
  69. data/lib/protostar/google/protobuf/timestamp_pb.rb +129 -0
  70. data/lib/protostar/google/protobuf/type_pb.rb +202 -0
  71. data/lib/protostar/google/protobuf/wrappers_pb.rb +125 -0
  72. data/lib/protostar/google_protobuf_shim.rb +24 -0
  73. data/lib/protostar/json/encoder.rb +340 -0
  74. data/lib/protostar/json/errors.rb +34 -0
  75. data/lib/protostar/json/parser.rb +437 -0
  76. data/lib/protostar/json/path.rb +39 -0
  77. data/lib/protostar/json.rb +95 -0
  78. data/lib/protostar/lint/command.rb +197 -0
  79. data/lib/protostar/lint/config.rb +98 -0
  80. data/lib/protostar/lint/context.rb +126 -0
  81. data/lib/protostar/lint/directives.rb +56 -0
  82. data/lib/protostar/lint/fixer.rb +165 -0
  83. data/lib/protostar/lint/naming.rb +54 -0
  84. data/lib/protostar/lint/rule.rb +120 -0
  85. data/lib/protostar/lint/rules/comment_enum.rb +31 -0
  86. data/lib/protostar/lint/rules/comment_enum_value.rb +32 -0
  87. data/lib/protostar/lint/rules/comment_field.rb +33 -0
  88. data/lib/protostar/lint/rules/comment_message.rb +31 -0
  89. data/lib/protostar/lint/rules/comment_rpc.rb +31 -0
  90. data/lib/protostar/lint/rules/comment_service.rb +31 -0
  91. data/lib/protostar/lint/rules/edition_recommended.rb +31 -0
  92. data/lib/protostar/lint/rules/enum_first_value_zero.rb +34 -0
  93. data/lib/protostar/lint/rules/enum_pascal_case.rb +25 -0
  94. data/lib/protostar/lint/rules/enum_value_prefix.rb +38 -0
  95. data/lib/protostar/lint/rules/enum_value_upper_snake_case.rb +28 -0
  96. data/lib/protostar/lint/rules/enum_zero_value_suffix.rb +41 -0
  97. data/lib/protostar/lint/rules/field_lower_snake_case.rb +34 -0
  98. data/lib/protostar/lint/rules/field_not_required.rb +39 -0
  99. data/lib/protostar/lint/rules/field_number_reuse_risk.rb +45 -0
  100. data/lib/protostar/lint/rules/field_numbers_hot_range.rb +44 -0
  101. data/lib/protostar/lint/rules/file_lower_snake_case.rb +27 -0
  102. data/lib/protostar/lint/rules/import_no_public.rb +29 -0
  103. data/lib/protostar/lint/rules/import_no_weak.rb +28 -0
  104. data/lib/protostar/lint/rules/import_used.rb +33 -0
  105. data/lib/protostar/lint/rules/message_pascal_case.rb +27 -0
  106. data/lib/protostar/lint/rules/no_deprecated_features.rb +50 -0
  107. data/lib/protostar/lint/rules/no_groups.rb +34 -0
  108. data/lib/protostar/lint/rules/oneof_lower_snake_case.rb +27 -0
  109. data/lib/protostar/lint/rules/package_defined.rb +28 -0
  110. data/lib/protostar/lint/rules/package_directory_match.rb +33 -0
  111. data/lib/protostar/lint/rules/package_lower_snake_case.rb +28 -0
  112. data/lib/protostar/lint/rules/package_same_directory.rb +39 -0
  113. data/lib/protostar/lint/rules/package_version_suffix.rb +31 -0
  114. data/lib/protostar/lint/rules/reserved_names_with_numbers.rb +44 -0
  115. data/lib/protostar/lint/rules/rpc_pascal_case.rb +25 -0
  116. data/lib/protostar/lint/rules/rpc_request_response_unique.rb +40 -0
  117. data/lib/protostar/lint/rules/rpc_request_standard_name.rb +32 -0
  118. data/lib/protostar/lint/rules/rpc_response_standard_name.rb +31 -0
  119. data/lib/protostar/lint/rules/service_pascal_case.rb +25 -0
  120. data/lib/protostar/lint/rules/service_suffix.rb +28 -0
  121. data/lib/protostar/lint/rules/syntax_specified.rb +35 -0
  122. data/lib/protostar/lint/runner.rb +135 -0
  123. data/lib/protostar/lint.rb +122 -0
  124. data/lib/protostar/message/accessors.rb +86 -0
  125. data/lib/protostar/message/coercion.rb +171 -0
  126. data/lib/protostar/message/field_info.rb +145 -0
  127. data/lib/protostar/message/reflection.rb +149 -0
  128. data/lib/protostar/message.rb +572 -0
  129. data/lib/protostar/plugin.rb +94 -0
  130. data/lib/protostar/schema/default_value.rb +113 -0
  131. data/lib/protostar/schema/descriptor.rb +41 -0
  132. data/lib/protostar/schema/draft/copy.rb +88 -0
  133. data/lib/protostar/schema/draft/resolution.rb +177 -0
  134. data/lib/protostar/schema/draft.rb +779 -0
  135. data/lib/protostar/schema/enum.rb +106 -0
  136. data/lib/protostar/schema/extension_range.rb +37 -0
  137. data/lib/protostar/schema/features.rb +153 -0
  138. data/lib/protostar/schema/field.rb +201 -0
  139. data/lib/protostar/schema/file.rb +217 -0
  140. data/lib/protostar/schema/freezer.rb +61 -0
  141. data/lib/protostar/schema/message.rb +104 -0
  142. data/lib/protostar/schema/oneof.rb +38 -0
  143. data/lib/protostar/schema/options.rb +367 -0
  144. data/lib/protostar/schema/pool.rb +179 -0
  145. data/lib/protostar/schema/proto.rb +576 -0
  146. data/lib/protostar/schema/resolver.rb +118 -0
  147. data/lib/protostar/schema/service.rb +89 -0
  148. data/lib/protostar/schema/source_info.rb +53 -0
  149. data/lib/protostar/schema.rb +65 -0
  150. data/lib/protostar/short_names/any.rb +7 -0
  151. data/lib/protostar/short_names/duration.rb +7 -0
  152. data/lib/protostar/short_names/empty.rb +7 -0
  153. data/lib/protostar/short_names/field_mask.rb +7 -0
  154. data/lib/protostar/short_names/struct.rb +9 -0
  155. data/lib/protostar/short_names/timestamp.rb +7 -0
  156. data/lib/protostar/short_names/wrappers.rb +15 -0
  157. data/lib/protostar/text/extensions.rb +114 -0
  158. data/lib/protostar/text/parser.rb +439 -0
  159. data/lib/protostar/text/printer.rb +304 -0
  160. data/lib/protostar/text/tokenizer.rb +116 -0
  161. data/lib/protostar/text.rb +67 -0
  162. data/lib/protostar/types.rb +152 -0
  163. data/lib/protostar/unknown_field.rb +58 -0
  164. data/lib/protostar/version.rb +5 -0
  165. data/lib/protostar/view/generator.rb +298 -0
  166. data/lib/protostar/view/map.rb +194 -0
  167. data/lib/protostar/view/repeated.rb +143 -0
  168. data/lib/protostar/view/scan.rb +183 -0
  169. data/lib/protostar/view.rb +283 -0
  170. data/lib/protostar/well_known/any.rb +52 -0
  171. data/lib/protostar/well_known/duration.rb +53 -0
  172. data/lib/protostar/well_known/list_value.rb +63 -0
  173. data/lib/protostar/well_known/struct.rb +55 -0
  174. data/lib/protostar/well_known/timestamp.rb +64 -0
  175. data/lib/protostar/well_known/value.rb +60 -0
  176. data/lib/protostar/well_known.rb +38 -0
  177. data/lib/protostar/wire/io_source.rb +40 -0
  178. data/lib/protostar/wire/reader.rb +254 -0
  179. data/lib/protostar/wire/writer.rb +201 -0
  180. data/lib/protostar/wire.rb +96 -0
  181. data/lib/protostar.rb +95 -0
  182. data/shims/google-protobuf/lib/google/protobuf/any_pb.rb +17 -0
  183. data/shims/google-protobuf/lib/google/protobuf/api_pb.rb +22 -0
  184. data/shims/google-protobuf/lib/google/protobuf/compiler/plugin_pb.rb +25 -0
  185. data/shims/google-protobuf/lib/google/protobuf/descriptor_pb.rb +72 -0
  186. data/shims/google-protobuf/lib/google/protobuf/descriptor_pool.rb +164 -0
  187. data/shims/google-protobuf/lib/google/protobuf/descriptors.rb +553 -0
  188. data/shims/google-protobuf/lib/google/protobuf/duration_pb.rb +17 -0
  189. data/shims/google-protobuf/lib/google/protobuf/empty_pb.rb +17 -0
  190. data/shims/google-protobuf/lib/google/protobuf/field_mask_pb.rb +17 -0
  191. data/shims/google-protobuf/lib/google/protobuf/internal/convert.rb +180 -0
  192. data/shims/google-protobuf/lib/google/protobuf/internal/extensions.rb +154 -0
  193. data/shims/google-protobuf/lib/google/protobuf/internal/json.rb +104 -0
  194. data/shims/google-protobuf/lib/google/protobuf/internal/well_known.rb +140 -0
  195. data/shims/google-protobuf/lib/google/protobuf/map.rb +216 -0
  196. data/shims/google-protobuf/lib/google/protobuf/message.rb +544 -0
  197. data/shims/google-protobuf/lib/google/protobuf/message_exts.rb +41 -0
  198. data/shims/google-protobuf/lib/google/protobuf/plugin_pb.rb +4 -0
  199. data/shims/google-protobuf/lib/google/protobuf/repeated_field.rb +366 -0
  200. data/shims/google-protobuf/lib/google/protobuf/source_context_pb.rb +17 -0
  201. data/shims/google-protobuf/lib/google/protobuf/struct_pb.rb +20 -0
  202. data/shims/google-protobuf/lib/google/protobuf/timestamp_pb.rb +17 -0
  203. data/shims/google-protobuf/lib/google/protobuf/type_pb.rb +27 -0
  204. data/shims/google-protobuf/lib/google/protobuf/well_known_types.rb +37 -0
  205. data/shims/google-protobuf/lib/google/protobuf/wrappers_pb.rb +25 -0
  206. data/shims/google-protobuf/lib/google/protobuf.rb +107 -0
  207. data/shims/google-protobuf/lib/google/protobuf_ffi.rb +5 -0
  208. data/shims/google-protobuf/lib/google/protobuf_native.rb +5 -0
  209. data/sig/protostar/google/protobuf/any_pb.rbs +77 -0
  210. data/sig/protostar/google/protobuf/api_pb.rbs +242 -0
  211. data/sig/protostar/google/protobuf/compiler/plugin_pb.rbs +264 -0
  212. data/sig/protostar/google/protobuf/descriptor_pb.rbs +2220 -0
  213. data/sig/protostar/google/protobuf/duration_pb.rbs +82 -0
  214. data/sig/protostar/google/protobuf/empty_pb.rbs +18 -0
  215. data/sig/protostar/google/protobuf/field_mask_pb.rbs +170 -0
  216. data/sig/protostar/google/protobuf/source_context_pb.rbs +24 -0
  217. data/sig/protostar/google/protobuf/struct_pb.rbs +135 -0
  218. data/sig/protostar/google/protobuf/timestamp_pb.rbs +102 -0
  219. data/sig/protostar/google/protobuf/type_pb.rbs +304 -0
  220. data/sig/protostar/google/protobuf/wrappers_pb.rbs +169 -0
  221. data/sig/protostar/lint.rbs +216 -0
  222. data/sig/protostar.rbs +326 -0
  223. metadata +366 -0
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ VERSION = "0.1.0.beta1"
5
+ end
@@ -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