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,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Codec
5
+ # Decodes the binary wire format into message objects, eagerly.
6
+ # @api private
7
+ module Decoder
8
+ RECURSION_LIMIT = 100
9
+
10
+ F = Message::FieldInfo
11
+ UTF_8 = Encoding::UTF_8
12
+ BINARY = Encoding::BINARY
13
+ # unpack directive of a packed run of fixed-width values
14
+ PACKED = F::FIXED.transform_values { |(_, d)| "#{d}*".freeze }.freeze
15
+
16
+ module_function
17
+
18
+ # @return [Message]
19
+ def decode(klass, bytes, recursion_limit: RECURSION_LIMIT, frozen: false)
20
+ r = Wire::Reader.new(bytes)
21
+ msg = klass.__empty
22
+ msg.__decode_fields(r, recursion_limit, 0, frozen)
23
+ msg.freeze if frozen
24
+ msg
25
+ end
26
+
27
+ # @return [Message, nil]
28
+ def decode_delimited(klass, source, **options)
29
+ if source.is_a?(String) || (defined?(IO::Buffer) && source.is_a?(IO::Buffer))
30
+ r = Wire::Reader.new(source)
31
+ length = r.read_varint
32
+ r.skip(length)
33
+ return decode(klass, r.buffer.byteslice(r.pos - length, length), **options)
34
+ end
35
+ length = read_io_varint(source)
36
+ return nil if length.nil?
37
+ data = length.zero? ? +"" : source.read(length)
38
+ if data.nil? || data.bytesize < length
39
+ raise DecodeError, "truncated delimited message: expected #{length} bytes, got #{data ? data.bytesize : 0}"
40
+ end
41
+ decode(klass, data, **options)
42
+ end
43
+
44
+ # A varint from an IO, byte by byte; nil at a clean end of input
45
+ def read_io_varint(io)
46
+ result = 0
47
+ shift = 0
48
+ loop do
49
+ byte = io.getbyte
50
+ if byte.nil?
51
+ return nil if shift == 0
52
+ raise DecodeError, "truncated length prefix"
53
+ end
54
+ result |= (byte & 0x7F) << shift
55
+ return result & Wire::MASK64 if byte < 0x80
56
+ shift += 7
57
+ raise DecodeError, "length prefix longer than 10 bytes" if shift >= 70
58
+ end
59
+ end
60
+
61
+ # Decodes one field whose tag was just read from offset `at`. The
62
+ # decoders generated for each class ({DecodeGenerator}) handle a class's
63
+ # own fields inline and come here for everything else.
64
+ # @return [Boolean] true when the tag ends the group being read
65
+ def field(msg, r, tag, at, depth, end_group, frozen)
66
+ number = tag >> 3
67
+ wt = tag & 7
68
+ if wt == Wire::END_GROUP
69
+ return true if number == end_group && end_group != 0
70
+ raise DecodeError.new("unmatched end of group #{number}", offset: at)
71
+ end
72
+ Wire::Reader.check_tag(tag, at)
73
+ info = msg.class.__by_number[number]
74
+ values = msg.__values
75
+ if info.nil?
76
+ unknown(msg, r, number, wt, depth)
77
+ elsif wt == info.wire_type
78
+ if info.map
79
+ map_entry(msg, values, info, r, depth, frozen)
80
+ elsif info.message?
81
+ if info.repeated
82
+ repeated_message(values, info, r, depth, frozen, at)
83
+ else
84
+ singular_message(values, info, r, depth, frozen, at)
85
+ end
86
+ elsif info.kind == F::ENUM
87
+ enum(msg, values, info, r)
88
+ elsif info.repeated
89
+ (values[info.index] ||= []) << scalar(r, info)
90
+ else
91
+ siblings = info.siblings
92
+ siblings&.each { |s| values[s] = nil }
93
+ values[info.index] = scalar(r, info)
94
+ end
95
+ elsif wt == Wire::LENGTH_DELIMITED && info.packable
96
+ packed(msg, values, info, r)
97
+ else
98
+ # a wire type that does not match the field makes it unknown, as in protoc
99
+ unknown(msg, r, number, wt, depth)
100
+ end
101
+ false
102
+ end
103
+
104
+ # Checks the end of a message's fields: a group must have seen its end tag
105
+ def finish(msg, r, end_group)
106
+ raise DecodeError.new("missing end of group #{end_group}", offset: r.pos) if end_group != 0
107
+ msg.__seal_unknown
108
+ end
109
+
110
+ def repeated_message(values, info, r, depth, frozen, at)
111
+ sub = info.message_class.__empty
112
+ nested(sub, info, r, depth, frozen, at)
113
+ (values[info.index] ||= []) << sub
114
+ end
115
+
116
+ def singular_message(values, info, r, depth, frozen, at)
117
+ i = info.index
118
+ sub = values[i]
119
+ if sub.nil?
120
+ siblings = info.siblings
121
+ siblings&.each { |s| values[s] = nil }
122
+ sub = values[i] = info.message_class.__empty
123
+ elsif sub.frozen?
124
+ sub = values[i] = sub.dup
125
+ end
126
+ nested(sub, info, r, depth, frozen, at)
127
+ end
128
+
129
+ # Reads a message or group value into `sub`
130
+ def nested(sub, info, r, depth, frozen, at)
131
+ raise DecodeError.new("recursion limit exceeded", offset: at) if depth <= 0
132
+ if info.kind == F::GROUP
133
+ sub.__decode_fields(r, depth - 1, info.number, frozen)
134
+ else
135
+ old = r.push_limit(r.read_varint)
136
+ sub.__decode_fields(r, depth - 1, 0, frozen)
137
+ r.pop_limit(old)
138
+ end
139
+ sub.freeze if frozen
140
+ end
141
+
142
+ def unknown(msg, r, number, wt, depth)
143
+ start = r.pos
144
+ r.skip_field(number, wt, depth)
145
+ bytes = r.buffer.byteslice(start, r.pos - start).force_encoding(BINARY).freeze
146
+ msg.__add_unknown(UnknownField.new(number, wt, bytes))
147
+ end
148
+
149
+ def enum(msg, values, info, r)
150
+ start = r.pos
151
+ n = r.read_int32
152
+ v = info.enum_names[n]
153
+ if v.nil?
154
+ if info.closed_enum
155
+ bytes = r.buffer.byteslice(start, r.pos - start).force_encoding(BINARY).freeze
156
+ msg.__add_unknown(UnknownField.new(info.number, Wire::VARINT, bytes))
157
+ return
158
+ end
159
+ v = n
160
+ end
161
+ if info.repeated
162
+ (values[info.index] ||= []) << v
163
+ else
164
+ siblings = info.siblings
165
+ siblings&.each { |s| values[s] = nil }
166
+ values[info.index] = v
167
+ end
168
+ end
169
+
170
+ # One value of a scalar field other than an enum
171
+ def scalar(r, info)
172
+ case info.kind
173
+ when F::INT32 then r.read_int32
174
+ when F::INT64 then r.read_int64
175
+ when F::UINT32 then r.read_uint32
176
+ when F::UINT64 then r.read_varint
177
+ when F::SINT32 then r.read_sint32
178
+ when F::SINT64 then r.read_sint64
179
+ when F::BOOL then r.read_varint != 0
180
+ when F::STRING then string(r, info)
181
+ when F::BYTES then r.read_bytes.freeze
182
+ when F::DOUBLE then r.read_double
183
+ when F::FLOAT then r.read_float
184
+ when F::FIXED32 then r.read_fixed32
185
+ when F::FIXED64 then r.read_fixed64
186
+ when F::SFIXED32 then r.read_sfixed32
187
+ when F::SFIXED64 then r.read_sfixed64
188
+ when F::ENUM then r.read_int32
189
+ else raise DecodeError, "#{info.descriptor.full_name} is not a scalar field"
190
+ end
191
+ end
192
+
193
+ def string(r, info)
194
+ s = r.read_bytes.force_encoding(UTF_8)
195
+ if info.utf8_validation && !s.valid_encoding?
196
+ raise DecodeError.new("invalid UTF-8 in #{info.descriptor.full_name}", offset: r.pos - s.bytesize)
197
+ end
198
+ s.freeze
199
+ end
200
+
201
+ # Keeps a closed enum's number that has no name as an unknown varint field
202
+ def closed_enum_value(msg, number, buf, start, pos)
203
+ bytes = buf.byteslice(start, pos - start).force_encoding(BINARY).freeze
204
+ msg.__add_unknown(UnknownField.new(number, Wire::VARINT, bytes))
205
+ end
206
+
207
+ def packed(msg, values, info, r)
208
+ old = r.push_limit(r.read_varint)
209
+ list = (values[info.index] ||= [])
210
+ kind = info.kind
211
+ if (fixed = F::FIXED[kind])
212
+ width = fixed[0]
213
+ length = r.limit - r.pos
214
+ raise DecodeError.new("packed #{info.descriptor.type} length #{length} is not a multiple of #{width}", offset: r.pos) unless (length % width).zero?
215
+ # one unpack for the whole run instead of a read per element
216
+ list.concat(r.read_raw(length).unpack(PACKED.fetch(kind)))
217
+ elsif kind == F::ENUM
218
+ names = info.enum_names
219
+ closed = info.closed_enum
220
+ while r.pos < r.limit
221
+ start = r.pos
222
+ n = r.read_int32
223
+ v = names[n]
224
+ if v
225
+ list << v
226
+ elsif closed
227
+ bytes = r.buffer.byteslice(start, r.pos - start).force_encoding(BINARY).freeze
228
+ msg.__add_unknown(UnknownField.new(info.number, Wire::VARINT, bytes))
229
+ else
230
+ list << n
231
+ end
232
+ end
233
+ else
234
+ list << scalar(r, info) while r.pos < r.limit
235
+ end
236
+ r.pop_limit(old)
237
+ end
238
+
239
+ def map_entry(msg, values, info, r, depth, frozen)
240
+ start = r.pos
241
+ old = r.push_limit(r.read_varint)
242
+ key_info = info.key
243
+ value_info = info.value
244
+ key = nil
245
+ value = nil
246
+ unknown_enum = false
247
+ while r.pos < r.limit
248
+ at = r.pos
249
+ tag = r.read_tag_varint
250
+ Wire::Reader.check_tag(tag, at)
251
+ number = tag >> 3
252
+ wt = tag & 7
253
+ if number == 1 && wt == key_info.wire_type
254
+ key = scalar(r, key_info)
255
+ elsif number == 2 && wt == value_info.wire_type
256
+ if value_info.message?
257
+ if value.nil?
258
+ value = value_info.message_class.__empty
259
+ elsif value.frozen?
260
+ value = value.dup
261
+ end
262
+ nested(value, value_info, r, depth, frozen, at)
263
+ elsif value_info.kind == F::ENUM
264
+ n = r.read_int32
265
+ value = value_info.enum_names[n]
266
+ if value.nil?
267
+ unknown_enum = value_info.closed_enum
268
+ value = n
269
+ else
270
+ unknown_enum = false
271
+ end
272
+ else
273
+ value = scalar(r, value_info)
274
+ end
275
+ else
276
+ raise DecodeError.new("unmatched end of group #{number}", offset: at) if wt == Wire::END_GROUP
277
+ r.skip_field(number, wt, depth)
278
+ end
279
+ end
280
+ r.pop_limit(old)
281
+ if unknown_enum
282
+ # protoc keeps the whole entry as an unknown field of the map
283
+ bytes = r.buffer.byteslice(start, r.pos - start).force_encoding(BINARY).freeze
284
+ msg.__add_unknown(UnknownField.new(info.number, Wire::LENGTH_DELIMITED, bytes))
285
+ return
286
+ end
287
+ key = key_info.default if key.nil?
288
+ if value.nil?
289
+ value = value_info.message? ? value_info.message_class.__empty : value_info.default
290
+ end
291
+ value.freeze if frozen
292
+ (values[info.index] ||= {})[key] = value
293
+ end
294
+ end
295
+ end
296
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Codec
5
+ # Writes the two encoding passes of one message class as Ruby source and
6
+ # evals them into the class: `__size(sizes)`, which returns the encoded
7
+ # size and records it (and every packed run's) in `sizes`, and
8
+ # `__write(w, buf, sizes)`, which appends the fields to the Writer's
9
+ # buffer. Fields come in ascending number order, tags are literal bytes,
10
+ # one-byte varints are appended inline, and enum names map to numbers
11
+ # through a literal `case`. Maps, packed runs and unknown fields go to
12
+ # {Sizer} and {Encoder}.
13
+ #
14
+ # The generic loop over field descriptors this replaces spent most of
15
+ # its time on method calls into the field table.
16
+ # @api private
17
+ module EncodeGenerator
18
+ F = Message::FieldInfo
19
+
20
+ INT_BOUNDS = {
21
+ F::INT32 => [-0x8000_0000, 0x7FFF_FFFF], F::SINT32 => [-0x8000_0000, 0x7FFF_FFFF],
22
+ F::SFIXED32 => [-0x8000_0000, 0x7FFF_FFFF], F::UINT32 => [0, 0xFFFF_FFFF], F::FIXED32 => [0, 0xFFFF_FFFF],
23
+ F::INT64 => [-0x8000_0000_0000_0000, 0x7FFF_FFFF_FFFF_FFFF],
24
+ F::SINT64 => [-0x8000_0000_0000_0000, 0x7FFF_FFFF_FFFF_FFFF],
25
+ F::SFIXED64 => [-0x8000_0000_0000_0000, 0x7FFF_FFFF_FFFF_FFFF],
26
+ F::UINT64 => [0, 0xFFFF_FFFF_FFFF_FFFF], F::FIXED64 => [0, 0xFFFF_FFFF_FFFF_FFFF]
27
+ }.freeze
28
+
29
+ VARINT_KINDS = [F::INT32, F::INT64, F::UINT32, F::UINT64].freeze
30
+
31
+ module_function
32
+
33
+ # @param klass [Class]
34
+ # @param fields [Array<Message::FieldInfo>]
35
+ def install(klass, fields)
36
+ ordered = fields.sort_by(&:number)
37
+ klass.class_eval(source(ordered), "(protostar encoder #{klass.descriptor.full_name})", 1)
38
+ end
39
+
40
+ def source(fields)
41
+ <<~RUBY
42
+ # frozen_string_literal: true
43
+ def __size(sizes)
44
+ #{"values = @values" unless fields.empty?}
45
+ total = 0
46
+ #{fields.map { |f| size_field(f) }.join}
47
+ total += Protostar::Codec::Sizer.unknown(@unknown) if @unknown
48
+ sizes[self] = total
49
+ end
50
+
51
+ def __write(w, buf, sizes)
52
+ #{"values = @values" unless fields.empty?}
53
+ #{fields.map { |f| write_field(f) }.join}
54
+ Protostar::Codec::Encoder.unknown(w, @unknown) if @unknown
55
+ w.flush_if_full
56
+ end
57
+ RUBY
58
+ end
59
+
60
+ def info(f)
61
+ "self.class.__fields[#{f.index}]"
62
+ end
63
+
64
+ # The condition under which a field is written, with its value in `v`
65
+ def present(f)
66
+ i = f.index
67
+ if f.repeated || f.map
68
+ "unless (v = values[#{i}]).nil? || v.empty?"
69
+ elsif f.presence
70
+ "unless (v = values[#{i}]).nil?"
71
+ else
72
+ "v = values[#{i}]\nif #{nonzero(f)}"
73
+ end
74
+ end
75
+
76
+ # Negative zero is written, as protoc compares the bits
77
+ def nonzero(f)
78
+ case f.kind
79
+ when F::DOUBLE, F::FLOAT then "v != 0 || (1.0 / v) < 0"
80
+ when F::BOOL then "v"
81
+ when F::STRING, F::BYTES then "!v.empty?"
82
+ when F::ENUM
83
+ zero = f.enum_names[0]
84
+ zero ? "!v.equal?(#{zero.inspect}) && v != 0" : "v != 0"
85
+ else "v != 0"
86
+ end
87
+ end
88
+
89
+ def tag_bytes(f, wire_type = f.wire_type)
90
+ Wire.tag_bytes(f.number, wire_type).bytes
91
+ end
92
+
93
+ def size_field(f)
94
+ tag = tag_bytes(f).size
95
+ body = if f.map
96
+ "total += Protostar::Codec::Sizer.map(#{info(f)}, v, sizes)\n"
97
+ elsif f.packed
98
+ "#{packed_size(f)}sizes[v] = run\n" \
99
+ "total += #{tag_bytes(f, Wire::LENGTH_DELIMITED).size} + #{varint_size("run")} + run\n"
100
+ elsif f.repeated
101
+ repeated_size(f, tag)
102
+ else
103
+ tags = (f.kind == F::GROUP) ? tag * 2 : tag
104
+ "#{value_size(f, "v", "n")}total += #{tags} + n\n"
105
+ end
106
+ "# #{f.name}\n#{present(f)}\n#{body}end\n"
107
+ end
108
+
109
+ # Statements leaving the payload size of packed run `v` in `run`,
110
+ # checking each element as {Sizer.packed} does, without a call per element
111
+ def packed_size(f)
112
+ check = (f.kind == F::ENUM) ? "" : "Protostar::Codec::Sizer.invalid(#{info(f)}, e) unless #{valid(f, "e")}\n"
113
+ case f.kind
114
+ when *VARINT_KINDS, F::SINT32, F::SINT64, F::ENUM
115
+ "run = 0\n#{each_element("#{check}#{value_size(f, "e", "n")}run += n\n")}"
116
+ else
117
+ width = (f.kind == F::BOOL) ? 1 : F::FIXED.fetch(f.kind).first
118
+ "#{each_element(check)}run = #{width} * v.size\n"
119
+ end
120
+ end
121
+
122
+ # Statements appending packed run `v`'s payload
123
+ def packed_write(f)
124
+ case f.kind
125
+ when *VARINT_KINDS, F::SINT32, F::SINT64, F::ENUM then each_element(write_value(f, "e"))
126
+ when F::BOOL then each_element("buf << (e ? 1 : 0)\n")
127
+ when F::FLOAT then "buf << Protostar::Wire.pack_floats(v)\n"
128
+ else "v.pack(#{"#{F::FIXED.fetch(f.kind).last}*".inspect}, buffer: buf)\n"
129
+ end
130
+ end
131
+
132
+ def repeated_size(f, tag)
133
+ tag *= 2 if f.kind == F::GROUP
134
+ src = "total += #{tag} * v.size\n"
135
+ src << "k = #{info(f)}.message_class\n" if f.message?
136
+ check = (f.kind == F::ENUM) ? "" : "Protostar::Codec::Sizer.invalid(#{info(f)}, e) unless #{valid(f, "e")}\n"
137
+ src << each_element("#{check}#{value_size(f, "e", "n")}total += n\n")
138
+ end
139
+
140
+ # A loop running `body` with each element of `v` in `e`
141
+ def each_element(body)
142
+ "v.each do |e|\n#{body}end\n"
143
+ end
144
+
145
+ # Whether an element of a repeated field can be written; elements may
146
+ # have been pushed into the plain Array past the writers' checks
147
+ def valid(f, var)
148
+ case f.kind
149
+ when F::DOUBLE, F::FLOAT then "(#{var}.is_a?(Float) || #{var}.is_a?(Integer))"
150
+ when F::BOOL then "(#{var} == true || #{var} == false)"
151
+ when F::STRING, F::BYTES then "#{var}.is_a?(String)"
152
+ when F::MESSAGE, F::GROUP then "#{var}.instance_of?(k)"
153
+ else
154
+ min, max = INT_BOUNDS.fetch(f.kind)
155
+ "(#{var}.is_a?(Integer) && #{var} >= #{min} && #{var} <= #{max})"
156
+ end
157
+ end
158
+
159
+ def varint_size(var)
160
+ "(#{var} < 0x80 && #{var} >= 0 ? 1 : (#{var} < 0x4000 && #{var} >= 0 ? 2 : Protostar::Wire.varint_size(#{var})))"
161
+ end
162
+
163
+ # Statements leaving the size of value `var`, tag excluded, in `out`
164
+ def value_size(f, var, out)
165
+ case f.kind
166
+ when *VARINT_KINDS then "#{out} = #{varint_size(var)}\n"
167
+ when F::SINT32, F::SINT64 then "z = (#{var} << 1) ^ (#{var} >> 63)\n#{out} = #{varint_size("z")}\n"
168
+ when F::BOOL then "#{out} = 1\n"
169
+ when F::ENUM then "z = #{enum_number(f, var)}\n#{out} = #{varint_size("z")}\n"
170
+ when F::STRING, F::BYTES then "#{out} = #{var}.bytesize\n#{out} += #{varint_size(out)}\n"
171
+ when F::MESSAGE then "#{out} = #{var}.__size(sizes)\n#{out} += #{varint_size(out)}\n"
172
+ when F::GROUP then "#{out} = #{var}.__size(sizes)\n"
173
+ else "#{out} = #{F::FIXED.fetch(f.kind).first}\n"
174
+ end
175
+ end
176
+
177
+ # An expression for the number of enum value `var`
178
+ def enum_number(f, var)
179
+ src = "case #{var}\n"
180
+ f.enum_numbers.each { |name, number| src << "when #{name.inspect} then #{number}\n" }
181
+ src << "when Integer then (#{var} >= -0x8000_0000 && #{var} <= 0x7FFF_FFFF) ? #{var} : " \
182
+ "Protostar::Codec::Encoder.enum_number(#{info(f)}, #{var})\n"
183
+ src << "else Protostar::Codec::Encoder.enum_number(#{info(f)}, #{var})\nend"
184
+ end
185
+
186
+ def write_field(f)
187
+ body = if f.map
188
+ "Protostar::Codec::Encoder.map(#{info(f)}, v, w, sizes)\n"
189
+ elsif f.packed
190
+ "#{append(tag_bytes(f, Wire::LENGTH_DELIMITED))}#{write_varint("sizes[v]")}#{packed_write(f)}"
191
+ elsif f.repeated
192
+ each_element("#{append(tag_bytes(f))}#{write_value(f, "e")}")
193
+ else
194
+ "#{append(tag_bytes(f))}#{write_value(f, "v")}"
195
+ end
196
+ "# #{f.name}\n#{present(f)}\n#{body}end\n"
197
+ end
198
+
199
+ # Appending an Integer to a binary String appends that byte
200
+ def append(bytes)
201
+ "buf#{bytes.map { |b| " << #{b}" }.join}\n"
202
+ end
203
+
204
+ def write_varint(expr)
205
+ "if (x = #{expr}) < 0x80 && x >= 0\nbuf << x\nelsif x < 0x4000 && x >= 0\nbuf << ((x & 0x7F) | 0x80) << (x >> 7)\nelse\nw.write_varint(x)\nend\n"
206
+ end
207
+
208
+ def write_value(f, var)
209
+ case f.kind
210
+ when *VARINT_KINDS then write_varint(var)
211
+ when F::SINT32, F::SINT64 then write_varint("(#{var} << 1) ^ (#{var} >> 63)")
212
+ when F::BOOL then "buf << (#{var} ? 1 : 0)\n"
213
+ when F::ENUM then write_varint(enum_number(f, var))
214
+ when F::STRING, F::BYTES then "#{write_varint("#{var}.bytesize")}w.write_raw(#{var})\n"
215
+ when F::MESSAGE then "#{write_varint("sizes[#{var}]")}#{var}.__write(w, buf, sizes)\n"
216
+ when F::GROUP then "#{var}.__write(w, buf, sizes)\n#{append(tag_bytes(f, Wire::END_GROUP))}"
217
+ when F::FLOAT
218
+ "if #{var} == #{var}\n[#{var}].pack(\"e\", buffer: buf)\nelse\nbuf << Protostar::Wire.float_nan(#{var})\nend\n"
219
+ else "[#{var}].pack(#{F::FIXED.fetch(f.kind).last.inspect}, buffer: buf)\n"
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Codec
5
+ # The second encoding pass writes fields in ascending number order, then
6
+ # unknown fields verbatim, using the sizes the first pass recorded. The
7
+ # per-class `__write` methods ({EncodeGenerator}) do the work; this
8
+ # module has the entry points and the parts they share.
9
+ # @api private
10
+ module Encoder
11
+ F = Message::FieldInfo
12
+ BINARY = Encoding::BINARY
13
+
14
+ module_function
15
+
16
+ # @return [String] binary
17
+ def encode(msg)
18
+ sizes = {}.compare_by_identity
19
+ size = msg.__size(sizes)
20
+ w = Wire::Writer.new(String.new(capacity: size, encoding: BINARY))
21
+ msg.__write(w, w.string, sizes)
22
+ w.string
23
+ end
24
+
25
+ def encode_to(msg, io)
26
+ sizes = {}.compare_by_identity
27
+ msg.__size(sizes)
28
+ w = Wire::Writer.new(io)
29
+ msg.__write(w, w.string, sizes)
30
+ w.flush
31
+ end
32
+
33
+ # @return [String, IO]
34
+ def encode_delimited(msg, io)
35
+ sizes = {}.compare_by_identity
36
+ size = msg.__size(sizes)
37
+ w = Wire::Writer.new(io || String.new(capacity: size + 10, encoding: BINARY))
38
+ w.write_varint(size)
39
+ msg.__write(w, w.string, sizes)
40
+ return w.string unless io
41
+ w.flush
42
+ io
43
+ end
44
+
45
+ # @return [Integer] the number of an enum value as stored (Symbol or Integer)
46
+ # @raise [EncodeError] for a name the enum does not have or an Integer out of int32 range
47
+ def enum_number(info, v)
48
+ if v.is_a?(Integer)
49
+ return v if Message::Coercion::ENUM_RANGE.cover?(v)
50
+ elsif (n = info.enum_numbers[v])
51
+ return n
52
+ end
53
+ raise EncodeError, "#{info.descriptor.full_name} holds #{v.inspect[0, 64]}, which is not a value of its enum"
54
+ end
55
+
56
+ # One value, without its tag; a group's end tag is written here
57
+ def value(info, v, w, sizes)
58
+ case info.kind
59
+ when F::INT32, F::INT64, F::UINT32, F::UINT64 then w.write_varint(v)
60
+ when F::ENUM then w.write_varint(enum_number(info, v))
61
+ when F::SINT32, F::SINT64 then w.write_varint((v << 1) ^ (v >> 63))
62
+ when F::BOOL then w.write_varint(v ? 1 : 0)
63
+ when F::STRING, F::BYTES then w.write_bytes(v)
64
+ when F::MESSAGE
65
+ w.write_varint(sizes.fetch(v))
66
+ v.__write(w, w.string, sizes)
67
+ when F::GROUP
68
+ v.__write(w, w.string, sizes)
69
+ w.write_raw(info.end_tag)
70
+ when F::DOUBLE then w.write_double(v)
71
+ when F::FLOAT then w.write_float(v)
72
+ when F::FIXED64 then w.write_fixed64(v)
73
+ when F::FIXED32 then w.write_fixed32(v)
74
+ when F::SFIXED64 then w.write_sfixed64(v)
75
+ when F::SFIXED32 then w.write_sfixed32(v)
76
+ end
77
+ end
78
+
79
+ # The payload of a packed run; tag and length are already written
80
+ def packed(info, list, w)
81
+ kind = info.kind
82
+ if kind == F::FLOAT
83
+ w.write_raw(Wire.pack_floats(list))
84
+ elsif (fixed = F::FIXED[kind])
85
+ # one pack for the whole run
86
+ w.write_raw(list.pack("#{fixed[1]}*"))
87
+ elsif kind == F::ENUM
88
+ list.each { |v| w.write_varint(enum_number(info, v)) }
89
+ elsif kind == F::BOOL
90
+ list.each { |v| w.write_varint(v ? 1 : 0) }
91
+ elsif kind == F::SINT32 || kind == F::SINT64
92
+ list.each { |v| w.write_varint((v << 1) ^ (v >> 63)) }
93
+ else
94
+ list.each { |v| w.write_varint(v) }
95
+ end
96
+ end
97
+
98
+ # Every entry of a map field, in insertion order, key then value
99
+ def map(info, hash, w, sizes)
100
+ key_info = info.key
101
+ value_info = info.value
102
+ hash.each do |key, val|
103
+ w.write_raw(info.tag)
104
+ w.write_varint(Sizer.entry(info, key, val, sizes))
105
+ w.write_raw(key_info.tag)
106
+ value(key_info, key, w, sizes)
107
+ w.write_raw(value_info.tag)
108
+ value(value_info, val, w, sizes)
109
+ end
110
+ end
111
+
112
+ # @param list [Array<UnknownField>]
113
+ def unknown(w, list)
114
+ list.each do |u|
115
+ w.write_varint((u.number << 3) | u.wire_type)
116
+ w.write_raw(u.bytes)
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end