Author: Armin Rigo <ar...@tunes.org>
Branch: 
Changeset: r90638:46167d4097c8
Date: 2017-03-12 18:57 +0100
http://bitbucket.org/pypy/pypy/changeset/46167d4097c8/

Log:    Issue #2492

        Modernize the interface of _minimal_curses. Also moves the logic to
        avoid any ``#include <term.h>`` from anywhere except a single small
        C file.

diff --git a/pypy/module/_minimal_curses/fficurses.py 
b/pypy/module/_minimal_curses/fficurses.py
--- a/pypy/module/_minimal_curses/fficurses.py
+++ b/pypy/module/_minimal_curses/fficurses.py
@@ -1,11 +1,8 @@
-""" The ffi for rpython, need to be imported for side effects
+""" The ffi for rpython
 """
 
 from rpython.rtyper.lltypesystem import rffi
-from rpython.rtyper.lltypesystem import lltype
 from rpython.rtyper.tool import rffi_platform
-from rpython.rtyper.extfunc import register_external
-from pypy.module._minimal_curses import interp_curses
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
 
 # We cannot trust ncurses5-config, it's broken in various ways in
@@ -58,86 +55,73 @@
 eci = guess_eci()
 
 
-INT = rffi.INT
-INTP = lltype.Ptr(lltype.Array(INT, hints={'nolength':True}))
-c_setupterm = rffi.llexternal('setupterm', [rffi.CCHARP, INT, INTP], INT,
-                              compilation_info=eci)
-c_tigetstr = rffi.llexternal('tigetstr', [rffi.CCHARP], rffi.CCHARP,
-                             compilation_info=eci)
-c_tparm = rffi.llexternal('tparm', [rffi.CCHARP, INT, INT, INT, INT, INT,
-                                    INT, INT, INT, INT], rffi.CCHARP,
-                          compilation_info=eci)
+# We should not use this 'eci' directly because it causes the #include
+# of term.h to appear in all generated C sources, and term.h contains a
+# poisonous quantity of #defines for common lower-case names like
+# 'buttons' or 'lines' (!!!).  It is basically dangerous to include
+# term.h in any C source file that may contain unrelated source code.
 
-ERR = rffi.CConstant('ERR', lltype.Signed)
-OK = rffi.CConstant('OK', lltype.Signed)
+include_lines = '\n'.join(['#include <%s>' % _incl for _incl in eci.includes])
+eci = eci.copy_without('includes')
 
-def curses_setupterm(term, fd):
-    intp = lltype.malloc(INTP.TO, 1, flavor='raw')
-    err = rffi.cast(lltype.Signed, c_setupterm(term, fd, intp))
-    try:
-        if err == ERR:
-            errret = rffi.cast(lltype.Signed, intp[0])
-            if errret == 0:
-                msg = "setupterm: could not find terminal"
-            elif errret == -1:
-                msg = "setupterm: could not find terminfo database"
-            else:
-                msg = "setupterm: unknown error"
-            raise interp_curses.curses_error(msg)
-        interp_curses.module_info.setupterm_called = True
-    finally:
-        lltype.free(intp, flavor='raw')
 
-def curses_setupterm_null_llimpl(fd):
-    curses_setupterm(lltype.nullptr(rffi.CCHARP.TO), fd)
+eci = eci.merge(ExternalCompilationInfo(
+   post_include_bits=[
+        "RPY_EXTERN char *rpy_curses_setupterm(char *, int);\n"
+        "RPY_EXTERN char *rpy_curses_tigetstr(char *);\n"
+        "RPY_EXTERN char *rpy_curses_tparm(char *, int, int, int, int,"
+        " int, int, int, int, int);"
+        ],
+    separate_module_sources=["""
 
-def curses_setupterm_llimpl(term, fd):
-    ll_s = rffi.str2charp(term)
-    try:
-        curses_setupterm(ll_s, fd)
-    finally:
-        rffi.free_charp(ll_s)
+%(include_lines)s
 
-register_external(interp_curses._curses_setupterm_null,
-                  [int], llimpl=curses_setupterm_null_llimpl,
-                  export_name='_curses.setupterm_null')
-register_external(interp_curses._curses_setupterm,
-                  [str, int], llimpl=curses_setupterm_llimpl,
-                  export_name='_curses.setupterm')
+RPY_EXTERN
+char *rpy_curses_setupterm(char *term, int fd)
+{
+    int errret = -42;
+    if (setupterm(term, fd, &errret) == ERR) {
+        switch (errret) {
+        case 0:
+            return "setupterm: could not find terminal";
+        case -1:
+            return "setupterm: could not find terminfo database";
+        default:
+            return "setupterm: unknown error";
+        }
+    }
+    return NULL;
+}
 
-def check_setup_invoked():
-    if not interp_curses.module_info.setupterm_called:
-        raise interp_curses.curses_error("must call (at least) setupterm() 
first")
+RPY_EXTERN
+char *rpy_curses_tigetstr(char *capname)
+{
+    char *res = tigetstr(capname);
+    if (res == (char *)-1)
+        res = NULL;
+    return res;
+}
 
-def tigetstr_llimpl(cap):
-    check_setup_invoked()
-    ll_cap = rffi.str2charp(cap)
-    try:
-        ll_res = c_tigetstr(ll_cap)
-        num = lltype.cast_ptr_to_int(ll_res)
-        if num == 0 or num == -1:
-            raise interp_curses.TermError()
-        res = rffi.charp2str(ll_res)
-        return res
-    finally:
-        rffi.free_charp(ll_cap)
+RPY_EXTERN
+char *rpy_curses_tparm(char *str, int x0, int x1, int x2, int x3,
+                       int x4, int x5, int x6, int x7, int x8)
+{
+    return tparm(str, x0, x1, x2, x3, x4, x5, x6, x7, x8);
+}
 
-register_external(interp_curses._curses_tigetstr, [str], str,
-                  export_name='_curses.tigetstr', llimpl=tigetstr_llimpl)
+""" % globals()]))
 
-def tparm_llimpl(s, args):
-    check_setup_invoked()
-    l = [0, 0, 0, 0, 0, 0, 0, 0, 0]
-    for i in range(min(len(args), 9)):
-        l[i] = args[i]
-    ll_s = rffi.str2charp(s)
-    # XXX nasty trick stolen from CPython
-    ll_res = c_tparm(ll_s, l[0], l[1], l[2], l[3], l[4], l[5], l[6],
-                     l[7], l[8])
-    rffi.free_charp(ll_s)
-    res = rffi.charp2str(ll_res)
-    return res
 
-register_external(interp_curses._curses_tparm, [str, [int]], str,
-                  export_name='_curses.tparm', llimpl=tparm_llimpl)
+rpy_curses_setupterm = rffi.llexternal(
+    "rpy_curses_setupterm", [rffi.CCHARP, rffi.INT], rffi.CCHARP,
+    compilation_info=eci)
 
+rpy_curses_tigetstr = rffi.llexternal(
+    "rpy_curses_tigetstr", [rffi.CCHARP], rffi.CCHARP,
+    compilation_info=eci)
+
+rpy_curses_tparm = rffi.llexternal(
+    "rpy_curses_tparm", [rffi.CCHARP, rffi.INT, rffi.INT, rffi.INT, rffi.INT,
+                         rffi.INT, rffi.INT, rffi.INT, rffi.INT, rffi.INT],
+    rffi.CCHARP,
+    compilation_info=eci)
diff --git a/pypy/module/_minimal_curses/interp_curses.py 
b/pypy/module/_minimal_curses/interp_curses.py
--- a/pypy/module/_minimal_curses/interp_curses.py
+++ b/pypy/module/_minimal_curses/interp_curses.py
@@ -1,44 +1,24 @@
-
 from pypy.interpreter.gateway import unwrap_spec
 from pypy.interpreter.error import OperationError
-from pypy.module._minimal_curses import _curses
+from pypy.module._minimal_curses import fficurses
+from rpython.rtyper.lltypesystem import lltype, rffi
+
 
 class ModuleInfo:
-    def __init__(self):
+    def __init__(self, space):
         self.setupterm_called = False
 
-module_info = ModuleInfo()
+def check_setup_invoked(space):
+    if not space.fromcache(ModuleInfo).setupterm_called:
+        raise curses_error(space, "must call (at least) setupterm() first")
 
-class curses_error(Exception):
-    def __init__(self, msg):
-        self.msg = msg
 
-from rpython.annotator.classdesc import FORCE_ATTRIBUTES_INTO_CLASSES
-from rpython.annotator.model import SomeString
-
-# this is necessary due to annmixlevel
-FORCE_ATTRIBUTES_INTO_CLASSES[curses_error] = {'msg': SomeString()}
-
-def convert_error(space, error):
-    msg = error.msg
+def curses_error(space, errmsg):
     w_module = space.getbuiltinmodule('_minimal_curses')
     w_exception_class = space.getattr(w_module, space.newtext('error'))
-    w_exception = space.call_function(w_exception_class, space.newtext(msg))
+    w_exception = space.call_function(w_exception_class, space.newtext(errmsg))
     return OperationError(w_exception_class, w_exception)
 
-def _curses_setupterm_null(fd):
-    # NOT_RPYTHON
-    try:
-        _curses.setupterm(None, fd)
-    except _curses.error as e:
-        raise curses_error(e.args[0])
-
-def _curses_setupterm(termname, fd):
-    # NOT_RPYTHON
-    try:
-        _curses.setupterm(termname, fd)
-    except _curses.error as e:
-        raise curses_error(e.args[0])
 
 @unwrap_spec(fd=int)
 def setupterm(space, w_termname=None, fd=-1):
@@ -47,48 +27,47 @@
                                  space.newtext('stdout'))
         fd = space.int_w(space.call_function(space.getattr(w_stdout,
                                              space.newtext('fileno'))))
-    try:
-        if space.is_none(w_termname):
-            _curses_setupterm_null(fd)
-        else:
-            _curses_setupterm(space.text_w(w_termname), fd)
-    except curses_error as e:
-        raise convert_error(space, e)
+    if space.is_none(w_termname):
+        termname = None
+    else:
+        termname = space.text_w(w_termname)
 
-class TermError(Exception):
-    pass
+    with rffi.scoped_str2charp(termname) as ll_term:
+        fd = rffi.cast(rffi.INT, fd)
+        ll_errmsg = fficurses.rpy_curses_setupterm(ll_term, fd)
+    if ll_errmsg:
+        raise curses_error(space, rffi.charp2str(ll_errmsg))
 
-def _curses_tigetstr(capname):
-    # NOT_RPYTHON
-    try:
-        res = _curses.tigetstr(capname)
-    except _curses.error as e:
-        raise curses_error(e.args[0])
-    if res is None:
-        raise TermError
-    return res
-
-def _curses_tparm(s, args):
-    # NOT_RPYTHON
-    try:
-        return _curses.tparm(s, *args)
-    except _curses.error as e:
-        raise curses_error(e.args[0])
+    space.fromcache(ModuleInfo).setupterm_called = True
 
 @unwrap_spec(capname='text')
 def tigetstr(space, capname):
-    try:
-        result = _curses_tigetstr(capname)
-    except TermError:
-        return space.w_None
-    except curses_error as e:
-        raise convert_error(space, e)
-    return space.newbytes(result)
+    check_setup_invoked(space)
+    with rffi.scoped_str2charp(capname) as ll_capname:
+        ll_result = fficurses.rpy_curses_tigetstr(ll_capname)
+        if ll_result:
+            return space.newbytes(rffi.charp2str(ll_result))
+        else:
+            return space.w_None
 
 @unwrap_spec(s='text')
 def tparm(space, s, args_w):
+    check_setup_invoked(space)
     args = [space.int_w(a) for a in args_w]
-    try:
-        return space.newbytes(_curses_tparm(s, args))
-    except curses_error as e:
-        raise convert_error(space, e)
+    # nasty trick stolen from CPython
+    x0 = args[0] if len(args) > 0 else 0
+    x1 = args[1] if len(args) > 1 else 0
+    x2 = args[2] if len(args) > 2 else 0
+    x3 = args[3] if len(args) > 3 else 0
+    x4 = args[4] if len(args) > 4 else 0
+    x5 = args[5] if len(args) > 5 else 0
+    x6 = args[6] if len(args) > 6 else 0
+    x7 = args[7] if len(args) > 7 else 0
+    x8 = args[8] if len(args) > 8 else 0
+    with rffi.scoped_str2charp(s) as ll_str:
+        ll_result = fficurses.rpy_curses_tparm(ll_str, x0, x1, x2, x3,
+                                               x4, x5, x6, x7, x8)
+        if ll_result:
+            return space.newbytes(rffi.charp2str(ll_result))
+        else:
+            raise curses_error(space, "tparm() returned NULL")
diff --git a/pypy/module/_minimal_curses/test/test_curses.py 
b/pypy/module/_minimal_curses/test/test_curses.py
--- a/pypy/module/_minimal_curses/test/test_curses.py
+++ b/pypy/module/_minimal_curses/test/test_curses.py
@@ -76,19 +76,27 @@
     """
     def test_csetupterm(self):
         from rpython.translator.c.test.test_genc import compile
-        from pypy.module._minimal_curses import interp_curses
+        from rpython.rtyper.lltypesystem import lltype, rffi
+        from pypy.module._minimal_curses import fficurses
+
         def runs_setupterm():
-            interp_curses._curses_setupterm_null(1)
+            null = lltype.nullptr(rffi.CCHARP.TO)
+            fficurses.rpy_curses_setupterm(null, 1)
 
         fn = compile(runs_setupterm, [])
         fn()
 
     def test_ctgetstr(self):
         from rpython.translator.c.test.test_genc import compile
-        from pypy.module._minimal_curses import interp_curses
+        from rpython.rtyper.lltypesystem import lltype, rffi
+        from pypy.module._minimal_curses import fficurses
+
         def runs_ctgetstr():
-            interp_curses._curses_setupterm("xterm", 1)
-            return interp_curses._curses_tigetstr('cup')
+            with rffi.scoped_str2charp("xterm") as ll_term:
+                fficurses.rpy_curses_setupterm(ll_term, 1)
+            with rffi.scoped_str2charp("cup") as ll_capname:
+                ll = fficurses.rpy_curses_tigetstr(ll_capname)
+                return rffi.charp2str(ll)
 
         fn = compile(runs_ctgetstr, [])
         res = fn()
@@ -96,11 +104,16 @@
 
     def test_ctparm(self):
         from rpython.translator.c.test.test_genc import compile
-        from pypy.module._minimal_curses import interp_curses
+        from rpython.rtyper.lltypesystem import lltype, rffi
+        from pypy.module._minimal_curses import fficurses
+
         def runs_tparm():
-            interp_curses._curses_setupterm("xterm", 1)
-            cup = interp_curses._curses_tigetstr('cup')
-            return interp_curses._curses_tparm(cup, [5, 3])
+            with rffi.scoped_str2charp("xterm") as ll_term:
+                fficurses.rpy_curses_setupterm(ll_term, 1)
+            with rffi.scoped_str2charp("cup") as ll_capname:
+                cup = fficurses.rpy_curses_tigetstr(ll_capname)
+                res = fficurses.rpy_curses_tparm(cup, 5, 3, 0, 0, 0, 0, 0, 0, 
0)
+                return rffi.charp2str(res)
 
         fn = compile(runs_tparm, [])
         res = fn()
diff --git a/rpython/translator/tool/cbuild.py 
b/rpython/translator/tool/cbuild.py
--- a/rpython/translator/tool/cbuild.py
+++ b/rpython/translator/tool/cbuild.py
@@ -334,3 +334,9 @@
         d['separate_module_files'] = ()
         d['separate_module_sources'] = ()
         return ExternalCompilationInfo(**d)
+
+    def copy_without(self, *names):
+        d = self._copy_attributes()
+        for name in names:
+            del d[name]
+        return ExternalCompilationInfo(**d)
_______________________________________________
pypy-commit mailing list
pypy-commit@python.org
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to