Hello
I made some experiences connecting Nim with Python. At this page http://akehrer.github.io/posts/connecting-nim-to-python/ there are some cool hints about how to do that. However, I got some issues interfacing with string types.
How can I declare a string at Nim and how should I treat it in Python ?
I have the following code in Nim ('nim2py.nim)':
proc AmericanSoundexCode(ch: char): int =
case ch:
of 'B', 'F', 'P', 'V' : return 1
of 'C', 'G', 'J', 'K', 'Q', 'S', 'X', 'Z' : return 2
of 'D', 'T' : return 3
of 'L' : return 4
of 'M', 'N' : return 5
of 'R' : return 6
of 'A', 'E', 'I', 'O', 'U', 'Y' : return -1
else : return 0
proc AmericanSoundex*(word: string): string {. exportc, dynlib .} =
var output = newString(4)
var lastCode = 0
var i, j = 0
# Get the first character
while i < word.len:
var ch = word[i]
inc i
# Reduce a lowercase character to an uppercase character
if ((ch.int >= 97) and (ch.int <= 122)): dec ch, 32
if ((ch.int >= 65) and (ch.int <= 90)):
lastCode = AmericanSoundexCode(ch)
if lastCode < 0: lastCode = 0
output[j] = ch
inc j
break
# Now lets hash the rest of this stuff
while i < word.len:
var ch = word[i]
inc i
# Reduce a lowercase character to an uppercase character
if ((ch.int >= 97) and (ch.int <= 122)): dec ch, 32
if ((ch.int >= 65) and (ch.int <= 90)):
var code = AmericanSoundexCode(ch)
case code:
of 0: continue
of -1:
lastCode = 0
else:
if code != lastCode:
lastCode = code
output[j] = char(code + 48)
inc j
if j == 4: break
# Pad the remainder of the string with zeroes
while j < 4:
output[j] = '0'
inc j
# We are done
return output
In Python I'm doing the following:
from ctypes import *
def main():
test_lib = CDLL('nim2py')
...
mystring = "robert james fischer"
soundex_res = test_lib.AmericanSoundex(mystring)
print('The Soundex is: %s'%soundex_res)
if __name__ == '__main__':
main()
Here's the output I'm getting:
ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
After converting the mystring to bytes I’m getting:
The Soundex is: b'x04'
Sorry, but I’m not sure where to go next other...
Try inserting this before your function call:
I tried with c_wchar_p, as well as, with c_char_p. Either way, didn't work.
mystring = c_char_p("robert james fischer")
test_lib.AmericanSoundex.argtype = c_char_p
test_lib.AmericanSoundex.restype = c_char_p
soundex_res = test_lib.AmericanSoundex(mystring)
print('The Soundex is: %s'%soundex_res)
After converting the mystring to bytes I’m getting: The Soundex is: b'x04'
Do you know what C type the nim code for AmericanSoundex is being converted into ?
I tried your code and it turned out that mystring wasn't passed correctly to the Nim proc. So I changed the type to cstring and added some test output prints. In the Python code you have to create a string buffer.
So this works for me:
proc AmericanSoundex*(word: cstring): cstring {. exportc, dynlib .} =
var output = newString(4)
var lastCode = 0
var i, j = 0
echo "Nim-Input: ", word
# Get the first character
while i < word.len:
var ch = word[i]
inc i
# Reduce a lowercase character to an uppercase character
if ((ch.int >= 97) and (ch.int <= 122)): dec ch, 32
if ((ch.int >= 65) and (ch.int <= 90)):
lastCode = AmericanSoundexCode(ch)
if lastCode < 0: lastCode = 0
output[j] = ch
inc j
break
# Now lets hash the rest of this stuff
while i < word.len:
var ch = word[i]
inc i
# Reduce a lowercase character to an uppercase character
if ((ch.int >= 97) and (ch.int <= 122)): dec ch, 32
if ((ch.int >= 65) and (ch.int <= 90)):
var code = AmericanSoundexCode(ch)
case code:
of 0: continue
of -1:
lastCode = 0
else:
if code != lastCode:
lastCode = code
output[j] = char(code + 48)
inc j
if j == 4: break
# Pad the remainder of the string with zeroes
while j < 4:
output[j] = '0'
inc j
# We are done
echo "Nim-Output: ", output
return output
from ctypes import *
def main():
test_lib = CDLL('./libAmSoundex.so')
test_lib.AmericanSoundex.argtype = c_char_p
test_lib.AmericanSoundex.restype = c_char_p
p_input = create_string_buffer(b"robert james fischer", 25)
soundex_res = test_lib.AmericanSoundex(p_input)
print('The Soundex is:', soundex_res)
if __name__ == '__main__':
main()
giving me the following output:
Nim-Input: robert james fischer Nim-Output: R163 The Soundex is: b'R163'
Yes ! It worked for me as well (in Windows). Thanks a lot Wolf.
However, I noticed the Nim Soundex function (https://github.com/Skrylar/Skylight) expects for a single token not for a full string. So, I made some chances in the Python code to properly handle that:
...
test_lib.AmericanSoundex.argtype = c_char_p
test_lib.AmericanSoundex.restype = c_char_p
mystring = 'robert james fischer'
soundex_res = ''
for token in mystring.split():
p_input = create_string_buffer(token, 100)
soundex_res = soundex_res + test_lib.AmericanSoundex(p_input) + ' '
print('The Soundex is:', soundex_res.strip())
...
The correct Soundex for the great "Robert James Fischer" is 'R163 J520 F260'
Cheers
@alfrednewman
Hm, your version doesn't run here under Python 3.4, I get a runtime error:
p_input = create_string_buffer(token, 100)
- File "/usr/lib/python3.4/ctypes/__init__.py", line 63, in create_string_buffer
- raise TypeError(init)
TypeError: robert
It seems the byte representation of the string (b"...") is needed.
@andrea
Thanks, will have a look at it.
I finally figured it out, had to add some encoding, decoding and converting. This is my running version:
from ctypes import cdll, c_char_p, create_string_buffer
def main():
test_lib = cdll.LoadLibrary('./libAmSoundex.so')
test_lib.AmericanSoundex.argtype = c_char_p
test_lib.AmericanSoundex.restype = c_char_p
mystring = "robert james fischer"
soundex_res = ''
for token in mystring.split():
buf_token = create_string_buffer(token.encode(), len(token))
soundex_res += str(test_lib.AmericanSoundex(buf_token).decode()) + ' '
print('The Soundex is:', soundex_res)
if __name__ == '__main__':
main()
Sorry for posting so much Python code. We have got a little handmade FFI for strings, at least. :-)
@wolf, thank you for your help !
@andrea, I'll have a look at nim-pymod.
Cheers