Last active
February 5, 2016 18:11
-
-
Save gurgeous/065c2cde257a61e2da71 to your computer and use it in GitHub Desktop.
Convert simple objc to swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# | |
# This is a braindead objc => swift converter. It only handles a few things, | |
# but it saved me many hours of mechanical translation. The output still | |
# requires editing. | |
# | |
# Many thinsg aren't handled. Most things, in fact. Have fun! | |
# | |
# Usage: objc_to_swift.rb Somefile.m | |
# (dumps swift to stdout) | |
# | |
class ObjcToSwift | |
attr_accessor :original | |
def initialize(text) | |
self.original = text | |
end | |
def swift | |
original.split("\n").map do |objc| | |
swift = translate(objc) | |
if swift.length == 0 && objc.length > 0 | |
swift = nil | |
end | |
swift | |
end.compact.join("\n") | |
end | |
protected | |
# rudimentary variable types. Be sure to use ?: | |
TYPES = [ | |
"(?:CA|CG|NS|UI)\\w+", | |
"(?:bool|double|float|int)", | |
"\\w+_t", | |
].join("|") | |
# translate objc type to swift type | |
TYPE_TRANSLATIONS = { | |
"bool" => "Bool", | |
"BOOL" => "Bool", | |
"double" => "Double", | |
"float" => "Float", | |
"int" => "Int", | |
} | |
ENUMS = %w(UIViewAutoresizing UIControlState) + | |
%w(SlideshowHintState) | |
RULES = [ | |
# remove trailing semicolons | |
[ /;(\s*\/\/.*)?$/, "\\1" ], | |
# remove @ from objc strings | |
[ /@"/, '"' ], | |
# remove () from if/while statements | |
[ /(if|while)\s*\((.*)\)\s*{/, "\\1 \\2 {" ], | |
# @xxx | |
[ /@(interface|implementation)/, "class" ], | |
# these just go away | |
[ /#import .*/, "" ], | |
[ /@end/, "" ], | |
# not strictly true, but helpful | |
[ /\bself\./, "" ], | |
# YES|NO => true|false | |
[ /\bYES\b/, "true" ], | |
[ /\bNO\b/, "false" ], | |
# pointers => . | |
[ "->", "." ], | |
# ceilf/floorf/roundf/fabs | |
[ /\b(ceil|floor|round)f\b/, "\\1" ], | |
[ /\bf(abs)\b/, "\\1" ], | |
# CG shortcuts turn into initializers | |
[ | |
/CGPointMake\(([^,]+),\s*([^)]+)\)/, | |
"CGPoint(x: \\1, y: \\2)" | |
], | |
[ | |
/CGRectMake\(([^,]+),\s*([^,]+),\s*([^,]+),\s*([^)]+)\)/, | |
"CGRect(x: \\1, y: \\2, width: \\3, height: \\4)" | |
], | |
[ | |
/CGSizeMake\(([^,]+),\s*([^)]+)\)/, | |
"CGSize(width: \\1, height: \\2)", | |
], | |
# make an effort to clean up blocks | |
[ "^{", "{" ], | |
[ "; }", " }" ], | |
# member variables prefixed with underscore | |
[ /\b_/, "" ], | |
] | |
def translate(s) | |
s = rules(s) | |
s = properties(s) | |
s = messages(s) | |
s = vars(s) | |
s = decls(s) | |
s = enums(s) | |
s | |
end | |
# big grab bag of regexes | |
def rules(s) | |
RULES.each do |src, dst| | |
s = s.gsub(src, dst) | |
end | |
s | |
end | |
# @property (...) IBOutlet type *name | |
def properties(s) | |
if s =~ /@property\s+(\([^\)]*\)\s+)?(IBOutlet\s+)?(\w+)\s*(?:\*\s*)(\w+)/ | |
attributes, iboutlet, type, name = $1, $2, $3, $4 | |
s = "var #{name}: #{type}" | |
s = "weak #{s}" if attributes =~ /weak/ | |
s = "@IBOutlet #{s}!" if iboutlet | |
end | |
s | |
end | |
# messages => function calls (very simple) | |
def messages(s) | |
# zero args | |
s = s.gsub(/\[(\w+) (\w+)\]/) do | |
receiver, msg = $1, $2 | |
"#{receiver}.#{msg}()" | |
end | |
# one arg | |
s = s.gsub(/\[(\w+) (\w+):([^:\]]+)\]/) do | |
receiver, msg, arg = $1, $2, $3 | |
"#{receiver}.#{msg}(#{arg})" | |
end | |
# two args | |
s = s.gsub(/\[(\w+) (\w+):([^:\] ]+) (\w+):([^:\] ]+)\]/) do | |
receiver, msg, arg, name2, arg2 = $1, $2, $3, $4, $5 | |
"#{receiver}.#{msg}(#{arg}, #{name2}: #{arg2})" | |
end | |
s | |
end | |
# variable declarations | |
def vars(s) | |
s = s.gsub(/^(\s*)(#{TYPES})\s+\*?(\w+)(.*)/i) do | |
prefix, type, var, suffix = $1, $2, $3, $4 | |
type = objc_type_to_swift_type(type) | |
if suffix =~ /\s*=/ | |
# usually the type is unnecessary | |
"#{prefix}let #{var}#{suffix}" | |
else | |
"#{prefix}let #{var}: #{type}#{suffix}" | |
end | |
end | |
s | |
end | |
# function declarations | |
def decls(s) | |
# zero args | |
s = s.gsub(/^(\s*)- \(([^)]+)\)(\w+)$/) do | |
indent, ret, name = $1, $2, $3 | |
ret = objc_type_to_swift_type(ret) | |
"#{indent}func #{name}() -> #{ret}" | |
end | |
# one arg | |
s = s.gsub(/^(\s*)- \(([^)]+)\)(\w+):\(([^)]+)\)(\w+)$/) do | |
indent, ret, name, ptype, pname = $1, $2, $3, $4, $5 | |
ret = objc_type_to_swift_type(ret) | |
ptype = objc_type_to_swift_type(ptype) | |
"#{indent}func #{name}(#{pname}: #{ptype}) -> #{ret}" | |
end | |
# strip voids | |
s = s.gsub("-> void", "") | |
s | |
end | |
def enums(s) | |
ENUMS.each do |enum| | |
s = s.gsub(/\b#{enum}(\w+)\b/, ".\\1") | |
end | |
s | |
end | |
def objc_type_to_swift_type(s) | |
s = TYPE_TRANSLATIONS[s] || s | |
s = s.gsub(/\s+\*$/, "") | |
s | |
end | |
end | |
if ARGV.length == 0 | |
puts "Error: give me a file, please" | |
exit 1 | |
end | |
ARGV.each do |filename| | |
text = File.read(filename) | |
puts ObjcToSwift.new(text).swift | |
puts if ARGV.length > 1 | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment