Repository: flightaware/tohil (language: python)

Issue:
Can't find Python globals when using the `tohil::call` command from tcl
```python
Python 3.7.6 (default, Jan 30 2020, 01:17:40)
[Clang 8.0.0 (tags/RELEASE_800/final 356365)] on freebsd11
Type "help", "copyright", "credits" or "license" for more information.
I >>> import tohil
I >>> tohil.package_require("tohil")
'4.0.0'
I >>> tohil.call("::tohil::call", "print", "hello world")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
tohil.TclError: ("module '__main__' has no attribute 'print'", <tohil.tcldict: '-code 1 -level 0 -errorcode {PYTHON AttributeError {module \'__main__\' has no attribute \'print\'}} -errorinfo {module \'__main__\' has no attribute \'print\' (failed to find object/function in python interpreter)\nfrom python code executed by tohil\n    invoked from within\n"::tohil::call print {hello world}"} -errorline 1'>)
```

This does work, though
```python
I >>> tohil.call("::tohil::eval", "print('hello world')")
hello world
<tohil.tclobj: ''>
```



Oh I just realized the bouncing back and forth is a red herring, this is failing just from `tclsh` as well
```tcl
 ~  tclsh
hopnu chris.roberts % package require tohil
4.0.0
hopnu chris.roberts % tohil::call print "hello world"
module '__main__' has no attribute 'print'
while evaluating tohil::call print "hello world"
```


Discussion / hints:
This works, not sure what it means just yet.  `tohil::call __builtins__.print "hello world"`
This also "fixes the glitch" `tohil::exec "from builtins import *"`
I suspect the code in TohilCall_Cmd that finds the callable to pass to PyObject_Call is clunky.  Yep, errorInfo includes `module '__main__' has no attribute 'print' (failed to find object/function in python interpreter)` which pins it down to PyObject_GetAttrString in the vicinity of line 759.

Fix patch:
diff --git a/generic/tohil.c b/generic/tohil.c
index 9147a17..0d3553d 100644
--- a/generic/tohil.c
+++ b/generic/tohil.c
@@ -798,10 +798,28 @@ TohilCall_Cmd(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *cons
     }
 
     PyObject *pFn = PyObject_GetAttrString(pObj, objandfn);
+
+    // if we didn't find anything and we weren't invoked with dotted notation,
+    // check builtins
+    if (pFn == NULL && pObjParent == NULL) {
+        // (PyObject_GetAttrString raised an exception above if pFn is null)
+        PyErr_Clear();
+
+        PyObject *builtins = PyEval_GetBuiltins();
+        pFn = PyDict_GetItemString(builtins, objandfn);
+    }
+
     Py_DECREF(pObj);
-    Tcl_DStringFree(&ds);
-    if (pFn == NULL)
+
+    if (pFn == NULL) {
+#define CALL_ERROR_STRING_SIZE 256
+        char errorString[CALL_ERROR_STRING_SIZE];
+        snprintf(errorString, CALL_ERROR_STRING_SIZE, "name '%.200s' is not defined.", objandfn);
+        Tcl_DStringFree(&ds);
+        PyErr_SetString(PyExc_NameError, errorString);
         return Tohil_ReturnExceptionToTcl(interp, "failed to find object/function in python interpreter");
+    }
+    Tcl_DStringFree(&ds);
 
     if (!PyCallable_Check(pFn)) {
         Py_DECREF(pFn);


Test patch:
diff --git a/tests/tohil.test b/tests/tohil.test
index 7fa08b7..1043c84 100755
--- a/tests/tohil.test
+++ b/tests/tohil.test
@@ -188,7 +188,7 @@ test tohil_call-1.10 {stacked call exception} \
 test tohil_call-1.11 {call of nonexistent functions} \
 	-body {tohil::call aosdin} \
 	-returnCodes error \
-	-result {module '__main__' has no attribute 'aosdin'}
+	-result {name 'aosdin' is not defined.}
 
 test tohil_call-1.12 {call of nonexistent object methods} \
 	-body {tohil::eval {a = "aaa"}} \
@@ -216,6 +216,12 @@ test tohil_call-1.15 {call with extra tohil::NONE sentinel action} \
 	} \
 	-result "None 42 None"
 
+test tohil_call-1.16 {make sure tohil::call of python builtins works} \
+	-body {
+		tohil::call int 255
+	} \
+	-result 255
+
 # =========
 # TYPES
 # =========