├── .gitignore ├── LICENSE ├── README.md ├── shard.yml ├── spec ├── autobind_spec.cr └── spec_helper.cr └── src ├── autobind.cr └── autobind ├── constant.cr ├── debug.cr ├── parser.cr └── type.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /lib 3 | /shard.lock 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Julien Portalier , Julien Reichardt 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Autobind 2 | 3 | **DEPRECATED** use https://github.com/crystal-lang/clang.cr instead. 4 | 5 | This project duplicates the original one, which have fixes not backported here. 6 | 7 | [![Apache2.0](https://img.shields.io/badge/License-Apache2.0-blue.svg?style=flat-square)](https://en.wikipedia.org/wiki/Apache_License) 8 | 9 | ### Automatic C bindings generator for Crystal 10 | 11 | Built from the awesome [clang.cr](https://github.com/ysbaddaden/clang.cr) done by [ysbaddaden](https://github.com/ysbaddaden) 12 | 13 | ## How to use (recommended) 14 | 15 | ## Installation 16 | 17 | Add this block to your application's shard.yml: 18 | 19 | ```yaml 20 | dependencies: 21 | autobind: 22 | github: j8r/crystal-autobind 23 | 24 | scripts: 25 | postinstall: ./gen-bindings.sh 26 | ``` 27 | 28 | You can create a `gen-bindings.sh` script, with the permissions `chmod 750` 29 | 30 | ```sh 31 | #!/bin/sh 32 | 33 | # Example of generating the bindings for `errno` at `src/libc/errno.cr` 34 | mkdir -p src/libc && lib/autobind/bin/autobind -I/usr/include errno.h > src/libc/errno.cr 35 | ``` 36 | 37 | The postinstall script will only be runned when included as a dependency on a project. 38 | 39 | ## Usage 40 | 41 | The newly generated `.cr` bindings can be used to create a documented shard wrapper, that can be then require and used as a shard in your application. 42 | 43 | The development headers of the library are required whether the bindings are previously generated or not. 44 | 45 | The only caveat is to have `clang`/`libclang` installed to regenerate the bindings. 46 | 47 | The bindings directory can thus be ignored in `.gitignore` to avoid versioning: 48 | 49 | ``` 50 | /bin 51 | /lib 52 | /shard.lock 53 | /src/libc 54 | ``` 55 | 56 | ## Build 57 | 58 | Ensure to have `libclang` installed 59 | 60 | Install dependencies: 61 | 62 | `shards install` 63 | 64 | Build autobind: 65 | 66 | `crystal build src/autobind.cr` 67 | 68 | ## Usage examples 69 | 70 | You will need the development headers of your targeted library, usually coming inside the `dev` packages of you distribution. 71 | 72 | ```sh 73 | ./autobind -I/usr/include errno.h > errno.cr 74 | 75 | ./autobind -I/usr/lib/llvm-6.0/include llvm-c/Core.h --remove-enum-prefix=LLVM --remove-enum-suffix > Core.cr 76 | 77 | ./autobind -I/usr/lib/llvm-6.0/include clang-c/Index.h --remove-enum-prefix > Index.cr 78 | ``` 79 | 80 | ## Reference 81 | 82 | - [C interface to Clang](http://clang.llvm.org/doxygen/group__CINDEX.html) 83 | 84 | ## License 85 | 86 | Distributed under the Apache 2.0 license. 87 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: autobind 2 | version: 0.5.1 3 | 4 | authors: 5 | - Julien Portalier 6 | - Julien Reichardt 7 | 8 | description: Automatic C binding generator for Crystal 9 | 10 | dependencies: 11 | clang: 12 | github: ysbaddaden/clang.cr 13 | commit: 349a9185bc9a1756dd4c682c000764ec31a3b5da 14 | 15 | targets: 16 | autobind: 17 | main: src/autobind.cr 18 | 19 | scripts: 20 | postinstall: shards build 21 | 22 | executables: 23 | - autobind 24 | 25 | license: Apache-2.0 26 | -------------------------------------------------------------------------------- /spec/autobind_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | 3 | macro assert_binding(header_file) 4 | it "generates bindings for {{header_file.id}}" do 5 | parser = Autobind::Parser.new {{header_file}}, ["-I/usr/include"] 6 | parser.parse 7 | parser.check.should be_true 8 | end 9 | end 10 | 11 | describe Autobind do 12 | assert_binding "errno.h" 13 | assert_binding "fcntl.h" 14 | assert_binding "syscall.h" 15 | assert_binding "ulimit.h" 16 | assert_binding "utime.h" 17 | end 18 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/autobind/constant" 3 | require "../src/autobind/parser" 4 | require "../src/autobind/type" 5 | -------------------------------------------------------------------------------- /src/autobind.cr: -------------------------------------------------------------------------------- 1 | require "./autobind/constant" 2 | require "./autobind/parser" 3 | require "./autobind/type" 4 | 5 | cflags = [] of String 6 | header = "" 7 | remove_enum_prefix = remove_enum_suffix = false 8 | lib_name = "LibC" 9 | mod_name = nil 10 | Help = <<-USAGE 11 | usage : autobind [--help] [options] 12 | 13 | Some available options are: 14 | -I Adds directory to search path for include files 15 | -D Adds an implicit #define 16 | 17 | In addition, the CFLAGS environment variable will be used, so you may set it 18 | up before compilation when search directories, defines, and other options 19 | aren't fixed and can be dynamic. 20 | 21 | The following options control how enum constants are cleaned up. By default 22 | the value is false (no cleanup), whereas true will remove matching patterns, 23 | while a fixed value will remove just that: 24 | --remove-enum-prefix[=true,false,] 25 | --remove-enum-suffix[=true,false,] 26 | 27 | The resulting library name can be controlled with the --lib-name argument, 28 | and it can be wrapped in a parent module with --module-name. 29 | --lib-name="LibC" (the default) 30 | --parent-module="Library" 31 | If no module is specified, the resulting code is not wrapped in a parent 32 | module. If the library name is not specified, it will be called "LibC". 33 | USAGE 34 | USAGE_EXIT_CODE = 64 35 | 36 | if arg = ENV["CFLAGS"]? 37 | cflags += arg.split(' ').reject(&.empty?) 38 | end 39 | 40 | until ARGV.empty? 41 | case arg = ARGV.shift 42 | when "-I", "-D" 43 | if value = ARGV[1]? 44 | cflags << value 45 | else 46 | abort "fatal : missing value for #{arg}\n#{Help}", USAGE_EXIT_CODE 47 | end 48 | when .starts_with?("-I"), .starts_with?("-D") 49 | cflags << arg 50 | when .ends_with?(".h") 51 | abort "FATAL: you can only specify one header\n#{Help}", USAGE_EXIT_CODE unless header.empty? 52 | header = arg 53 | when "--remove-enum-prefix" 54 | remove_enum_prefix = true 55 | when .starts_with?("--remove-enum-prefix=") 56 | case value = arg[21..-1] 57 | when "", "false" then remove_enum_prefix = false 58 | when "true" then remove_enum_prefix = true 59 | else remove_enum_prefix = value 60 | end 61 | when "--remove-enum-suffix" 62 | remove_enum_suffix = true 63 | when .starts_with?("--remove-enum-suffix=") 64 | case value = arg[21..-1] 65 | when "", "false" then remove_enum_suffix = false 66 | when "true" then remove_enum_suffix = true 67 | else remove_enum_suffix = value 68 | end 69 | when .starts_with? "--lib-name" 70 | abort "Only one library name can be specified", USAGE_EXIT_CODE unless lib_name == "LibC" 71 | if arg.includes? '=' 72 | lib_name = arg[11..-1] 73 | elsif name = ARGV[1]? 74 | lib_name = name 75 | else 76 | abort "FATAL: library name argument was specified but no value was received.\n#{Help}", USAGE_EXIT_CODE 77 | end 78 | when .starts_with? "--parent-module" 79 | abort "FATAL: only one module name can be specified", USAGE_EXIT_CODE if mod_name 80 | err_str = "FATAL: module name argument was specified but no value was received." 81 | if arg.includes? '=' 82 | mod_name = arg[16..-1] 83 | abort err_str + '\n' + Help, USAGE_EXIT_CODE if mod_name.empty? 84 | else 85 | mod_name = ARGV[1]? || abort err_str + '\n' + Help, USAGE_EXIT_CODE 86 | end 87 | when "--help", "-h" 88 | STDERR.puts Help 89 | exit 0 90 | else 91 | abort "FATAL: Unknown option: #{arg}\n#{Help}", USAGE_EXIT_CODE 92 | end 93 | end 94 | 95 | Clang.default_c_include_directories cflags 96 | 97 | abort "FATAL: no header to create bindings for.\n#{Help}", USAGE_EXIT_CODE if header.empty? 98 | 99 | parser = Autobind::Parser.new( 100 | header, 101 | cflags, 102 | remove_enum_prefix: remove_enum_prefix, 103 | remove_enum_suffix: remove_enum_suffix, 104 | name: lib_name, 105 | module_name: mod_name # <-- possibly nil 106 | ) 107 | 108 | parser.parse 109 | puts parser.libc_output 110 | check = parser.check 111 | abort check if check != true 112 | -------------------------------------------------------------------------------- /src/autobind/constant.cr: -------------------------------------------------------------------------------- 1 | module Autobind::Constant 2 | def self.to_crystal(spelling) 3 | case spelling 4 | when "uint8_t" then "UInt8" 5 | when "uint16_t" then "UInt16" 6 | when "uint32_t" then "UInt32" 7 | when "uint64_t" then "UInt64" 8 | else 9 | spelling = spelling[6..-1] if spelling.starts_with?("const ") 10 | spelling = spelling.lstrip('_') 11 | 12 | if spelling[0]?.try(&.ascii_uppercase?) 13 | spelling 14 | else 15 | spelling.camelcase 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /src/autobind/debug.cr: -------------------------------------------------------------------------------- 1 | # TODO: needs refactoring 2 | # This sample parses a C header file, and prints all Clang cursors to STDOUT. 3 | module Autobind 4 | extend self 5 | 6 | def visit(parent, deep = 0) 7 | parent.visit_children do |cursor| 8 | if deep == 0 9 | unless cursor.kind.macro_definition? || 10 | cursor.kind.macro_expansion? || 11 | cursor.kind.inclusion_directive? 12 | puts 13 | end 14 | else 15 | print " " * deep 16 | end 17 | 18 | puts "#{cursor.kind}: spelling=#{cursor.spelling} type.kind=#{cursor.type.kind} type.spelling=#{cursor.type.spelling.inspect} at #{cursor.location}" 19 | 20 | case cursor.kind 21 | when .class_decl? 22 | puts [:class, cursor.type.size_of].inspect 23 | when .field_decl? 24 | puts [:field, cursor.offset_of_field].inspect 25 | when .function_decl? 26 | puts [:function, cursor.mangling].inspect 27 | when .constructor? 28 | puts [:constructor, cursor.cxx_manglings].inspect 29 | when .destructor? 30 | puts [:destructor, cursor.cxx_manglings].inspect 31 | # when .cxx_method? 32 | # puts [:cxx_method, cursor.spelling].inspect 33 | when .cxx_access_specifier? 34 | puts [:cxx_access_specifier, cursor.cxx_access_specifier].inspect 35 | end 36 | 37 | visit(cursor, deep + 2) 38 | 39 | Clang::ChildVisitResult::Continue 40 | end 41 | end 42 | 43 | index = Clang::Index.new 44 | 45 | file_name = ARGV[0]? || "clang-c/Documentation.h" 46 | args = [ 47 | "-I/usr/include", 48 | "-I/usr/lib/llvm-5.0/lib/clang/5.0.0/include", 49 | "-I/usr/lib/llvm-5.0/include", 50 | ] 51 | options = Clang::TranslationUnit.default_options | 52 | Clang::TranslationUnit::Options::DetailedPreprocessingRecord | 53 | Clang::TranslationUnit::Options::SkipFunctionBodies 54 | 55 | case File.extname(file_name) 56 | when ".h" 57 | files = [Clang::UnsavedFile.new("input.c", "#include <#{file_name}>\n")] 58 | tu = Clang::TranslationUnit.from_source(index, files, args, options) 59 | when ".hpp" 60 | files = [Clang::UnsavedFile.new("input.cpp", "#include <#{file_name}>\n")] 61 | tu = Clang::TranslationUnit.from_source(index, files, args, options) 62 | else 63 | # tu = Clang::TranslationUnit.from_source_file(index, file_name, args) 64 | files = [Clang::UnsavedFile.new(file_name, File.read(file_name))] 65 | tu = Clang::TranslationUnit.from_source(index, files, args, options) 66 | end 67 | 68 | visit(tu.cursor) 69 | end 70 | -------------------------------------------------------------------------------- /src/autobind/parser.cr: -------------------------------------------------------------------------------- 1 | require "clang" 2 | require "compiler/crystal/formatter" 3 | require "compiler/crystal/syntax" 4 | 5 | module Autobind 6 | class Parser 7 | protected getter index : Clang::Index 8 | protected getter translation_unit : Clang::TranslationUnit 9 | getter output = "" 10 | getter name = "LibC" 11 | getter? module_name : String? = nil 12 | 13 | def libc_output 14 | check( 15 | if mod = module_name? 16 | "module #{mod}\nlib #{name}\n#{@output}end\nend\n" 17 | else 18 | "lib LibC\n#{@output}end\n" 19 | end 20 | ) 21 | end 22 | 23 | private def check(output) 24 | # check formatting 25 | formatted = Crystal.format output 26 | # check syntax 27 | Crystal::Parser.parse formatted 28 | formatted 29 | rescue err : Crystal::SyntaxException 30 | STDERR.puts "\ 31 | WARNING: invalid crystal code was generated for #{@header_name}. You \ 32 | will need to edit the generated code before it will run!\n\ 33 | Error: #{err}" 34 | output 35 | end 36 | 37 | def check 38 | # check formatting 39 | Crystal.format libc_output 40 | # check syntax 41 | Crystal::Parser.parse libc_output 42 | true 43 | rescue ex 44 | "The generated binding results of invalid Crystal code:\n#{ex}" 45 | end 46 | 47 | @remove_enum_prefix : String | Bool 48 | @remove_enum_suffix : String | Bool 49 | 50 | enum Process 51 | EVERYTHING 52 | FILE 53 | end 54 | 55 | def self.parse(header_name, args = [] of String, process : Process = Process::FILE) 56 | new(header_name, args).parse 57 | end 58 | 59 | def initialize(@header_name : String, args = [] of String, 60 | @process : Process = Process::FILE, 61 | @remove_enum_prefix = false, 62 | @remove_enum_suffix = false, 63 | @name = "LibC", 64 | @module_name = nil) 65 | # TODO: support C++ (rename input.c to input.cpp) 66 | # TODO: support local filename (use quotes instead of angle brackets) 67 | files = [ 68 | Clang::UnsavedFile.new("input.c", "#include <#{@header_name}>\n"), 69 | ] 70 | options = Clang::TranslationUnit.default_options | 71 | Clang::TranslationUnit::Options.flags(DetailedPreprocessingRecord, SkipFunctionBodies) 72 | 73 | @index = Clang::Index.new 74 | @translation_unit = Clang::TranslationUnit.from_source(index, files, args, options) 75 | end 76 | 77 | def parse 78 | translation_unit.cursor.visit_children do |cursor| 79 | if @process.everything? || cursor.location.file_name.try(&.ends_with?("/#{@header_name}")) 80 | case cursor.kind 81 | when .macro_definition? then visit_define(cursor, translation_unit) 82 | when .typedef_decl? then visit_typedef(cursor) 83 | when .enum_decl? then visit_enum(cursor) unless cursor.spelling.empty? 84 | when .struct_decl? then visit_struct(cursor) unless cursor.spelling.empty? 85 | when .union_decl? then visit_union(cursor) 86 | when .function_decl? then visit_function(cursor) 87 | when .var_decl? then visit_var(cursor) 88 | # when .class_decl? 89 | # TODO: C++ classes 90 | # when .namespace_decl? 91 | # TODO: C++ namespaces 92 | when .macro_expansion?, .macro_instantiation?, .inclusion_directive? 93 | # skip 94 | else 95 | " # WARNING: unexpected #{cursor.kind} child cursor" 96 | end 97 | end 98 | Clang::ChildVisitResult::Continue 99 | end 100 | end 101 | 102 | def visit_define(cursor, translation_unit) 103 | # TODO: analyze the tokens to build the constant value (e.g. type cast, ...) 104 | 105 | value = String.build do |str| 106 | previous = nil 107 | translation_unit.tokenize(cursor.extent, skip: 1) do |token| 108 | case token.kind 109 | when .comment? 110 | next 111 | when .punctuation? 112 | break if token.spelling == "#" 113 | when .literal? 114 | parse_literal_token(token.spelling, str) 115 | previous = token 116 | next 117 | else 118 | str << ' ' if previous 119 | end 120 | str << token.spelling 121 | previous = token 122 | end 123 | end 124 | 125 | if value.starts_with?('(') && value.ends_with?(')') 126 | value = value[1..-2] 127 | end 128 | 129 | variable = case value 130 | when .starts_with? "0x" 131 | # hexadecimal number 132 | " #{cursor.spelling.lstrip('_')} = #{value}" 133 | when .starts_with? '0' 134 | # octal number 135 | " #{cursor.spelling.lstrip('_')} = 0o#{value}" 136 | else 137 | " #{cursor.spelling.lstrip('_')} = #{value}" 138 | end 139 | 140 | begin 141 | Crystal::Parser.parse variable 142 | @output += " #{variable}\n" 143 | rescue ex 144 | @output += "# #{variable}\n" 145 | end 146 | end 147 | 148 | private def parse_literal_token(literal, io) 149 | if literal =~ /^((0[X])?([+\-0-9A-F.e]+))(F|L|U|UL|LL|ULL)?$/i 150 | number, prefix, digits, suffix = $1, $2?, $3, $4? 151 | 152 | if prefix == "0x" && suffix == "F" && digits.size.odd? 153 | # false-positive: matched 0xFF, 0xffff, ... 154 | io << literal 155 | else 156 | case suffix.try(&.upcase) 157 | when "U" 158 | io << "UInt.new(" << number << ')' 159 | when "L" 160 | if number.index('.') 161 | io << "LongDouble.new(" << number << ')' 162 | else 163 | io << "Long.new(" << number << ')' 164 | end 165 | when "F" then io << number << "_f32" 166 | when "UL" then io << "ULong.new(" << number << ')' 167 | when "LL" then io << "LongLong.new(" << number << ')' 168 | when "ULL" then io << "ULongLong.new(" << number << ')' 169 | else io << number 170 | end 171 | end 172 | else 173 | io << literal 174 | end 175 | end 176 | 177 | def visit_typedef(cursor) 178 | children = [] of Clang::Cursor 179 | 180 | cursor.visit_children do |c| 181 | children << c 182 | Clang::ChildVisitResult::Continue 183 | end 184 | 185 | if children.size <= 1 186 | type = cursor.typedef_decl_underlying_type 187 | 188 | if type.kind.elaborated? 189 | t = type.named_type 190 | c = t.cursor 191 | 192 | # did the typedef named the anonymous struct? in which case we do 193 | # process the struct now, or did the struct already have a name? in 194 | # which case we already processed it: 195 | return unless c.spelling.empty? 196 | 197 | case t.kind 198 | when .record? 199 | # visit_typedef_struct(cursor, type.named_type.cursor) 200 | visit_struct(c, cursor.spelling) 201 | when .enum? 202 | # visit_typedef_enum(cursor, type.named_type.cursor) 203 | visit_enum(c, cursor.spelling) 204 | else 205 | puts " # WARNING: unexpected #{t.kind} within #{cursor.kind} (visit_typedef)" 206 | end 207 | else 208 | name = Constant.to_crystal(cursor.spelling) 209 | @output += " alias #{name} = #{Type.to_crystal(type)}\n" 210 | end 211 | else 212 | visit_typedef_proc(cursor, children) 213 | end 214 | end 215 | 216 | # private def visit_typedef_struct(cursor, c) 217 | # case c.spelling 218 | # when .empty? 219 | # visit_struct(c, cursor.spelling) 220 | # when cursor.spelling 221 | # # skip 222 | # else 223 | # name = Constant.to_crystal(cursor.spelling) 224 | # type = Constant.to_crystal(c.spelling) 225 | # puts " alias #{name} = #{type}" 226 | # end 227 | # end 228 | 229 | # private def visit_typedef_enum(cursor, c) 230 | # case c.spelling 231 | # when .empty? 232 | # visit_enum(c, cursor.spelling) 233 | # when cursor.spelling 234 | # # skip 235 | # else 236 | # name = Constant.to_crystal(cursor.spelling) 237 | # type = Type.to_crystal(c.type) 238 | # puts " alias #{name} = #{type}" 239 | # end 240 | # end 241 | 242 | private def visit_typedef_type(cursor, c) 243 | name = Constant.to_crystal(cursor.spelling) 244 | type = Type.to_crystal(c.type.canonical_type) 245 | @output += " alias #{name} = #{type}\n" 246 | end 247 | 248 | private def visit_typedef_proc(cursor, children) 249 | if children.first.kind.parm_decl? 250 | ret = "Void" 251 | else 252 | ret = Type.to_crystal(children.shift.type.canonical_type) 253 | end 254 | 255 | @output += String.build do |str| 256 | str << " alias #{Constant.to_crystal(cursor.spelling)} = (" 257 | children.each_with_index do |c, index| 258 | str << ", " unless index == 0 259 | # unless c.spelling.empty? 260 | # print c.spelling.underscore 261 | # print " : " 262 | # end 263 | str << Type.to_crystal c.type 264 | end 265 | str.puts ") -> #{ret}" 266 | end 267 | end 268 | 269 | def visit_enum(cursor, spelling = cursor.spelling) 270 | type = cursor.enum_decl_integer_type.canonical_type 271 | @output += " enum #{Constant.to_crystal(spelling)} : #{Type.to_crystal(type)}\n" 272 | 273 | values = [] of {String, Int64 | UInt64} 274 | 275 | cursor.visit_children do |c| 276 | case c.kind 277 | when .enum_constant_decl? 278 | value = case type.kind 279 | when .u_int? then c.enum_constant_decl_unsigned_value 280 | else c.enum_constant_decl_value 281 | end 282 | values << {c.spelling, value} 283 | else 284 | puts " # WARNING: unexpected #{c.kind} within #{cursor.kind} (visit_enum)" 285 | end 286 | Clang::ChildVisitResult::Continue 287 | end 288 | 289 | prefix = cleanup_prefix_from_enum_constant(cursor, values) 290 | suffix = cleanup_suffix_from_enum_constant(cursor, values) 291 | 292 | values.each do |name, value| 293 | if name.includes?(spelling) 294 | # when the enum spelling is fully duplicated in constants: remove it all 295 | constant = name.sub(spelling, "").lstrip('_') 296 | else 297 | # remove similar prefix/suffix patterns from all constants: 298 | start = prefix.size 299 | stop = Math.max(suffix.size + 1, 1) 300 | constant = name[start..-stop] 301 | end 302 | 303 | unless constant[0].ascii_uppercase? 304 | constant = Constant.to_crystal(constant) 305 | end 306 | 307 | @output += " #{constant} = #{value}\n" 308 | end 309 | 310 | @output += " end\n" 311 | end 312 | 313 | private def cleanup_prefix_from_enum_constant(cursor, values) 314 | prefix = "" 315 | reference = values.size > 1 ? values.first[0] : cursor.spelling 316 | 317 | if pre = @remove_enum_prefix 318 | reference = pre if pre.is_a?(String) 319 | 320 | reference.each_char do |char| 321 | testing = prefix + char 322 | 323 | if values.all? &.first.starts_with?(testing) 324 | prefix = testing 325 | else 326 | # TODO: try to match a word delimitation, to only remove whole words 327 | # not a few letters that happen to match. 328 | return prefix 329 | end 330 | end 331 | end 332 | 333 | prefix 334 | end 335 | 336 | private def cleanup_suffix_from_enum_constant(cursor, values) 337 | suffix = "" 338 | reference = values.size > 1 ? values.first[0] : cursor.spelling 339 | 340 | if suf = @remove_enum_suffix 341 | reference = suf if suf.is_a?(String) 342 | 343 | reference.reverse.each_char do |char| 344 | testing = char + suffix 345 | 346 | if values.all? &.first.ends_with?(testing) 347 | suffix = testing 348 | else 349 | # try to match a word delimitation, to only remove whole words not a 350 | # few letters that happen to match: 351 | a, b = suffix[0]?, suffix[1]? 352 | return suffix if a && b && (a == '_' || (a.ascii_uppercase? && !b.ascii_uppercase?)) 353 | return "" 354 | end 355 | end 356 | end 357 | 358 | suffix 359 | end 360 | 361 | def visit_struct(cursor, spelling = cursor.spelling) 362 | members_count = 0 363 | 364 | definition = String.build do |str| 365 | str.puts " struct #{Constant.to_crystal(spelling)}" 366 | 367 | cursor.visit_children do |c| 368 | members_count += 1 369 | 370 | case c.kind 371 | when .field_decl? 372 | str.puts " #{c.spelling.underscore} : #{Type.to_crystal(c.type)}" 373 | when .struct_decl? 374 | if c.type.kind.record? 375 | # skip 376 | else 377 | p [:TODO, :inner_struct, c] 378 | end 379 | else 380 | str.puts " # WARNING: unexpected #{c.kind} within #{cursor.kind} (visit_struct)" 381 | end 382 | Clang::ChildVisitResult::Continue 383 | end 384 | 385 | str.puts " end" 386 | end 387 | 388 | @output += if members_count == 0 389 | " type #{Constant.to_crystal(spelling)} = Void" 390 | else 391 | definition 392 | end + '\n' 393 | end 394 | 395 | def visit_union(cursor) 396 | # p [:union, cursor.spelling] 397 | # TODO: visit_union 398 | end 399 | 400 | def visit_function(cursor) 401 | arguments = cursor.arguments 402 | 403 | @output += String.build do |str| 404 | str << " fun #{cursor.spelling}(" 405 | cursor.arguments.each_with_index do |c, index| 406 | str << ", " unless index == 0 407 | str << Type.to_crystal(c.type) # .canonical_type 408 | end 409 | str.puts ") : #{Type.to_crystal(cursor.result_type)}" # .canonical_type 410 | end 411 | end 412 | 413 | def visit_var(cursor) 414 | type = Type.to_crystal(cursor.type.canonical_type) 415 | " $#{cursor.spelling} : #{type}" 416 | end 417 | end 418 | end 419 | -------------------------------------------------------------------------------- /src/autobind/type.cr: -------------------------------------------------------------------------------- 1 | module Autobind::Type 2 | def self.to_crystal(type : Clang::Type) 3 | case type.kind 4 | when .void? then "Void" 5 | when .bool? then "Bool" 6 | when .char_u?, .u_char? then "LibC::Char" 7 | when .char16?, .u_short? then "LibC::Short" 8 | when .char32? then "LibC::UInt32" 9 | when .u_int? then "LibC::UInt" 10 | when .u_long? then "LibC::ULong" 11 | when .u_long_long? then "LibC::ULongLong" 12 | when .w_char? then "LibC::WChar" 13 | when .char_s?, .s_char? then "LibC::Char" 14 | when .short? then "LibC::Short" 15 | when .int? then "LibC::Int" 16 | when .long? then "LibC::Long" 17 | when .long_long? then "LibC::LongLong" 18 | when .float? then "LibC::Float" 19 | when .double? then "LibC::Double" 20 | when .long_double? then "LibC::LongDouble" 21 | when .pointer? then visit_pointer(type) 22 | when .enum?, .record? 23 | spelling = type.cursor.spelling 24 | spelling = type.spelling if type.cursor.spelling.empty? 25 | Constant.to_crystal(spelling) 26 | when .elaborated? then to_crystal(type.named_type) 27 | when .typedef? 28 | if (spelling = type.spelling).starts_with?('_') 29 | to_crystal(type.canonical_type) 30 | else 31 | Constant.to_crystal(spelling) 32 | end 33 | when .constant_array? then visit_constant_array(type) 34 | # when .vector? then visit_vector(type) 35 | when .incomplete_array? then visit_incomplete_array(type) 36 | # when .variable_array? then visit_variable_array(type) 37 | # when .dependent_sized_array? then visit_dependent_sized_array(type) 38 | when .function_proto? then visit_function_proto(type) 39 | when .function_no_proto? then visit_function_no_proto(type) 40 | when .unexposed? then to_crystal(type.canonical_type) 41 | else 42 | raise "unsupported C type: #{type}" 43 | end 44 | end 45 | 46 | def self.visit_pointer(type) 47 | "#{to_crystal type.pointee_type}*" 48 | end 49 | 50 | def self.visit_constant_array(type) 51 | "StaticArray(#{to_crystal type.array_element_type}, #{type.array_size})" 52 | end 53 | 54 | def self.visit_function_proto(type) 55 | String.build do |str| 56 | str << '(' 57 | type.arguments.each_with_index do |t, index| 58 | str << ", " unless index == 0 59 | str << Type.to_crystal(t) 60 | end 61 | str << ") -> " 62 | str << Type.to_crystal(type.result_type) 63 | end 64 | end 65 | 66 | def self.visit_function_no_proto(type) 67 | raise "# UNSUPPORTED: FunctionNoProto #{type.inspect}" 68 | end 69 | 70 | def self.visit_incomplete_array(type) 71 | # element_type = Type.to_crystal(type.array_element_type.canonical_type) 72 | "#{Type.to_crystal type.array_element_type}*" 73 | end 74 | end 75 | --------------------------------------------------------------------------------