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