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,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ module Codec
5
+ # The first encoding pass computes the encoded size of a message and of
6
+ # every message and packed run inside it, recorded in `sizes` (an
7
+ # identity Hash that lives for one encode) so the writer knows each
8
+ # length prefix before it writes the bytes it covers. The per-class
9
+ # `__size` methods ({EncodeGenerator}) do the work; this module has the
10
+ # parts they share.
11
+ # @api private
12
+ module Sizer
13
+ F = Message::FieldInfo
14
+
15
+ module_function
16
+
17
+ # @param msg [Message]
18
+ # @param sizes [Hash] compare_by_identity
19
+ # @return [Integer]
20
+ def message(msg, sizes)
21
+ msg.__size(sizes)
22
+ end
23
+
24
+ # Size of one value, without its tag; a group's end tag is not included either
25
+ def value(info, v, sizes)
26
+ case info.kind
27
+ when F::INT32, F::INT64, F::UINT32, F::UINT64 then Wire.varint_size(v)
28
+ when F::ENUM then Wire.varint_size(Encoder.enum_number(info, v))
29
+ when F::SINT32, F::SINT64 then Wire.varint_size((v << 1) ^ (v >> 63))
30
+ when F::BOOL then 1
31
+ when F::STRING, F::BYTES
32
+ n = v.bytesize
33
+ Wire.varint_size(n) + n
34
+ when F::MESSAGE
35
+ n = v.__size(sizes)
36
+ Wire.varint_size(n) + n
37
+ when F::GROUP then v.__size(sizes)
38
+ when F::DOUBLE, F::FIXED64, F::SFIXED64 then 8
39
+ else 4
40
+ end
41
+ end
42
+
43
+ # Payload size of a packed run, without tag or length prefix
44
+ def packed(info, list)
45
+ list.each { |e| check(info, e) }
46
+ case info.kind
47
+ when F::DOUBLE, F::FIXED64, F::SFIXED64 then 8 * list.size
48
+ when F::FLOAT, F::FIXED32, F::SFIXED32 then 4 * list.size
49
+ when F::BOOL then list.size
50
+ when F::SINT32, F::SINT64 then list.sum { |v| Wire.varint_size((v << 1) ^ (v >> 63)) }
51
+ when F::ENUM then list.sum { |v| Wire.varint_size(Encoder.enum_number(info, v)) }
52
+ else list.sum { |v| Wire.varint_size(v) }
53
+ end
54
+ end
55
+
56
+ # Size of every entry of a map field, tags and length prefixes included
57
+ def map(info, hash, sizes)
58
+ tag = info.tag.bytesize
59
+ total = 0
60
+ hash.each do |key, val|
61
+ n = entry(info, key, val, sizes)
62
+ total += tag + Wire.varint_size(n) + n
63
+ end
64
+ total
65
+ end
66
+
67
+ # Size of a map entry message: key field then value field, both always written
68
+ def entry(info, key, val, sizes)
69
+ k = info.key
70
+ v = info.value
71
+ check(k, key)
72
+ check(v, val)
73
+ k.tag.bytesize + value(k, key, sizes) + v.tag.bytesize + value(v, val, sizes)
74
+ end
75
+
76
+ # @param list [Array<UnknownField>]
77
+ def unknown(list)
78
+ list.sum { |u| Wire.varint_size((u.number << 3) | u.wire_type) + u.bytes.bytesize }
79
+ end
80
+
81
+ def check(info, value)
82
+ invalid(info, value) unless Message::Coercion.storable?(info, value)
83
+ end
84
+
85
+ # @raise [EncodeError]
86
+ def invalid(info, value)
87
+ raise EncodeError, "#{info.descriptor.full_name} holds #{value.inspect[0, 64]}, which it cannot encode"
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ # Binary encoding and decoding of {Message} objects. Use the methods on
5
+ # message classes and instances; these modules are their implementation.
6
+ module Codec
7
+ # Needed while a class is defined, never after
8
+ autoload :DecodeGenerator, ::File.expand_path("codec/decode_generator", __dir__)
9
+ autoload :EncodeGenerator, ::File.expand_path("codec/encode_generator", __dir__)
10
+ end
11
+ end
12
+
13
+ require_relative "codec/decoder"
14
+ require_relative "codec/sizer"
15
+ require_relative "codec/encoder"
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ # The parse tree of a `.proto` file. It holds what protoc's parser puts
6
+ # in a `FileDescriptorProto` before linking: names as written, options
7
+ # uninterpreted, map entries, groups and the synthetic oneofs of proto3
8
+ # `optional` fields already expanded, as protoc's parser expands them.
9
+ #
10
+ # Every node records where its parts start in {Node#positions}, keyed by
11
+ # the error locations protoc's DescriptorPool reports against (`:name`,
12
+ # `:number`, `:type`, `:extendee`, `:default_value`, `:option_name`,
13
+ # `:option_value`, `:input_type`, `:output_type`, `:editions`, `:other`),
14
+ # each a 0-based `[line, column]`.
15
+ module AST
16
+ # @api private
17
+ class Node
18
+ # @return [Hash{Symbol => Array(Integer, Integer)}]
19
+ attr_reader :positions
20
+ # Linker state: full name and source path
21
+ attr_accessor :full_name, :path
22
+
23
+ def initialize
24
+ @positions = {}
25
+ end
26
+
27
+ # @return [Array(Integer, Integer), nil]
28
+ def position(kind)
29
+ @positions[kind]
30
+ end
31
+ end
32
+
33
+ # A whole `.proto` file
34
+ class File < Node
35
+ attr_accessor :name, :package, :syntax, :edition, :options
36
+ attr_reader :dependency, :public_dependency, :weak_dependency, :option_dependency,
37
+ :message_type, :enum_type, :service, :extension, :import_positions
38
+ # @return [Array<SourceInfo::Location>] in protoc's order
39
+ attr_accessor :locations
40
+
41
+ def initialize
42
+ super
43
+ @dependency = []
44
+ @public_dependency = []
45
+ @weak_dependency = []
46
+ @option_dependency = []
47
+ @message_type = []
48
+ @enum_type = []
49
+ @service = []
50
+ @extension = []
51
+ @import_positions = {}
52
+ @locations = []
53
+ end
54
+ end
55
+
56
+ # The options of one element: the uninterpreted `option` statements and
57
+ # the few options protoc's parser sets itself (`map_entry`)
58
+ class Options
59
+ # @return [Array<Option>]
60
+ attr_reader :uninterpreted
61
+ # @return [Hash] options set directly, e.g. `{map_entry: true}`
62
+ attr_reader :known
63
+
64
+ def initialize
65
+ @uninterpreted = []
66
+ @known = {}
67
+ end
68
+
69
+ def empty?
70
+ @uninterpreted.empty? && @known.empty?
71
+ end
72
+
73
+ # A copy whose options carry no positions, as protoc's message copy
74
+ # leaves them out of its location table
75
+ def copy
76
+ other = Options.new
77
+ other.known.merge!(@known)
78
+ @uninterpreted.each { |o| other.uninterpreted << o.copy }
79
+ other
80
+ end
81
+ end
82
+
83
+ # One uninterpreted option: `(my.ext).sub.field = value`
84
+ class Option < Node
85
+ # @return [Array<Array(String, Boolean)>] name parts and whether each is an extension
86
+ attr_reader :name
87
+ # @return [Symbol, nil] which value field is set: :identifier_value,
88
+ # :positive_int_value, :negative_int_value, :double_value,
89
+ # :string_value or :aggregate_value
90
+ attr_accessor :value_kind
91
+ attr_accessor :value
92
+
93
+ def initialize
94
+ super
95
+ @name = []
96
+ end
97
+
98
+ def copy
99
+ other = Option.new
100
+ @name.each { |part| other.name << part }
101
+ other.value_kind = @value_kind
102
+ other.value = @value
103
+ other
104
+ end
105
+
106
+ # @return [String] the name as protoc prints it in errors: `(a.b).c`
107
+ def display_name
108
+ @name.map { |part, ext| ext ? "(#{part})" : part }.join(".")
109
+ end
110
+ end
111
+
112
+ # A message, including synthesized map entries and group bodies
113
+ class Message < Node
114
+ attr_accessor :name, :options, :visibility
115
+ attr_accessor :parent, :index
116
+ attr_reader :field, :nested_type, :enum_type, :extension_range, :extension, :oneof_decl,
117
+ :reserved_range, :reserved_name
118
+
119
+ def initialize
120
+ super
121
+ @field = []
122
+ @nested_type = []
123
+ @enum_type = []
124
+ @extension_range = []
125
+ @extension = []
126
+ @oneof_decl = []
127
+ @reserved_range = []
128
+ @reserved_name = []
129
+ end
130
+ end
131
+
132
+ # A field or an extension
133
+ class Field < Node
134
+ attr_accessor :name, :number, :label, :type, :type_name, :extendee, :default_value, :json_name,
135
+ :oneof_index, :proto3_optional, :options
136
+ # Set by the linker: the declaring message (nil at file level), whether
137
+ # this is an extension, and its index
138
+ attr_accessor :parent, :extension, :index
139
+ end
140
+
141
+ # A `oneof` declaration
142
+ class Oneof < Node
143
+ attr_accessor :name, :options
144
+ attr_accessor :parent, :index
145
+ end
146
+
147
+ # An `extensions` range (end exclusive) or a `reserved` range
148
+ # (end exclusive for messages, inclusive for enums)
149
+ class Range < Node
150
+ attr_accessor :start, :end, :options
151
+ attr_accessor :parent, :index
152
+ end
153
+
154
+ # An enum
155
+ class Enum < Node
156
+ attr_accessor :name, :options, :visibility
157
+ attr_accessor :parent, :index
158
+ attr_reader :value, :reserved_range, :reserved_name
159
+
160
+ def initialize
161
+ super
162
+ @value = []
163
+ @reserved_range = []
164
+ @reserved_name = []
165
+ end
166
+ end
167
+
168
+ # An enum value
169
+ class EnumValue < Node
170
+ attr_accessor :name, :number, :options
171
+ attr_accessor :parent, :index
172
+ end
173
+
174
+ # A service
175
+ class Service < Node
176
+ attr_accessor :name, :options
177
+ attr_accessor :index
178
+ attr_reader :rpcs
179
+
180
+ def initialize
181
+ super
182
+ @rpcs = []
183
+ end
184
+ end
185
+
186
+ # An rpc
187
+ class Method < Node
188
+ attr_accessor :name, :input_type, :output_type, :options, :client_streaming, :server_streaming
189
+ attr_accessor :parent, :index
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ module Emitters
6
+ # Writes a serialized `google.protobuf.FileDescriptorSet`, as protoc's
7
+ # `--descriptor_set_out` does
8
+ class DescriptorSet
9
+ # @param include_imports [Boolean] also write every file the given ones import,
10
+ # dependencies first (`import option` files included, as protoc does)
11
+ # @param include_source_info [Boolean] keep `source_code_info`
12
+ # @param pool [Schema::Pool, nil] where `import option` files and their extensions are found
13
+ def initialize(include_imports: false, include_source_info: false, pool: nil)
14
+ @include_imports = include_imports
15
+ @include_source_info = include_source_info
16
+ @pool = pool
17
+ end
18
+
19
+ # @param files [Array<Schema::File>] the files named on the command line
20
+ # @param available [Array<Schema::File>] files `import option` names are looked up in
21
+ # before the pool, such as everything {Compiler#compile} returned
22
+ # @return [String] binary
23
+ def emit(files, available: [])
24
+ out = "".b
25
+ w = Wire::Writer.new(out)
26
+ @available = available.to_h { |f| [f.name, f] }
27
+ ordered(files).each do |file|
28
+ w.write_tag(1, Wire::LENGTH_DELIMITED)
29
+ w.write_bytes(file.serialize(source_info: @include_source_info))
30
+ end
31
+ out
32
+ end
33
+
34
+ private
35
+
36
+ # protoc's GetTransitiveDependencies: depth first, each file after its imports
37
+ def ordered(files)
38
+ return files unless @include_imports
39
+ seen = {}
40
+ out = []
41
+ visit = lambda do |file|
42
+ next if seen[file.name]
43
+ seen[file.name] = true
44
+ file.dependency_files.each { |dep| visit.call(dep) }
45
+ file.option_dependencies.each do |name|
46
+ dep = @available[name] || @pool&.file(name)
47
+ visit.call(dep) if dep
48
+ end
49
+ out << file
50
+ end
51
+ files.each { |f| visit.call(f) }
52
+ out
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ module Emitters
6
+ # Writes the `_pb.rb` file protoc's own Ruby generator writes
7
+ # (ruby_generator.cc): the serialized descriptor added to
8
+ # google-protobuf's generated pool, and constants looked up from it.
9
+ class GoogleRuby
10
+ # @param pool [Schema::Pool, nil] where extensions used in options but defined in
11
+ # `import option` files are found
12
+ def initialize(pool: nil)
13
+ @pool = pool
14
+ end
15
+
16
+ # @param files [Array<Schema::File>]
17
+ # @return [Hash{String => String}] output path to source
18
+ def emit(files)
19
+ files.to_h { |f| [Naming.require_name(f.name) + ".rb", file(f)] }
20
+ end
21
+
22
+ # @param file [Schema::File]
23
+ # @return [String]
24
+ # @raise [Emitters::Error] for a `ruby_package` protoc refuses
25
+ def file(file)
26
+ Naming.check_ruby_package(file)
27
+ p = Printer.new
28
+ p.print("# frozen_string_literal: true\n# Generated by the protocol buffer compiler. DO NOT EDIT!\n" \
29
+ "# source: #{file.name}\n\n")
30
+ p.print("require 'google/protobuf'\n\n")
31
+ unless file.dependencies.empty?
32
+ file.dependencies.each { |dep| p.print("require '#{Naming.require_name(dep)}'\n") }
33
+ p.print("\n")
34
+ end
35
+ p.print("\ndescriptor_data = \"#{GoogleRuby.ruby_escape(GoogleRuby.serialized(file))}\"\n\n" \
36
+ "pool = ::Google::Protobuf::DescriptorPool.generated_pool\npool.add_serialized_file(descriptor_data)\n\n")
37
+ modules = Naming.module_path(file)
38
+ modules.each do |m|
39
+ p.print("module #{m}\n")
40
+ p.indent
41
+ end
42
+ file.messages.each { |m| message_assignment(p, "", m) }
43
+ file.enums.each { |e| enum_assignment(p, "", e) }
44
+ modules.size.times do
45
+ p.outdent
46
+ p.print("end\n")
47
+ end
48
+ p.to_s
49
+ end
50
+
51
+ # What protoc embeds: the file as `FileDescriptor::CopyTo` writes it,
52
+ # without source info and with `json_name` only where the .proto set
53
+ # one. Whether it was set is not kept, so a `json_name` equal to the
54
+ # derived one is taken as not set.
55
+ # @return [String] binary
56
+ def self.serialized(file)
57
+ file.serialize(source_info: false, default_json_names: false)
58
+ end
59
+
60
+ PRINTABLE = (0x20..0x7E)
61
+ HEX = /[0-9A-Fa-f]/
62
+ C_ESCAPES = {"\n" => "\\n", "\r" => "\\r", "\t" => "\\t", "\"" => "\\\"", "'" => "\\'", "\\" => "\\\\"}.freeze
63
+
64
+ # absl::CHexEscape, then a backslash before every `#` that would
65
+ # start an interpolation: ruby_generator.cc's RubyEscape
66
+ def self.ruby_escape(bytes)
67
+ escaped = +""
68
+ last_hex = false
69
+ bytes.each_byte do |b|
70
+ c = b.chr
71
+ hex = false
72
+ if (e = C_ESCAPES[c])
73
+ escaped << e
74
+ elsif !PRINTABLE.cover?(b) || (last_hex && HEX.match?(c))
75
+ escaped << format("\\x%02x", b)
76
+ hex = true
77
+ else
78
+ escaped << c
79
+ end
80
+ last_hex = hex
81
+ end
82
+ escaped.gsub(/#(?=[{$@])/) { "\\#" }
83
+ end
84
+
85
+ private
86
+
87
+ def message_assignment(p, prefix, message)
88
+ return if message.map_entry?
89
+ name = Naming.rubify_constant(message.name)
90
+ p.print("#{prefix}#{name} = ::Google::Protobuf::DescriptorPool.generated_pool." \
91
+ "lookup(\"#{message.full_name}\").msgclass\n")
92
+ message.nested_messages.each { |m| message_assignment(p, "#{prefix}#{name}::", m) }
93
+ message.nested_enums.each { |e| enum_assignment(p, "#{prefix}#{name}::", e) }
94
+ end
95
+
96
+ def enum_assignment(p, prefix, enum)
97
+ p.print("#{prefix}#{Naming.rubify_constant(enum.name)} = ::Google::Protobuf::DescriptorPool.generated_pool." \
98
+ "lookup(\"#{enum.full_name}\").enummodule\n")
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Protostar
4
+ class Compiler
5
+ module Emitters
6
+ # Writes the `_services_pb.rb` stubs grpc-tools' Ruby plugin writes
7
+ # (grpc's src/compiler/ruby_generator.cc). Comments come from source
8
+ # info, so compile with it.
9
+ class Grpc
10
+ # @param files [Array<Schema::File>]
11
+ # @return [Hash{String => String}] output path to source, for files with services only
12
+ def emit(files)
13
+ files.each_with_object({}) do |f, out|
14
+ source = file(f)
15
+ out[Grpc.output_name(f.name)] = source unless source.empty?
16
+ end
17
+ end
18
+
19
+ # @param name [String] "foo/bar.proto"
20
+ # @return [String] "foo/bar_services_pb.rb"
21
+ # @raise [Emitters::Error] for a name protoc's plugin refuses
22
+ def self.output_name(name)
23
+ unless name.size > 6 && ".proto".include?(name[-1])
24
+ raise Error, "Invalid proto file name: must end with .proto"
25
+ end
26
+ name[0, name.size - 6] + "_services_pb.rb"
27
+ end
28
+
29
+ # @param file [Schema::File]
30
+ # @return [String] empty when the file declares no services
31
+ def file(file)
32
+ return "" if file.services.empty?
33
+ @source = file.source_info
34
+ package = ruby_package(file)
35
+ p = Printer.new
36
+ p.print("# Generated by the protocol buffer compiler. DO NOT EDIT!\n")
37
+ p.print("# Source: #{file.name} for package '#{package}'\n")
38
+ file_comments = file_comments(file)
39
+ unless file_comments.empty?
40
+ p.print("# Original file comments:\n")
41
+ p.print(file_comments)
42
+ end
43
+ p.print("\nrequire 'grpc'\n")
44
+ p.print("require '#{file.name.sub(".proto", "_pb")}'\n\n")
45
+ modules = split(package)
46
+ modules.each do |m|
47
+ p.print("module #{Naming.package_to_module(m)}\n")
48
+ p.indent
49
+ end
50
+ file.services.each_with_index { |s, i| service(p, s, [6, i]) }
51
+ modules.size.times do
52
+ p.outdent
53
+ p.print("end\n")
54
+ end
55
+ p.to_s
56
+ ensure
57
+ @source = nil
58
+ end
59
+
60
+ private
61
+
62
+ def service(p, service, path)
63
+ return if service.rpcs.empty?
64
+ p.print("module #{modularize(service.name)}\n")
65
+ p.indent
66
+ p.print(comments(path, true))
67
+ p.print("class Service\n")
68
+ p.indent
69
+ p.print("\ninclude ::GRPC::GenericService\n\n")
70
+ p.print("self.marshal_class_method = :encode\nself.unmarshal_class_method = :decode\n")
71
+ p.print("self.service_name = '#{service.full_name}'\n\n")
72
+ service.rpcs.each_with_index do |rpc, i|
73
+ input = type_of(rpc.input_type)
74
+ input = "stream(#{input})" if rpc.client_streaming?
75
+ output = type_of(rpc.output_type)
76
+ output = "stream(#{output})" if rpc.server_streaming?
77
+ p.print(comments(path + [2, i], true))
78
+ p.print("rpc :#{rpc.name}, #{input}, #{output}\n")
79
+ p.print(comments(path + [2, i], false))
80
+ end
81
+ p.outdent
82
+ p.print("end\n\nStub = Service.rpc_stub_class\n")
83
+ p.outdent
84
+ p.print("end\n")
85
+ p.print(comments(path, false))
86
+ end
87
+
88
+ # The package in dotted form, `ruby_package`'s `::` turned into dots
89
+ def ruby_package(file)
90
+ file.options.ruby_package&.gsub("::", ".") || file.package
91
+ end
92
+
93
+ # std::getline splitting: no trailing empty piece
94
+ def split(text)
95
+ parts = text.split(".", -1)
96
+ parts.pop if parts.last == ""
97
+ parts
98
+ end
99
+
100
+ def modularize(name)
101
+ return name if name.empty?
102
+ out = name[0].upcase(:ascii)
103
+ last_underscore = false
104
+ name[1..].each_char do |c|
105
+ if last_underscore && c != "_"
106
+ out << c.upcase(:ascii)
107
+ elsif c != "_"
108
+ out << c
109
+ end
110
+ last_underscore = c == "_"
111
+ end
112
+ out
113
+ end
114
+
115
+ # grpc's RubyTypeOf: the message's Ruby constant path as grpc spells it
116
+ def type_of(message)
117
+ name = message.full_name
118
+ file = message.file
119
+ if file.options.ruby_package
120
+ name = name.delete_prefix(file.package) if name.start_with?(file.package)
121
+ name = name.delete_prefix(".")
122
+ name = "#{ruby_package(file)}.#{name}"
123
+ end
124
+ parts = split(".#{name}")
125
+ parts.each_with_index.map do |part, i|
126
+ (i < parts.size - 1) ? modularize(part) : Naming.rubify_constant(part)
127
+ end.join("::")
128
+ end
129
+
130
+ def lines(text)
131
+ return [] if text.nil?
132
+ parts = text.split("\n", -1)
133
+ parts.pop if parts.last == ""
134
+ parts
135
+ end
136
+
137
+ # GetPrefixedComments: detached and leading comments, or trailing ones
138
+ def comments(path, leading)
139
+ location = @source&.location(path)
140
+ return "" unless location
141
+ out = []
142
+ if leading
143
+ location.leading_detached_comments.each { |c| out.concat(lines(c)) << "" }
144
+ out.concat(lines(location.leading_comments))
145
+ else
146
+ out.concat(lines(location.trailing_comments))
147
+ end
148
+ prefixed(out)
149
+ end
150
+
151
+ def prefixed(out)
152
+ out.map do |line|
153
+ if line.empty?
154
+ "#\n"
155
+ elsif line.start_with?(" ")
156
+ "##{line}\n"
157
+ else
158
+ "# #{line}\n"
159
+ end
160
+ end.join
161
+ end
162
+
163
+ # The comments above the syntax line, printed raw: a `$` comes out doubled
164
+ def file_comments(file)
165
+ location = @source&.location([12])
166
+ return "" unless location
167
+ out = []
168
+ location.leading_detached_comments.each { |c| out.concat(lines(c)) << "" }
169
+ out.concat(lines(location.leading_comments))
170
+ prefixed(out).gsub("$", "$$")
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end