diff options
| author | Armin Rigo <arigo@tunes.org> | 2015-05-17 10:00:14 +0200 |
|---|---|---|
| committer | Armin Rigo <arigo@tunes.org> | 2015-05-17 10:00:14 +0200 |
| commit | 48d677b2578080c605794afd08736176b3c8f928 (patch) | |
| tree | 086c5f2367dfb1989cdd41aacd675afb26bfc1e2 | |
| parent | acf8dc3c0a4a389d3e89732a9fa96cff3abace83 (diff) | |
| parent | cb8e0566f3bf8f905324c6e77a00201fc6a9ab52 (diff) | |
| download | cffi-release-0.9.tar.gz | |
hg merge defaultrelease-0.9
| -rw-r--r-- | c/_cffi_backend.c | 7 | ||||
| -rw-r--r-- | c/libffi_msvc/ffi.c | 2 | ||||
| -rw-r--r-- | cffi/cparser.py | 14 | ||||
| -rw-r--r-- | cffi/ffiplatform.py | 13 | ||||
| -rw-r--r-- | cffi/model.py | 18 | ||||
| -rw-r--r-- | cffi/vengine_cpy.py | 26 | ||||
| -rw-r--r-- | cffi/vengine_gen.py | 38 | ||||
| -rw-r--r-- | doc/source/index.rst | 24 | ||||
| -rw-r--r-- | setup.py | 10 | ||||
| -rw-r--r-- | setup_base.py | 2 | ||||
| -rw-r--r-- | testing/backend_tests.py | 13 | ||||
| -rw-r--r-- | testing/test_ctypes.py | 3 | ||||
| -rw-r--r-- | testing/test_ffi_backend.py | 54 | ||||
| -rw-r--r-- | testing/test_function.py | 1 | ||||
| -rw-r--r-- | testing/test_verify.py | 79 |
15 files changed, 256 insertions, 48 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c index ae31a97..92aa9b9 100644 --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -3761,9 +3761,10 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) for (i=0; i<nb_fields; i++) { PyObject *fname; CTypeDescrObject *ftype; - int fbitsize = -1, falign, do_align, foffset = -1; + int fbitsize = -1, falign, do_align; + Py_ssize_t foffset = -1; - if (!PyArg_ParseTuple(PyList_GET_ITEM(fields, i), "O!O!|ii:list item", + if (!PyArg_ParseTuple(PyList_GET_ITEM(fields, i), "O!O!|in:list item", &PyText_Type, &fname, &CTypeDescr_Type, &ftype, &fbitsize, &foffset)) @@ -5581,7 +5582,7 @@ static PyMethodDef FFIBackendMethods[] = { static RETURNTYPE _cffi_to_c_i##SIZE(PyObject *obj) { \ PY_LONG_LONG tmp = _my_PyLong_AsLongLong(obj); \ if ((tmp > (PY_LONG_LONG)((1ULL<<(SIZE-1)) - 1)) || \ - (tmp < (PY_LONG_LONG)(-(1ULL<<(SIZE-1))))) \ + (tmp < (PY_LONG_LONG)(0ULL-(1ULL<<(SIZE-1))))) \ if (!PyErr_Occurred()) \ return (RETURNTYPE)_convert_overflow(obj, #SIZE "-bit int"); \ return (RETURNTYPE)tmp; \ diff --git a/c/libffi_msvc/ffi.c b/c/libffi_msvc/ffi.c index a66f9d8..836f171 100644 --- a/c/libffi_msvc/ffi.c +++ b/c/libffi_msvc/ffi.c @@ -119,7 +119,7 @@ void ffi_prep_args(char *stack, extended_cif *ecif) argp += z; } - if (argp - stack > ecif->cif->bytes) + if (argp - stack > (long)ecif->cif->bytes) { Py_FatalError("FFI BUG: not enough stack space for arguments"); } diff --git a/cffi/cparser.py b/cffi/cparser.py index 49ca9b9..1c85ad4 100644 --- a/cffi/cparser.py +++ b/cffi/cparser.py @@ -266,7 +266,10 @@ class Parser(object): # if decl.name: tp = self._get_type(node, partial_length_ok=True) - if self._is_constant_globalvar(node): + if tp.is_raw_function: + tp = self._get_type_pointer(tp) + self._declare('function ' + decl.name, tp) + elif self._is_constant_globalvar(node): self._declare('constant ' + decl.name, tp) else: self._declare('variable ' + decl.name, tp) @@ -290,9 +293,13 @@ class Parser(object): assert '__dotdotdot__' not in name.split() self._declarations[name] = obj - def _get_type_pointer(self, type, const=False): + def _get_type_pointer(self, type, const=False, declname=None): if isinstance(type, model.RawFunctionType): return type.as_function_pointer() + if (isinstance(type, model.StructOrUnionOrEnum) and + type.name.startswith('$') and type.name[1:].isdigit() and + type.forcename is None and declname is not None): + return model.NamedPointerType(type, declname) if const: return model.ConstPointerType(type) return model.PointerType(type) @@ -319,7 +326,8 @@ class Parser(object): # pointer type const = (isinstance(typenode.type, pycparser.c_ast.TypeDecl) and 'const' in typenode.type.quals) - return self._get_type_pointer(self._get_type(typenode.type), const) + return self._get_type_pointer(self._get_type(typenode.type), const, + declname=name) # if isinstance(typenode, pycparser.c_ast.TypeDecl): type = typenode.type diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py index 365b3e8..732df77 100644 --- a/cffi/ffiplatform.py +++ b/cffi/ffiplatform.py @@ -1,4 +1,4 @@ -import os +import sys, os class VerificationError(Exception): @@ -14,7 +14,17 @@ class VerificationMissing(Exception): LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', 'extra_objects', 'depends'] +def _hack_at_distutils(): + # Windows-only workaround for some configurations: see + # https://bugs.python.org/issue23246 (Python 2.7.9) + if sys.platform == "win32": + try: + import setuptools # for side-effects, patches distutils + except ImportError: + pass + def get_extension(srcfilename, modname, sources=(), **kwds): + _hack_at_distutils() # *before* the following import from distutils.core import Extension allsources = [srcfilename] allsources.extend(sources) @@ -37,6 +47,7 @@ def compile(tmpdir, ext): def _build(tmpdir, ext): # XXX compact but horrible :-( + _hack_at_distutils() from distutils.core import Distribution import distutils.errors # diff --git a/cffi/model.py b/cffi/model.py index dc0fa4a..a6b28c7 100644 --- a/cffi/model.py +++ b/cffi/model.py @@ -102,8 +102,26 @@ class PrimitiveType(BaseType): 'uint32_t': 'i', 'int64_t': 'i', 'uint64_t': 'i', + 'int_least8_t': 'i', + 'uint_least8_t': 'i', + 'int_least16_t': 'i', + 'uint_least16_t': 'i', + 'int_least32_t': 'i', + 'uint_least32_t': 'i', + 'int_least64_t': 'i', + 'uint_least64_t': 'i', + 'int_fast8_t': 'i', + 'uint_fast8_t': 'i', + 'int_fast16_t': 'i', + 'uint_fast16_t': 'i', + 'int_fast32_t': 'i', + 'uint_fast32_t': 'i', + 'int_fast64_t': 'i', + 'uint_fast64_t': 'i', 'intptr_t': 'i', 'uintptr_t': 'i', + 'intmax_t': 'i', + 'uintmax_t': 'i', 'ptrdiff_t': 'i', 'size_t': 'i', 'ssize_t': 'i', diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py index 1c4668c..9d5bf67 100644 --- a/cffi/vengine_cpy.py +++ b/cffi/vengine_cpy.py @@ -141,19 +141,23 @@ class VCPythonEngine(object): def load_library(self, flags=None): # XXX review all usages of 'self' here! # import it as a new extension module - if hasattr(sys, "getdlopenflags"): - previous_flags = sys.getdlopenflags() + imp.acquire_lock() try: - if hasattr(sys, "setdlopenflags") and flags is not None: - sys.setdlopenflags(flags) - module = imp.load_dynamic(self.verifier.get_module_name(), - self.verifier.modulefilename) - except ImportError as e: - error = "importing %r: %s" % (self.verifier.modulefilename, e) - raise ffiplatform.VerificationError(error) + if hasattr(sys, "getdlopenflags"): + previous_flags = sys.getdlopenflags() + try: + if hasattr(sys, "setdlopenflags") and flags is not None: + sys.setdlopenflags(flags) + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise ffiplatform.VerificationError(error) + finally: + if hasattr(sys, "setdlopenflags"): + sys.setdlopenflags(previous_flags) finally: - if hasattr(sys, "setdlopenflags"): - sys.setdlopenflags(previous_flags) + imp.release_lock() # # call loading_cpy_struct() to get the struct layout inferred by # the C compiler diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py index 8e1412c..dc73ac0 100644 --- a/cffi/vengine_gen.py +++ b/cffi/vengine_gen.py @@ -149,15 +149,21 @@ class VGenericEngine(object): context = 'argument of %s' % name arglist = [type.get_c_name(' %s' % arg, context) for type, arg in zip(tp.args, argnames)] + tpresult = tp.result + if isinstance(tpresult, model.StructOrUnion): + arglist.insert(0, tpresult.get_c_name(' *r', context)) + tpresult = model.void_type arglist = ', '.join(arglist) or 'void' wrappername = '_cffi_f_%s' % name self.export_symbols.append(wrappername) funcdecl = ' %s(%s)' % (wrappername, arglist) context = 'result of %s' % name - prnt(tp.result.get_c_name(funcdecl, context)) + prnt(tpresult.get_c_name(funcdecl, context)) prnt('{') # - if not isinstance(tp.result, model.VoidType): + if isinstance(tp.result, model.StructOrUnion): + result_code = '*r = ' + elif not isinstance(tp.result, model.VoidType): result_code = 'return ' else: result_code = '' @@ -174,15 +180,26 @@ class VGenericEngine(object): else: indirections = [] base_tp = tp - if any(isinstance(typ, model.StructOrUnion) for typ in tp.args): + if (any(isinstance(typ, model.StructOrUnion) for typ in tp.args) + or isinstance(tp.result, model.StructOrUnion)): indirect_args = [] for i, typ in enumerate(tp.args): if isinstance(typ, model.StructOrUnion): typ = model.PointerType(typ) indirections.append((i, typ)) indirect_args.append(typ) + indirect_result = tp.result + if isinstance(indirect_result, model.StructOrUnion): + if indirect_result.fldtypes is None: + raise TypeError("'%s' is used as result type, " + "but is opaque" % ( + indirect_result._get_c_name(),)) + indirect_result = model.PointerType(indirect_result) + indirect_args.insert(0, indirect_result) + indirections.insert(0, ("result", indirect_result)) + indirect_result = model.void_type tp = model.FunctionPtrType(tuple(indirect_args), - tp.result, tp.ellipsis) + indirect_result, tp.ellipsis) BFunc = self.ffi._get_cached_btype(tp) wrappername = '_cffi_f_%s' % name newfunction = module.load_function(BFunc, wrappername) @@ -195,9 +212,16 @@ class VGenericEngine(object): def _make_struct_wrapper(self, oldfunc, i, tp, base_tp): backend = self.ffi._backend BType = self.ffi._get_cached_btype(tp) - def newfunc(*args): - args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] - return oldfunc(*args) + if i == "result": + ffi = self.ffi + def newfunc(*args): + res = ffi.new(BType) + oldfunc(res, *args) + return res[0] + else: + def newfunc(*args): + args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] + return oldfunc(*args) newfunc._cffi_base_type = base_tp return newfunc diff --git a/doc/source/index.rst b/doc/source/index.rst index 9ab96b5..19ddf31 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -437,9 +437,9 @@ can assume to exist are the standard types: types ``TBYTE TCHAR LPCTSTR PCTSTR LPTSTR PTSTR PTBYTE PTCHAR`` are no longer automatically defined; see ``ffi.set_unicode()`` below. -* *New in version 0.9:* the other standard integer types from stdint.h, +* *New in version 0.9.3:* the other standard integer types from stdint.h, as long as they map to integers of 1, 2, 4 or 8 bytes. Larger integers - are not supported. + are not supported. (Actually added in version 0.9 but this was buggy.) .. _`common Windows types`: http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751%28v=vs.85%29.aspx @@ -1078,6 +1078,23 @@ if necessary with ``ffi.cast()``:: C.printf("hello, %f\n", ffi.cast("double", 42)) C.printf("hello, %s\n", ffi.new("char[]", "world")) +Note that if you are using ``dlopen()``, the function declaration in the +``cdef()`` must match the original one in C exactly, as usual --- in +particular, if this function is variadic in C, then its ``cdef()`` +declaration must also be variadic. You cannot declare it in the +``cdef()`` with fixed arguments instead, even if you plan to only call +it with these argument types. The reason is that some architectures +have a different calling convention depending on whether the function +signature is fixed or not. (On x86-64, the difference can sometimes be +seen in PyPy's JIT-generated code if some arguments are ``double``.) + +Note that the function signature ``int foo();`` is interpreted by CFFI +as equivalent to ``int foo(void);``. This differs from the C standard, +in which ``int foo();`` is really like ``int foo(...);`` and can be +called with any arguments. (This feature of C is a pre-C89 relic: the +arguments cannot be accessed at all in the body of ``foo()`` without +relying on compiler-specific extensions.) + Callbacks --------- @@ -1260,7 +1277,8 @@ points to the data of the given Python object, which must support the buffer interface. This is the opposite of ``ffi.buffer()``. It gives a (read-write) reference to the existing data, not a copy; for this reason, and for PyPy compatibility, it does not work with the built-in -types str or unicode or bytearray. It is meant to be used on objects +types str or unicode or bytearray (or buffers/memoryviews on them). +It is meant to be used on objects containing large quantities of raw data, like ``array.array`` or numpy arrays. It supports both the old buffer API (in Python 2.x) and the new memoryview API. The original object is kept alive (and, in case @@ -19,7 +19,7 @@ def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False): p = subprocess.Popen([pkg_config, option, 'libffi'], stdout=subprocess.PIPE) except OSError as e: - if e.errno != errno.ENOENT: + if e.errno not in [errno.ENOENT, errno.EACCES]: raise else: t = p.stdout.read().decode().strip() @@ -109,11 +109,14 @@ else: use_pkg_config() ask_supports_thread() +if 'freebsd' in sys.platform: + include_dirs.append('/usr/local/include') + if __name__ == '__main__': from setuptools import setup, Extension ext_modules = [] - if '__pypy__' not in sys.modules: + if '__pypy__' not in sys.builtin_module_names: ext_modules.append(Extension( name='_cffi_backend', include_dirs=include_dirs, @@ -162,5 +165,8 @@ Contact 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', ], ) diff --git a/setup_base.py b/setup_base.py index 8a56b90..667c7d5 100644 --- a/setup_base.py +++ b/setup_base.py @@ -8,7 +8,7 @@ from setup import library_dirs, extra_compile_args, extra_link_args if __name__ == '__main__': from distutils.core import setup from distutils.extension import Extension - standard = '__pypy__' not in sys.modules + standard = '__pypy__' not in sys.builtin_module_names setup(packages=['cffi'], requires=['pycparser'], ext_modules=[Extension(name = '_cffi_backend', diff --git a/testing/backend_tests.py b/testing/backend_tests.py index d4e8cf6..d3b5ca1 100644 --- a/testing/backend_tests.py +++ b/testing/backend_tests.py @@ -1388,6 +1388,17 @@ class BackendTests: assert p.c == 14 assert p.d == 14 + def test_nested_field_offset_align(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + struct foo_s { + struct { int a; char b; }; + union { char c; }; + }; + """) + assert ffi.offsetof("struct foo_s", "c") == 2 * SIZE_OF_INT + assert ffi.sizeof("struct foo_s") == 3 * SIZE_OF_INT + def test_nested_anonymous_union(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" @@ -1692,5 +1703,3 @@ class BackendTests: assert lib.DOT_HEX == 0x100 assert lib.DOT_HEX2 == 0x10 assert lib.DOT_UL == 1000 - - diff --git a/testing/test_ctypes.py b/testing/test_ctypes.py index 23e88b1..d2b53ed 100644 --- a/testing/test_ctypes.py +++ b/testing/test_ctypes.py @@ -28,6 +28,9 @@ class TestCTypes(backend_tests.BackendTests): def test_nested_anonymous_struct(self): py.test.skip("ctypes backend: not supported: nested anonymous struct") + def test_nested_field_offset_align(self): + py.test.skip("ctypes backend: not supported: nested anonymous struct") + def test_nested_anonymous_union(self): py.test.skip("ctypes backend: not supported: nested anonymous union") diff --git a/testing/test_ffi_backend.py b/testing/test_ffi_backend.py index 7cb5eee..4ea8b69 100644 --- a/testing/test_ffi_backend.py +++ b/testing/test_ffi_backend.py @@ -222,3 +222,57 @@ class TestBitfield: assert ffi.typeof(c) is ffi.typeof("char[]") ffi.cast("unsigned short *", c)[1] += 500 assert list(a) == [10000, 20500, 30000] + + def test_all_primitives(self): + ffi = FFI() + for name in [ + "char", + "short", + "int", + "long", + "long long", + "signed char", + "unsigned char", + "unsigned short", + "unsigned int", + "unsigned long", + "unsigned long long", + "float", + "double", + "long double", + "wchar_t", + "_Bool", + "int8_t", + "uint8_t", + "int16_t", + "uint16_t", + "int32_t", + "uint32_t", + "int64_t", + "uint64_t", + "int_least8_t", + "uint_least8_t", + "int_least16_t", + "uint_least16_t", + "int_least32_t", + "uint_least32_t", + "int_least64_t", + "uint_least64_t", + "int_fast8_t", + "uint_fast8_t", + "int_fast16_t", + "uint_fast16_t", + "int_fast32_t", + "uint_fast32_t", + "int_fast64_t", + "uint_fast64_t", + "intptr_t", + "uintptr_t", + "intmax_t", + "uintmax_t", + "ptrdiff_t", + "size_t", + "ssize_t", + ]: + x = ffi.sizeof(name) + assert 1 <= x <= 16 diff --git a/testing/test_function.py b/testing/test_function.py index 8edd4ae..d87bafb 100644 --- a/testing/test_function.py +++ b/testing/test_function.py @@ -292,7 +292,6 @@ class TestFunction(object): assert ffi.string(a) == b'4.4.4.4' def test_function_typedef(self): - py.test.skip("using really obscure C syntax") ffi = FFI(backend=self.Backend()) ffi.cdef(""" typedef double func_t(double); diff --git a/testing/test_verify.py b/testing/test_verify.py index 6a4400f..6a07168 100644 --- a/testing/test_verify.py +++ b/testing/test_verify.py @@ -657,9 +657,9 @@ def test_global_constants(): # case the 'static' is completely ignored. ffi.cdef("static const int AA, BB, CC, DD;") lib = ffi.verify("#define AA 42\n" - "#define BB (-43)\n" - "#define CC (22*2)\n" - "#define DD ((unsigned int)142)\n") + "#define BB (-43) // blah\n" + "#define CC (22*2) /* foobar */\n" + "#define DD ((unsigned int)142) /* foo\nbar */\n") assert lib.AA == 42 assert lib.BB == -43 assert lib.CC == 44 @@ -1197,6 +1197,15 @@ def test_typedef_enum_as_function_result(): """) assert lib.foo_func(lib.BB) == lib.BB == 2 +def test_function_typedef(): + ffi = FFI() + ffi.cdef(""" + typedef double func_t(double); + func_t sin; + """) + lib = ffi.verify('#include <math.h>', libraries=lib_m) + assert lib.sin(1.23) == math.sin(1.23) + def test_callback_calling_convention(): py.test.skip("later") if sys.platform != 'win32': @@ -1217,11 +1226,11 @@ def test_callback_calling_convention(): xxx def test_opaque_integer_as_function_result(): - import platform - if platform.machine().startswith('sparc'): - py.test.skip('Breaks horribly on sparc (SIGILL + corrupted stack)') - elif platform.machine() == 'mips64' and sys.maxsize > 2**32: - py.test.skip('Segfaults on mips64el') + #import platform + #if platform.machine().startswith('sparc'): + # py.test.skip('Breaks horribly on sparc (SIGILL + corrupted stack)') + #elif platform.machine() == 'mips64' and sys.maxsize > 2**32: + # py.test.skip('Segfaults on mips64el') # XXX bad abuse of "struct { ...; }". It only works a bit by chance # anyway. XXX think about something better :-( ffi = FFI() @@ -1236,11 +1245,45 @@ def test_opaque_integer_as_function_result(): h = lib.foo() assert ffi.sizeof(h) == ffi.sizeof("short") +def test_return_partial_struct(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(void); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(void) { foo_t r = { 45, 81 }; return r; } + """) + h = lib.foo() + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 81 + +def test_take_and_return_partial_structs(): + ffi = FFI() + ffi.cdef(""" + typedef struct { int x; ...; } foo_t; + foo_t foo(foo_t, foo_t); + """) + lib = ffi.verify(""" + typedef struct { int y, x; } foo_t; + foo_t foo(foo_t a, foo_t b) { + foo_t r = { 100, a.x * 5 + b.x * 7 }; + return r; + } + """) + args = ffi.new("foo_t[3]") + args[0].x = 1000 + args[2].x = -498 + h = lib.foo(args[0], args[2]) + assert ffi.sizeof(h) == 2 * ffi.sizeof("int") + assert h.x == 1000 * 5 - 498 * 7 + def test_cannot_name_struct_type(): ffi = FFI() - ffi.cdef("typedef struct { int x; } *sp; void foo(sp);") + ffi.cdef("typedef struct { int x; } **sp; void foo(sp);") e = py.test.raises(VerificationError, ffi.verify, - "typedef struct { int x; } *sp; void foo(sp);") + "typedef struct { int x; } **sp; void foo(sp x) { }") assert 'in argument of foo: unknown type name' in str(e.value) def test_dont_check_unnamable_fields(): @@ -1637,9 +1680,8 @@ def test_struct_returned_by_func(): e = py.test.raises(TypeError, ffi.verify, "typedef struct { int x; } foo_t; " "foo_t myfunc(void) { foo_t x = { 42 }; return x; }") - assert str(e.value) in [ - "function myfunc: 'foo_t' is used as result type, but is opaque", - "function myfunc: result type 'foo_t' is opaque"] + assert str(e.value) == ( + "function myfunc: 'foo_t' is used as result type, but is opaque") def test_include(): ffi1 = FFI() @@ -1667,6 +1709,17 @@ def test_include_enum(): res = lib2.myfunc(lib2.AA) assert res == 2 +def test_named_pointer_as_argument(): + ffi = FFI() + ffi.cdef("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p);") + lib = ffi.verify("typedef struct { int x; } *mystruct_p;\n" + "mystruct_p ff5a(mystruct_p p) { p->x += 40; return p; }") + p = ffi.new("mystruct_p", [-2]) + q = lib.ff5a(p) + assert q == p + assert p.x == 38 + def test_enum_size(): cases = [('123', 4, 4294967295), ('4294967295U', 4, 4294967295), |
