Module: MilkTea::Bindgen::Generator::GeneratorEmitter
- Included in:
- MilkTea::Bindgen::Generator
- Defined in:
- lib/milk_tea/bindings/bindgen/emitter.rb
Instance Method Summary collapse
- #aggregate_field_name(field, aggregate_node:) ⇒ Object
- #aggregate_field_type(field, owner_name:, aggregate_node:) ⇒ Object
- #anonymous_record_decl_for_field(field, aggregate_node) ⇒ Object
- #bindgen_field_name(name) ⇒ Object
- #bindgen_param_name(name) ⇒ Object
- #discover_synthetic_aggregate_dependencies(declarations) ⇒ Object
- #discover_synthetic_field_type_dependencies(declarations) ⇒ Object
- #emit_aggregate_declaration(kind, name, node) ⇒ Object
- #emit_declaration(declaration) ⇒ Object
- #emit_enum_declaration(kind, name, node) ⇒ Object
- #emit_float_value(value, expected_type) ⇒ Object
- #emit_function_declaration(declaration) ⇒ Object
- #emit_module(declarations) ⇒ Object
- #emitted_name(raw_name) ⇒ Object
- #enum_member_values(node) ⇒ Object
- #generated_binding_name_conflict?(name) ⇒ Boolean
- #generated_module_path ⇒ Object
- #integer_value(node) ⇒ Object
- #lower_aggregate_init_list(values, aggregate:, expected_type:, context:) ⇒ Object
- #lower_constant_expression(node, expected_type:, context:) ⇒ Object
- #lower_init_list_expression(node, expected_type:, context:) ⇒ Object
- #lower_zero_value(expected_type:, context:) ⇒ Object
- #parse_array_type(type) ⇒ Object
- #pointer_zero_literal(expected_type, value) ⇒ Object
- #source_location_key(location) ⇒ Object
- #synthetic_aggregate_name(owner_name, field, aggregate_node) ⇒ Object
Instance Method Details
#aggregate_field_name(field, aggregate_node:) ⇒ Object
170 171 172 173 174 175 176 177 178 179 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 170 def aggregate_field_name(field, aggregate_node:) name = field["name"] return name if name && !name.empty? anonymous_record = anonymous_record_decl_for_field(field, aggregate_node) return name unless anonymous_record field_index = Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.index { |child| child["id"] == field["id"] } || 0 "anonymous_#{anonymous_record.fetch("tagUsed")}_#{field_index}" end |
#aggregate_field_type(field, owner_name:, aggregate_node:) ⇒ Object
160 161 162 163 164 165 166 167 168 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 160 def aggregate_field_type(field, owner_name:, aggregate_node:) override = field_type_override(owner_name, field["name"]) return override if override anonymous_record = anonymous_record_decl_for_field(field, aggregate_node) return synthetic_aggregate_name(owner_name, field, aggregate_node) if anonymous_record map_type_node(field, context: "field #{owner_name}.#{field["name"]}") end |
#anonymous_record_decl_for_field(field, aggregate_node) ⇒ Object
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 185 def anonymous_record_decl_for_field(field, aggregate_node) qual_type = type_qual_type(field) return unless qual_type tag_match = qual_type.match(/\A(struct|union)\b/) return unless tag_match return unless qual_type.include?("(unnamed at ") || qual_type.include?("(anonymous at ") field_begin = source_location_key(field.dig("range", "begin")) return unless field_begin expected_tag = tag_match[1] Array(aggregate_node["inner"]).find do |child| next false unless child["kind"] == "RecordDecl" next false unless child["tagUsed"] == expected_tag source_location_key(child["loc"]) == field_begin end end |
#bindgen_field_name(name) ⇒ Object
85 86 87 88 89 90 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 85 def bindgen_field_name(name) name = emitted_name(name) return [name, nil] unless Token::KEYWORDS.key?(name) ["#{name}_", nil] end |
#bindgen_param_name(name) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 92 def bindgen_param_name(name) name = emitted_name(name) return [name, nil] unless generated_binding_name_conflict?(name) ["#{name}_", nil] end |
#discover_synthetic_aggregate_dependencies(declarations) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 103 def discover_synthetic_aggregate_dependencies(declarations) pending = declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration| [declaration[:name], declaration[:node]] end seen = {} until pending.empty? owner_name, aggregate_node = pending.shift key = [owner_name, aggregate_node["id"]] next if seen[key] seen[key] = true Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.each do |field| anonymous_record = anonymous_record_decl_for_field(field, aggregate_node) next unless anonymous_record synthetic_name = synthetic_aggregate_name(owner_name, field, aggregate_node) unless @synthetic_declarations.any? { |declaration| declaration[:name] == synthetic_name } @synthetic_declarations << { kind: anonymous_record.fetch("tagUsed"), name: synthetic_name, node: anonymous_record } @aggregate_declarations[synthetic_name] = anonymous_record end pending << [synthetic_name, anonymous_record] end end end |
#discover_synthetic_field_type_dependencies(declarations) ⇒ Object
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 131 def discover_synthetic_field_type_dependencies(declarations) pending = declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration| [declaration[:name], declaration[:node]] end pending.concat( @synthetic_declarations.select { |declaration| %w[struct union].include?(declaration[:kind]) }.map do |declaration| [declaration[:name], declaration[:node]] end, ) seen = {} until pending.empty? owner_name, aggregate_node = pending.shift key = [owner_name, aggregate_node["id"]] next if seen[key] seen[key] = true Array(aggregate_node["inner"]).select { |child| child["kind"] == "FieldDecl" }.each do |field| aggregate_field_type(field, owner_name:, aggregate_node:) anonymous_record = anonymous_record_decl_for_field(field, aggregate_node) next unless anonymous_record pending << [synthetic_aggregate_name(owner_name, field, aggregate_node), anonymous_record] end end end |
#emit_aggregate_declaration(kind, name, node) ⇒ Object
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 69 def emit_aggregate_declaration(kind, name, node) explicit_c_name = aggregate_explicit_c_name(name, node) stripped = emitted_name(name) header = "#{kind} #{stripped}" header += " = c#{explicit_c_name.inspect}" if explicit_c_name header += ":" lines = [header] fields = Array(node["inner"]).select { |child| child["kind"] == "FieldDecl" } fields.each do |field| field_type = aggregate_field_type(field, owner_name: name, aggregate_node: node) mt_name, = bindgen_field_name(emitted_name(aggregate_field_name(field, aggregate_node: node))) lines << " #{mt_name}: #{field_type}" end lines end |
#emit_declaration(declaration) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 47 def emit_declaration(declaration) case declaration[:kind] when "struct", "union" emit_aggregate_declaration(declaration[:kind], declaration[:name], declaration[:node]) when "opaque" name = emitted_name(declaration[:name]) line = "opaque #{name}" line += " = c#{declaration[:linkage_name].inspect}" if declaration[:linkage_name] [line] when "enum", "flags" emit_enum_declaration(declaration[:kind], emitted_name(declaration[:name]), declaration[:node]) when "type_alias" ["type #{emitted_name(declaration[:name])} = #{declaration[:mapped_type]}"] when "const" ["const #{emitted_name(declaration[:name])}: #{declaration[:type]} = #{declaration[:value]}"] when "function" emit_function_declaration(declaration) else raise BindgenError, "unsupported bindgen declaration kind #{declaration[:kind]}" end end |
#emit_enum_declaration(kind, name, node) ⇒ Object
215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 215 def emit_enum_declaration(kind, name, node) members = enum_member_values(node) backing_type = if members.empty? "int" else map_c_type(members.first.dig(:node, "type", "qualType"), context: "enum #{name}") end lines = ["#{kind} #{name}: #{backing_type}"] members.each do |member| lines << " #{member[:node]["name"]} = #{member[:value]}" end lines end |
#emit_float_value(value, expected_type) ⇒ Object
316 317 318 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 316 def emit_float_value(value, expected_type) value.match?(/[.eE]/) ? value : "#{value}.0" end |
#emit_function_declaration(declaration) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 250 def emit_function_declaration(declaration) original_name = declaration[:name] stripped_name = emitted_name(original_name) params = declaration[:params].map do |param| param_name, = bindgen_param_name(param[:name]) "#{param_name}: #{param[:type]}" end params << "..." if declaration[:variadic] line = "external function #{stripped_name}(#{params.join(', ')}) -> #{declaration[:return_type]}" if stripped_name != original_name line += " = c#{original_name.inspect}" end [line] end |
#emit_module(declarations) ⇒ Object
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 13 def emit_module(declarations) declarations = synthetic_declarations_for(declarations) + declarations lines = [] lines << "# generated by mtc bindgen from #{@header_path}" lines << "external" lines << "" @module_imports.each do |import| lines << %(import #{import.fetch(:module_name)} as #{import.fetch(:alias)}) end directives = [] @link_libraries.each do |library| directives << %(link #{library.dump}) end includes = @include_directives && !@include_directives.empty? ? @include_directives : [File.basename(@header_path)] includes.each do |include_name| directives << %(include #{include_name.dump}) end lines.concat(directives) declarations.each do |declaration| lines.concat(emit_declaration(declaration)) end source = lines.join("\n") + "\n" Formatter.format_source(source, path: generated_module_path, mode: :tidy) end |
#emitted_name(raw_name) ⇒ Object
7 8 9 10 11 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 7 def emitted_name(raw_name) return raw_name unless @strip_leading_underscores raw_name.sub(/\A_+/, "") end |
#enum_member_values(node) ⇒ Object
229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 229 def enum_member_values(node) next_value = 0 Array(node["inner"]).filter_map do |child| next unless child["kind"] == "EnumConstantDecl" explicit_value = integer_value(child) value = explicit_value ? Integer(explicit_value, 10) : next_value next_value = value + 1 { node: child, value: } end end |
#generated_binding_name_conflict?(name) ⇒ Boolean
99 100 101 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 99 def generated_binding_name_conflict?(name) Token::KEYWORDS.key?(name) || Types::RESERVED_VALUE_TYPE_NAMES.include?(name) end |
#generated_module_path ⇒ Object
43 44 45 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 43 def generated_module_path "#{@module_name.tr('.', '/')}" + ".mt" end |
#integer_value(node) ⇒ Object
242 243 244 245 246 247 248 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 242 def integer_value(node) constant = Array(node["inner"]).find { |child| child["kind"] == "ConstantExpr" } return constant["value"] if constant && constant.key?("value") literal = Array(node["inner"]).find { |child| child["kind"] == "IntegerLiteral" } literal&.[]("value") end |
#lower_aggregate_init_list(values, aggregate:, expected_type:, context:) ⇒ Object
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 320 def lower_aggregate_init_list(values, aggregate:, expected_type:, context:) fields = Array(aggregate["inner"]).select { |child| child["kind"] == "FieldDecl" } raise BindgenError, "initializer field count mismatch for #{context}" if values.length > fields.length arguments = fields.each_with_index.map do |field, index| field_type = map_type_node(field, context: "field #{expected_type}.#{field["name"]}") value = values[index] lowered = if value lower_constant_expression(value, expected_type: field_type, context: "field #{field["name"]} of #{context}") else lower_zero_value(expected_type: field_type, context: "field #{field["name"]} of #{context}") end "#{field["name"]} = #{lowered}" end "#{expected_type}(#{arguments.join(', ')})" end |
#lower_constant_expression(node, expected_type:, context:) ⇒ Object
265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 265 def lower_constant_expression(node, expected_type:, context:) raise BindgenError, "missing initializer for #{context}" unless node case node["kind"] when "IntegerLiteral" integer_value = node.fetch("value") typed_null = pointer_zero_literal(expected_type, integer_value) return typed_null if typed_null integer_value when "FloatingLiteral" emit_float_value(node.fetch("value"), expected_type) when "StringLiteral" %(c#{node.fetch("value")}) when "ImplicitValueInitExpr" lower_zero_value(expected_type:, context:) when "ImplicitCastExpr", "ConstantExpr", "CompoundLiteralExpr", "ParenExpr" child = Array(node["inner"]).first lower_constant_expression(child, expected_type:, context:) when "UnaryOperator" operator = node["opcode"] operand = lower_constant_expression(Array(node["inner"]).first, expected_type:, context:) "#{operator}#{operand}" when "InitListExpr" lower_init_list_expression(node, expected_type:, context:) else raise BindgenError, "unsupported constant initializer #{node["kind"]} for #{context}" end end |
#lower_init_list_expression(node, expected_type:, context:) ⇒ Object
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 295 def lower_init_list_expression(node, expected_type:, context:) values = Array(node["inner"]) aggregate = @aggregate_declarations[expected_type] return lower_aggregate_init_list(values, aggregate:, expected_type:, context:) if aggregate element_type, length = parse_array_type(expected_type) raise BindgenError, "unsupported aggregate constant type #{expected_type} for #{context}" unless element_type raise BindgenError, "initializer field count mismatch for #{context}" if values.length > length arguments = (0...length).map do |index| value = values[index] if value lower_constant_expression(value, expected_type: element_type, context: "element #{index} of #{context}") else lower_zero_value(expected_type: element_type, context: "element #{index} of #{context}") end end "#{expected_type}(#{arguments.join(', ')})" end |
#lower_zero_value(expected_type:, context:) ⇒ Object
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 337 def lower_zero_value(expected_type:, context:) return "false" if expected_type == "bool" return "0" if %w[char byte ubyte short ushort int uint long ulong ptr_int ptr_uint].include?(expected_type) return "0.0" if %w[float double].include?(expected_type) return "null[ptr[char]]" if expected_type == "cstr" return "null" if expected_type == "cstr?" return "null[#{expected_type}]" if expected_type.start_with?("ptr[") || expected_type.start_with?("const_ptr[") if @aggregate_declarations.key?(expected_type) return lower_aggregate_init_list([], aggregate: @aggregate_declarations.fetch(expected_type), expected_type:, context:) end element_type, length = parse_array_type(expected_type) if element_type values = Array.new(length) do |index| lower_zero_value(expected_type: element_type, context: "element #{index} of #{context}") end return "#{expected_type}(#{values.join(', ')})" end "#{expected_type}<-0" end |
#parse_array_type(type) ⇒ Object
372 373 374 375 376 377 378 379 380 381 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 372 def parse_array_type(type) return [nil, nil] unless type.start_with?("array[") && type.end_with?("]") parts = split_top_level_csv(type.delete_prefix("array[").delete_suffix("]")) return [nil, nil] unless parts.length == 2 [parts[0], Integer(parts[1], 10)] rescue ArgumentError [nil, nil] end |
#pointer_zero_literal(expected_type, value) ⇒ Object
360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 360 def pointer_zero_literal(expected_type, value) return nil unless Integer(value, 10).zero? return "null[ptr[char]]" if expected_type == "cstr" return "null" if expected_type == "cstr?" return "null[#{expected_type}]" if expected_type.start_with?("ptr[") || expected_type.start_with?("const_ptr[") nil rescue ArgumentError nil end |
#source_location_key(location) ⇒ Object
205 206 207 208 209 210 211 212 213 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 205 def source_location_key(location) return unless location [ location["offset"], location.dig("includedFrom", "file"), location["file"], ] end |
#synthetic_aggregate_name(owner_name, field, aggregate_node) ⇒ Object
181 182 183 |
# File 'lib/milk_tea/bindings/bindgen/emitter.rb', line 181 def synthetic_aggregate_name(owner_name, field, aggregate_node) "#{owner_name}_#{aggregate_field_name(field, aggregate_node:)}" end |