Skip to content
forked from naitoh/py2rb

A code translator using AST from Python to Ruby.

License

Notifications You must be signed in to change notification settings

nanobowers/py2rb

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

py2rb.py

A code translator using AST from Python to Ruby. This is basically a NodeVisitor with Ruby output. See ast documentation (https://docs.python.org/3/library/ast.html) for more information.

Installation

git clone https://github.com/nanobowers/py2rb

Versions

  • Python 3.5 .. 3.10
  • Ruby 2.4 .. 3.1

Dependencies

Python

pip install six 
pip install pyyaml 
pip install numpy

Ruby

gem install numo-narray

Methodology

In addition to walking and writing the AST tree and writing a Ruby syntax output, this tool either:

  • Monkey-patches (or refines) some common Ruby Modules and Classes in order to emulate the Python equivalent.
  • Calls equivalent Ruby methods to the Python equivalent
  • Calls wrapped ruby methods that provide Python equivalent functionality

Test Errata

  • tests/os/path_dirname.py known to fail on python 3.9+, due to known change in python behavior from 3.8 to 3.9
  • tests/basic/raise2.py known to fail on ruby 3.1+, reason unknown
  • tests/numpy/ext_slice.py known to fail on python 3.9+, reason unknown

Usage

Sample 1:

$ cat tests/basic/oo_inherit_simple.py
class bar(object):

    def __init__(self,name):
        self.name = name

    def setname(self,name):
        self.name = name

class foo(bar):

    registered = []

    def __init__(self,val,name):
        self.fval = val
        self.register(self)
        self.name = name

    def inc(self):
        self.fval += 1

    def msg(self, a=None, b=None, c=None):
        txt = ''
        varargs = a, b, c
        for arg in varargs:
            if arg is None:
                continue
            txt += str(arg)
            txt += ","
        return txt + self.name + " says:"+str(self.fval)

    @staticmethod
    def register(f):
        foo.registered.append(f)

    @staticmethod
    def printregistered():
        for r in foo.registered:
            print(r.msg())

a = foo(10,'a')
a.setname('aaa')
b = foo(20,'b')
c = foo(30,'c')

a.inc()
a.inc()
c.inc()

print(a.msg())
print(b.msg())
print(c.msg(2,3,4))

print("---")

foo.printregistered()

The above will result in :

$ py2rb tests/basic/oo_inherit_simple.py
class Bar
  def initialize(name)
    @name = name
  end
  def setname(name)
    @name = name
  end
end
class Foo < Bar
  def method_missing(method, *args)
    self.class.__send__ method, *args
  end
  @@registered = []
  def initialize(val, name)
    @fval = val
    Foo.register(self)
    @name = name
  end
  def inc()
    @fval += 1
  end
  def msg(a: nil, b: nil, c: nil)
    txt = ""
    varargs = [a, b, c]
    for arg in varargs
      if arg === nil
        next
      end
      txt += arg.to_s
      txt += ","
    end
    return ((txt + @name) + " says:") + @fval.to_s
  end
  def self.register(f)
    @@registered.push(f)
  end
  def self.printregistered()
    for r in @@registered
      print(r.msg())
    end
  end
  def self.registered; @@registered; end
  def self.registered=(val); @@registered=val; end
  def registered; @registered = @@registered if @registered.nil?; @registered; end
  def registered=(val); @registered=val; end
end
a = Foo.new(10, "a")
a.setname("aaa")
b = Foo.new(20, "b")
c = Foo.new(30, "c")
a.inc()
a.inc()
c.inc()
print(a.msg())
print(b.msg())
print(c.msg(a: 2, b: 3, c: 4))
print("---")
Foo.printregistered()

Sample 2:

$ cat tests/deep-learning-from-scratch/and_gate.py
# coding: utf-8
import numpy as np

def AND(x1, x2):
    x = np.array([x1, x2])
    w = np.array([0.5, 0.5])
    b = -0.7
    tmp = np.sum(w*x) + b
    if tmp <= 0:
        return 0
    else:
        return 1

if __name__ == '__main__':
    for xs in [(0, 0), (1, 0), (0, 1), (1, 1)]:
        y = AND(xs[0], xs[1])
        print(str(xs) + " -> " + str(y))

The above will result in :

$ py2rb tests/deep-learning-from-scratch/and_gate.py
require 'numo/narray'
def AND(x1, x2)
  x = Numo::NArray.cast([x1, x2])
  w = Numo::NArray.cast([0.5, 0.5])
  b = -0.7
  tmp = ((w * x).sum()) + b
  if tmp <= 0
    return 0
  else
    return 1
  end
end

if __FILE__ == $0
  for xs in [[0, 0], [1, 0], [0, 1], [1, 1]]
    y = AND(xs[0], xs[1])
    print((xs.to_s + (" -> ")) + y.to_s)
  end
end

Sample 3 (Convert all local dependent module files of specified Python file):

$ git clone git://github.com/chainer/chainer.git
$ py2rb chainer/chainer/__init__.py -m -p chainer -r -w -f
Try : chainer/chainer/__init__.py -> chainer/chainer/__init__.rb : [OK]
Warning : yield is not supported :
Warning : yield is not supported :
Try  : chainer/chainer/configuration.py -> chainer/chainer/configuration.rb : [Warning]
Try  : chainer/chainer/cuda.py -> chainer/chainer/cuda.rb : [OK]
      :
      :
Try  : chainer/chainer/utils/array.py -> chainer/chainer/utils/array.rb : [OK]

Tests

$ ./run_tests.py

Will run all tests, that are supposed to work. If any test fails, it's a bug.

$ ./run_tests.py -a

Will run all tests including those that are known to fail (currently). It should be understandable from the output.

$ ./run_tests.py -x or $ ./run_tests.py --no-error

Will run tests but ignore if an error is raised by the test. This is not affecting the error generated by the test files in the tests directory.

For additional information on flags, run:

./run_tests.py -h

Writing new tests

Adding tests for most new or existing functionality involves adding additional python files at tests/<subdirectory/<testname>.py.

The test-runner scripts will automatically run py2rb to produce a ruby script, then run both the python and ruby scripts, then compare stdout/stderr and check return codes.

For special test-cases, it is possible to provide a configuration YAML file on a per test basis named tests/<subdirectory>/<testname>.config.yaml which overrides defaults for testing. The following keys/values are supported:

min_python_version: [int, int] # minimum major/minor version
max_python_version: [int, int] # maximum major/minor version
expected_exit_status: int      # exit status for py/rb test script
argument_list: [str, ... str]  # list of strings as extra args for argv

License

MIT, see the LICENSE file for exact details.

About

A code translator using AST from Python to Ruby.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • Python 95.3%
  • Ruby 4.7%