Skip to content

Commit

Permalink
v0.0.2 : fixing up Tuples to work properly and solving some more pesk…
Browse files Browse the repository at this point in the history
…y use-cases
  • Loading branch information
nanobowers committed Oct 17, 2021
1 parent 2a54d6f commit cc276c9
Show file tree
Hide file tree
Showing 20 changed files with 221 additions and 84 deletions.
51 changes: 33 additions & 18 deletions py2cr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class RB(object):
'int' : 'Int32',
'float' : 'Float64',
'list' : 'Array',
'tuple' : 'Array',
'tuple' : 'Tuple',
'dict' : 'Hash',
'__file__' : '__FILE__',
}
Expand All @@ -102,7 +102,7 @@ class RB(object):
#'StopIteration' : 'StopIteration',
#'TypeError' : 'ArgumentError',
#'ValueError' : 'ArgumentError',
'AssertionError' : 'RuntimeError', # assert => raise
'AssertionError' : 'AssertionError', # assert => raise
#'AttributeError' : 'NoMethodError',
'Exception' : 'Exception',
#'EOFError' : 'EOFError',
Expand Down Expand Up @@ -217,14 +217,9 @@ class RB(object):
'join',
])

list_map = set([ # Array
'list',
'tuple',
])

dict_map = set([ # Hash
'dict',
])
list_map = set(['list']) # Array
tuple_map = set(['tuple']) # Tuple
dict_map = set(['dict']) # Hash

iter_map = set([ # Array
'map',
Expand Down Expand Up @@ -325,7 +320,11 @@ def __init__(self, path='', dir_path='', base_path_count=0, mod_paths = {}, verb
self._rclass_name = None

# This is use () case of the tuple that we are currently in:
self._tuple_type = '[]' # '()' : "(a, b)" , '[]' : "[a, b]", '=>': "%s => %s" (Hash), '': 'a, b'
# '()' : "(a, b)"
# '[]' : "[a, b]"
# '=>' : "%s => %s" (Hash)
# '' : 'a, b'
self._tuple_type = '[]'
self._func_args_len = 0
self._dict_format = False # True : Symbol ":", False : String "=>"

Expand Down Expand Up @@ -789,6 +788,8 @@ def visit_ClassDef(self, node):
for t in stmt.targets:
var = self.visit(t)
self.write("@@%s = %s" % (var, value))
valuetype = types.CrystalTypes.constant(stmt.value)
self.write("@%s : %s = %s" % (var, valuetype, "nil"))
self._class_variables.append(var)
else:
self.visit(stmt)
Expand Down Expand Up @@ -1239,9 +1240,9 @@ def visit_Assert(self, node):
test = self.visit(node.test)

if node.msg is not None:
self.write("raise %s unless %s" % (self.visit(node.msg), test))
self.write("raise AssertionError.new(%s) unless %s" % (self.visit(node.msg), test))
else:
self.write("raise unless %s" % test)
self.write("raise AssertionError.new unless %s" % test)

def visit_Import(self, node):
"""
Expand Down Expand Up @@ -2123,6 +2124,7 @@ def visit_Call(self, node, crytype = None):
"""
Call(expr func, expr* args, keyword* keywords)
"""

rb_args = [ self.visit(arg) for arg in node.args ]
""" [method argument set Method Object] :
<Python> def describe():
Expand Down Expand Up @@ -2320,7 +2322,7 @@ def visit_Call(self, node, crytype = None):
rb_args_s = ", ".join(rb_args)

if isinstance(node.func, ast.Call):
return "%s.(%s)" % (func, rb_args_s)
return "%s.py_call(%s)" % (func, rb_args_s)

if func in self.ignore.keys():
""" [Function convert to Method]
Expand Down Expand Up @@ -2388,15 +2390,28 @@ def visit_Call(self, node, crytype = None):
'step':self.ope_filter(rb_args[2])
}
return "PyRange.range(%(start)s, %(end)s, %(step)s)" % rangedict
elif func in self.tuple_map:
"""
[list]
<Python> tuple(range(3))
<Crystal> 3.times.to_a
"""
if len(node.args) == 0:
return "Tuple.new()"
elif (len(node.args) == 1) and isinstance(node.args[0], ast.Str):
return "%s.split(\"\")" % (rb_args_s)
else:
return "%s.to_a" % (rb_args_s)
elif func in self.list_map:
""" [list]
"""
[list]
<Python> list(range(3))
<Crystal> 3.times.to_a
"""
if len(node.args) == 0:
return self.empty_list(node, crytype)
elif (len(node.args) == 1) and isinstance(node.args[0], ast.Str):
return "%s.split('')" % (rb_args_s)
return "%s.split(\"\")" % (rb_args_s)
else:
return "%s.to_a" % (rb_args_s)
elif func in self.dict_map:
Expand Down Expand Up @@ -2459,7 +2474,7 @@ def visit_Call(self, node, crytype = None):

if (func in self._scope or func[0] == '@') and \
func.find('.') == -1: # Proc call
return "%s.(%s)" % (func, rb_args_s)
return "%s.py_call(%s)" % (func, rb_args_s)

if func[-1] == ')':
return "%s" % (func)
Expand Down Expand Up @@ -2708,7 +2723,7 @@ def visit_Tuple(self, node):
elif self._tuple_type == '()':
return "(%s)" % (", ".join(els))
elif self._tuple_type == '[]':
return "[%s]" % (", ".join(els))
return "{%s}" % (", ".join(els))
elif self._tuple_type == '=>':
return "%s => %s" % (els[0], els[1])
elif self._tuple_type == '':
Expand Down
51 changes: 45 additions & 6 deletions py2cr/types.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,23 @@
import sys
import ast

# apparently something changed in the AST between
# python 3.8 and 3.9 here.
def node_slice_value(node):
pymajor = sys.version_info.major
pyminor = sys.version_info.minor
if pymajor >= 3 and pyminor >=9:
return node.slice
else:
return node.slice.value

class CrystalTypes:

name_map = {
'bool' : 'Bool',
'True' : 'true', # python 2.x
'False' : 'false', # python 2.x
'None' : 'nil', # python 2.x
'None' : 'Nil', # python 2.x
'str' : 'String',
'int' : 'Int32',
'float' : 'Float64',
Expand All @@ -19,7 +31,6 @@ class CrystalTypes:
def __init__(self, node):
self.node = node


def unwrap_list(self):
node = self.node
# expect node to be something to unwrap
Expand All @@ -29,7 +40,18 @@ def unwrap_list(self):
subscript_name = str(node.value.id)
if subscript_name.lower() != "list":
raise Exception(f"Expecting ast.Subscript value to be a list but got {subscript_name}")
return self.visit(node.slice.value)
return self.visit(node.slice)

def unwrap_tuple(self):
node = self.node
# expect node to be something to unwrap
if not isinstance(node, ast.Subscript):
raise Exception(f"Expecting a ast.Subscript to unwrap, got {type(node)}")
# check that subscript value
subscript_name = str(node.value.id)
if subscript_name.lower() != "tuple":
raise Exception(f"Expecting ast.Subscript value to be a tuple but got {subscript_name}")
return self.visit(node.slice)

def unwrap_dict(self):
node = self.node
Expand All @@ -40,7 +62,9 @@ def unwrap_dict(self):
subscript_name = str(node.value.id)
if subscript_name.lower() != "dict":
raise Exception(f"Expecting ast.Subscript value to be a dict but got {subscript_name}")
nsv = node.slice.value


nsv = node_slice_value(node)
if not isinstance(nsv, ast.Tuple):
raise

Expand Down Expand Up @@ -81,11 +105,26 @@ def visit_Subscript(self, node):
Subscripts for Crystal Annotations use (), not []
"""
name = self.visit(node.value)

node_slice = node_slice_value(node)
if name == "Union":
pipeargs = [self.visit(e) for e in node.slice.value.elts]
pipeargs = [self.visit(e) for e in node_slice.elts]
pipetypes = " | ".join(pipeargs)
return "( " + pipetypes + " )"
elif name == "Optional":
return self.visit(node.slice.value) + "?"
return self.visit(node_slice) + "?"
else:
return "%s(%s)" % (self.visit(node.value), self.visit(node.slice))

@classmethod
def constant(cls, node, nilable=True):
const_typename = node.value.__class__.__name__
if const_typename in cls.name_map:
crystal_typename = cls.name_map[const_typename]
if nilable:
return crystal_typename + "?"
else:
return crystal_typename
else:
return "_"

9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,19 @@

setup(
name='py2cr',
version='0.0.1',
version='0.0.2',
description='A code translator using AST from Python to Crystal',
long_description=long_description,
url='https://github.com/naitoh/py2cr',
author='NAITOH Jun',
author_email='naitoh@gmail.com',
url='https://github.com/nanobowers/py2cr',
author='Ben Bowers',
author_email='nanobowers@gmail.com',
license='MIT',
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
'Topic :: Software Development',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: py2cr
version: 0.0.1
version: 0.0.2

authors:
- Ben Bowers <nanobowers@gmail.com>
Expand Down
57 changes: 54 additions & 3 deletions src/py2cr.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# require "pathname"

# To match a python `assert` error
class AssertionError < Exception
end

class Method
# Monkeypath Method#to_s to provide a consistent output for multiple
Expand Down Expand Up @@ -35,6 +38,11 @@ end
# Foo.call() or Foo.() is nothing => Foo.new() call.
#
class Class

def py_call(*args,**kwargs)
self.new(*args, **kwargs)
end

def method_missing(method, *args)
if method == :call
self.new(*args)
Expand All @@ -51,6 +59,20 @@ struct Set
end
end

class Array(T)
def remove(val) : Void
idx = self.index(val)
unless idx.nil?
self.delete_at(idx)
end
end

# TODO: Add range-based slice assignment
#def []=(idxrange : Range(Int32, Int32), arrayval : Array(T))
#end

end

module PythonMethodEx
def getattr(*a)
if singleton_class.class_variables.includes? "@@#{a[0]}".to_sym
Expand All @@ -69,7 +91,7 @@ end
# Note that we allow nils to propagate from Crystal Enumerable#zip,
# and then we have to filter out any cases with nils, then convert to an array.
def py_zip(a, *otherargs)
return a.zip(*otherargs).select(&.all?).map(&.to_a)
return a.zip?(*otherargs).select(&.all?).map(&.to_a)
end

# Array python-zip with one arg.
Expand Down Expand Up @@ -266,6 +288,35 @@ class Array
end
end

struct Tuple

def py_count(x)
self.count(x)
end

# Monkeypatch to_s to use parentheses when printing a Tuple
# so that tuples will print the same way as Python
def to_s(io : IO) : Nil
io << '('
join(io, ", ") { |item|
if item.responds_to?(:py_inspect)
item.py_inspect(io)
else
item.inspect(io)
end
}
io << ')'
end

end

module ENV
# handles "key" in os.environ case
def self.py_in?(key : String) : Bool
self.has_key?(key)
end
end

class Hash
def py_in?(element)
self.has_key?(element)
Expand Down Expand Up @@ -293,7 +344,7 @@ module Enumerable
result = false
self.each do |a|
result = true unless a.nil? || a == false || a == 0 || a == ""
result = true unless a.responds_to?(:empty?) && a.empty?
result = a.py_any? if a.responds_to?(:each)
end
return result
end
Expand Down
12 changes: 1 addition & 11 deletions tests/algorithms/sqrt.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,7 @@ def sqrt(x):
return r

def p(x):
prec = 11
l = list(iter(str(x)))[:prec]
if not "." in l:
l.append(".")
l.append("0")
while len(l) < prec:
l.append("0")
s = ""
for c in l:
s += c
return s
return "%.10f" % x

print((p(sqrt(1))))
print((p(sqrt(2))))
Expand Down
Loading

0 comments on commit cc276c9

Please sign in to comment.