girparser: Guess virtual method wrapper variant when possible
[vala-lang.git] / vala / valacodecontext.vala
blobcfb0a9bbc23b5b64044c22121c2ad840c52b08ae
1 /* valacodecontext.vala
3 * Copyright (C) 2006-2009 Jürg Billeter
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 * Author:
20 * Jürg Billeter <j@bitron.ch>
23 using GLib;
25 /**
26 * The root of the code tree.
28 public class Vala.CodeContext {
29 /**
30 * Enable run-time checks for programming errors.
32 public bool assert { get; set; }
34 /**
35 * Enable additional run-time checks such as type checks.
37 public bool checking { get; set; }
39 /**
40 * Do not warn when using deprecated features.
42 public bool deprecated { get; set; }
44 /**
45 * Do not warn when using experimental features.
47 public bool experimental { get; set; }
49 /**
50 * Enable experimental enhancements for non-null types.
52 public bool experimental_non_null { get; set; }
54 /**
55 * Enable transformation of D-Bus member names in dynamic client support.
57 public bool dbus_transformation { get; set; }
59 /**
60 * Output C code, don't compile to object code.
62 public bool ccode_only { get; set; }
64 /**
65 * Output C header file.
67 public string? header_filename { get; set; }
69 /**
70 * Output internal C header file.
72 public string? internal_header_filename { get; set; }
74 public bool use_header { get; set; }
76 /**
77 * Base directory used for header_filename in the VAPIs.
79 public string? includedir { get; set; }
81 /**
82 * Output symbols file.
84 public string? symbols_filename { get; set; }
86 /**
87 * Compile but do not link.
89 public bool compile_only { get; set; }
91 /**
92 * Output filename.
94 public string output { get; set; }
96 /**
97 * Base source directory.
99 public string basedir { get; set; }
102 * Code output directory.
104 public string directory { get; set; }
107 * List of directories where to find .vapi files.
109 public string[] vapi_directories;
112 * List of directories where to find .gir files.
114 public string[] gir_directories;
117 * Produce debug information.
119 public bool debug { get; set; }
122 * Optimization level.
124 public int optlevel { get; set; }
127 * Enable multithreading support.
129 public bool thread { get; set; }
132 * Enable memory profiler.
134 public bool mem_profiler { get; set; }
137 * Specifies the optional module initialization method.
139 public Method module_init_method { get; set; }
142 * Keep temporary files produced by the compiler.
144 public bool save_temps { get; set; }
146 public Profile profile { get; set; }
149 * Target major version number of glib for code generation.
151 public int target_glib_major { get; set; }
154 * Target minor version number of glib for code generation.
156 public int target_glib_minor { get; set; }
158 public bool verbose_mode { get; set; }
160 public bool version_header { get; set; }
162 public bool nostdpkg { get; set; }
165 * Returns true if the target version of glib is greater than or
166 * equal to the specified version.
168 public bool require_glib_version (int major, int minor) {
169 return (target_glib_major > major) || (target_glib_major == major && target_glib_minor >= minor);
172 public bool save_csources {
173 get { return save_temps; }
176 public Report report { get; set; default = new Report ();}
178 public Method? entry_point { get; set; }
180 public string entry_point_name { get; set; }
182 public bool run_output { get; set; }
184 private List<SourceFile> source_files = new ArrayList<SourceFile> ();
185 private List<string> c_source_files = new ArrayList<string> ();
186 private Namespace _root = new Namespace (null);
188 private List<string> packages = new ArrayList<string> (str_equal);
190 private Set<string> defines = new HashSet<string> (str_hash, str_equal);
192 static StaticPrivate context_stack_key = StaticPrivate ();
195 * The root namespace of the symbol tree.
197 * @return root namespace
199 public Namespace root {
200 get { return _root; }
203 public SymbolResolver resolver { get; private set; }
205 public SemanticAnalyzer analyzer { get; private set; }
207 public FlowAnalyzer flow_analyzer { get; private set; }
210 * The selected code generator.
212 public CodeGenerator codegen { get; set; }
214 public CodeContext () {
215 resolver = new SymbolResolver ();
216 analyzer = new SemanticAnalyzer ();
217 flow_analyzer = new FlowAnalyzer ();
221 * Return the topmost context from the context stack.
223 public static CodeContext get () {
224 List<CodeContext>* context_stack = context_stack_key.get ();
226 return context_stack->get (context_stack->size - 1);
230 * Push the specified context to the context stack.
232 public static void push (CodeContext context) {
233 ArrayList<CodeContext>* context_stack = context_stack_key.get ();
234 if (context_stack == null) {
235 context_stack = new ArrayList<CodeContext> ();
236 context_stack_key.set (context_stack, null);
239 context_stack->add (context);
243 * Remove the topmost context from the context stack.
245 public static void pop () {
246 List<CodeContext>* context_stack = context_stack_key.get ();
248 context_stack->remove_at (context_stack->size - 1);
252 * Returns a copy of the list of source files.
254 * @return list of source files
256 public List<SourceFile> get_source_files () {
257 return source_files;
261 * Returns a copy of the list of C source files.
263 * @return list of C source files
265 public List<string> get_c_source_files () {
266 return c_source_files;
270 * Adds the specified file to the list of source files.
272 * @param file a source file
274 public void add_source_file (SourceFile file) {
275 source_files.add (file);
279 * Adds the specified file to the list of C source files.
281 * @param file a C source file
283 public void add_c_source_file (string file) {
284 c_source_files.add (file);
288 * Returns a copy of the list of used packages.
290 * @return list of used packages
292 public List<string> get_packages () {
293 return packages;
297 * Returns whether the specified package is being used.
299 * @param pkg a package name
300 * @return true if the specified package is being used
302 public bool has_package (string pkg) {
303 return packages.contains (pkg);
307 * Adds the specified package to the list of used packages.
309 * @param pkg a package name
311 public void add_package (string pkg) {
312 packages.add (pkg);
316 * Pull the specified package into the context.
317 * The method is tolerant if the package has been already loaded.
319 * @param pkg a package name
320 * @return false if the package could not be loaded
323 public bool add_external_package (string pkg) {
324 if (has_package (pkg)) {
325 // ignore multiple occurences of the same package
326 return true;
329 // first try .vapi
330 var path = get_vapi_path (pkg);
331 if (path == null) {
332 // try with .gir
333 path = get_gir_path (pkg);
335 if (path == null) {
336 Report.error (null, "Package `%s' not found in specified Vala API directories or GObject-Introspection GIR directories".printf (pkg));
337 return false;
340 add_package (pkg);
342 add_source_file (new SourceFile (this, SourceFileType.PACKAGE, path));
344 var deps_filename = Path.build_filename (Path.get_dirname (path), "%s.deps".printf (pkg));
345 if (!add_packages_from_file (deps_filename)) {
346 return false;
349 return true;
353 * Read the given filename and pull in packages.
354 * The method is tolerant if the file does not exist.
356 * @param filename a filanem
357 * @return false if an error occurs while reading the file or if a package could not be added
359 public bool add_packages_from_file (string filename) {
360 if (!FileUtils.test (filename, FileTest.EXISTS)) {
361 return true;
364 try {
365 string contents;
366 FileUtils.get_contents (filename, out contents);
367 foreach (string package in contents.split ("\n")) {
368 package = package.strip ();
369 if (package != "") {
370 add_external_package (package);
373 } catch (FileError e) {
374 Report.error (null, "Unable to read dependency file: %s".printf (e.message));
375 return false;
378 return true;
382 * Add the specified source file to the context. Only .vala, .vapi, .gs,
383 * and .c extensions are supported.
385 * @param filename a filename
386 * @param is_source true to force adding the file as .vala or .gs
387 * @return false if the file is not recognized or the file does not exist
389 public bool add_source_filename (string filename, bool is_source = false) {
390 if (!FileUtils.test (filename, FileTest.EXISTS)) {
391 Report.error (null, "%s not found".printf (filename));
392 return false;
395 var rpath = realpath (filename);
396 if (is_source || filename.has_suffix (".vala") || filename.has_suffix (".gs")) {
397 var source_file = new SourceFile (this, SourceFileType.SOURCE, rpath);
398 source_file.relative_filename = filename;
400 if (profile == Profile.POSIX) {
401 // import the Posix namespace by default (namespace of backend-specific standard library)
402 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "Posix", null));
403 source_file.add_using_directive (ns_ref);
404 root.add_using_directive (ns_ref);
405 } else if (profile == Profile.GOBJECT) {
406 // import the GLib namespace by default (namespace of backend-specific standard library)
407 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "GLib", null));
408 source_file.add_using_directive (ns_ref);
409 root.add_using_directive (ns_ref);
410 } else if (profile == Profile.DOVA) {
411 // import the Dova namespace by default (namespace of backend-specific standard library)
412 var ns_ref = new UsingDirective (new UnresolvedSymbol (null, "Dova", null));
413 source_file.add_using_directive (ns_ref);
414 root.add_using_directive (ns_ref);
417 add_source_file (source_file);
418 } else if (filename.has_suffix (".vapi") || filename.has_suffix (".gir")) {
419 var source_file = new SourceFile (this, SourceFileType.PACKAGE, rpath);
420 source_file.relative_filename = filename;
422 add_source_file (source_file);
423 // look for a local .deps
424 var deps_filename = "%s.deps".printf (filename.ndup (filename.length - ".vapi".length));
425 if (!add_packages_from_file (deps_filename)) {
426 return false;
428 } else if (filename.has_suffix (".c")) {
429 add_c_source_file (rpath);
430 } else {
431 Report.error (null, "%s is not a supported source file type. Only .vala, .vapi, .gs, and .c files are supported.".printf (filename));
432 return false;
435 return true;
439 * Visits the complete code tree file by file.
440 * It is possible to add new source files while visiting the tree.
442 * @param visitor the visitor to be called when traversing
444 public void accept (CodeVisitor visitor) {
445 root.accept (visitor);
447 // support queueing new source files
448 int index = 0;
449 while (index < source_files.size) {
450 var source_file = source_files[index];
451 source_file.accept (visitor);
452 index++;
457 * Resolve and analyze.
459 public void check () {
460 resolver.resolve (this);
462 if (report.get_errors () > 0) {
463 return;
466 analyzer.analyze (this);
468 if (report.get_errors () > 0) {
469 return;
472 flow_analyzer.analyze (this);
475 public void add_define (string define) {
476 defines.add (define);
479 public bool is_defined (string define) {
480 return (define in defines);
483 public string? get_vapi_path (string pkg) {
484 var path = get_file_path (pkg + ".vapi", "vala" + Config.PACKAGE_SUFFIX + "/vapi", "vala/vapi", vapi_directories);
486 if (path == null) {
487 /* last chance: try the package compiled-in vapi dir */
488 var filename = Path.build_filename (Config.PACKAGE_DATADIR, "vapi", pkg + ".vapi");
489 if (FileUtils.test (filename, FileTest.EXISTS)) {
490 path = filename;
494 return path;
497 public string? get_gir_path (string gir) {
498 return get_file_path (gir + ".gir", "gir-1.0", null, gir_directories);
501 string? get_file_path (string basename, string versioned_data_dir, string? data_dir, string[] directories) {
502 string filename = null;
504 if (directories != null) {
505 foreach (string dir in directories) {
506 filename = Path.build_filename (dir, basename);
507 if (FileUtils.test (filename, FileTest.EXISTS)) {
508 return filename;
513 foreach (string dir in Environment.get_system_data_dirs ()) {
514 filename = Path.build_filename (dir, versioned_data_dir, basename);
515 if (FileUtils.test (filename, FileTest.EXISTS)) {
516 return filename;
520 if (data_dir != null) {
521 foreach (string dir in Environment.get_system_data_dirs ()) {
522 filename = Path.build_filename (dir, data_dir, basename);
523 if (FileUtils.test (filename, FileTest.EXISTS)) {
524 return filename;
529 return null;
532 public void write_dependencies (string filename) {
533 var stream = FileStream.open (filename, "w");
535 if (stream == null) {
536 Report.error (null, "unable to open `%s' for writing".printf (filename));
537 return;
540 stream.printf ("%s:", filename);
541 foreach (var src in source_files) {
542 if (src.file_type == SourceFileType.FAST && src.used) {
543 stream.printf (" %s", src.filename);
546 stream.printf ("\n\n");
549 private static bool ends_with_dir_separator (string s) {
550 return Path.is_dir_separator (s.offset (s.length - 1).get_char ());
553 /* ported from glibc */
554 public static string realpath (string name) {
555 string rpath;
557 // start of path component
558 weak string start;
559 // end of path component
560 weak string end;
562 if (!Path.is_absolute (name)) {
563 // relative path
564 rpath = Environment.get_current_dir ();
566 start = end = name;
567 } else {
568 // set start after root
569 start = end = Path.skip_root (name);
571 // extract root
572 rpath = name.substring (0, name.pointer_to_offset (start));
575 long root_len = rpath.pointer_to_offset (Path.skip_root (rpath));
577 for (; start.get_char () != 0; start = end) {
578 // skip sequence of multiple path-separators
579 while (Path.is_dir_separator (start.get_char ())) {
580 start = start.next_char ();
583 // find end of path component
584 long len = 0;
585 for (end = start; end.get_char () != 0 && !Path.is_dir_separator (end.get_char ()); end = end.next_char ()) {
586 len++;
589 if (len == 0) {
590 break;
591 } else if (len == 1 && start.get_char () == '.') {
592 // do nothing
593 } else if (len == 2 && start.has_prefix ("..")) {
594 // back up to previous component, ignore if at root already
595 if (rpath.length > root_len) {
596 do {
597 rpath = rpath.substring (0, rpath.length - 1);
598 } while (!ends_with_dir_separator (rpath));
600 } else {
601 if (!ends_with_dir_separator (rpath)) {
602 rpath += Path.DIR_SEPARATOR_S;
605 rpath += start.substring (0, len);
609 if (rpath.length > root_len && ends_with_dir_separator (rpath)) {
610 rpath = rpath.substring (0, rpath.length - 1);
613 if (Path.DIR_SEPARATOR != '/') {
614 // don't use backslashes internally,
615 // to avoid problems in #include directives
616 string[] components = rpath.split ("\\");
617 rpath = string.joinv ("/", components);
620 return rpath;