builder.rb 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. #!/usr/bin/env ruby
  2. require 'optparse'
  3. RE_BODY = /struct builder \{(.*?)\}/m
  4. RE_FUNC = /ForeignPtr\(\*(\w+)\)\((.*?)\);/
  5. CHECK_COOKIES = false
  6. TYPE_CONVERSION = {
  7. "ForeignPtr" => "NodeId",
  8. "const token*" => "*const TokenPtr",
  9. "const node_list*" => "*mut NodeListPtr",
  10. "bool" => "bool",
  11. "size_t" => "size_t",
  12. "SelfPtr" => "*mut Builder",
  13. }
  14. class Argument
  15. attr_reader :type
  16. attr_reader :name
  17. def initialize(type, name)
  18. @type = type
  19. @name = name
  20. end
  21. def as_arg
  22. "#{name}: #{type}"
  23. end
  24. def as_safe_arg
  25. if type == "*mut Builder"
  26. return "&mut self"
  27. end
  28. t = case type
  29. when "NodeId"
  30. "Option<Rc<Node>>"
  31. when "*mut NodeListPtr"
  32. "Vec<Rc<Node>>"
  33. when "*const TokenPtr"
  34. "Option<Token>"
  35. when "size_t"
  36. "usize"
  37. else
  38. type
  39. end
  40. "#{name}: #{t}"
  41. end
  42. def as_param
  43. name
  44. end
  45. def convert
  46. case type
  47. when "*mut Builder"
  48. "let #{name} = &mut *#{name}"
  49. when "NodeId"
  50. "let #{name} = node_from_c(builder, #{name})"
  51. when "*mut NodeListPtr"
  52. "let #{name} = node_list_from_c(builder, #{name})"
  53. when "*const TokenPtr"
  54. "let #{name} = token_from_c(#{name})"
  55. when "size_t"
  56. "let #{name} = #{name} as usize"
  57. end
  58. end
  59. end
  60. class Interface
  61. attr_reader :name
  62. attr_reader :args
  63. def initialize(name, args)
  64. @name = name
  65. @args = args
  66. end
  67. def arg_block
  68. args.map {|a| a.as_arg }.join(", ")
  69. end
  70. def signature
  71. "pub #{name}: unsafe extern \"C\" fn(#{arg_block}) -> NodeId"
  72. end
  73. def signature_safe
  74. _args = args.map {|a| a.as_safe_arg }.join(", ")
  75. "fn #{name}(#{_args}) -> Rc<Node>"
  76. end
  77. def definition
  78. "unsafe extern \"C\" fn #{name}(#{arg_block}) -> NodeId"
  79. end
  80. def callsite
  81. _args = args[1..-1].map {|a| a.as_param }.join(", ")
  82. "(*#{args.first.name}).#{name}(#{_args})"
  83. end
  84. def cookie_check
  85. "assert_eq!((*#{args.first.name}).cookie, 12345678)"
  86. end
  87. end
  88. def get_definitions(filename)
  89. cpp = File.read(filename)
  90. builder = RE_BODY.match(cpp)
  91. abort("failed to match 'struct builder' body in #{filename}") unless builder
  92. defs = builder[1].split("\n").map { |d| d.strip }.reject { |d| d.empty? }
  93. defs.map do |d|
  94. match = RE_FUNC.match(d)
  95. abort("bad definition: '#{d}'") unless match
  96. method, args = match[1], match[2]
  97. args = args.split(",").map { |a| a.strip }
  98. args = args.map do |arg|
  99. arg = arg.split(' ')
  100. argname = arg.pop
  101. ctype = arg.join(' ')
  102. rstype = TYPE_CONVERSION[ctype]
  103. abort("unknown C type: #{ctype}") unless rstype
  104. Argument.new(rstype, argname)
  105. end
  106. Interface.new(method, args)
  107. end
  108. end
  109. def generate_rs(apis, out)
  110. out.puts "// This file is autogenerated by builder.rb"
  111. out.puts "// DO NOT MODIFY"
  112. out.puts "#[repr(C)]"
  113. out.puts "struct BuilderInterface {"
  114. apis.each do |api|
  115. out.puts "\t#{api.signature},"
  116. end
  117. out.puts "}"
  118. out.puts "\n\n"
  119. apis.each do |api|
  120. out.puts "#{api.definition} {"
  121. api.args.each do |arg|
  122. cv = arg.convert
  123. out.puts "\t#{cv};" if cv
  124. end
  125. out.puts "\t#{api.cookie_check};" if CHECK_COOKIES
  126. out.puts "\t#{api.callsite}.to_raw(builder)"
  127. out.puts "}"
  128. end
  129. out.puts "\n\n"
  130. out.puts "static CALLBACKS: BuilderInterface = BuilderInterface {"
  131. apis.each do |api|
  132. out.puts "\t#{api.name}: #{api.name},"
  133. end
  134. out.puts "};"
  135. end
  136. BUILDER_H = File.join(File.dirname(__FILE__), '..', 'include', 'ruby_parser', 'builder.hh')
  137. OptionParser.new do |opts|
  138. opts.banner = "Usage: ruby builder.rb [--rs=FILE]"
  139. opts.on("--rs [FILE]") do |file|
  140. file = file ? File.open(file, "w") : $stdout
  141. abort("failed to open '#{file}'") unless file
  142. apis = get_definitions(BUILDER_H)
  143. generate_rs(apis, file)
  144. end
  145. end.parse!