[pypy-commit] pypy default: Merge a rudimentary and optional file support in RPython

fijal noreply at buildbot.pypy.org
Tue Sep 24 09:49:46 CEST 2013


Author: Maciej Fijalkowski <fijall at gmail.com>
Branch: 
Changeset: r67078:a0bf8a530d84
Date: 2013-09-24 09:49 +0200
http://bitbucket.org/pypy/pypy/changeset/a0bf8a530d84/

Log:	Merge a rudimentary and optional file support in RPython

diff --git a/rpython/rlib/rfile.py b/rpython/rlib/rfile.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rfile.py
@@ -0,0 +1,55 @@
+
+""" This file makes open() and friends RPython
+"""
+
+import os
+from rpython.annotator.model import SomeObject, SomeString, SomeInteger
+from rpython.rtyper.extregistry import ExtRegistryEntry
+from rpython.rtyper.extfunc import register_external
+
+class SomeFile(SomeObject):
+    def method_write(self, s_arg):
+        assert isinstance(s_arg, SomeString)
+
+    def method_read(self, s_arg=None):
+        if s_arg is not None:
+            assert isinstance(s_arg, SomeInteger)
+        return SomeString(can_be_None=False)
+
+    def method_close(self):
+        pass
+
+    def method_seek(self, s_arg, s_whence=None):
+        assert isinstance(s_arg, SomeInteger)
+        if s_whence is not None:
+            assert isinstance(s_whence, SomeInteger)
+
+    def rtyper_makekey(self):
+        return self.__class__,
+
+    def rtyper_makerepr(self, rtyper):
+        from rpython.rtyper.lltypesystem.rfile import FileRepr
+
+        return FileRepr(rtyper)
+
+class FileEntry(ExtRegistryEntry):
+    _about_ = open
+
+    def compute_result_annotation(self, s_name, s_mode=None):
+        assert isinstance(s_name, SomeString)
+        if s_mode is not None:
+            assert isinstance(s_mode, SomeString)
+        return SomeFile()
+
+    def specialize_call(self, hop):
+        return hop.r_result.rtype_constructor(hop)
+
+class OSTempfileEntry(ExtRegistryEntry):
+    _about_ = os.tmpfile
+
+    def compute_result_annotation(self):
+        return SomeFile()
+
+    def specialize_call(self, hop):
+        return hop.r_result.rtype_tempfile(hop)
+
diff --git a/rpython/rlib/test/test_rfile.py b/rpython/rlib/test/test_rfile.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/test/test_rfile.py
@@ -0,0 +1,80 @@
+
+import os
+from rpython.rtyper.test.tool import BaseRtypingTest
+from rpython.tool.udir import udir
+from rpython.rlib import rfile
+
+class TestFile(BaseRtypingTest):
+    def setup_class(cls):
+        cls.tmpdir = udir.join('test_rfile')
+        cls.tmpdir.ensure(dir=True)
+
+    def test_open(self):
+        fname = str(self.tmpdir.join('file_1'))
+
+        def f():
+            f = open(fname, "w")
+            f.write("dupa")
+            f.close()
+
+        self.interpret(f, [])
+        assert open(fname, "r").read() == "dupa"
+
+    def test_read_write(self):
+        fname = str(self.tmpdir.join('file_2'))
+
+        def f():
+            f = open(fname, "w")
+            f.write("dupa")
+            f.close()
+            f2 = open(fname)
+            dupa = f2.read()
+            assert dupa == "dupa"
+            f2.close()
+
+        self.interpret(f, [])
+
+    def test_read_sequentially(self):
+        fname = self.tmpdir.join('file_3')
+        fname.write("dupa")
+        fname = str(fname)
+
+        def f():
+            f = open(fname)
+            a = f.read(1)
+            b = f.read(1)
+            c = f.read(1)
+            d = f.read(1)
+            e = f.read()
+            f.close()
+            assert a == "d"
+            assert b == "u"
+            assert c == "p"
+            assert d == "a"
+            assert e == ""
+
+        self.interpret(f, [])
+
+    def test_seek(self):
+        fname = str(self.tmpdir.join('file_4'))
+
+        def f():
+            f = open(fname, "w+")
+            f.write("xxx")
+            f.seek(0)
+            assert f.read() == "xxx"
+            f.close()
+
+        f()
+        self.interpret(f, [])
+
+    def test_tempfile(self):
+        def f():
+            f = os.tmpfile()
+            f.write("xxx")
+            f.seek(0)
+            assert f.read() == "xxx"
+            f.close()
+
+        f()
+        self.interpret(f, [])
diff --git a/rpython/rtyper/lltypesystem/rfile.py b/rpython/rtyper/lltypesystem/rfile.py
new file mode 100644
--- /dev/null
+++ b/rpython/rtyper/lltypesystem/rfile.py
@@ -0,0 +1,195 @@
+
+import os
+from rpython.rlib import rposix
+from rpython.rlib.rarithmetic import r_uint
+from rpython.annotator import model as annmodel
+from rpython.rtyper.rtyper import Repr
+from rpython.rlib.rstring import StringBuilder
+from rpython.rtyper.lltypesystem import lltype, rffi, llmemory
+from rpython.rtyper.lltypesystem.rstr import string_repr, STR
+from rpython.translator.tool.cbuild import ExternalCompilationInfo
+from rpython.rtyper.annlowlevel import hlstr
+from rpython.rtyper.lltypesystem.lloperation import llop
+
+FILE = lltype.Struct('FILE') # opaque type maybe
+FILE_WRAPPER = lltype.GcStruct("FileWrapper", ('file', lltype.Ptr(FILE)))
+
+eci = ExternalCompilationInfo(includes=['stdio.h'])
+
+def llexternal(*args):
+    return rffi.llexternal(*args, compilation_info=eci)
+
+c_open = llexternal('fopen', [rffi.CCHARP, rffi.CCHARP], lltype.Ptr(FILE))
+c_close = llexternal('fclose', [lltype.Ptr(FILE)], rffi.INT)
+c_write = llexternal('fwrite', [rffi.CCHARP, rffi.SIZE_T, rffi.SIZE_T,
+                                     lltype.Ptr(FILE)], rffi.SIZE_T)
+c_read = llexternal('fread', [rffi.CCHARP, rffi.SIZE_T, rffi.SIZE_T,
+                                   lltype.Ptr(FILE)], rffi.SIZE_T)
+c_feof = llexternal('feof', [lltype.Ptr(FILE)], rffi.INT)
+c_ferror = llexternal('ferror', [lltype.Ptr(FILE)], rffi.INT)
+c_clearerror = llexternal('clearerr', [lltype.Ptr(FILE)], lltype.Void)
+c_fseek = llexternal('fseek', [lltype.Ptr(FILE), rffi.LONG, rffi.INT],
+                          rffi.INT)
+c_tmpfile = llexternal('tmpfile', [], lltype.Ptr(FILE))
+
+def ll_open(name, mode):
+    file_wrapper = lltype.malloc(FILE_WRAPPER)
+    ll_name = rffi.str2charp(name)
+    ll_mode = rffi.str2charp(mode)
+    try:
+        ll_f = c_open(ll_name, ll_mode)
+        if not ll_f:
+            errno = rposix.get_errno()
+            raise OSError(errno, os.strerror(errno))
+        file_wrapper.file = ll_f
+    finally:
+        lltype.free(ll_name, flavor='raw')
+        lltype.free(ll_mode, flavor='raw')
+    return file_wrapper
+
+def ll_tmpfile():
+    file_wrapper = lltype.malloc(FILE_WRAPPER)
+    res = c_tmpfile()
+    if not res:
+        errno = rposix.get_errno()
+        raise OSError(errno, os.strerror(errno))
+    file_wrapper.file = res
+    return file_wrapper
+
+def ll_write(file_wrapper, value):
+    ll_file = file_wrapper.file
+    if not ll_file:
+        raise ValueError("I/O operation on closed file")
+    value = hlstr(value)
+    assert value is not None
+    ll_value = rffi.get_nonmovingbuffer(value)
+    try:
+        # note that since we got a nonmoving buffer, it is either raw
+        # or already cannot move, so the arithmetics below are fine
+        total_bytes = 0
+        ll_current = ll_value
+        while total_bytes < len(value):
+            bytes = c_write(ll_current, 1, len(value) - r_uint(total_bytes),
+                            ll_file)
+            if bytes == 0:
+                errno = rposix.get_errno()
+                raise OSError(errno, os.strerror(errno))
+            total_bytes += bytes
+            ll_current = rffi.cast(rffi.CCHARP,
+                                   rffi.cast(lltype.Unsigned, ll_value) +
+                                   total_bytes)
+    finally:
+        rffi.free_nonmovingbuffer(value, ll_value)
+
+BASE_BUF_SIZE = 4096
+
+def ll_read(file_wrapper, size):
+    ll_file = file_wrapper.file
+    if not ll_file:
+        raise ValueError("I/O operation on closed file")
+    if size < 0:
+        # read the entire contents
+        buf = lltype.malloc(rffi.CCHARP.TO, BASE_BUF_SIZE, flavor='raw')
+        try:
+            s = StringBuilder()
+            while True:
+                returned_size = c_read(buf, 1, BASE_BUF_SIZE, ll_file)
+                if returned_size == 0:
+                    if c_feof(ll_file):
+                        # ok, finished
+                        return s.build()
+                    errno = c_ferror(ll_file)
+                    c_clearerror(ll_file)
+                    raise OSError(errno, os.strerror(errno))
+                s.append_charpsize(buf, returned_size)
+        finally:
+            lltype.free(buf, flavor='raw')
+    else:
+        raw_buf, gc_buf = rffi.alloc_buffer(size)
+        try:
+            returned_size = c_read(raw_buf, 1, size, ll_file)
+            if returned_size == 0:
+                if not c_feof(ll_file):
+                    errno = c_ferror(ll_file)
+                    raise OSError(errno, os.strerror(errno))
+            s = rffi.str_from_buffer(raw_buf, gc_buf, size,
+                                     rffi.cast(lltype.Signed, returned_size))
+        finally:
+            rffi.keep_buffer_alive_until_here(raw_buf, gc_buf)
+        return s
+def ll_seek(file_wrapper, pos, whence):
+    ll_file = file_wrapper.file
+    if not ll_file:
+        raise ValueError("I/O operation on closed file")
+    res = c_fseek(ll_file, pos, whence)
+    if res == -1:
+        errno = rposix.get_errno()
+        raise OSError(errno, os.strerror(errno))
+
+def ll_close(file_wrapper):
+    if file_wrapper.file:
+        # double close is allowed
+        res = c_close(file_wrapper.file)
+        file_wrapper.file = lltype.nullptr(FILE)
+        if res == -1:
+            errno = rposix.get_errno()
+            raise OSError(errno, os.strerror(errno))
+
+class FileRepr(Repr):
+    lowleveltype = lltype.Ptr(FILE_WRAPPER)
+
+    def __init__(self, typer):
+        Repr.__init__(self)
+
+    def rtype_constructor(self, hop):
+        repr = hop.rtyper.getrepr(annmodel.SomeString())
+        arg_0 = hop.inputarg(repr, 0)
+        if len(hop.args_v) == 1:
+            arg_1 = hop.inputconst(string_repr, "r")
+        else:
+            arg_1 = hop.inputarg(repr, 1)
+        hop.exception_is_here()
+        open = hop.rtyper.getannmixlevel().delayedfunction(
+            ll_open, [annmodel.SomeString()] * 2,
+            annmodel.SomePtr(self.lowleveltype))
+        v_open = hop.inputconst(lltype.typeOf(open), open)
+        return hop.genop('direct_call', [v_open, arg_0, arg_1],
+                         resulttype=self)
+
+    def rtype_tempfile(self, hop):
+        tmpfile = hop.rtyper.getannmixlevel().delayedfunction(
+            ll_tmpfile, [], annmodel.SomePtr(self.lowleveltype))
+        v_tmpfile = hop.inputconst(lltype.typeOf(tmpfile), tmpfile)
+        hop.exception_is_here()
+        return hop.genop('direct_call', [v_tmpfile], resulttype=self)
+
+
+    def rtype_method_write(self, hop):
+        args_v = hop.inputargs(self, string_repr)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_write, *args_v)
+
+    def rtype_method_close(self, hop):
+        r_self = hop.inputarg(self, 0)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_close, r_self)
+
+    def rtype_method_read(self, hop):
+        r_self = hop.inputarg(self, 0)
+        if len(hop.args_v) != 2:
+            arg_1 = hop.inputconst(lltype.Signed, -1)
+        else:
+            arg_1 = hop.inputarg(lltype.Signed, 1)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_read, r_self, arg_1)
+
+    def rtype_method_seek(self, hop):
+        r_self = hop.inputarg(self, 0)
+        arg_1 = hop.inputarg(lltype.Signed, 1)
+        if len(hop.args_v) != 3:
+            arg_2 = hop.inputconst(lltype.Signed, os.SEEK_SET)
+        else:
+            arg_2 = hop.inputarg(lltype.Signed, 2)
+        hop.exception_is_here()
+        return hop.gendirectcall(ll_seek, r_self, arg_1, arg_2)
+


More information about the pypy-commit mailing list