Skip to content

Instantly share code, notes, and snippets.

@gurgeous
Last active February 5, 2016 18:11
Show Gist options
  • Save gurgeous/065c2cde257a61e2da71 to your computer and use it in GitHub Desktop.
Save gurgeous/065c2cde257a61e2da71 to your computer and use it in GitHub Desktop.
Convert simple objc to swift
#!/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