Skip to content

Commit c52ed59

Browse files
committed
python#2505: allow easier creation of AST nodes.
1 parent c87c580 commit c52ed59

3 files changed

Lines changed: 238 additions & 32 deletions

File tree

Doc/library/_ast.rst

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,32 @@ node. The utf8 offset is recorded because the parser uses utf8 internally.
4646
If these attributes are marked as optional in the grammar (using a question
4747
mark), the value might be ``None``. If the attributes can have zero-or-more
4848
values (marked with an asterisk), the values are represented as Python lists.
49+
All possible attributes must be present and have valid values when compiling an
50+
AST with :func:`compile`.
51+
52+
The constructor of a class ``_ast.T`` parses their arguments as follows:
53+
54+
* If there are positional arguments, there must be as many as there are items in
55+
``T._fields``; they will be assigned as attributes of these names.
56+
* If there are keyword arguments, they will set the attributes of the same names
57+
to the given values.
58+
59+
For example, to create and populate a ``UnaryOp`` node, you could use ::
60+
61+
node = _ast.UnaryOp()
62+
node.op = _ast.USub()
63+
node.operand = _ast.Num()
64+
node.operand.n = 5
65+
node.operand.lineno = 0
66+
node.operand.col_offset = 0
67+
node.lineno = 0
68+
node.col_offset = 0
69+
70+
or the more compact ::
71+
72+
node = _ast.UnaryOp(_ast.USub(), _ast.Num(5, lineno=0, col_offset=0),
73+
lineno=0, col_offset=0)
4974

50-
The constructors of all ``_ast`` classes don't take arguments; instead, if you
51-
create instances, you must assign the required attributes separately.
5275

5376

5477
Abstract Grammar

Parser/asdl_c.py

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,98 @@ class PyTypesVisitor(PickleVisitor):
578578

579579
def visitModule(self, mod):
580580
self.emit("""
581+
static int
582+
ast_type_init(PyObject *self, PyObject *args, PyObject *kw)
583+
{
584+
Py_ssize_t i, numfields = 0;
585+
int res = -1;
586+
PyObject *key, *value, *fields;
587+
fields = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "_fields");
588+
if (!fields)
589+
PyErr_Clear();
590+
if (fields) {
591+
numfields = PySequence_Size(fields);
592+
if (numfields == -1)
593+
goto cleanup;
594+
}
595+
res = 0; /* if no error occurs, this stays 0 to the end */
596+
if (PyTuple_GET_SIZE(args) > 0) {
597+
if (numfields != PyTuple_GET_SIZE(args)) {
598+
PyErr_Format(PyExc_TypeError, "%.400s constructor takes either 0 or "
599+
"%d positional argument%s", Py_TYPE(self)->tp_name,
600+
numfields, numfields == 1 ? "" : "s");
601+
res = -1;
602+
goto cleanup;
603+
}
604+
for (i = 0; i < PyTuple_GET_SIZE(args); i++) {
605+
/* cannot be reached when fields is NULL */
606+
PyObject *name = PySequence_GetItem(fields, i);
607+
if (!name) {
608+
res = -1;
609+
goto cleanup;
610+
}
611+
res = PyObject_SetAttr(self, name, PyTuple_GET_ITEM(args, i));
612+
Py_DECREF(name);
613+
if (res < 0)
614+
goto cleanup;
615+
}
616+
}
617+
if (kw) {
618+
i = 0; /* needed by PyDict_Next */
619+
while (PyDict_Next(kw, &i, &key, &value)) {
620+
res = PyObject_SetAttr(self, key, value);
621+
if (res < 0)
622+
goto cleanup;
623+
}
624+
}
625+
cleanup:
626+
Py_XDECREF(fields);
627+
return res;
628+
}
629+
630+
static PyTypeObject AST_type = {
631+
PyVarObject_HEAD_INIT(&PyType_Type, 0)
632+
"AST",
633+
sizeof(PyObject),
634+
0,
635+
0, /* tp_dealloc */
636+
0, /* tp_print */
637+
0, /* tp_getattr */
638+
0, /* tp_setattr */
639+
0, /* tp_compare */
640+
0, /* tp_repr */
641+
0, /* tp_as_number */
642+
0, /* tp_as_sequence */
643+
0, /* tp_as_mapping */
644+
0, /* tp_hash */
645+
0, /* tp_call */
646+
0, /* tp_str */
647+
PyObject_GenericGetAttr, /* tp_getattro */
648+
PyObject_GenericSetAttr, /* tp_setattro */
649+
0, /* tp_as_buffer */
650+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
651+
0, /* tp_doc */
652+
0, /* tp_traverse */
653+
0, /* tp_clear */
654+
0, /* tp_richcompare */
655+
0, /* tp_weaklistoffset */
656+
0, /* tp_iter */
657+
0, /* tp_iternext */
658+
0, /* tp_methods */
659+
0, /* tp_members */
660+
0, /* tp_getset */
661+
0, /* tp_base */
662+
0, /* tp_dict */
663+
0, /* tp_descr_get */
664+
0, /* tp_descr_set */
665+
0, /* tp_dictoffset */
666+
(initproc)ast_type_init, /* tp_init */
667+
PyType_GenericAlloc, /* tp_alloc */
668+
PyType_GenericNew, /* tp_new */
669+
PyObject_Del, /* tp_free */
670+
};
671+
672+
581673
static PyTypeObject* make_type(char *type, PyTypeObject* base, char**fields, int num_fields)
582674
{
583675
PyObject *fnames, *result;
@@ -606,15 +698,15 @@ def visitModule(self, mod):
606698
static int add_attributes(PyTypeObject* type, char**attrs, int num_fields)
607699
{
608700
int i, result;
609-
PyObject *s, *l = PyList_New(num_fields);
701+
PyObject *s, *l = PyTuple_New(num_fields);
610702
if (!l) return 0;
611703
for(i = 0; i < num_fields; i++) {
612704
s = PyString_FromString(attrs[i]);
613705
if (!s) {
614706
Py_DECREF(l);
615707
return 0;
616708
}
617-
PyList_SET_ITEM(l, i, s);
709+
PyTuple_SET_ITEM(l, i, s);
618710
}
619711
result = PyObject_SetAttrString((PyObject*)type, "_attributes", l) >= 0;
620712
Py_DECREF(l);
@@ -716,7 +808,6 @@ def visitModule(self, mod):
716808
self.emit("{", 0)
717809
self.emit("static int initialized;", 1)
718810
self.emit("if (initialized) return 1;", 1)
719-
self.emit('AST_type = make_type("AST", &PyBaseObject_Type, NULL, 0);', 1)
720811
for dfn in mod.dfns:
721812
self.visit(dfn)
722813
self.emit("initialized = 1;", 1)
@@ -728,12 +819,13 @@ def visitProduct(self, prod, name):
728819
fields = name.value+"_fields"
729820
else:
730821
fields = "NULL"
731-
self.emit('%s_type = make_type("%s", AST_type, %s, %d);' %
822+
self.emit('%s_type = make_type("%s", &AST_type, %s, %d);' %
732823
(name, name, fields, len(prod.fields)), 1)
733824
self.emit("if (!%s_type) return 0;" % name, 1)
734825

735826
def visitSum(self, sum, name):
736-
self.emit('%s_type = make_type("%s", AST_type, NULL, 0);' % (name, name), 1)
827+
self.emit('%s_type = make_type("%s", &AST_type, NULL, 0);' %
828+
(name, name), 1)
737829
self.emit("if (!%s_type) return 0;" % name, 1)
738830
if sum.attributes:
739831
self.emit("if (!add_attributes(%s_type, %s_attributes, %d)) return 0;" %
@@ -772,7 +864,7 @@ def visitModule(self, mod):
772864
self.emit('m = Py_InitModule3("_ast", NULL, NULL);', 1)
773865
self.emit("if (!m) return;", 1)
774866
self.emit("d = PyModule_GetDict(m);", 1)
775-
self.emit('if (PyDict_SetItemString(d, "AST", (PyObject*)AST_type) < 0) return;', 1)
867+
self.emit('if (PyDict_SetItemString(d, "AST", (PyObject*)&AST_type) < 0) return;', 1)
776868
self.emit('if (PyModule_AddIntConstant(m, "PyCF_ONLY_AST", PyCF_ONLY_AST) < 0)', 1)
777869
self.emit("return;", 2)
778870
# Value of version: "$Revision$"
@@ -979,7 +1071,7 @@ class PartingShots(StaticVisitor):
9791071
int PyAST_Check(PyObject* obj)
9801072
{
9811073
init_types();
982-
return PyObject_IsInstance(obj, (PyObject*)AST_type);
1074+
return PyObject_IsInstance(obj, (PyObject*)&AST_type);
9831075
}
9841076
"""
9851077

@@ -1035,7 +1127,7 @@ def main(srcfile):
10351127
print >> f, '#include "Python.h"'
10361128
print >> f, '#include "%s-ast.h"' % mod.name
10371129
print >> f
1038-
print >>f, "static PyTypeObject* AST_type;"
1130+
print >>f, "static PyTypeObject AST_type;"
10391131
v = ChainOfVisitors(
10401132
PyTypesDeclareVisitor(f),
10411133
PyTypesVisitor(f),

0 commit comments

Comments
 (0)