mirror of https://github.com/opencv/opencv.git
Open Source Computer Vision Library
https://opencv.org/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
631 lines
17 KiB
631 lines
17 KiB
#!/usr/bin/env python |
|
|
|
import sys |
|
from string import Template |
|
|
|
class argument: |
|
def __init__(self, fields): |
|
self.ty = fields[0] |
|
self.nm = fields[1] |
|
self.flags = "" |
|
self.init = None |
|
|
|
if len(fields) > 2: |
|
if fields[2][0] == '/': |
|
self.flags = fields[2][1:].split(",") |
|
else: |
|
self.init = fields[2] |
|
|
|
api = [] |
|
for l in open("%s/api" % sys.argv[1]): |
|
if l[0] == '#': |
|
continue |
|
l = l.rstrip() |
|
if (not l.startswith(' ')) and ('/' in l): |
|
(l, flags) = l.split('/') |
|
else: |
|
flags = "" |
|
f = l.split() |
|
if len(f) != 0: |
|
if l[0] != ' ': |
|
if len(f) > 1: |
|
ty = f[1] |
|
else: |
|
ty = None |
|
api.append((f[0], [], ty, flags)) |
|
else: |
|
api[-1][1].append(argument(f)) |
|
|
|
# Validation: check that any optional arguments are last |
|
had_error = False |
|
for (f, args, ty, flags) in api: |
|
has_init = [(a.init != None) for a in args if not 'O' in a.flags] |
|
if True in has_init and not all(has_init[has_init.index(True):]): |
|
print 'Error in definition for "%s", optional arguments must be last' % f |
|
had_error = True |
|
|
|
if had_error: |
|
sys.exit(1) |
|
|
|
def cname(n): |
|
if n.startswith("CV"): |
|
return '_' + n |
|
elif n[0].isdigit(): |
|
return '_' + n |
|
else: |
|
return n |
|
|
|
# RHS is how the aggregate gets expanded in the C call |
|
aggregate = { |
|
'pts_npts_contours' : '!.pts,!.npts,!.contours', |
|
'cvarr_count' : '!.cvarr,!.count', |
|
'cvarr_plane_count' : '!.cvarr,!.count', |
|
'floats' : '!.f', |
|
'ints' : '!.i', |
|
'ints0' : '!.i', |
|
'CvPoints' : '!.p,!.count', |
|
'CvPoint2D32fs' : '!.p,!.count', |
|
'CvPoint3D32fs' : '!.p,!.count', |
|
'cvarrseq' : '!.seq', |
|
'CvArrs' : '!.ims', |
|
'IplImages' : '!.ims', |
|
'intpair' : '!.pairs,!.count', |
|
'cvpoint2d32f_count' : '!.points,&!.count' |
|
} |
|
conversion_types = [ |
|
'char', |
|
'CvArr', |
|
'CvArrSeq', |
|
'CvBox2D', # '((ff)(ff)f)', |
|
'CvBox2D*', |
|
'CvCapture*', |
|
'CvStereoBMState*', |
|
'CvStereoGCState*', |
|
'CvKalman*', |
|
'CvVideoWriter*', |
|
'CvContourTree*', |
|
'CvFont', |
|
'CvFont*', |
|
'CvHaarClassifierCascade*', |
|
'CvHistogram', |
|
'CvMat', |
|
'CvMatND', |
|
'CvMemStorage', |
|
'CvMoments', |
|
'CvMoments*', |
|
'CvNextEdgeType', |
|
'CvPoint', |
|
'CvPoint*', |
|
'CvPoint2D32f', # '(ff)', |
|
'CvPoint2D32f*', |
|
'CvPoint3D32f*', |
|
'CvPoint2D64f', |
|
'CvPOSITObject*', |
|
'CvRect', |
|
'CvRect*', |
|
'CvRNG*', |
|
'CvScalar', |
|
'CvSeq', |
|
'CvSeqOfCvConvexityDefect', |
|
'CvSize', |
|
'CvSlice', |
|
'CvStarDetectorParams', |
|
'CvSubdiv2D*', |
|
'CvSubdiv2DEdge', |
|
'CvTermCriteria', |
|
'generic', |
|
'IplConvKernel*', |
|
'IplImage', |
|
'PyObject*', |
|
'PyCallableObject*' |
|
] |
|
|
|
def safename(s): |
|
return s.replace('*', 'PTR').replace('[', '_').replace(']', '_') |
|
|
|
def has_optional(al): |
|
""" return true if any argument is optional """ |
|
return any([a.init for a in al]) |
|
|
|
def gen(name, args, ty, flags): |
|
yield "" |
|
if has_optional(args): |
|
yield "static PyObject *pycv%s(PyObject *self, PyObject *args, PyObject *kw)" % cname(name) |
|
else: |
|
yield "static PyObject *pycv%s(PyObject *self, PyObject *args)" % cname(name) |
|
if 'doconly' in flags: |
|
yield ";" |
|
else: |
|
yield "{" |
|
|
|
destinations = [] |
|
for a in args: |
|
remap = { |
|
'CvArr' : 'CvArr*', |
|
'CvMat' : 'CvMat*', |
|
'CvMatND' : 'CvMatND*', |
|
'IplImage' : 'IplImage*', |
|
'CvMemStorage' : 'CvMemStorage*', |
|
'CvHistogram':'CvHistogram*', |
|
'CvSeq':'CvSeq*', |
|
'CvHaarClassifierCascade' : 'CvHaarClassifierCascade*' |
|
} |
|
ctype = remap.get(a.ty, a.ty) |
|
if a.init: |
|
init = " = %s" % a.init |
|
else: |
|
init = '' |
|
yield " %s %s%s;" % (ctype, a.nm, init) |
|
if 'O' in a.flags: |
|
continue |
|
if a.ty in (conversion_types + aggregate.keys()): |
|
yield ' PyObject *pyobj_%s = NULL;' % (a.nm) |
|
destinations.append('&pyobj_%s' % (a.nm)) |
|
elif a.ty in [ 'CvPoint2D32f' ]: |
|
destinations.append('&%s.x, &%s.y' % (a.nm, a.nm)) |
|
elif a.ty in [ 'CvTermCriteria' ]: |
|
destinations.append('&%s.type, &%s.max_iter, &%s.epsilon' % ((a.nm,)*3)) |
|
elif a.ty in [ 'CvSURFParams' ]: |
|
destinations.append('&%s.extended, &%s.hessianThreshold, &%s.nOctaves, &%s.nOctaveLayers' % ((a.nm,)*4)) |
|
elif a.nm in [ 'CvBox2D' ]: |
|
s = ", ".join([('&' + a.nm +'.' + fld) for fld in [ 'center.x', 'center.y', 'size.width', 'size.height', 'angle' ] ]) |
|
destinations.append(s) |
|
else: |
|
destinations.append('&%s' % a.nm) |
|
fmap = { |
|
'CvSURFParams' : '(idii)', |
|
'double' : 'd', |
|
'float' : 'f', |
|
'int' : 'i', |
|
'int64' : 'L', |
|
'char*' : 's', |
|
} |
|
for k in (conversion_types + aggregate.keys()): |
|
fmap[k] = 'O' |
|
in_args = [ a for a in args if not 'O' in a.flags ] |
|
fmt0 = "".join([ fmap[a.ty] for a in in_args if not a.init]) |
|
fmt1 = "".join([ fmap[a.ty] for a in in_args if a.init]) |
|
|
|
yield '' |
|
if len(fmt0 + fmt1) > 0: |
|
if len(fmt1) > 0: |
|
yield ' const char *keywords[] = { %s };' % (", ".join([ '"%s"' % arg.nm for arg in args if not 'O' in arg.flags ] + ['NULL'])) |
|
yield ' if (!PyArg_ParseTupleAndKeywords(args, kw, "%s|%s", %s))' % (fmt0, fmt1, ", ".join(['(char**)keywords'] + destinations)) |
|
if '(' in (fmt0 + fmt1): |
|
print "Tuple with kwargs is not allowed, function", name |
|
sys.exit(1) |
|
else: |
|
yield ' if (!PyArg_ParseTuple(args, "%s", %s))' % (fmt0, ", ".join(destinations)) |
|
yield ' return NULL;' |
|
|
|
# Do the conversions: |
|
for a in args: |
|
joinwith = [f[2:] for f in a.flags if f.startswith("J:")] |
|
if len(joinwith) > 0: |
|
yield 'preShareData(%s, &%s);' % (joinwith[0], a.nm) |
|
if 'O' in a.flags: |
|
continue |
|
if a.ty in (conversion_types + aggregate.keys()): |
|
if a.init: |
|
pred = '(pyobj_%s != NULL) && ' % a.nm |
|
else: |
|
pred = '' |
|
yield ' if (%s!convert_to_%s(pyobj_%s, &%s, "%s")) return NULL;' % (pred, safename(a.ty), a.nm, a.nm, a.nm) |
|
|
|
yield '#ifdef CVPY_VALIDATE_%s' % name |
|
yield 'CVPY_VALIDATE_%s();' % name |
|
yield '#endif' |
|
|
|
def invokename(a): |
|
if 'K' in a.flags: |
|
prefix = "(const CvArr **)" |
|
elif 'O' in a.flags and not 'A' in a.flags: |
|
prefix = "&" |
|
else: |
|
prefix = "" |
|
if a.ty in aggregate: |
|
return prefix + aggregate[a.ty].replace('!', a.nm) |
|
else: |
|
return prefix + a.nm |
|
|
|
def funcname(s): |
|
# The name by which the function is called, in C |
|
if s.startswith("CV"): |
|
return s |
|
else: |
|
return "cv" + s |
|
tocall = '%s(%s)' % (funcname(name), ", ".join(invokename(a) for a in args)) |
|
if 'stub' in flags: |
|
yield ' return stub%s(%s);' % (name, ", ".join(invokename(a) for a in args)) |
|
elif ty == None: |
|
yield ' ERRWRAP(%s);' % tocall |
|
yield ' Py_RETURN_NONE;' |
|
else: |
|
Rtypes = [ |
|
'int', |
|
'int64', |
|
'double', |
|
'CvCapture*', |
|
'CvVideoWriter*', |
|
'CvPOSITObject*', |
|
'CvScalar', |
|
'CvSize', |
|
'CvRect', |
|
'CvSeq*', |
|
'CvBox2D', |
|
'CvSeqOfCvAvgComp*', |
|
'CvSeqOfCvConvexityDefect*', |
|
'CvSeqOfCvStarKeypoint*', |
|
'CvSeqOfCvSURFPoint*', |
|
'CvSeqOfCvSURFDescriptor*', |
|
'CvContourTree*', |
|
'IplConvKernel*', |
|
'IplImage*', |
|
'CvMat*', |
|
'constCvMat*', |
|
'ROCvMat*', |
|
'CvMatND*', |
|
'CvPoint2D32f_4', |
|
'CvRNG', |
|
'CvSubdiv2D*', |
|
'CvSubdiv2DPoint*', |
|
'CvSubdiv2DEdge', |
|
'ROIplImage*', |
|
'CvStereoBMState*', |
|
'CvStereoGCState*', |
|
'CvKalman*', |
|
'float', |
|
'generic', |
|
'unsigned' ] |
|
|
|
if ty in Rtypes: |
|
yield ' %s r;' % (ty) |
|
yield ' ERRWRAP(r = %s);' % (tocall) |
|
yield ' return FROM_%s(r);' % safename(ty) |
|
else: |
|
all_returns = ty.split(",") |
|
return_value_from_call = len(set(Rtypes) & set(all_returns)) != 0 |
|
if return_value_from_call: |
|
yield ' %s r;' % list(set(Rtypes) & set(all_returns))[0] |
|
yield ' ERRWRAP(r = %s);' % (tocall) |
|
else: |
|
yield ' ERRWRAP(%s);' % (tocall) |
|
typed = dict([ (a.nm,a.ty) for a in args]) |
|
for i in range(len(all_returns)): |
|
if all_returns[i] in Rtypes: |
|
typed['r'] = all_returns[i] |
|
all_returns[i] = "r" |
|
if len(all_returns) == 1: |
|
af = dict([ (a.nm,a.flags) for a in args]) |
|
joinwith = [f[2:] for f in af.get(all_returns[0], []) if f.startswith("J:")] |
|
if len(joinwith) > 0: |
|
yield ' return shareData(pyobj_%s, %s, %s);' % (joinwith[0], joinwith[0], all_returns[0]) |
|
else: |
|
yield ' return FROM_%s(%s);' % (safename(typed[all_returns[0]]), all_returns[0]) |
|
else: |
|
yield ' return Py_BuildValue("%s", %s);' % ("N" * len(all_returns), ", ".join(["FROM_%s(%s)" % (safename(typed[n]), n) for n in all_returns])) |
|
|
|
yield '}' |
|
|
|
gen_c = [ open("generated%d.i" % i, "w") for i in range(5) ] |
|
|
|
print "Generated %d functions" % len(api) |
|
for nm,args,ty,flags in sorted(api): |
|
|
|
# Figure out docstring into ds_* |
|
ds_args = [] |
|
mandatory = [a.nm for a in args if not ('O' in a.flags) and not a.init] |
|
optional = [a.nm for a in args if not ('O' in a.flags) and a.init] |
|
ds_args = ", ".join(mandatory) |
|
def o2s(o): |
|
if o == []: |
|
return "" |
|
else: |
|
return ' [, %s%s]' % (o[0], o2s(o[1:])) |
|
ds_args += o2s(optional) |
|
|
|
ds = "%s(%s) -> %s" % (nm, ds_args, str(ty)) |
|
#print ds |
|
|
|
if has_optional(args): |
|
entry = '{"%%s", (PyCFunction)pycv%s, METH_KEYWORDS, "%s"},' % (cname(nm), ds) |
|
else: |
|
entry = '{"%%s", pycv%s, METH_VARARGS, "%s"},' % (cname(nm), ds) |
|
print >>gen_c[1], entry % (nm) |
|
if nm.startswith('CV_'): |
|
print >>gen_c[1], entry % (nm[3:]) |
|
for l in gen(nm,args,ty,flags): |
|
print >>gen_c[0], l |
|
|
|
for l in open("%s/defs" % sys.argv[1]): |
|
print >>gen_c[2], "PUBLISH(%s);" % l.split()[1] |
|
|
|
######################################################################## |
|
# Generated objects. |
|
######################################################################## |
|
|
|
# gen_c[3] is the code, gen_c[4] initializers |
|
|
|
gensimple = Template(""" |
|
/* |
|
${cvtype} is the OpenCV C struct |
|
${ourname}_t is the Python object |
|
*/ |
|
|
|
struct ${ourname}_t { |
|
PyObject_HEAD |
|
${cvtype} v; |
|
}; |
|
|
|
static PyObject *${ourname}_repr(PyObject *self) |
|
{ |
|
${ourname}_t *p = (${ourname}_t*)self; |
|
char str[1000]; |
|
sprintf(str, "<${ourname} %p>", p); |
|
return PyString_FromString(str); |
|
} |
|
|
|
${getset_funcs} |
|
|
|
static PyGetSetDef ${ourname}_getseters[] = { |
|
|
|
${getset_inits} |
|
{NULL} /* Sentinel */ |
|
}; |
|
|
|
static PyTypeObject ${ourname}_Type = { |
|
PyObject_HEAD_INIT(&PyType_Type) |
|
0, /*size*/ |
|
MODULESTR".${ourname}", /*name*/ |
|
sizeof(${ourname}_t), /*basicsize*/ |
|
}; |
|
|
|
static void ${ourname}_specials(void) |
|
{ |
|
${ourname}_Type.tp_repr = ${ourname}_repr; |
|
${ourname}_Type.tp_getset = ${ourname}_getseters; |
|
} |
|
|
|
static PyObject *FROM_${cvtype}(${cvtype} r) |
|
{ |
|
${ourname}_t *m = PyObject_NEW(${ourname}_t, &${ourname}_Type); |
|
m->v = r; |
|
return (PyObject*)m; |
|
} |
|
|
|
static int convert_to_${cvtype}PTR(PyObject *o, ${cvtype}** dst, const char *name = "no_name") |
|
{ |
|
${allownull} |
|
if (PyType_IsSubtype(o->ob_type, &${ourname}_Type)) { |
|
*dst = &(((${ourname}_t*)o)->v); |
|
return 1; |
|
} else { |
|
(*dst) = (${cvtype}*)NULL; |
|
return failmsg("Expected ${cvtype} for argument '%s'", name); |
|
} |
|
} |
|
|
|
""") |
|
|
|
genptr = Template(""" |
|
/* |
|
${cvtype} is the OpenCV C struct |
|
${ourname}_t is the Python object |
|
*/ |
|
|
|
struct ${ourname}_t { |
|
PyObject_HEAD |
|
${cvtype} *v; |
|
}; |
|
|
|
static void ${ourname}_dealloc(PyObject *self) |
|
{ |
|
${ourname}_t *p = (${ourname}_t*)self; |
|
cvRelease${ourname}(&p->v); |
|
PyObject_Del(self); |
|
} |
|
|
|
static PyObject *${ourname}_repr(PyObject *self) |
|
{ |
|
${ourname}_t *p = (${ourname}_t*)self; |
|
char str[1000]; |
|
sprintf(str, "<${ourname} %p>", p); |
|
return PyString_FromString(str); |
|
} |
|
|
|
${getset_funcs} |
|
|
|
static PyGetSetDef ${ourname}_getseters[] = { |
|
|
|
${getset_inits} |
|
{NULL} /* Sentinel */ |
|
}; |
|
|
|
static PyTypeObject ${ourname}_Type = { |
|
PyObject_HEAD_INIT(&PyType_Type) |
|
0, /*size*/ |
|
MODULESTR".${ourname}", /*name*/ |
|
sizeof(${ourname}_t), /*basicsize*/ |
|
}; |
|
|
|
static void ${ourname}_specials(void) |
|
{ |
|
${ourname}_Type.tp_dealloc = ${ourname}_dealloc; |
|
${ourname}_Type.tp_repr = ${ourname}_repr; |
|
${ourname}_Type.tp_getset = ${ourname}_getseters; |
|
} |
|
|
|
static PyObject *FROM_${cvtype}PTR(${cvtype} *r) |
|
{ |
|
${ourname}_t *m = PyObject_NEW(${ourname}_t, &${ourname}_Type); |
|
m->v = r; |
|
return (PyObject*)m; |
|
} |
|
|
|
static int convert_to_${cvtype}PTR(PyObject *o, ${cvtype}** dst, const char *name = "no_name") |
|
{ |
|
${allownull} |
|
if (PyType_IsSubtype(o->ob_type, &${ourname}_Type)) { |
|
*dst = ((${ourname}_t*)o)->v; |
|
return 1; |
|
} else { |
|
(*dst) = (${cvtype}*)NULL; |
|
return failmsg("Expected ${cvtype} for argument '%s'", name); |
|
} |
|
} |
|
|
|
""") |
|
|
|
getset_func_template = Template(""" |
|
static PyObject *${ourname}_get_${member}(${ourname}_t *p, void *closure) |
|
{ |
|
return ${rconverter}(p->v${accessor}${member}); |
|
} |
|
|
|
static int ${ourname}_set_${member}(${ourname}_t *p, PyObject *value, void *closure) |
|
{ |
|
if (value == NULL) { |
|
PyErr_SetString(PyExc_TypeError, "Cannot delete the ${member} attribute"); |
|
return -1; |
|
} |
|
|
|
if (! ${checker}(value)) { |
|
PyErr_SetString(PyExc_TypeError, "The ${member} attribute value must be a ${typename}"); |
|
return -1; |
|
} |
|
|
|
p->v${accessor}${member} = ${converter}(value); |
|
return 0; |
|
} |
|
|
|
""") |
|
|
|
getset_init_template = Template(""" |
|
{(char*)"${member}", (getter)${ourname}_get_${member}, (setter)${ourname}_set_${member}, (char*)"${member}", NULL}, |
|
""") |
|
|
|
objects = [ |
|
( 'IplConvKernel', ['allownull'], { |
|
"nCols" : 'i', |
|
"nRows" : 'i', |
|
"anchorX" : 'i', |
|
"anchorY" : 'i', |
|
}), |
|
( 'CvCapture', [], {}), |
|
( 'CvHaarClassifierCascade', [], {}), |
|
( 'CvPOSITObject', [], {}), |
|
( 'CvVideoWriter', [], {}), |
|
( 'CvStereoBMState', [], { |
|
"preFilterType" : 'i', |
|
"preFilterSize" : 'i', |
|
"preFilterCap" : 'i', |
|
"SADWindowSize" : 'i', |
|
"minDisparity" : 'i', |
|
"numberOfDisparities" : 'i', |
|
"textureThreshold" : 'i', |
|
"uniquenessRatio" : 'i', |
|
"speckleWindowSize" : 'i', |
|
"speckleRange" : 'i', |
|
}), |
|
( 'CvStereoGCState', [], { |
|
"Ithreshold" : 'i', |
|
"interactionRadius" : 'i', |
|
"K" : 'f', |
|
"lambda" : 'f', |
|
"lambda1" : 'f', |
|
"lambda2" : 'f', |
|
"occlusionCost" : 'i', |
|
"minDisparity" : 'i', |
|
"numberOfDisparities" : 'i', |
|
"maxIters" : 'i', |
|
}), |
|
( 'CvKalman', [], { |
|
"MP" : 'i', |
|
"DP" : 'i', |
|
"CP" : 'i', |
|
"state_pre" : 'mr', |
|
"state_post" : 'mr', |
|
"transition_matrix" : 'mr', |
|
"control_matrix" : 'mr', |
|
"measurement_matrix" : 'mr', |
|
"control_matrix" : 'mr', |
|
"process_noise_cov" : 'mr', |
|
"measurement_noise_cov" : 'mr', |
|
"error_cov_pre" : 'mr', |
|
"gain" : 'mr', |
|
"error_cov_post" : 'mr', |
|
}), |
|
( 'CvMoments', ['copy'], { |
|
"m00" : 'f', |
|
"m10" : 'f', |
|
"m01" : 'f', |
|
"m20" : 'f', |
|
"m11" : 'f', |
|
"m02" : 'f', |
|
"m30" : 'f', |
|
"m21" : 'f', |
|
"m12" : 'f', |
|
"m03" : 'f', |
|
"mu20" : 'f', |
|
"mu11" : 'f', |
|
"mu02" : 'f', |
|
"mu30" : 'f', |
|
"mu21" : 'f', |
|
"mu12" : 'f', |
|
"mu03" : 'f', |
|
"inv_sqrt_m00" : 'f', |
|
}), |
|
] |
|
|
|
checkers = { |
|
'i' : 'PyNumber_Check', |
|
'f' : 'PyNumber_Check', |
|
'm' : 'is_cvmat', |
|
'mr' : 'is_cvmat' |
|
} |
|
# Python -> C |
|
converters = { |
|
'i' : 'PyInt_AsLong', |
|
'f' : 'PyFloat_AsDouble', |
|
'm' : 'PyCvMat_AsCvMat', |
|
'mr' : 'PyCvMat_AsCvMat' |
|
} |
|
# C -> Python |
|
rconverters = { |
|
'i' : 'PyInt_FromLong', |
|
'f' : 'PyFloat_FromDouble', |
|
'm' : 'FROM_CvMat', |
|
'mr' : 'FROM_ROCvMatPTR' |
|
} |
|
# Human-readable type names |
|
typenames = { |
|
'i' : 'integer', |
|
'f' : 'float', |
|
'm' : 'list of CvMat', |
|
'mr' : 'list of CvMat', |
|
} |
|
|
|
for (t, flags, members) in objects: |
|
map = {'cvtype' : t, |
|
'ourname' : t.replace('Cv', '')} |
|
# gsf is all the generated code for the member accessors |
|
if 'copy' in flags: |
|
a = '.' |
|
else: |
|
a = '->' |
|
gsf = "".join([getset_func_template.substitute(map, accessor = a, member = m, checker = checkers[t], converter = converters[t], rconverter = rconverters[t], typename = typenames[t]) for (m, t) in members.items()]) |
|
# gsi is the generated code for the initializer for each accessor |
|
gsi = "".join([getset_init_template.substitute(map, member = m) for (m, t) in members.items()]) |
|
# s is the template that pulls everything together |
|
if 'allownull' in flags: |
|
nullcode = """if (o == Py_None) { *dst = (%s*)NULL; return 1; }""" % map['cvtype'] |
|
else: |
|
nullcode = "" |
|
if 'copy' in flags: |
|
print >>gen_c[3], gensimple.substitute(map, getset_funcs = gsf, getset_inits = gsi, allownull = nullcode) |
|
else: |
|
print >>gen_c[3], genptr.substitute(map, getset_funcs = gsf, getset_inits = gsi, allownull = nullcode) |
|
print >>gen_c[4], "MKTYPE(%s);" % map['ourname'] |
|
|
|
for f in gen_c: |
|
f.close()
|
|
|