Skip to content

Commit cc53818

Browse files
committed
Implement C API function PyOS_double_to_string.
1 parent b8ad4a3 commit cc53818

File tree

6 files changed

+155
-1
lines changed

6 files changed

+155
-1
lines changed

graalpython/com.oracle.graal.python.cext/include/Python.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@
122122
#include "weakrefobject.h"
123123
#include "sysmodule.h"
124124
#include "fileutils.h"
125+
#include "pystrtod.h"
125126

126127
// TODO: we must extend the refcounting behavior to support handles to managed objects
127128
#undef Py_DECREF
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/* Copyright (c) 2019, Oracle and/or its affiliates.
2+
* Copyright (C) 1996-2017 Python Software Foundation
3+
*
4+
* Licensed under the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
5+
*/
6+
7+
#ifndef Py_STRTOD_H
8+
#define Py_STRTOD_H
9+
10+
#ifdef __cplusplus
11+
extern "C" {
12+
#endif
13+
14+
15+
PyAPI_FUNC(double) PyOS_string_to_double(const char *str,
16+
char **endptr,
17+
PyObject *overflow_exception);
18+
19+
/* The caller is responsible for calling PyMem_Free to free the buffer
20+
that's is returned. */
21+
PyAPI_FUNC(char *) PyOS_double_to_string(double val,
22+
char format_code,
23+
int precision,
24+
int flags,
25+
int *type);
26+
27+
#ifndef Py_LIMITED_API
28+
PyAPI_FUNC(PyObject *) _Py_string_to_number_with_underscores(
29+
const char *str, Py_ssize_t len, const char *what, PyObject *obj, void *arg,
30+
PyObject *(*innerfunc)(const char *, Py_ssize_t, void *));
31+
32+
PyAPI_FUNC(double) _Py_parse_inf_or_nan(const char *p, char **endptr);
33+
#endif
34+
35+
36+
/* PyOS_double_to_string's "flags" parameter can be set to 0 or more of: */
37+
#define Py_DTSF_SIGN 0x01 /* always add the sign */
38+
#define Py_DTSF_ADD_DOT_0 0x02 /* if the result is an integer add ".0" */
39+
#define Py_DTSF_ALT 0x04 /* "alternate" formatting. it's format_code
40+
specific */
41+
42+
/* PyOS_double_to_string's "type", if non-NULL, will be set to one of: */
43+
#define Py_DTST_FINITE 0
44+
#define Py_DTST_INFINITE 1
45+
#define Py_DTST_NAN 2
46+
47+
#ifdef __cplusplus
48+
}
49+
#endif
50+
51+
#endif /* !Py_STRTOD_H */

graalpython/com.oracle.graal.python.cext/src/pystrtod.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,19 @@ double PyOS_string_to_double(const char *s, char **endptr, PyObject *overflow_ex
5353
}
5454
return result;
5555
}
56+
57+
/* translation macro to be independent of changes in 'pystrtod.h' */
58+
#define TRANSLATE_TYPE(__tc__) ((__tc__) == 0 ? Py_DTST_FINITE : ((__tc__) == 1 ? Py_DTST_INFINITE : Py_DTST_NAN))
59+
60+
UPCALL_ID(PyTruffle_OS_DoubleToString);
61+
char * PyOS_double_to_string(double val, char format_code, int precision, int flags, int *type) {
62+
char* result = NULL;
63+
PyObject* resultTuple = UPCALL_CEXT_O(_jls_PyTruffle_OS_DoubleToString, val, (int32_t)format_code, precision, flags);
64+
if (resultTuple != NULL) {
65+
result = (char *) PyTuple_GetItem(resultTuple, 0);
66+
if (type != NULL) {
67+
*type = TRANSLATE_TYPE(as_int(PyTuple_GetItem(resultTuple, 1)));
68+
}
69+
}
70+
return result;
71+
}

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_misc.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ def _reference_importmodule(args):
4646
return __import__(args[0], fromlist=["*"])
4747

4848

49+
def _reference_format_float(args):
50+
val, format_spec, prec = args
51+
if format_spec == b'r':
52+
return repr(val)
53+
return float(val).__format__("." + str(prec) + format_spec.decode())
54+
55+
4956
class TestMisc(CPyExtTestCase):
5057

5158
def compile_module(self, name):
@@ -212,3 +219,25 @@ def compile_module(self, name):
212219
arguments=["PyObject* pyVal", "PyObject* fun"],
213220
cmpfunc=unhandled_error_compare
214221
)
222+
223+
test_PyOS_double_to_string = CPyExtFunction(
224+
_reference_format_float,
225+
lambda: (
226+
(1.2, b"f", 2),
227+
(float('nan'), b"f", 2),
228+
(1.23456789, b"f", 2),
229+
(123.456789, b"f", 6),
230+
(123.456789, b"e", 6),
231+
(123.456789, b"r", 0),
232+
),
233+
code="""
234+
char* wrap_PyOS_double_to_string(double val, char format, int prec) {
235+
return PyOS_double_to_string(val, format, prec, 0, NULL);
236+
}
237+
""",
238+
resultspec="s",
239+
argspec="dci",
240+
arguments=["double val", "char format", "int prec"],
241+
callfunction="wrap_PyOS_double_to_string",
242+
cmpfunc=unhandled_error_compare
243+
)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/PythonCextBuiltins.java

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@
7070
import com.oracle.graal.python.builtins.PythonBuiltinClassType;
7171
import com.oracle.graal.python.builtins.PythonBuiltins;
7272
import com.oracle.graal.python.builtins.modules.PythonCextBuiltinsFactory.CheckFunctionResultNodeGen;
73-
import com.oracle.graal.python.builtins.modules.PythonCextBuiltinsFactory.ExternalFunctionNodeGen;
7473
import com.oracle.graal.python.builtins.modules.PythonCextBuiltinsFactory.GetByteArrayNodeGen;
7574
import com.oracle.graal.python.builtins.modules.PythonCextBuiltinsFactory.TrufflePInt_AsPrimitiveFactory;
75+
import com.oracle.graal.python.builtins.modules.PythonCextBuiltinsFactory.ExternalFunctionNodeGen;
7676
import com.oracle.graal.python.builtins.objects.PNone;
7777
import com.oracle.graal.python.builtins.objects.PythonAbstractObject;
7878
import com.oracle.graal.python.builtins.objects.bytes.BytesBuiltins;
@@ -153,6 +153,8 @@
153153
import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode;
154154
import com.oracle.graal.python.nodes.call.InvokeNode;
155155
import com.oracle.graal.python.nodes.call.PythonCallNode;
156+
import com.oracle.graal.python.nodes.call.special.LookupAndCallBinaryNode;
157+
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
156158
import com.oracle.graal.python.nodes.classes.IsSubtypeNode;
157159
import com.oracle.graal.python.nodes.datamodel.IsSequenceNode;
158160
import com.oracle.graal.python.nodes.expression.BinaryComparisonNode;
@@ -170,6 +172,7 @@
170172
import com.oracle.graal.python.nodes.truffle.PythonTypes;
171173
import com.oracle.graal.python.nodes.util.CastToByteNode;
172174
import com.oracle.graal.python.nodes.util.CastToIndexNode;
175+
import com.oracle.graal.python.nodes.util.CastToStringNode;
173176
import com.oracle.graal.python.runtime.ExecutionContext.CalleeContext;
174177
import com.oracle.graal.python.runtime.ExecutionContext.IndirectCallContext;
175178
import com.oracle.graal.python.runtime.PythonContext;
@@ -2688,6 +2691,59 @@ private static Number parse(String source) throws ParseException {
26882691
}
26892692
}
26902693

2694+
@Builtin(name = "PyTruffle_OS_DoubleToString", minNumOfPositionalArgs = 5, declaresExplicitSelf = true)
2695+
@GenerateNodeFactory
2696+
@ImportStatic(SpecialMethodNames.class)
2697+
abstract static class PyTruffle_OS_DoubleToString extends NativeBuiltin {
2698+
2699+
/* keep in sync with macro 'TRANSLATE_TYPE' in 'pystrtod.c' */
2700+
private static final int Py_DTST_FINITE = 0;
2701+
private static final int Py_DTST_INFINITE = 1;
2702+
private static final int Py_DTST_NAN = 2;
2703+
2704+
@Specialization(guards = "isReprFormatCode(formatCode)")
2705+
@SuppressWarnings("unused")
2706+
PTuple doRepr(VirtualFrame frame, Object module, double val, int formatCode, int precision, int flags,
2707+
@Cached("create(__REPR__)") LookupAndCallUnaryNode callReprNode,
2708+
@Cached CastToStringNode castToStringNode,
2709+
@Cached GetNativeNullNode getNativeNullNode) {
2710+
Object reprString = callReprNode.executeObject(frame, val);
2711+
return createResult(new CStringWrapper(castToStringNode.execute(frame, reprString)), val);
2712+
}
2713+
2714+
@Specialization(guards = "!isReprFormatCode(formatCode)")
2715+
Object doGeneric(VirtualFrame frame, Object module, double val, int formatCode, int precision, @SuppressWarnings("unused") int flags,
2716+
@Cached("create(__FORMAT__)") LookupAndCallBinaryNode callReprNode,
2717+
@Cached CastToStringNode castToStringNode,
2718+
@Cached GetNativeNullNode getNativeNullNode) {
2719+
try {
2720+
Object reprString = callReprNode.executeObject(frame, val, "." + precision + Character.toString((char) formatCode));
2721+
return createResult(new CStringWrapper(castToStringNode.execute(frame, reprString)), val);
2722+
} catch (PException e) {
2723+
transformToNative(frame, e);
2724+
return getNativeNullNode.execute(module);
2725+
}
2726+
}
2727+
2728+
private PTuple createResult(Object str, double val) {
2729+
return factory().createTuple(new Object[]{str, getTypeCode(val)});
2730+
}
2731+
2732+
private static int getTypeCode(double val) {
2733+
if (Double.isInfinite(val)) {
2734+
return Py_DTST_INFINITE;
2735+
} else if (Double.isNaN(val)) {
2736+
return Py_DTST_NAN;
2737+
}
2738+
assert Double.isFinite(val);
2739+
return Py_DTST_FINITE;
2740+
}
2741+
2742+
protected static boolean isReprFormatCode(int formatCode) {
2743+
return (char) formatCode == 'r';
2744+
}
2745+
}
2746+
26912747
@Builtin(name = "PyUnicode_Decode", minNumOfPositionalArgs = 5, declaresExplicitSelf = true)
26922748
@GenerateNodeFactory
26932749
abstract static class PyUnicode_Decode extends NativeUnicodeBuiltin {

mx.graalpython/copyrights/overrides

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ graalpython/com.oracle.graal.python.cext/include/pymem.h,python.copyright
9393
graalpython/com.oracle.graal.python.cext/include/pyport.h,python.copyright
9494
graalpython/com.oracle.graal.python.cext/include/pystate.h,python.copyright
9595
graalpython/com.oracle.graal.python.cext/include/pystrhex.h,python.copyright
96+
graalpython/com.oracle.graal.python.cext/include/pystrtod.h,python.copyright
9697
graalpython/com.oracle.graal.python.cext/include/pythonrun.h,python.copyright
9798
graalpython/com.oracle.graal.python.cext/include/pythread.h,python.copyright
9899
graalpython/com.oracle.graal.python.cext/include/pytime.h,python.copyright

0 commit comments

Comments
 (0)