Add experimental .gir support to vapigen
authorJürg Billeter <j@bitron.ch>
Fri, 17 Oct 2008 13:41:33 +0000 (13:41 +0000)
committerJürg Billeter <juergbi@src.gnome.org>
Fri, 17 Oct 2008 13:41:33 +0000 (13:41 +0000)
2008-10-17  Jürg Billeter  <j@bitron.ch>

* vapigen/Makefile.am:
* vapigen/valagirparser.vala:
* vapigen/valamarkupreader.vala:
* vapigen/valavapigen.vala:

Add experimental .gir support to vapigen

svn path=/trunk/; revision=1854

ChangeLog
vapigen/Makefile.am
vapigen/valagirparser.vala [new file with mode: 0644]
vapigen/valamarkupreader.vala [new file with mode: 0644]
vapigen/valavapigen.vala

index ff84a27..6141d24 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,14 @@
 2008-10-17  Jürg Billeter  <j@bitron.ch>
 
+       * vapigen/Makefile.am:
+       * vapigen/valagirparser.vala:
+       * vapigen/valamarkupreader.vala:
+       * vapigen/valavapigen.vala:
+
+       Add experimental .gir support to vapigen
+
+2008-10-17  Jürg Billeter  <j@bitron.ch>
+
        * vala/valasymbolresolver.vala:
 
        Ignore non-type symbols when resolving types
index 51c27b0..62a6d52 100644 (file)
@@ -21,6 +21,8 @@ BUILT_SOURCES = vapigen.vala.stamp vapicheck.vala.stamp
 
 vapigen_VALASOURCES = \
        valagidlparser.vala \
+       valagirparser.vala \
+       valamarkupreader.vala \
        valavapigen.vala \
        $(NULL)
 
diff --git a/vapigen/valagirparser.vala b/vapigen/valagirparser.vala
new file mode 100644 (file)
index 0000000..12d851d
--- /dev/null
@@ -0,0 +1,769 @@
+/* valagirparser.vala
+ *
+ * Copyright (C) 2008  Jürg Billeter
+ *
+ * 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;
+using Gee;
+
+/**
+ * Code visitor parsing all Vala source files.
+ */
+public class Vala.GirParser : CodeVisitor {
+       MarkupReader reader;
+
+       CodeContext context;
+
+       SourceFile current_source_file;
+       SourceLocation begin;
+       SourceLocation end;
+       MarkupTokenType current_token;
+
+       HashMap<string,string> attributes_map = new HashMap<string,string> (str_hash, str_equal);
+
+       /**
+        * 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;
+               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 ();
+
+               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") {
+                               context.root.add_namespace (parse_namespace ());
+                       } else if (reader.name == "include") {
+                               parse_include ();
+                       } 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");
+       }
+
+       Namespace parse_namespace () {
+               start_element ("namespace");
+               var ns = new Namespace (reader.get_attribute ("name"));
+               string cheader = get_attribute (ns.name, "c:header-filename");
+               if (cheader != null) {
+                       ns.set_cheader_filename (cheader);
+               }
+               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") {
+                               sym = parse_function ();
+                       } 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") {
+                               parse_boxed ();
+                       } else if (reader.name == "union") {
+                               parse_union ();
+                       } 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");
+               return ns;
+       }
+
+       Struct parse_alias () {
+               start_element ("alias");
+               var st = new Struct (reader.get_attribute ("name"));
+               st.access = SymbolAccessibility.PUBLIC;
+               st.add_base_type (parse_type_from_name (reader.get_attribute ("target")));
+               next ();
+               end_element ("alias");
+               return st;
+       }
+
+       Enum parse_enumeration () {
+               start_element ("enumeration");
+               var en = new Enum (reader.get_attribute ("name"));
+               en.access = SymbolAccessibility.PUBLIC;
+               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 ("_") ||
+                                      (cname.offset (common_prefix.size ()).get_char ().isdigit ()) && (cname.len () - common_prefix.len ()) <= 1)) {
+                                       // 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");
+               var en = new Enum (reader.get_attribute ("name"));
+               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;
+       }
+
+       Method parse_function () {
+               start_element ("function");
+               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 ();
+               }
+               var m = new Method (name, return_type);
+               m.access = SymbolAccessibility.PUBLIC;
+               m.binding = MemberBinding.STATIC;
+               var parameters = new ArrayList<FormalParameter> ();
+               var array_length_parameters = new ArrayList<int> ();
+               if (current_token == MarkupTokenType.START_ELEMENT && reader.name == "parameters") {
+                       start_element ("parameters");
+                       next ();
+                       while (current_token == MarkupTokenType.START_ELEMENT) {
+                               int array_length_idx = -1;
+                               var param = parse_parameter (out array_length_idx);
+                               if (array_length_idx != -1) {
+                                       array_length_parameters.add (array_length_idx);
+                               }
+                               parameters.add (param);
+                       }
+                       end_element ("parameters");
+               }
+               int i = 0;
+               foreach (FormalParameter param in parameters) {
+                       if (!array_length_parameters.contains (i)) {
+                               m.add_parameter (param);
+                       }
+                       i++;
+               }
+               end_element ("function");
+               return m;
+       }
+
+       DataType parse_return_value () {
+               start_element ("return-value");
+               string transfer = reader.get_attribute ("transfer-ownership");
+               next ();
+               var type = parse_type ();
+               if (transfer == "full") {
+                       type.value_owned = true;
+               }
+               end_element ("return-value");
+               return type;
+       }
+
+       FormalParameter parse_parameter (out int array_length_idx = null) {
+               FormalParameter param = null;
+
+               start_element ("parameter");
+               string name = reader.get_attribute ("name");
+               string direction = reader.get_attribute ("direction");
+               string transfer = reader.get_attribute ("transfer-ownership");
+               next ();
+               if (reader.name == "varargs") {
+                       start_element ("varargs");
+                       next ();
+                       param = new FormalParameter.with_ellipsis ();
+                       end_element ("varargs");
+               } else {
+                       var type = parse_type (out array_length_idx);
+                       if (transfer == "full") {
+                               type.value_owned = true;
+                       }
+                       param = new FormalParameter (name, type);
+                       if (direction == "out") {
+                               param.direction = ParameterDirection.OUT;
+                       } else if (direction == "inout") {
+                               param.direction = ParameterDirection.REF;
+                       }
+               }
+               end_element ("parameter");
+               return param;
+       }
+
+       DataType parse_type (out int array_length_index = null) {
+               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);
+               } else {
+                       start_element ("type");
+                       DataType type = parse_type_from_name (reader.get_attribute ("name"));
+                       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";
+                       } 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
+                               string namespace_name = type_components[0];
+                               string transformed_type_name = type_components[1];
+                               if (namespace_name == "GObject") {
+                                       namespace_name = "GLib";
+                               } else if (namespace_name == "Gio") {
+                                       namespace_name = "GLib";
+                               }
+                               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;
+       }
+
+       Struct parse_record () {
+               start_element ("record");
+               var st = new Struct (reader.get_attribute ("name"));
+               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") {
+                               parse_callback ();
+                       } else if (reader.name == "constructor") {
+                               parse_constructor ();
+                       } else if (reader.name == "method") {
+                               parse_method ();
+                       } else {
+                               // error
+                               Report.error (get_current_src (), "unknown child element `%s' in `record'".printf (reader.name));
+                               break;
+                       }
+               }
+               end_element ("record");
+               return st;
+       }
+
+       Class parse_class () {
+               start_element ("class");
+               var cl = new Class (reader.get_attribute ("name"));
+               cl.access = SymbolAccessibility.PUBLIC;
+
+               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> ();
+               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");
+                       } else if (reader.name == "field") {
+                               fields.add (parse_field ());
+                       } else if (reader.name == "property") {
+                               cl.add_property (parse_property ());
+                       } else if (reader.name == "constructor") {
+                               cl.add_method (parse_constructor ());
+                       } else if (reader.name == "method") {
+                               methods.add (parse_method ());
+                       } else if (reader.name == "callback") {
+                               parse_callback ();
+                       } 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));
+                       }
+               }
+
+               // 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
+                       } 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");
+               var iface = new Interface (reader.get_attribute ("name"));
+               iface.access = SymbolAccessibility.PUBLIC;
+               next ();
+               var methods = new ArrayList<Method> ();
+               while (current_token == MarkupTokenType.START_ELEMENT) {
+                       if (reader.name == "field") {
+                               parse_field ();
+                       } else if (reader.name == "property") {
+                               iface.add_property (parse_property ());
+                       } else if (reader.name == "callback") {
+                               parse_callback ();
+                       } else if (reader.name == "method") {
+                               methods.add (parse_method ());
+                       } 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;
+                       }
+               }
+
+               // 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;
+                       } 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");
+               next ();
+               var type = parse_type ();
+               var field = new Field (name, type, null);
+               field.access = SymbolAccessibility.PUBLIC;
+               end_element ("field");
+               return field;
+       }
+
+       Property parse_property () {
+               start_element ("property");
+               string name = string.joinv ("_", reader.get_attribute ("name").split ("-"));
+               next ();
+               var type = parse_type ();
+               var prop = new Property (name, type, null, null);
+               prop.access = SymbolAccessibility.PUBLIC;
+               prop.get_accessor = new PropertyAccessor (true, false, false, null, null);
+               prop.set_accessor = new PropertyAccessor (false, true, false, null, null);
+               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 ();
+               }
+               var d = new Delegate (name, return_type);
+               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;
+       }
+
+       Method parse_constructor () {
+               start_element ("constructor");
+               string name = reader.get_attribute ("name");
+               next ();
+
+               var return_type = parse_return_value ();
+
+               var m = new CreationMethod (null, name);
+               m.access = SymbolAccessibility.PUBLIC;
+               m.has_construct_function = false;
+               if (m.name.has_prefix ("new_")) {
+                       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");
+               }
+               end_element ("constructor");
+               return m;
+       }
+
+       Method parse_method () {
+               start_element ("method");
+               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 ();
+               }
+               var m = new Method (name, return_type);
+               m.access = SymbolAccessibility.PUBLIC;
+               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");
+               }
+               end_element ("method");
+               return m;
+       }
+
+       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;
+               sig.is_virtual = true;
+               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;
+               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") {
+                               st.add_method (parse_method ());
+                       } 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;
+               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") {
+                               st.add_method (parse_method ());
+                       } 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 ();
+               var c = new Constant (name, type, null, null);
+               c.access = SymbolAccessibility.PUBLIC;
+               end_element ("constant");
+               return c;
+       }
+
+       public void parse_metadata (string metadata_filename) {
+               if (FileUtils.test (metadata_filename, FileTest.EXISTS)) {
+                       try {
+                               string metadata;
+                               ulong metadata_len;
+                               FileUtils.get_contents (metadata_filename, out metadata, out metadata_len);
+                               
+                               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);
+                                               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));
+               }
+       }
+
+       string? get_attribute (string node, string key) {
+               return attributes_map["%s/@%s".printf (node, key)];
+       }
+}
+
diff --git a/vapigen/valamarkupreader.vala b/vapigen/valamarkupreader.vala
new file mode 100644 (file)
index 0000000..81b2b44
--- /dev/null
@@ -0,0 +1,233 @@
+/* valamarkupreader.vala
+ *
+ * Copyright (C) 2008  Jürg Billeter
+ *
+ * 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;
+using Gee;
+
+/**
+ * Simple reader for a subset of XML.
+ */
+public class Vala.MarkupReader : Object {
+       public string filename { get; construct; }
+
+       public string name { get; private set; }
+
+       MappedFile mapped_file;
+
+       char* begin;
+       char* current;
+       char* end;
+
+       int line;
+       int column;
+
+       Map<string,string> attributes = new HashMap<string,string> (str_hash, str_equal);
+       bool empty_element;
+
+       public MarkupReader (string filename) {
+               this.filename = filename;
+       }
+       
+       construct {
+               try {
+                       mapped_file = new MappedFile (filename, false);
+                       begin = mapped_file.get_contents ();
+                       end = begin + mapped_file.get_length ();
+
+                       current = begin;
+
+                       line = 1;
+                       column = 1;
+               } catch (FileError e) {
+                       Report.error (null, "Unable to map file `%s': %s".printf (filename, e.message));
+               }
+       }
+
+       public string? get_attribute (string attr) {
+               return attributes[attr];
+       }
+
+       string read_name () {
+               char* begin = current;
+               while (current < end) {
+                       if (current[0] == ' ' || current[0] == '>'
+                           || current[0] == '/' || current[0] == '=') {
+                               break;
+                       }
+                       unichar u = ((string) current).get_char_validated ((long) (end - current));
+                       if (u != (unichar) (-1)) {
+                               current += u.to_utf8 (null);
+                       } else {
+                               Report.error (null, "invalid UTF-8 character");
+                       }
+               }
+               if (current == begin) {
+                       // syntax error: invalid name
+               }
+               return ((string) begin).ndup (current - begin);
+       }
+
+       public MarkupTokenType read_token (out SourceLocation token_begin, out SourceLocation token_end) {
+               attributes.clear ();
+
+               if (empty_element) {
+                       empty_element = false;
+                       return MarkupTokenType.END_ELEMENT;
+               }
+
+               space ();
+
+               MarkupTokenType type;
+               char* begin = current;
+               token_begin.pos = begin;
+               token_begin.line = line;
+               token_begin.column = column;
+
+               if (current >= end) {
+                       type = MarkupTokenType.EOF;
+               } else if (current[0] == '<') {
+                       current++;
+                       if (current >= end) {
+                               // error
+                       } else if (current[0] == '?') {
+                               // processing instruction
+                       } else if (current[0] == '!') {
+                               // comment or doctype
+                       } else if (current[0] == '/') {
+                               type = MarkupTokenType.END_ELEMENT;
+                               current++;
+                               name = read_name ();
+                               if (current >= end || current[0] != '>') {
+                                       // error
+                               }
+                               current++;
+                       } else {
+                               type = MarkupTokenType.START_ELEMENT;
+                               name = read_name ();
+                               space ();
+                               while (current < end && current[0] != '>' && current[0] != '/') {
+                                       string attr_name = read_name ();
+                                       if (current >= end || current[0] != '=') {
+                                               // error
+                                       }
+                                       current++;
+                                       // FIXME allow single quotes
+                                       if (current >= end || current[0] != '"') {
+                                               // error
+                                       }
+                                       current++;
+                                       char* attr_begin = current;
+                                       while (current < end && current[0] != '"') {
+                                               if (current[0] == '&') {
+                                                       // process &amp; &gt; &lt; &quot; &apos;
+                                               } else {
+                                                       unichar u = ((string) current).get_char_validated ((long) (end - current));
+                                                       if (u != (unichar) (-1)) {
+                                                               current += u.to_utf8 (null);
+                                                       } else {
+                                                               Report.error (null, "invalid UTF-8 character");
+                                                       }
+                                               }
+                                       }
+                                       string attr_value = ((string) attr_begin).ndup (current - attr_begin);
+                                       if (current >= end || current[0] != '"') {
+                                               // error
+                                       }
+                                       current++;
+                                       attributes.set (attr_name, attr_value);
+                                       space ();
+                               }
+                               if (current[0] == '/') {
+                                       empty_element = true;
+                                       current++;
+                                       space ();
+                               } else {
+                                       empty_element = false;
+                               }
+                               if (current >= end || current[0] != '>') {
+                                       // error
+                               }
+                               current++;
+                       }
+               } else {
+                       space ();
+                       char* text_begin = current;
+                       while (current < end && current[0] != '<') {
+                               if (current[0] == '&') {
+                                       // process &amp; &gt; &lt; &quot; &apos;
+                               } else {
+                                       unichar u = ((string) current).get_char_validated ((long) (end - current));
+                                       if (u != (unichar) (-1)) {
+                                               current += u.to_utf8 (null);
+                                       } else {
+                                               Report.error (null, "invalid UTF-8 character");
+                                       }
+                               }
+                       }
+                       if (text_begin == current) {
+                               // no text
+                               // read next token
+                               return read_token (out token_begin, out token_end);
+                       }
+                       type = MarkupTokenType.TEXT;
+                       string text = ((string) text_begin).ndup (current - text_begin);
+               }
+
+               column += (int) (current - begin);
+
+               token_end.pos = current;
+               token_end.line = line;
+               token_end.column = column - 1;
+
+               return type;
+       }
+
+       void space () {
+               while (current < end && current[0].isspace ()) {
+                       if (current[0] == '\n') {
+                               line++;
+                               column = 0;
+                       }
+                       current++;
+                       column++;
+               }
+       }
+}
+
+public enum Vala.MarkupTokenType {
+       NONE,
+       START_ELEMENT,
+       END_ELEMENT,
+       TEXT,
+       EOF;
+
+       public weak string to_string () {
+               switch (this) {
+               case START_ELEMENT: return "start element";
+               case END_ELEMENT: return "end element";
+               case TEXT: return "text";
+               case EOF: return "end of file";
+               default: return "unknown token type";
+               }
+       }
+}
+
index cb4882f..dded1d1 100644 (file)
@@ -33,12 +33,14 @@ class Vala.VAPIGen : Object {
        static string library;
        [NoArrayLength ()]
        static string[] packages;
+       static string metadata_filename;
        CodeContext context;
 
        const OptionEntry[] options = {
                { "vapidir", 0, 0, OptionArg.FILENAME_ARRAY, ref vapi_directories, "Look for package bindings in DIRECTORY", "DIRECTORY..." },
                { "pkg", 0, 0, OptionArg.STRING_ARRAY, ref packages, "Include binding for PACKAGE", "PACKAGE..." },
                { "library", 0, 0, OptionArg.STRING, ref library, "Library name", "NAME" },
+               { "metadata", 0, 0, OptionArg.FILENAME, ref metadata_filename, "Metadata filename", "FILE" },
                { "directory", 'd', 0, OptionArg.FILENAME, ref directory, "Output directory", "DIRECTORY" },
                { "version", 0, 0, OptionArg.NONE, ref version, "Display version number", null },
                { "quiet", 'q', 0, OptionArg.NONE, ref quiet_mode, "Do not print messages to the console", null },
@@ -157,6 +159,16 @@ class Vala.VAPIGen : Object {
                        return quit ();
                }
                
+               var girparser = new GirParser ();
+               if (metadata_filename != null) {
+                       girparser.parse_metadata (metadata_filename);
+               }
+               girparser.parse (context);
+               
+               if (Report.get_errors () > 0) {
+                       return quit ();
+               }
+               
                var gidlparser = new GIdlParser ();
                gidlparser.parse (context);