valagirparser.vala 33.3 KB
Newer Older
1 2
/* valagirparser.vala
 *
3
 * Copyright (C) 2008-2009  Jürg Billeter
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.

 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
 *
 * Author:
 * 	Jürg Billeter <j@bitron.ch>
 */

using GLib;

/**
 * Code visitor parsing all Vala source files.
 */
public class Vala.GirParser : CodeVisitor {
	MarkupReader reader;

	CodeContext context;
32
	Namespace glib_ns;
33 34 35 36 37 38

	SourceFile current_source_file;
	SourceLocation begin;
	SourceLocation end;
	MarkupTokenType current_token;

39
	string[] cheader_filenames;
40
	string[] package_names;
41

42 43
	HashMap<string,string> attributes_map = new HashMap<string,string> (str_hash, str_equal);

44 45
	HashMap<string,ArrayList<Method>> gtype_callbacks = new HashMap<string,ArrayList<Method>> (str_hash, str_equal);

46 47 48 49 50 51 52 53
	/**
	 * Parses all .gir source files in the specified code
	 * context and builds a code tree.
	 *
	 * @param context a code context
	 */
	public void parse (CodeContext context) {
		this.context = context;
54
		glib_ns = context.root.scope.lookup ("GLib") as Namespace;
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
		context.accept (this);
	}

	public override void visit_source_file (SourceFile source_file) {
		if (source_file.filename.has_suffix (".gir")) {
			parse_file (source_file);
		}
	}

	public void parse_file (SourceFile source_file) {
		this.current_source_file = source_file;
		reader = new MarkupReader (source_file.filename);

		// xml prolog
		next ();
		next ();

		next ();
		parse_repository ();

75 76
		var remove_queue = new ArrayList<CodeNode> ();

77 78 79 80 81 82 83 84
		foreach (CodeNode node in source_file.get_nodes ()) {
			if (node is Class) {
				var cl = (Class) node;
				var ns = cl.parent_symbol as Namespace;
				// remove Class records
				var class_struct = ns.scope.lookup (cl.name + "Class") as Struct;
				if (class_struct != null) {
					ns.remove_struct ((Struct) class_struct);
85
					remove_queue.add (class_struct);
86 87 88 89 90 91 92 93
				}
			} else if (node is Interface) {
				var iface = (Interface) node;
				var ns = iface.parent_symbol as Namespace;
				// remove Iface records
				var iface_struct = ns.scope.lookup (iface.name + "Iface") as Struct;
				if (iface_struct != null) {
					ns.remove_struct ((Struct) iface_struct);
94
					remove_queue.add (iface_struct);
95 96 97 98
				}
			}
		}

99 100 101 102
		foreach (CodeNode node in remove_queue) {
			source_file.remove_node (node);
		}

103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
		reader = null;
		this.current_source_file = null;
	}

	void next () {
		current_token = reader.read_token (out begin, out end);
	}

	void start_element (string name) {
		if (current_token != MarkupTokenType.START_ELEMENT || reader.name != name) {
			// error
			Report.error (get_current_src (), "expected start element of `%s'".printf (name));
		}
	}

	void end_element (string name) {
		if (current_token != MarkupTokenType.END_ELEMENT || reader.name != name) {
			// error
			Report.error (get_current_src (), "expected end element of `%s'".printf (name));
		}
		next ();
	}

	SourceReference get_current_src () {
		return new SourceReference (this.current_source_file, begin.line, begin.column, end.line, end.column);
	}

	void parse_repository () {
		start_element ("repository");
		next ();
		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "namespace") {
135 136 137 138
				var ns = parse_namespace ();
				if (ns != null) {
					context.root.add_namespace (ns);
				}
139 140
			} else if (reader.name == "include") {
				parse_include ();
141 142 143 144
			} else if (reader.name == "package") {
				parse_package ();
			} else if (reader.name == "c:include") {
				parse_c_include ();
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `repository'".printf (reader.name));
				break;
			}
		}
		end_element ("repository");
	}

	void parse_include () {
		start_element ("include");
		next ();
		end_element ("include");
	}

160 161
	void parse_package () {
		start_element ("package");
162
		add_package_name (reader.get_attribute ("name"));
163 164 165 166 167 168 169 170 171 172 173
		next ();
		end_element ("package");
	}

	void parse_c_include () {
		start_element ("c:include");
		cheader_filenames += reader.get_attribute ("name");
		next ();
		end_element ("c:include");
	}

174
	Namespace? parse_namespace () {
175
		start_element ("namespace");
176 177 178 179 180 181 182 183

		bool new_namespace = false;
		string namespace_name = transform_namespace_name (reader.get_attribute ("name"));
		var ns = context.root.scope.lookup (namespace_name) as Namespace;
		if (ns == null) {
			ns = new Namespace (namespace_name);
			new_namespace = true;
		} else {
184 185 186 187
			if (ns.external_package) {
				ns.attributes = null;
				ns.source_reference = get_current_src ();
			}
188 189
		}

190 191
		string? cprefix = reader.get_attribute ("c:prefix");
		if (cprefix != null) {
192 193
			ns.add_cprefix (cprefix);
			ns.set_lower_case_cprefix (Symbol.camel_case_to_lower_case (cprefix) + "_");
194 195
		}

196 197
		foreach (string c_header in cheader_filenames) {
			ns.add_cheader_filename (c_header);
198 199 200 201 202 203 204 205 206 207 208
		}
		next ();
		while (current_token == MarkupTokenType.START_ELEMENT) {
			Symbol sym = null;
			if (reader.name == "alias") {
				sym = parse_alias ();
			} else if (reader.name == "enumeration") {
				sym = parse_enumeration ();
			} else if (reader.name == "bitfield") {
				sym = parse_bitfield ();
			} else if (reader.name == "function") {
209
				sym = parse_method ("function");
210 211 212 213 214 215 216 217 218
			} else if (reader.name == "callback") {
				sym = parse_callback ();
			} else if (reader.name == "record") {
				sym = parse_record ();
			} else if (reader.name == "class") {
				sym = parse_class ();
			} else if (reader.name == "interface") {
				sym = parse_interface ();
			} else if (reader.name == "glib:boxed") {
219
				sym = parse_boxed ();
220
			} else if (reader.name == "union") {
221
				sym = parse_union ();
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249
			} else if (reader.name == "constant") {
				sym = parse_constant ();
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `namespace'".printf (reader.name));
				break;
			}

			if (sym is Class) {
				ns.add_class ((Class) sym);
			} else if (sym is Interface) {
				ns.add_interface ((Interface) sym);
			} else if (sym is Struct) {
				ns.add_struct ((Struct) sym);
			} else if (sym is Enum) {
				ns.add_enum ((Enum) sym);
			} else if (sym is Delegate) {
				ns.add_delegate ((Delegate) sym);
			} else if (sym is Method) {
				ns.add_method ((Method) sym);
			} else if (sym is Constant) {
				ns.add_constant ((Constant) sym);
			} else if (sym == null) {
				continue;
			}
			current_source_file.add_node (sym);
		}
		end_element ("namespace");
250

251 252
		postprocess_gtype_callbacks (ns);

253 254 255 256
		if (!new_namespace) {
			ns = null;
		}

257 258 259 260 261
		return ns;
	}

	Struct parse_alias () {
		start_element ("alias");
262
		var st = new Struct (reader.get_attribute ("name"), get_current_src ());
263
		st.access = SymbolAccessibility.PUBLIC;
264
		st.base_type = parse_type_from_name (reader.get_attribute ("target"));
265
		st.external = true;
266 267 268 269 270 271 272
		next ();
		end_element ("alias");
		return st;
	}

	Enum parse_enumeration () {
		start_element ("enumeration");
273
		var en = new Enum (reader.get_attribute ("name"), get_current_src ());
274
		en.access = SymbolAccessibility.PUBLIC;
275 276 277 278 279 280

		string enum_cname = reader.get_attribute ("c:type");
		if (enum_cname != null) {
			en.set_cname (enum_cname);
		}

281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
		next ();

		string common_prefix = null;
		
		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "member") {
				var ev = parse_enumeration_member ();
				en.add_value (ev);

				string cname = ev.get_cname ();

				if (common_prefix == null) {
					common_prefix = cname;
					while (common_prefix.len () > 0 && !common_prefix.has_suffix ("_")) {
						// FIXME: could easily be made faster
						common_prefix = common_prefix.ndup (common_prefix.size () - 1);
					}
				} else {
					while (!cname.has_prefix (common_prefix)) {
						common_prefix = common_prefix.ndup (common_prefix.size () - 1);
					}
				}
				while (common_prefix.len () > 0 && (!common_prefix.has_suffix ("_") ||
304
				       (cname.offset (common_prefix.length).get_char ().isdigit ()) && (cname.len () - common_prefix.len ()) <= 1)) {
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321
					// enum values may not consist solely of digits
					common_prefix = common_prefix.ndup (common_prefix.size () - 1);
				}
			} else {
				// error
				break;
			}
		}

		en.set_cprefix (common_prefix);

		end_element ("enumeration");
		return en;
	}

	Enum parse_bitfield () {
		start_element ("bitfield");
322
		var en = new Enum (reader.get_attribute ("name"), get_current_src ());
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345
		en.access = SymbolAccessibility.PUBLIC;
		next ();
		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "member") {
				en.add_value (parse_enumeration_member ());
			} else {
				// error
				break;
			}
		}
		end_element ("bitfield");
		return en;
	}

	EnumValue parse_enumeration_member () {
		start_element ("member");
		var ev = new EnumValue (string.joinv ("_", reader.get_attribute ("name").up ().split ("-")));
		ev.set_cname (reader.get_attribute ("c:identifier"));
		next ();
		end_element ("member");
		return ev;
	}

346
	DataType parse_return_value (out string? ctype = null) {
347 348
		start_element ("return-value");
		string transfer = reader.get_attribute ("transfer-ownership");
349
		string allow_none = reader.get_attribute ("allow-none");
350
		next ();
351
		var type = &ctype != null ? parse_type(out ctype) : parse_type ();
352 353 354
		if (transfer == "full") {
			type.value_owned = true;
		}
355 356 357
		if (allow_none == "1") {
			type.nullable = true;
		}
358 359 360 361
		end_element ("return-value");
		return type;
	}

362
	FormalParameter parse_parameter (out int array_length_idx = null, out int closure_idx = null, out int destroy_idx = null) {
363 364 365 366 367
		FormalParameter param;

		if (&array_length_idx != null) {
			array_length_idx = -1;
		}
368 369 370 371 372 373
		if (&closure_idx != null) {
			closure_idx = -1;
		}
		if (&destroy_idx != null) {
			destroy_idx = -1;
		}
374 375 376 377 378

		start_element ("parameter");
		string name = reader.get_attribute ("name");
		string direction = reader.get_attribute ("direction");
		string transfer = reader.get_attribute ("transfer-ownership");
379
		string allow_none = reader.get_attribute ("allow-none");
380 381 382 383 384 385 386 387 388 389

		string closure = reader.get_attribute ("closure");
		string destroy = reader.get_attribute ("destroy");
		if (closure != null && &closure_idx != null) {
			closure_idx = closure.to_int ();
		}
		if (destroy != null && &destroy_idx != null) {
			destroy_idx = destroy.to_int ();
		}

390 391 392 393
		next ();
		if (reader.name == "varargs") {
			start_element ("varargs");
			next ();
394
			param = new FormalParameter.with_ellipsis (get_current_src ());
395 396
			end_element ("varargs");
		} else {
397
			var type = parse_type (null, out array_length_idx);
398 399 400
			if (transfer == "full") {
				type.value_owned = true;
			}
401 402 403
			if (allow_none == "1") {
				type.nullable = true;
			}
404
			param = new FormalParameter (name, type, get_current_src ());
405 406 407 408 409 410 411 412 413 414
			if (direction == "out") {
				param.direction = ParameterDirection.OUT;
			} else if (direction == "inout") {
				param.direction = ParameterDirection.REF;
			}
		}
		end_element ("parameter");
		return param;
	}

415
	DataType parse_type (out string? ctype = null, out int array_length_index = null) {
416 417 418 419 420 421 422 423 424 425
		if (reader.name == "array") {
			start_element ("array");
			if (reader.get_attribute ("length") != null
			    && &array_length_index != null) {
				array_length_index = reader.get_attribute ("length").to_int ();
			}
			next ();
			var element_type = parse_type ();
			end_element ("array");
			return new ArrayType (element_type, 1, null);
426 427 428
		} else if (reader.name == "callback"){
			var callback = parse_callback ();
			return new DelegateType (callback);
429 430 431
		} else {
			start_element ("type");
			DataType type = parse_type_from_name (reader.get_attribute ("name"));
432 433 434
			if (&ctype != null) {
				ctype = reader.get_attribute("c:type");
			}
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
			next ();

			// type arguments / element types
			while (current_token == MarkupTokenType.START_ELEMENT) {
				parse_type ();
			}

			end_element ("type");
			return type;
		}
	}

	DataType parse_type_from_name (string type_name) {
		DataType type;
		if (type_name == "none") {
			type = new VoidType ();
		} else if (type_name == "any") {
			type = new PointerType (new VoidType ());
		} else if (type_name == "GObject.Strv") {
			type = new ArrayType (new UnresolvedType.from_symbol (new UnresolvedSymbol (null, "string")), 1, null);
		} else {
			if (type_name == "utf8") {
				type_name = "string";
			} else if (type_name == "boolean") {
				type_name = "bool";
Jürg Billeter's avatar
Jürg Billeter committed
460 461
			} else if (type_name == "GLib.offset") {
				type_name = "int64";
462 463 464 465 466 467 468 469 470 471 472 473 474 475
			} else if (type_name == "GType") {
				type_name = "GLib.Type";
			} else if (type_name == "GObject.String") {
				type_name = "GLib.StringBuilder";
			} else if (type_name == "GObject.Class") {
				type_name = "GLib.ObjectClass";
			} else if (type_name == "GLib.unichar") {
				type_name = "unichar";
			} else if (type_name == "GLib.Data") {
				type_name = "GLib.Datalist";
			}
			string[] type_components = type_name.split (".");
			if (type_components[1] != null) {
				// namespaced name
476
				string namespace_name = transform_namespace_name (type_components[0]);
477 478 479 480 481 482 483 484 485 486
				string transformed_type_name = type_components[1];
				type = new UnresolvedType.from_symbol (new UnresolvedSymbol (new UnresolvedSymbol (null, namespace_name), transformed_type_name));
			} else {
				type = new UnresolvedType.from_symbol (new UnresolvedSymbol (null, type_name));
			}
		}

		return type;
	}

487 488 489 490 491
	string transform_namespace_name (string gir_module_name) {
		if (gir_module_name == "GObject") {
			return "GLib";
		} else if (gir_module_name == "Gio") {
			return "GLib";
492 493
		} else if (gir_module_name == "GModule") {
			return "GLib";
494 495 496 497
		}
		return gir_module_name;
	}

498 499
	Struct parse_record () {
		start_element ("record");
500
		var st = new Struct (reader.get_attribute ("name"), get_current_src ());
501
		st.external = true;
502 503 504

		string glib_is_gtype_struct_for = reader.get_attribute ("glib:is-gtype-struct-for");

505 506 507 508 509 510
		st.access = SymbolAccessibility.PUBLIC;
		next ();
		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "field") {
				st.add_field (parse_field ());
			} else if (reader.name == "callback") {
511 512 513 514 515 516 517 518 519 520
				if (glib_is_gtype_struct_for != null) {
					ArrayList<Method> callbacks = gtype_callbacks.get (glib_is_gtype_struct_for);
					if (callbacks == null) {
						callbacks = new ArrayList<Method> ();
						gtype_callbacks.set (glib_is_gtype_struct_for, callbacks);
					}
					callbacks.add (parse_method ("callback"));
				} else {
					parse_callback ();
				}
521 522 523
			} else if (reader.name == "constructor") {
				parse_constructor ();
			} else if (reader.name == "method") {
Jürg Billeter's avatar
Jürg Billeter committed
524
				st.add_method (parse_method ("method"));
525 526 527 528 529 530 531 532
			} else if (reader.name == "union") {
				Struct s = parse_union ();
				var s_fields = s.get_fields ();
				foreach (var f in s_fields) {
					f.set_cname (s.get_cname () + "." + f.get_cname ());
					f.name = s.name + "_" + f.name;
					st.add_field (f);
				}
533 534 535 536 537 538 539 540 541 542
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `record'".printf (reader.name));
				break;
			}
		}
		end_element ("record");
		return st;
	}

543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567
	void postprocess_gtype_callbacks (Namespace ns) {
		foreach (string gtype_name in gtype_callbacks.get_keys ()) {
			var gtype = ns.scope.lookup (gtype_name) as ObjectTypeSymbol;
			ArrayList<Method> callbacks = gtype_callbacks.get (gtype_name);
			foreach (Method m in callbacks) {
				var symbol = gtype.scope.lookup (m.name);
				if (symbol == null) {
					continue;
				} else if (symbol is Method)  {
					var meth = (Method) symbol;
					if (gtype is Class) {
						meth.is_virtual = true;
					} else if (gtype is Interface) {
						meth.is_abstract = true;
					}
				} else if (symbol is Signal) {
					var sig = (Signal) symbol;
					sig.is_virtual = true;
				} else {
					Report.error (get_current_src (), "unknown member type `%s' in `%s'".printf (m.name, gtype.name));
				}
			}
		}
	}

568 569
	Class parse_class () {
		start_element ("class");
570
		var cl = new Class (reader.get_attribute ("name"), get_current_src ());
571
		cl.access = SymbolAccessibility.PUBLIC;
572
		cl.external = true;
573

574 575 576 577 578
		string cname = reader.get_attribute ("c:type");
		if (cname != null) {
			cl.set_cname (cname);
		}

579 580 581 582 583 584 585 586
		string parent = reader.get_attribute ("parent");
		if (parent != null) {
			cl.add_base_type (parse_type_from_name (parent));
		}

		next ();
		var signals = new ArrayList<Signal> ();
		var methods = new ArrayList<Method> ();
Jürg Billeter's avatar
Jürg Billeter committed
587
		var vmethods = new ArrayList<Method> ();
588 589 590 591 592 593 594
		var fields = new ArrayList<Field> ();
		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "implements") {
				start_element ("implements");
				cl.add_base_type (parse_type_from_name (reader.get_attribute ("name")));
				next ();
				end_element ("implements");
595 596
			} else if (reader.name == "constant") {
				cl.add_constant (parse_constant ());
597 598 599 600 601
			} else if (reader.name == "field") {
				fields.add (parse_field ());
			} else if (reader.name == "property") {
				cl.add_property (parse_property ());
			} else if (reader.name == "constructor") {
602
				cl.add_method (parse_constructor (cname));
603
			} else if (reader.name == "function") {
604
				methods.add (parse_method ("function"));
605
			} else if (reader.name == "method") {
606
				methods.add (parse_method ("method"));
607 608
			} else if (reader.name == "virtual-method") {
				vmethods.add (parse_method ("virtual-method"));
609 610 611 612 613 614 615 616
			} else if (reader.name == "union") {
				Struct s = parse_union ();
				var s_fields = s.get_fields ();
				foreach (var f in s_fields) {
					f.set_cname (s.get_cname () + "." + f.get_cname ());
					f.name = s.name + "_" + f.name;
					fields.add (f);
				}
617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637
			} else if (reader.name == "glib:signal") {
				signals.add (parse_signal ());
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `class'".printf (reader.name));
				break;
			}
		}

		// signal merging
		foreach (Signal sig in signals) {
			var symbol = cl.scope.lookup (sig.name);
			if (symbol == null) {
				cl.add_signal (sig);
			} else if (symbol is Property) {
				// properties take precedence
			} else {
				Report.error (get_current_src (), "duplicate member `%s' in `%s'".printf (sig.name, cl.name));
			}
		}

Jürg Billeter's avatar
Jürg Billeter committed
638 639 640 641 642 643 644 645 646 647 648 649 650 651 652
		// virtual method merging
		foreach (Method m in vmethods) {
			var symbol = cl.scope.lookup (m.name);
			if (symbol == null) {
				cl.add_method (m);
			} else if (symbol is Signal) {
				var sig = (Signal) symbol;
				sig.is_virtual = true;
			} else if (symbol is Property || symbol is Field) {
				// assume method is getter for property/field ignore method
			} else {
				Report.error (get_current_src (), "duplicate member `%s' in `%s'".printf (m.name, cl.name));
			}
		}

653 654 655 656 657 658 659 660 661 662
		// method merging
		foreach (Method m in methods) {
			var symbol = cl.scope.lookup (m.name);
			if (symbol == null) {
				cl.add_method (m);
			} else if (symbol is Signal) {
				var sig = (Signal) symbol;
				sig.has_emitter = true;
			} else if (symbol is Property || symbol is Field) {
				// assume method is getter for property/field ignore method
Jürg Billeter's avatar
Jürg Billeter committed
663 664
			} else if (symbol is Method) {
				// assume method is wrapper for virtual method
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683
			} else {
				Report.error (get_current_src (), "duplicate member `%s' in `%s'".printf (m.name, cl.name));
			}
		}

		// fields have lowest priority
		foreach (Field f in fields) {
			var symbol = cl.scope.lookup (f.name);
			if (symbol == null) {
				cl.add_field (f);
			}
		}

		end_element ("class");
		return cl;
	}

	Interface parse_interface () {
		start_element ("interface");
684
		var iface = new Interface (reader.get_attribute ("name"), get_current_src ());
685
		iface.access = SymbolAccessibility.PUBLIC;
686
		iface.external = true;
687 688 689 690 691 692

		string cname = reader.get_attribute ("c:type");
		if (cname != null) {
			iface.set_cname (cname);
		}

693 694
		next ();
		var methods = new ArrayList<Method> ();
695
		var vmethods = new ArrayList<Method> ();
696
		while (current_token == MarkupTokenType.START_ELEMENT) {
697 698 699 700 701 702
			if (reader.name == "prerequisite") {
				start_element ("prerequisite");
				iface.add_prerequisite (parse_type_from_name (reader.get_attribute ("name")));
				next ();
				end_element ("prerequisite");
			} else if (reader.name == "field") {
703 704 705
				parse_field ();
			} else if (reader.name == "property") {
				iface.add_property (parse_property ());
706 707
			} else if (reader.name == "virtual-method") {
				vmethods.add (parse_method ("virtual-method"));
708 709
			} else if (reader.name == "function") {
				methods.add (parse_method ("function"));
710
			} else if (reader.name == "method") {
711
				methods.add (parse_method ("method"));
712 713 714 715 716 717 718 719 720
			} else if (reader.name == "glib:signal") {
				iface.add_signal (parse_signal ());
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `interface'".printf (reader.name));
				break;
			}
		}

721 722 723 724 725 726 727 728 729 730 731 732 733
		// virtual method merging
		foreach (Method m in vmethods) {
			var symbol = iface.scope.lookup (m.name);
			if (symbol == null) {
				iface.add_method (m);
			} else if (symbol is Signal) {
				var sig = (Signal) symbol;
				sig.is_virtual = true;
			} else {
				Report.error (get_current_src (), "duplicate member `%s' in `%s'".printf (m.name, iface.name));
			}
		}

734 735 736 737 738 739 740 741
		// method merging
		foreach (Method m in methods) {
			var symbol = iface.scope.lookup (m.name);
			if (symbol == null) {
				iface.add_method (m);
			} else if (symbol is Signal) {
				var sig = (Signal) symbol;
				sig.has_emitter = true;
742 743
			} else if (symbol is Method) {
				// assume method is wrapper for virtual method
744 745 746 747 748 749 750 751 752 753 754 755
			} else {
				Report.error (get_current_src (), "duplicate member `%s' in `%s'".printf (m.name, iface.name));
			}
		}

		end_element ("interface");
		return iface;
	}

	Field parse_field () {
		start_element ("field");
		string name = reader.get_attribute ("name");
756
		string allow_none = reader.get_attribute ("allow-none");
757 758
		next ();
		var type = parse_type ();
759
		var field = new Field (name, type, null, get_current_src ());
760
		field.access = SymbolAccessibility.PUBLIC;
761 762 763
		if (allow_none == "1") {
			type.nullable = true;
		}
764 765 766 767 768 769 770
		end_element ("field");
		return field;
	}

	Property parse_property () {
		start_element ("property");
		string name = string.joinv ("_", reader.get_attribute ("name").split ("-"));
771 772 773
		string readable = reader.get_attribute ("readable");
		string writable = reader.get_attribute ("writable");
		string construct_ = reader.get_attribute ("construct");
774
		string construct_only = reader.get_attribute ("construct-only");
775 776
		next ();
		var type = parse_type ();
777
		var prop = new Property (name, type, null, null, get_current_src ());
778
		prop.access = SymbolAccessibility.PUBLIC;
779
		if (readable != "0") {
780
			prop.get_accessor = new PropertyAccessor (true, false, false, prop.property_type.copy (), null, null);
781
		}
782 783
		if (writable == "1" || construct_only == "1") {
			prop.set_accessor = new PropertyAccessor (false, (construct_only != "1") && (writable == "1"), (construct_only == "1") || (construct_ == "1"), prop.property_type.copy (), null, null);
784
		}
785 786 787 788 789 790 791 792 793 794 795 796 797 798
		end_element ("property");
		return prop;
	}

	Delegate parse_callback () {
		start_element ("callback");
		string name = reader.get_attribute ("name");
		next ();
		DataType return_type;
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "return-value") {
			return_type = parse_return_value ();
		} else {
			return_type = new VoidType ();
		}
799
		var d = new Delegate (name, return_type, get_current_src ());
800 801 802 803 804 805 806 807 808 809 810 811 812
		d.access = SymbolAccessibility.PUBLIC;
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "parameters") {
			start_element ("parameters");
			next ();
			while (current_token == MarkupTokenType.START_ELEMENT) {
				d.add_parameter (parse_parameter ());
			}
			end_element ("parameters");
		}
		end_element ("callback");
		return d;
	}

813
	Method parse_constructor (string? parent_ctype = null) {
814 815
		start_element ("constructor");
		string name = reader.get_attribute ("name");
816
		string throws_string = reader.get_attribute ("throws");
817 818
		next ();

819 820
		string? ctype;
		parse_return_value (out ctype);
821

822
		var m = new CreationMethod (null, name, get_current_src ());
823 824
		m.access = SymbolAccessibility.PUBLIC;
		m.has_construct_function = false;
825 826 827
		if (ctype != null && (parent_ctype == null || ctype != parent_ctype + "*")) {
			m.custom_return_type_cname = ctype;
		}
828 829 830
		if (m.name == "new") {
			m.name = null;
		} else if (m.name.has_prefix ("new_")) {
831 832 833 834 835 836 837 838 839 840
			m.name = m.name.offset ("new_".len ());
		}
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "parameters") {
			start_element ("parameters");
			next ();
			while (current_token == MarkupTokenType.START_ELEMENT) {
				m.add_parameter (parse_parameter ());
			}
			end_element ("parameters");
		}
841

842 843 844
		if (throws_string == "1") {
			m.add_error_type (new ErrorType (null, null));
		}
845 846 847 848
		end_element ("constructor");
		return m;
	}

849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866
	class MethodInfo {
		public MethodInfo (FormalParameter param, int array_length_idx, int closure_idx, int destroy_idx) {
			this.param = param;
			this.array_length_idx = array_length_idx;
			this.closure_idx = closure_idx;
			this.destroy_idx = destroy_idx;
			this.vala_idx = 0.0F;
			this.keep = false;
		}

		public FormalParameter param;
		public float vala_idx;
		public int array_length_idx;
		public int closure_idx;
		public int destroy_idx;
		public bool keep;
	}

867 868
	Method parse_method (string element_name) {
		start_element (element_name);
869
		string name = reader.get_attribute ("name");
870
		string cname = reader.get_attribute ("c:identifier");
871
		string throws_string = reader.get_attribute ("throws");
872
		string invoker = reader.get_attribute ("invoker");
873 874 875 876 877 878 879
		next ();
		DataType return_type;
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "return-value") {
			return_type = parse_return_value ();
		} else {
			return_type = new VoidType ();
		}
880
		var m = new Method (name, return_type, get_current_src ());
881
		m.access = SymbolAccessibility.PUBLIC;
882 883 884
		if (cname != null) {
			m.set_cname (cname);
		}
885

886
		if (element_name == "virtual-method" || element_name == "callback") {
887
			m.is_virtual = true;
888 889 890
			if (invoker != null){
				m.name = invoker;
			}
891 892
		} else if (element_name == "function") {
			m.binding = MemberBinding.STATIC;
Jürg Billeter's avatar
Jürg Billeter committed
893
		}
894

895
		var parameters = new ArrayList<MethodInfo> ();
896 897 898
		var array_length_parameters = new ArrayList<int> ();
		var closure_parameters = new ArrayList<int> ();
		var destroy_parameters = new ArrayList<int> ();
Jürg Billeter's avatar
Jürg Billeter committed
899 900 901
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "parameters") {
			start_element ("parameters");
			next ();
902

Jürg Billeter's avatar
Jürg Billeter committed
903 904
			bool first = true;
			while (current_token == MarkupTokenType.START_ELEMENT) {
905 906 907 908 909 910 911 912 913 914 915 916 917
				int array_length_idx, closure_idx, destroy_idx;
				var param = parse_parameter (out array_length_idx, out closure_idx, out destroy_idx);
				if (array_length_idx != -1) {
					array_length_parameters.add (array_length_idx);
				}
				if (closure_idx != -1) {
					closure_parameters.add (closure_idx);
				}
				if (destroy_idx != -1) {
					destroy_parameters.add (destroy_idx);
				}
				// first parameter is instance pointer in virtual methods, ignore
				if (element_name != "callback" || !first) {
918
					parameters.add (new MethodInfo(param, array_length_idx, closure_idx, destroy_idx));
Jürg Billeter's avatar
Jürg Billeter committed
919 920 921 922 923 924
				} else {
					first = false;
				}
			}
			end_element ("parameters");
		}
925
		int i = 0, j=1, add=0;
926

927
		if (element_name == "method" || element_name == "virtual-method" || element_name == "callback") {
928
			// implicit instance parameter
929
			add = 1;
930 931
		}

932
		int last = -1;
933 934 935 936 937 938 939 940
		foreach (MethodInfo info in parameters) {
			if (!array_length_parameters.contains (i+add)
			    && !closure_parameters.contains (i+add)
			    && !destroy_parameters.contains (i+add)) {
				info.vala_idx = (float) j;
				info.keep = true;

				/* interpolate for vala_idx between this and last*/
941 942 943 944
				float last_idx = 0.0F;
				if (last != -1) {
					last_idx = parameters[last].vala_idx;
				}
945 946 947 948 949
				for (int k=last+1; k < i; k++) {
					parameters[k].vala_idx =  last_idx + (((j - last_idx) / (i-last)) * (k-last));
				}
				last = i+1;
				j++;
950 951 952
			}
			i++;
		}
953 954 955

		foreach (MethodInfo info in parameters) {
			if (info.keep) {
956 957 958 959 960

				/* add_parameter sets carray_length_parameter_position and cdelegate_target_parameter_position
				 so do it first*/
				m.add_parameter (info.param);

961 962 963 964 965 966 967
				if (info.array_length_idx != -1) {
					if ((info.array_length_idx) - add >= parameters.size) {
						Report.error (get_current_src (), "invalid array_length index");
						continue;
					}
					info.param.carray_length_parameter_position = parameters[info.array_length_idx-add].vala_idx;
				}
968 969 970
				if (info.param.parameter_type is ArrayType && info.array_length_idx == -1) {
					info.param.no_array_length = true;
				}
971 972 973 974 975 976 977 978

				if (info.closure_idx != -1) {
					if ((info.closure_idx - add) >= parameters.size) {
						Report.error (get_current_src (), "invalid closure index");
						continue;
					}
					info.param.cdelegate_target_parameter_position = parameters[info.closure_idx - add].vala_idx;
				}
979
/* Leaving this as a TODO, needs more testing
980 981 982 983 984 985 986 987 988 989 990
				if (info.destroy_idx != -1) {
					if (info.destroy_idx - add >= parameters.size) {
						Report.error (get_current_src (), "invalid destroy index");
						continue;
					}
					info.param.cdelegate_target_parameter_position = parameters[info.destroy_idx - add].vala_idx;
				}
*/
			}
		}

Jürg Billeter's avatar
Jürg Billeter committed
991
		if (throws_string == "1") {
992
			m.add_error_type (new ErrorType (null, null));
Jürg Billeter's avatar
Jürg Billeter committed
993
		}
994
		end_element (element_name);
Jürg Billeter's avatar
Jürg Billeter committed
995 996 997
		return m;
	}

998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
	Signal parse_signal () {
		start_element ("glib:signal");
		string name = string.joinv ("_", reader.get_attribute ("name").split ("-"));
		next ();
		DataType return_type;
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "return-value") {
			return_type = parse_return_value ();
		} else {
			return_type = new VoidType ();
		}
		var sig = new Signal (name, return_type);
		sig.access = SymbolAccessibility.PUBLIC;
1010
		sig.external = true;
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026
		if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "parameters") {
			start_element ("parameters");
			next ();
			while (current_token == MarkupTokenType.START_ELEMENT) {
				sig.add_parameter (parse_parameter ());
			}
			end_element ("parameters");
		}
		end_element ("glib:signal");
		return sig;
	}

	Struct parse_boxed () {
		start_element ("glib:boxed");
		var st = new Struct (reader.get_attribute ("glib:name"));
		st.access = SymbolAccessibility.PUBLIC;
1027
		st.external = true;
1028 1029 1030 1031 1032 1033

		string cname = reader.get_attribute ("c:type");
		if (cname != null) {
			st.set_cname (cname);
		}

1034 1035 1036 1037 1038 1039 1040 1041
		next ();

		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "field") {
				st.add_field (parse_field ());
			} else if (reader.name == "constructor") {
				parse_constructor ();
			} else if (reader.name == "method") {
1042
				st.add_method (parse_method ("method"));
1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `class'".printf (reader.name));
				break;
			}
		}

		end_element ("glib:boxed");
		return st;
	}

	Struct parse_union () {
		start_element ("union");
		var st = new Struct (reader.get_attribute ("name"));
		st.access = SymbolAccessibility.PUBLIC;
1058
		st.external = true;
1059 1060 1061 1062 1063 1064 1065 1066
		next ();

		while (current_token == MarkupTokenType.START_ELEMENT) {
			if (reader.name == "field") {
				st.add_field (parse_field ());
			} else if (reader.name == "constructor") {
				parse_constructor ();
			} else if (reader.name == "method") {
1067
				st.add_method (parse_method ("method"));
1068 1069 1070 1071 1072 1073 1074 1075
			} else if (reader.name == "record") {
				Struct s = parse_record ();
				var fs = s.get_fields ();
				foreach (var f in fs) {
					f.set_cname (s.get_cname () + "." + f.get_cname ());
					f.name = s.name + "_" + f.name;
					st.add_field (f);
				}
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091
			} else {
				// error
				Report.error (get_current_src (), "unknown child element `%s' in `union'".printf (reader.name));
				break;
			}
		}

		end_element ("union");
		return st;
	}

	Constant parse_constant () {
		start_element ("constant");
		string name = reader.get_attribute ("name");
		next ();
		var type = parse_type ();
1092
		var c = new Constant (name, type, null, get_current_src ());
1093
		c.access = SymbolAccessibility.PUBLIC;
1094
		c.external = true;
1095 1096 1097 1098 1099 1100 1101 1102
		end_element ("constant");
		return c;
	}

	public void parse_metadata (string metadata_filename) {
		if (FileUtils.test (metadata_filename, FileTest.EXISTS)) {
			try {
				string metadata;
1103
				FileUtils.get_contents (metadata_filename, out metadata, null);
1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
				
				foreach (string line in metadata.split ("\n")) {
					if (line.has_prefix ("#")) {
						// ignore comment lines
						continue;
					}

					string[] tokens = line.split (" ", 2);

					if (null == tokens[0]) {
						continue;
					}

					foreach (string attribute in tokens[1].split (" ")) {
						string[] pair = attribute.split ("=", 2);
1119 1120 1121 1122
						if (pair[0] == null || pair[1] == null) {
							continue;
						}

1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
						string key = "%s/@%s".printf (tokens[0], pair[0]);
						attributes_map.set (key, pair[1].substring (1, pair[1].length - 2));
					}
				}
			} catch (FileError e) {
				Report.error (null, "Unable to read metadata file: %s".printf (e.message));
			}
		} else {
			Report.error (null, "Metadata file `%s' not found".printf (metadata_filename));
		}
	}
1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151

	void add_package_name (string name) {
		if (package_names == null) {
			package_names = new string[0];
		}

		foreach (var existing in package_names) {
			if (name == existing) {
				return;
			}
		}

		package_names += name;
	}

	public string[]? get_package_names () {
		return package_names;
	}
1152 1153
}