mirror of
https://github.com/chocolate-doom/tools.git
synced 2024-11-21 20:21:30 +00:00
9aefefc09c
Subversion-branch: /tools Subversion-revision: 1954
340 lines
6.4 KiB
Ruby
Executable file
340 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
|
|
|