PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/py2.rb

https://bitbucket.org/snej/py2rb
Ruby | 226 lines | 155 code | 31 blank | 40 comment | 22 complexity | 62bbf62df968373fa501ecc6f24c0ff8 MD5 | raw file
  1. #! /usr/bin/env ruby
  2. #
  3. # py2.rb -- A first pass at porting Python source code to Ruby.
  4. # Reads Python code from stdin and writes Ruby code to stdout.
  5. #
  6. # This script just does the obvious, easy transformations, giving you more time to work on the harder ones :)
  7. # It is NOT a real parser, just a bunch of kludgy regex operations, so it can't do anything fancy.
  8. # It may get some things wrong, and won't even attempt some other things that it's very likely to get wrong.
  9. # The output will definitely have to be hand edited by someone familiar with both languages, before it can
  10. # be expected to compile as Ruby, much less run correctly. The goal is simply to require _less_ hand editing,
  11. # and less mechanical replacing, than you would have had to do without this script.
  12. #
  13. # Version 2 ... by Jens Alfke, 1 May 2010.
  14. # Placed in the public domain. "Do what thou wilt" shall be the whole of the law.
  15. # I'd appreciate it if you contributed improvements or fixes back to the repository, though!
  16. # http://bitbucket.org/snej/py2rb
  17. INDENT_WIDTH = 2 # Indentation width assumed in the input Python code
  18. # Python reserved words mapped to Ruby equivalents
  19. ReservedWordMap = {
  20. 'None' => 'nil',
  21. 'True' => 'true',
  22. 'False' => 'false',
  23. 'elif' => 'elsif',
  24. '\s*is' => '.equal?',
  25. '\s*is not' => '.not_equal?', # Doesn't actually exist; you'll have to convert it to != by hand
  26. 'end' => 'end_', # not reserved in Python but is in Ruby, so rename it
  27. }
  28. # Python functions mapped to Ruby methods
  29. FunctionsToMethods = {
  30. 'len' => 'length',
  31. 'str' => 'to_s',
  32. 'int' => 'to_i',
  33. }
  34. # Python functions mapped to Ruby functions
  35. FunctionsToFunctions = {
  36. 'open' => 'File.open',
  37. }
  38. # Special method names for constructors and operator overloading
  39. SpecialMethods = {
  40. '__init__' => 'initialize',
  41. '__eq__' => '==',
  42. '__str__' => 'to_s',
  43. '__repr__' => 'to_s',
  44. '__getitem__' => '[]',
  45. '__setitem__' => '[]=',
  46. # Incomplete so far; add any extra ones you need.
  47. }
  48. # Global variables:
  49. $line = '' # Current line being translated, trimmed of whitespace
  50. $indentStr = '' # The leading whitespace before $line
  51. $indent = 0 # The character width of the leading whitespace
  52. $lastIndent = 0 # The indent of the previous line
  53. $blankLines = 0 # The number of consecutive blank lines being skipped
  54. $continuation = false # Set to true when $line ends with a "\"
  55. $classname = "???" # Name of the last class definition entered
  56. $methodIsStatic = false # Set to true when "@staticmethod" or "@classmethod" is seen
  57. $methodIsProperty = false # Set to true when "@property" is seen
  58. # Returns the width of a string in characters, interpreting tabs as 8 chars wide.
  59. def indentWidth(str)
  60. width = 0
  61. str.each_byte do |ch|
  62. if ch == '\t' then
  63. width = (width + 7) / 8
  64. else
  65. width += 1
  66. end
  67. end
  68. return width
  69. end
  70. # Updates $indent and $indentStr.
  71. def setIndent(indent)
  72. $indent = indent
  73. $indentStr = " "*$indent
  74. end
  75. # Assuming $line starts with a triple-quote Python block comment, emits the entire comment as a regular
  76. # comment prefixed with "#" characters.
  77. def emitBlockComment()
  78. $line = $line[3..-1]
  79. begin
  80. $line = $line.strip
  81. if $line =~ /'''$/
  82. $line = $line[0..-4]
  83. puts($indentStr + '# ' + $line)
  84. break
  85. end
  86. puts($indentStr + '# ' + $line)
  87. end while $line = gets
  88. end
  89. # Compare $indent to $lastIndent and write the appropriate "end" lines. Also writes earlier blank lines.
  90. def emitEndsAndBlanks()
  91. # If de-indenting, emit 'end' lines:
  92. while $indent < $lastIndent
  93. $lastIndent -= INDENT_WIDTH
  94. $lastIndent = 0 if $lastIndent < 0
  95. break if $lastIndent == $indent and ($line=="else" || $line=~/^except/)
  96. puts((" "*$lastIndent) + "end")
  97. end
  98. $lastIndent = $indent
  99. # Add any earlier blank lines (so they come after the 'end', if any)
  100. $blankLines.times {puts($indentStr)}
  101. $blankLines = 0
  102. end
  103. def convertFunction(fn, args)
  104. method = FunctionsToMethods[fn]
  105. if method then
  106. args = "(#{args})" unless args.match(/[\w\s.]/)
  107. return "#{args}.#{method}"
  108. end
  109. rubyfn = FunctionsToFunctions[fn]
  110. return "#{rubyfn}(#{args})" if rubyfn
  111. return "#{fn}(#{args})"
  112. end
  113. # OK, main loop starts here!
  114. while $line = gets()
  115. # Trim leading whitespace from $line but keep track of its width in $indent.
  116. match = /^(\s*)(.*)$/.match($line)
  117. $lastIndent = $indent
  118. setIndent( indentWidth(match[1]) )
  119. $line = match[2].rstrip
  120. # Various expression-level transformations:
  121. $line.gsub!(/r'([^']*)'/, '/\1/') # Python raw strings to Ruby rexexps
  122. $line.gsub!(/("[^"]*")\s*%\s*\(/, 'sprintf(\1, ') # %-style formatting to sprintf
  123. $line.gsub!(/('[^']*')\s*%\s*\(/, 'sprintf(\1, ') # Same but with single-quoted strings
  124. # Replace some standard Python functions with equivalent Ruby functions or method calls.
  125. #TODO: Make sure the preceding char isn't '.', i.e. this isn't already a method call
  126. $line.gsub!(/\b(\w+)\s*\(([^)]+)\)/) {convertFunction($1,$2)}
  127. unless $line.match(/^class\b/) then
  128. $line.gsub!(/(\b[A-Z]\w+)\(/, '\1.new(') # Instantiation: X(...) --> X.new(...)
  129. end
  130. ReservedWordMap.each_pair do |pyword, rbword|
  131. $line.gsub!(Regexp.new('\b'+pyword+'\b'), rbword)
  132. end
  133. $line.gsub!(/\bself\.(\w+\s*)\(/, '\1(') # Remove "self." before method names
  134. $line.gsub!(/\bself\._?/, '@') # ...and change it to '@' before variables
  135. if $line == "" then
  136. # Blank line: don't emit it yet, just keep count
  137. $blankLines += 1
  138. setIndent($lastIndent)
  139. elsif $line =~ /^'''/ then
  140. # Triple-quoted string on its own line: convert to a multi-line comment
  141. emitBlockComment()
  142. elsif $continuation then
  143. # Continuation line: don't mess with its indent or treat it as a new statement.
  144. puts($indentStr + $line)
  145. $continuation = ($line =~ /\\$/)
  146. setIndent($lastIndent)
  147. else
  148. # Check if line has a continuation (ends with '\')
  149. $continuation = $line =~ /\\$/
  150. $line = $line.chop.rstrip if $continuation
  151. $line.sub!(/:$/, "") # Strip trailing ':'
  152. # If indent decreased, emit 'end' statements. Also emit any pending blank lines.
  153. emitEndsAndBlanks()
  154. # Handle various types of statements:
  155. if $line.sub!(/^import\b/, "require")
  156. elsif $line.sub!(/^from\s+(\w+)\s+import.*$/, 'require \1')
  157. elsif m = $line.match(/^class\s+(\w+)\s*\((\w+)\)/)
  158. $classname = m[1]
  159. $line = "class #{$classname} < #{m[2]}"
  160. elsif $line == "@staticmethod" or $line == "@classmethod"
  161. $methodIsStatic = true
  162. next
  163. elsif $line == "@property"
  164. # Ruby syntax doesn't need anything special for this
  165. next
  166. elsif m = $line.match(/^def\s+(\w+)\s*\((.*)\)/)
  167. # Function/method definition:
  168. name = m[1]
  169. name = SpecialMethods.fetch(name, name)
  170. if $methodIsStatic then
  171. name = "#{$classname}.#{name}"
  172. $methodIsStatic = false
  173. end
  174. $line = "def #{name}"
  175. args = m[2].strip.split(/\s*,\s*/)
  176. args.delete_at(0) if args.length > 0 and args[0] == "self" # Remove leading 'self' parameter
  177. $line += " (" + args.join(", ") + ")" if args.length > 0
  178. elsif $line.sub!(/^try\b/, "begin")
  179. elsif $line.sub!(/^except\s+(\w+)\s*,\s*(.*)/, 'rescue \1 => \2')
  180. elsif $line.sub!(/^except\s+(\w+)\s*as\s*(.*)/, 'rescue \1 => \2')
  181. elsif m = $line.match(/^with\s+(.*)\s+as\s+(\w+)$/)
  182. $line = "#{m[1]} do |#{m[2]}|"
  183. elsif m = $line.match(/^with\s+(.*)$/)
  184. $line = "#{m[1]} do"
  185. elsif $line.gsub!(/^assert\s*\((.*)\)$/, 'fail unless \1')
  186. end
  187. $line += '\\' if $continuation # Restore the continuation mark if any
  188. puts($indentStr + $line)
  189. end
  190. end
  191. setIndent(0)
  192. emitEndsAndBlanks()