tools/minus-minus

341 lines
6.4 KiB
Ruby
Executable File

#!/usr/bin/env ruby
require "json"
class ZClass
attr_reader :name
attr_accessor :fields
attr_accessor :methods
def initialize(name)
@name = name
@fields = {}
@methods = {}
end
end
class ZField
attr_reader :name, :type
def initialize(name, type)
@name = name
@type = type
end
end
# In the given string, replace text matching the given regexp with
# whitespace.
def blank_re(re, text)
text.gsub(re) do |match|
$~[0].gsub(/(\S+)/) do
" " * $1.length
end
end
end
def strip_comments(text)
text = blank_re(/\/\/.*?\n/, text)
text = blank_re(/\/\*.*?\*\//m, text)
# Strip preprocessor as well.
text = blank_re(/\#[^\n]*\n/, text)
# Also these:
text = blank_re(/(public|protected|private):/, text)
text
end
def scan_ignore_comments(re, text)
text = strip_comments(text)
text.scan(re) do |match|
yield $~
end
end
def find_block_end(text, start_offset)
depth = 0
scan_ignore_comments(/([\{\}])/, text[start_offset, text.length]) do |match|
if match[1] == "{"
depth += 1
else
depth -= 1
end
if depth == 0
return start_offset + match.pre_match.length
end
end
raise "Failed to find function end"
end
# Iterate over each method or function in the specified text.
def gsub_functions(text)
last_function_end = 0
result = text
adjust = 0
scan_ignore_comments(/((\w+)::)?(\w+)\s*\(([^\)]*?)\)\s*\{/m, text) do |match|
offset = match.pre_match.length
if offset > last_function_end
class_name = match[2]
name = match[3]
end_offset = find_block_end(text, offset) + 1
block = text[offset, end_offset - offset]
# Get replacement text.
replacement = yield class_name, name, block
# Substitute old text for new text:
before_text = result[0, offset + adjust]
after_text = result[end_offset + adjust,
result.length]
result = before_text + replacement + after_text
adjust += replacement.length - block.length
# Save end position of function so that we won't
# change anything in this block again.
last_function_end = end_offset
end
end
result
end
# Iterate over each field and method definition in the specified class
# body.
def each_field(text)
last_field_end = 0
scan_ignore_comments(/([\{\;])/, text) do |match|
offset = match.pre_match.length
#puts "#{offset} <=> #{last_field_end}"
next if offset < last_field_end
if match[1] == "{"
block_end = find_block_end(text, offset)
field_text = text[last_field_end, block_end - last_field_end]
yield strip_comments(field_text)
last_field_end = block_end + 1
else
field_text = text[last_field_end, offset-last_field_end]
if field_text =~ /\S/m
yield strip_comments(field_text)
end
last_field_end = offset + 1
end
end
end
# Iterate over each class in the specified text.
def each_class(text)
scan_ignore_comments(/(struct|class)\s+(\w+)[^\{\;]*?\{/m, text) do |match|
offset = match.pre_match.length
block_start = offset + match[0].length
name = match[2]
end_offset = find_block_end(text, offset)
body = text[block_start, end_offset - block_start]
yield name, body
end
end
def parse_field(text)
# Field or method?
if text =~ /(\w+)\s*\(/
return [ :method, $1 ]
elsif text !~ /\{/
# Split into subfields and identify names.
text = text.sub(/(\w+)/, "")
field_type = $1
subfields = text.split(/,/)
result = [ :field, field_type, [] ]
for subfield in subfields
if subfield =~ /(\w+)/
result[2].push($1)
end
end
return result
else
return nil
end
end
def parse_classes(text)
classes = {}
each_class(text) do |name, body|
zclass = ZClass.new(name)
classes[name] = zclass
each_field(body) do |field_data|
data = parse_field(field_data)
if data == nil
# ...
elsif data[0] == :method
zclass.methods[data[1]] = true
elsif data[0] == :field
for field_name in data[2]
field = ZField.new(field_name, data[1])
zclass.fields[field_name] = field
end
end
end
end
classes
end
def identify_class_for_method(method_name, classes)
result = nil
classes.each_pair do |class_name, zclass|
zclass.methods.each_pair do |mname, method|
if mname != method_name
# ...
elsif result == nil
result = class_name
else
return nil # Ambiguous.
end
end
end
result
end
def transform_method_calls(body, zclass, classes)
body = body.gsub(/((\.|\-\>)\s*)?(\w+)(\s*)\((\s*)/) do
deref_type = $2
method_name = $3
outer_space = $4
inner_space = $5
method_class = identify_class_for_method(method_name, classes)
if method_class == nil
method_class = "????"
end
if deref_type == "."
# Can't transform actual method calls.
". /* METHOD */ #{method_class}__#{method_name}" +
"(#{inner_space}&????, "
elsif deref_type == "->"
# Can't transform actual method calls.
"-> /* METHOD */ #{method_class}__#{method_name}" +
"(#{inner_space}????, "
elsif zclass.methods.has_key? method_name
# Can transform implicit method calls.
"#{zclass.name}__#{method_name}(#{inner_space}self, "
else
# Global function? No change.
"#{method_name}#{outer_space}(#{inner_space}"
end
end
end
# Transform a method body to C.
def transform_method_body(body, zclass, name, classes)
# Transform the method definition first.
body = body.sub(/#{zclass.name}\s*::\s*#{name}\s*\(\s*/,
"#{zclass.name}__#{name}(#{zclass.name} *self, ")
# Fields must have self-> preceding them.
zclass.fields.each_pair do |fname, field|
body = body.gsub(/\b#{fname}\b/, "self->#{fname}")
end
# Method calls
body = transform_method_calls(body, zclass, classes)
# Use "self" instead of "this".
body = body.gsub(/\bthis\b/, "self")
# Clean up left-over spurious commas.
body = body.gsub(/self,\s*\)/, "self)")
body = body.gsub(/\?\?\?\?,\s*\)/, "????)")
body
end
def dump_class_info(classes)
classes.each_pair do |name, zclass|
puts name
puts "\tfields:"
zclass.fields.each_pair do |fname, field|
puts "\t\t#{field.type} #{fname}"
end
puts "\tmethods:"
zclass.methods.each_key do |mname|
puts "\t\t#{mname}"
end
end
end
header = File.readlines(ARGV[1]).join
classes = parse_classes(header)
#dump_class_info(classes)
data = File.readlines(ARGV[0]).join
transformed = gsub_functions(data) do |class_name, name, body|
# puts "#{class_name}, #{name}:"
zclass = classes[class_name]
if zclass != nil
body = transform_method_body(body, zclass, name, classes)
end
# puts "=" * 40
# puts body
# puts "=" * 40
body
end
puts transformed