Cython has moved to github.

cython

view Cython/Compiler/Main.py @ 1149:7b8970eb4837

Option to emit #line directives, ticket #53
author Robert Bradshaw <robertwb@math.washington.edu>
date Sat Sep 13 12:48:45 2008 -0700 (3 years ago)
parents b1516f6bf662
children 075511424c24
line source
1 #
2 # Cython Top Level
3 #
5 import os, sys, re, codecs
6 if sys.version_info[:2] < (2, 3):
7 sys.stderr.write("Sorry, Cython requires Python 2.3 or later\n")
8 sys.exit(1)
10 try:
11 set
12 except NameError:
13 # Python 2.3
14 from sets import Set as set
16 from time import time
17 import Code
18 import Errors
19 import Parsing
20 import Version
21 from Scanning import PyrexScanner, FileSourceDescriptor
22 from Errors import PyrexError, CompileError, InternalError, error
23 from Symtab import BuiltinScope, ModuleScope
24 from Cython import Utils
25 from Cython.Utils import open_new_file, replace_suffix
26 import CythonScope
28 module_name_pattern = re.compile(r"[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$")
30 verbose = 0
32 def dumptree(t):
33 # For quick debugging in pipelines
34 print t.dump()
35 return t
37 class CompilationData:
38 # Bundles the information that is passed from transform to transform.
39 # (For now, this is only)
41 # While Context contains every pxd ever loaded, path information etc.,
42 # this only contains the data related to a single compilation pass
43 #
44 # pyx ModuleNode Main code tree of this compilation.
45 # pxds {string : ModuleNode} Trees for the pxds used in the pyx.
46 # codewriter CCodeWriter Where to output final code.
47 # options CompilationOptions
48 # result CompilationResult
49 pass
51 class Context:
52 # This class encapsulates the context needed for compiling
53 # one or more Cython implementation files along with their
54 # associated and imported declaration files. It includes
55 # the root of the module import namespace and the list
56 # of directories to search for include files.
57 #
58 # modules {string : ModuleScope}
59 # include_directories [string]
60 # future_directives [object]
62 def __init__(self, include_directories, pragma_overrides):
63 #self.modules = {"__builtin__" : BuiltinScope()}
64 import Builtin, CythonScope
65 self.modules = {"__builtin__" : Builtin.builtin_scope}
66 self.modules["cython"] = CythonScope.create_cython_scope(self)
67 self.include_directories = include_directories
68 self.future_directives = set()
69 self.pragma_overrides = pragma_overrides
71 self.pxds = {} # full name -> node tree
73 standard_include_path = os.path.abspath(
74 os.path.join(os.path.dirname(__file__), '..', 'Includes'))
75 self.include_directories = include_directories + [standard_include_path]
77 def create_pipeline(self, pxd):
78 from Visitor import PrintTree
79 from ParseTreeTransforms import WithTransform, NormalizeTree, PostParse, PxdPostParse
80 from ParseTreeTransforms import AnalyseDeclarationsTransform, AnalyseExpressionsTransform
81 from ParseTreeTransforms import CreateClosureClasses, MarkClosureVisitor, DecoratorTransform
82 from ParseTreeTransforms import ResolveOptions
83 from Optimize import FlattenInListTransform, SwitchTransform, OptimizeRefcounting
84 from Buffer import IntroduceBufferAuxiliaryVars
85 from ModuleNode import check_c_classes
87 if pxd:
88 _check_c_classes = None
89 _specific_post_parse = PxdPostParse(self)
90 else:
91 _check_c_classes = check_c_classes
92 _specific_post_parse = None
94 return [
95 NormalizeTree(self),
96 PostParse(self),
97 _specific_post_parse,
98 ResolveOptions(self, self.pragma_overrides),
99 FlattenInListTransform(),
100 WithTransform(self),
101 DecoratorTransform(self),
102 AnalyseDeclarationsTransform(self),
103 IntroduceBufferAuxiliaryVars(self),
104 _check_c_classes,
105 AnalyseExpressionsTransform(self),
106 SwitchTransform(),
107 OptimizeRefcounting(self),
108 # SpecialFunctions(self),
109 # CreateClosureClasses(context),
110 ]
112 def create_pyx_pipeline(self, options, result):
113 def generate_pyx_code(module_node):
114 module_node.process_implementation(options, result)
115 result.compilation_source = module_node.compilation_source
116 return result
118 def inject_pxd_code(module_node):
119 from textwrap import dedent
120 stats = module_node.body.stats
121 for name, (statlistnode, scope) in self.pxds.iteritems():
122 # Copy over function nodes to the module
123 # (this seems strange -- I believe the right concept is to split
124 # ModuleNode into a ModuleNode and a CodeGenerator, and tell that
125 # CodeGenerator to generate code both from the pyx and pxd ModuleNodes.
126 stats.append(statlistnode)
127 # Until utility code is moved to code generation phase everywhere,
128 # we need to copy it over to the main scope
129 module_node.scope.utility_code_list.extend(scope.utility_code_list)
130 return module_node
132 return ([
133 create_parse(self),
134 ] + self.create_pipeline(pxd=False) + [
135 inject_pxd_code,
136 generate_pyx_code,
137 ])
139 def create_pxd_pipeline(self, scope, module_name):
140 def parse_pxd(source_desc):
141 tree = self.parse(source_desc, scope, pxd=True,
142 full_module_name=module_name)
143 tree.scope = scope
144 tree.is_pxd = True
145 return tree
147 from CodeGeneration import ExtractPxdCode
149 # The pxd pipeline ends up with a CCodeWriter containing the
150 # code of the pxd, as well as a pxd scope.
151 return [parse_pxd] + self.create_pipeline(pxd=True) + [
152 ExtractPxdCode(self),
153 ]
155 def process_pxd(self, source_desc, scope, module_name):
156 pipeline = self.create_pxd_pipeline(scope, module_name)
157 result = self.run_pipeline(pipeline, source_desc)
158 return result
160 def nonfatal_error(self, exc):
161 return Errors.report_error(exc)
163 def run_pipeline(self, pipeline, source):
164 err = None
165 data = source
166 try:
167 for phase in pipeline:
168 if phase is not None:
169 data = phase(data)
170 except CompileError, err:
171 # err is set
172 Errors.report_error(err)
173 except InternalError, err:
174 # Only raise if there was not an earlier error
175 if Errors.num_errors == 0:
176 raise
177 return (err, data)
179 def find_module(self, module_name,
180 relative_to = None, pos = None, need_pxd = 1):
181 # Finds and returns the module scope corresponding to
182 # the given relative or absolute module name. If this
183 # is the first time the module has been requested, finds
184 # the corresponding .pxd file and process it.
185 # If relative_to is not None, it must be a module scope,
186 # and the module will first be searched for relative to
187 # that module, provided its name is not a dotted name.
188 debug_find_module = 0
189 if debug_find_module:
190 print("Context.find_module: module_name = %s, relative_to = %s, pos = %s, need_pxd = %s" % (
191 module_name, relative_to, pos, need_pxd))
193 scope = None
194 pxd_pathname = None
195 if not module_name_pattern.match(module_name):
196 if pos is None:
197 pos = (module_name, 0, 0)
198 raise CompileError(pos,
199 "'%s' is not a valid module name" % module_name)
200 if "." not in module_name and relative_to:
201 if debug_find_module:
202 print("...trying relative import")
203 scope = relative_to.lookup_submodule(module_name)
204 if not scope:
205 qualified_name = relative_to.qualify_name(module_name)
206 pxd_pathname = self.find_pxd_file(qualified_name, pos)
207 if pxd_pathname:
208 scope = relative_to.find_submodule(module_name)
209 if not scope:
210 if debug_find_module:
211 print("...trying absolute import")
212 scope = self
213 for name in module_name.split("."):
214 scope = scope.find_submodule(name)
215 if debug_find_module:
216 print("...scope =", scope)
217 if not scope.pxd_file_loaded:
218 if debug_find_module:
219 print("...pxd not loaded")
220 scope.pxd_file_loaded = 1
221 if not pxd_pathname:
222 if debug_find_module:
223 print("...looking for pxd file")
224 pxd_pathname = self.find_pxd_file(module_name, pos)
225 if debug_find_module:
226 print("......found ", pxd_pathname)
227 if not pxd_pathname and need_pxd:
228 error(pos, "'%s.pxd' not found" % module_name)
229 if pxd_pathname:
230 try:
231 if debug_find_module:
232 print("Context.find_module: Parsing %s" % pxd_pathname)
233 source_desc = FileSourceDescriptor(pxd_pathname)
234 err, result = self.process_pxd(source_desc, scope, module_name)
235 if err:
236 raise err
237 (pxd_codenodes, pxd_scope) = result
238 self.pxds[module_name] = (pxd_codenodes, pxd_scope)
239 except CompileError:
240 pass
241 return scope
243 def find_pxd_file(self, qualified_name, pos):
244 # Search include path for the .pxd file corresponding to the
245 # given fully-qualified module name.
246 # Will find either a dotted filename or a file in a
247 # package directory. If a source file position is given,
248 # the directory containing the source file is searched first
249 # for a dotted filename, and its containing package root
250 # directory is searched first for a non-dotted filename.
251 return self.search_include_directories(qualified_name, ".pxd", pos)
253 def find_pyx_file(self, qualified_name, pos):
254 # Search include path for the .pyx file corresponding to the
255 # given fully-qualified module name, as for find_pxd_file().
256 return self.search_include_directories(qualified_name, ".pyx", pos)
258 def find_include_file(self, filename, pos):
259 # Search list of include directories for filename.
260 # Reports an error and returns None if not found.
261 path = self.search_include_directories(filename, "", pos,
262 include=True)
263 if not path:
264 error(pos, "'%s' not found" % filename)
265 return path
267 def search_include_directories(self, qualified_name, suffix, pos,
268 include=False):
269 # Search the list of include directories for the given
270 # file name. If a source file position is given, first
271 # searches the directory containing that file. Returns
272 # None if not found, but does not report an error.
273 # The 'include' option will disable package dereferencing.
274 dirs = self.include_directories
275 if pos:
276 file_desc = pos[0]
277 if not isinstance(file_desc, FileSourceDescriptor):
278 raise RuntimeError("Only file sources for code supported")
279 if include:
280 dirs = [os.path.dirname(file_desc.filename)] + dirs
281 else:
282 dirs = [self.find_root_package_dir(file_desc.filename)] + dirs
284 dotted_filename = qualified_name + suffix
285 if not include:
286 names = qualified_name.split('.')
287 package_names = names[:-1]
288 module_name = names[-1]
289 module_filename = module_name + suffix
290 package_filename = "__init__" + suffix
292 for dir in dirs:
293 path = os.path.join(dir, dotted_filename)
294 if os.path.exists(path):
295 return path
296 if not include:
297 package_dir = self.check_package_dir(dir, package_names)
298 if package_dir is not None:
299 path = os.path.join(package_dir, module_filename)
300 if os.path.exists(path):
301 return path
302 path = os.path.join(dir, package_dir, module_name,
303 package_filename)
304 if os.path.exists(path):
305 return path
306 return None
308 def find_root_package_dir(self, file_path):
309 dir = os.path.dirname(file_path)
310 while self.is_package_dir(dir):
311 parent = os.path.dirname(dir)
312 if parent == dir:
313 break
314 dir = parent
315 return dir
317 def is_package_dir(self, dir):
318 package_init = os.path.join(dir, "__init__.py")
319 return os.path.exists(package_init) or \
320 os.path.exists(package_init + "x") # same with .pyx
322 def check_package_dir(self, dir, package_names):
323 package_dir = os.path.join(dir, *package_names)
324 if not os.path.exists(package_dir):
325 return None
326 for dirname in package_names:
327 dir = os.path.join(dir, dirname)
328 if not self.is_package_dir(dir):
329 return None
330 return package_dir
332 def c_file_out_of_date(self, source_path):
333 c_path = Utils.replace_suffix(source_path, ".c")
334 if not os.path.exists(c_path):
335 return 1
336 c_time = Utils.modification_time(c_path)
337 if Utils.file_newer_than(source_path, c_time):
338 return 1
339 pos = [source_path]
340 pxd_path = Utils.replace_suffix(source_path, ".pxd")
341 if os.path.exists(pxd_path) and Utils.file_newer_than(pxd_path, c_time):
342 return 1
343 for kind, name in self.read_dependency_file(source_path):
344 if kind == "cimport":
345 dep_path = self.find_pxd_file(name, pos)
346 elif kind == "include":
347 dep_path = self.search_include_directories(name, pos)
348 else:
349 continue
350 if dep_path and Utils.file_newer_than(dep_path, c_time):
351 return 1
352 return 0
354 def find_cimported_module_names(self, source_path):
355 return [ name for kind, name in self.read_dependency_file(source_path)
356 if kind == "cimport" ]
358 def is_package_dir(self, dir_path):
359 # Return true if the given directory is a package directory.
360 for filename in ("__init__.py", "__init__.pyx"):
361 path = os.path.join(dir_path, filename)
362 if os.path.exists(path):
363 return 1
365 def read_dependency_file(self, source_path):
366 dep_path = Utils.replace_suffix(source_path, ".dep")
367 if os.path.exists(dep_path):
368 f = open(dep_path, "rU")
369 chunks = [ line.strip().split(" ", 1)
370 for line in f.readlines()
371 if " " in line.strip() ]
372 f.close()
373 return chunks
374 else:
375 return ()
377 def lookup_submodule(self, name):
378 # Look up a top-level module. Returns None if not found.
379 return self.modules.get(name, None)
381 def find_submodule(self, name):
382 # Find a top-level module, creating a new one if needed.
383 scope = self.lookup_submodule(name)
384 if not scope:
385 scope = ModuleScope(name,
386 parent_module = None, context = self)
387 self.modules[name] = scope
388 return scope
390 def parse(self, source_desc, scope, pxd, full_module_name):
391 if not isinstance(source_desc, FileSourceDescriptor):
392 raise RuntimeError("Only file sources for code supported")
393 source_filename = Utils.encode_filename(source_desc.filename)
394 # Parse the given source file and return a parse tree.
395 try:
396 f = Utils.open_source_file(source_filename, "rU")
397 try:
398 s = PyrexScanner(f, source_desc, source_encoding = f.encoding,
399 scope = scope, context = self)
400 tree = Parsing.p_module(s, pxd, full_module_name)
401 finally:
402 f.close()
403 except UnicodeDecodeError, msg:
404 #import traceback
405 #traceback.print_exc()
406 error((source_desc, 0, 0), "Decoding error, missing or incorrect coding=<encoding-name> at top of source (%s)" % msg)
407 if Errors.num_errors > 0:
408 raise CompileError
409 return tree
411 def extract_module_name(self, path, options):
412 # Find fully_qualified module name from the full pathname
413 # of a source file.
414 dir, filename = os.path.split(path)
415 module_name, _ = os.path.splitext(filename)
416 if "." in module_name:
417 return module_name
418 if module_name == "__init__":
419 dir, module_name = os.path.split(dir)
420 names = [module_name]
421 while self.is_package_dir(dir):
422 parent, package_name = os.path.split(dir)
423 if parent == dir:
424 break
425 names.append(package_name)
426 dir = parent
427 names.reverse()
428 return ".".join(names)
430 def setup_errors(self, options):
431 if options.use_listing_file:
432 result.listing_file = Utils.replace_suffix(source, ".lis")
433 Errors.open_listing_file(result.listing_file,
434 echo_to_stderr = options.errors_to_stderr)
435 else:
436 Errors.open_listing_file(None)
438 def teardown_errors(self, err, options, result):
439 source_desc = result.compilation_source.source_desc
440 if not isinstance(source_desc, FileSourceDescriptor):
441 raise RuntimeError("Only file sources for code supported")
442 Errors.close_listing_file()
443 result.num_errors = Errors.num_errors
444 if result.num_errors > 0:
445 err = True
446 if err and result.c_file:
447 try:
448 Utils.castrate_file(result.c_file, os.stat(source_desc.filename))
449 except EnvironmentError:
450 pass
451 result.c_file = None
452 if result.c_file and not options.c_only and c_compile:
453 result.object_file = c_compile(result.c_file,
454 verbose_flag = options.show_version,
455 cplus = options.cplus)
456 if not options.obj_only and c_link:
457 result.extension_file = c_link(result.object_file,
458 extra_objects = options.objects,
459 verbose_flag = options.show_version,
460 cplus = options.cplus)
462 def create_parse(context):
463 def parse(compsrc):
464 source_desc = compsrc.source_desc
465 full_module_name = compsrc.full_module_name
466 initial_pos = (source_desc, 1, 0)
467 scope = context.find_module(full_module_name, pos = initial_pos, need_pxd = 0)
468 tree = context.parse(source_desc, scope, pxd = 0, full_module_name = full_module_name)
469 tree.compilation_source = compsrc
470 tree.scope = scope
471 tree.is_pxd = False
472 return tree
473 return parse
475 def create_default_resultobj(compilation_source, options):
476 result = CompilationResult()
477 result.main_source_file = compilation_source.source_desc.filename
478 result.compilation_source = compilation_source
479 source_desc = compilation_source.source_desc
480 if options.output_file:
481 result.c_file = os.path.join(compilation_source.cwd, options.output_file)
482 else:
483 if options.cplus:
484 c_suffix = ".cpp"
485 else:
486 c_suffix = ".c"
487 result.c_file = Utils.replace_suffix(source_desc.filename, c_suffix)
488 return result
490 def run_pipeline(source, options, full_module_name = None):
491 # Set up context
492 context = Context(options.include_path, options.pragma_overrides)
494 # Set up source object
495 cwd = os.getcwd()
496 source_desc = FileSourceDescriptor(os.path.join(cwd, source))
497 full_module_name = full_module_name or context.extract_module_name(source, options)
498 source = CompilationSource(source_desc, full_module_name, cwd)
500 # Set up result object
501 result = create_default_resultobj(source, options)
503 # Get pipeline
504 pipeline = context.create_pyx_pipeline(options, result)
506 context.setup_errors(options)
507 err, enddata = context.run_pipeline(pipeline, source)
508 context.teardown_errors(err, options, result)
509 return result
511 #------------------------------------------------------------------------
512 #
513 # Main Python entry points
514 #
515 #------------------------------------------------------------------------
517 class CompilationSource(object):
518 """
519 Contains the data necesarry to start up a compilation pipeline for
520 a single compilation unit.
521 """
522 def __init__(self, source_desc, full_module_name, cwd):
523 self.source_desc = source_desc
524 self.full_module_name = full_module_name
525 self.cwd = cwd
527 class CompilationOptions:
528 """
529 Options to the Cython compiler:
531 show_version boolean Display version number
532 use_listing_file boolean Generate a .lis file
533 errors_to_stderr boolean Echo errors to stderr when using .lis
534 include_path [string] Directories to search for include files
535 output_file string Name of generated .c file
536 generate_pxi boolean Generate .pxi file for public declarations
537 recursive boolean Recursively find and compile dependencies
538 timestamps boolean Only compile changed source files. If None,
539 defaults to true when recursive is true.
540 verbose boolean Always print source names being compiled
541 quiet boolean Don't print source names in recursive mode
542 pragma_overrides dict Overrides for pragma options (see Options.py)
544 Following options are experimental and only used on MacOSX:
546 c_only boolean Stop after generating C file (default)
547 obj_only boolean Stop after compiling to .o file
548 objects [string] Extra .o files to link with
549 cplus boolean Compile as c++ code
550 """
552 def __init__(self, defaults = None, c_compile = 0, c_link = 0, **kw):
553 self.include_path = []
554 self.objects = []
555 if defaults:
556 if isinstance(defaults, CompilationOptions):
557 defaults = defaults.__dict__
558 else:
559 defaults = default_options
560 self.__dict__.update(defaults)
561 self.__dict__.update(kw)
562 if c_compile:
563 self.c_only = 0
564 if c_link:
565 self.obj_only = 0
568 class CompilationResult:
569 """
570 Results from the Cython compiler:
572 c_file string or None The generated C source file
573 h_file string or None The generated C header file
574 i_file string or None The generated .pxi file
575 api_file string or None The generated C API .h file
576 listing_file string or None File of error messages
577 object_file string or None Result of compiling the C file
578 extension_file string or None Result of linking the object file
579 num_errors integer Number of compilation errors
580 compilation_source CompilationSource
581 """
583 def __init__(self):
584 self.c_file = None
585 self.h_file = None
586 self.i_file = None
587 self.api_file = None
588 self.listing_file = None
589 self.object_file = None
590 self.extension_file = None
591 self.main_source_file = None
594 class CompilationResultSet(dict):
595 """
596 Results from compiling multiple Pyrex source files. A mapping
597 from source file paths to CompilationResult instances. Also
598 has the following attributes:
600 num_errors integer Total number of compilation errors
601 """
603 num_errors = 0
605 def add(self, source, result):
606 self[source] = result
607 self.num_errors += result.num_errors
610 def compile_single(source, options, full_module_name = None):
611 """
612 compile_single(source, options, full_module_name)
614 Compile the given Pyrex implementation file and return a CompilationResult.
615 Always compiles a single file; does not perform timestamp checking or
616 recursion.
617 """
618 return run_pipeline(source, options, full_module_name)
621 def compile_multiple(sources, options):
622 """
623 compile_multiple(sources, options)
625 Compiles the given sequence of Pyrex implementation files and returns
626 a CompilationResultSet. Performs timestamp checking and/or recursion
627 if these are specified in the options.
628 """
629 sources = [os.path.abspath(source) for source in sources]
630 processed = set()
631 results = CompilationResultSet()
632 recursive = options.recursive
633 timestamps = options.timestamps
634 if timestamps is None:
635 timestamps = recursive
636 verbose = options.verbose or ((recursive or timestamps) and not options.quiet)
637 for source in sources:
638 if source not in processed:
639 # Compiling multiple sources in one context doesn't quite
640 # work properly yet.
641 if not timestamps or context.c_file_out_of_date(source):
642 if verbose:
643 sys.stderr.write("Compiling %s\n" % source)
645 result = run_pipeline(source, options)
646 results.add(source, result)
647 processed.add(source)
648 if recursive:
649 for module_name in context.find_cimported_module_names(source):
650 path = context.find_pyx_file(module_name, [source])
651 if path:
652 sources.append(path)
653 else:
654 sys.stderr.write(
655 "Cannot find .pyx file for cimported module '%s'\n" % module_name)
656 return results
658 def compile(source, options = None, c_compile = 0, c_link = 0,
659 full_module_name = None, **kwds):
660 """
661 compile(source [, options], [, <option> = <value>]...)
663 Compile one or more Pyrex implementation files, with optional timestamp
664 checking and recursing on dependecies. The source argument may be a string
665 or a sequence of strings If it is a string and no recursion or timestamp
666 checking is requested, a CompilationResult is returned, otherwise a
667 CompilationResultSet is returned.
668 """
669 options = CompilationOptions(defaults = options, c_compile = c_compile,
670 c_link = c_link, **kwds)
671 if isinstance(source, basestring) and not options.timestamps \
672 and not options.recursive:
673 return compile_single(source, options, full_module_name)
674 else:
675 return compile_multiple(source, options)
677 #------------------------------------------------------------------------
678 #
679 # Main command-line entry point
680 #
681 #------------------------------------------------------------------------
683 def main(command_line = 0):
684 args = sys.argv[1:]
685 any_failures = 0
686 if command_line:
687 from CmdLine import parse_command_line
688 options, sources = parse_command_line(args)
689 else:
690 options = CompilationOptions(default_options)
691 sources = args
693 if options.show_version:
694 sys.stderr.write("Cython version %s\n" % Version.version)
695 if options.working_path!="":
696 os.chdir(options.working_path)
697 try:
698 result = compile(sources, options)
699 if result.num_errors > 0:
700 any_failures = 1
701 except (EnvironmentError, PyrexError), e:
702 sys.stderr.write(str(e) + '\n')
703 any_failures = 1
704 if any_failures:
705 sys.exit(1)
709 #------------------------------------------------------------------------
710 #
711 # Set the default options depending on the platform
712 #
713 #------------------------------------------------------------------------
715 default_options = dict(
716 show_version = 0,
717 use_listing_file = 0,
718 errors_to_stderr = 1,
719 c_only = 1,
720 obj_only = 1,
721 cplus = 0,
722 output_file = None,
723 annotate = False,
724 generate_pxi = 0,
725 working_path = "",
726 recursive = 0,
727 timestamps = None,
728 verbose = 0,
729 quiet = 0,
730 pragma_overrides = {},
731 emit_linenums = False,
732 )
733 if sys.platform == "mac":
734 from Cython.Mac.MacSystem import c_compile, c_link, CCompilerError
735 default_options['use_listing_file'] = 1
736 elif sys.platform == "darwin":
737 from Cython.Mac.DarwinSystem import c_compile, c_link, CCompilerError
738 else:
739 c_compile = None
740 c_link = None