/* gtkcommentparser.vala * * Copyright (C) 2011-2014 Florian Brosch * * 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: * Florian Brosch */ using Valadoc.Content; using Valadoc.Gtkdoc; public class Valadoc.Gtkdoc.Parser : Object, ResourceLocator { private Scanner scanner = new Scanner (); private Token current; private Vala.List stack = new Vala.ArrayList (str_equal); private Vala.List> footnotes = new Vala.ArrayList> (); private ContentFactory factory; private ErrorReporter reporter; private Settings settings; private Api.Tree tree; private Api.Node? element; private bool show_warnings; private Api.SourceComment comment; private unowned string instance_param_name; private string[]? comment_lines; private Regex? is_numeric_regex = null; private Regex? normalize_regex = null; private Regex regex_source_lang = null; private Importer.InternalIdRegistrar id_registrar = null; private GirMetaData? current_metadata = null; private inline string fix_resource_path (string path) { return this.current_metadata.get_resource_path (path); } private void reset (Api.SourceComment comment) { this.scanner.reset (comment.content); this.show_warnings = !comment.file.package.is_package; this.comment_lines = null; this.footnotes.clear (); this.comment = comment; this.current = null; this.stack.clear (); } private string normalize (string text) { try { return normalize_regex.replace (text, -1, 0, " "); } catch (RegexError e) { assert_not_reached (); } } private bool is_numeric (string str) { return is_numeric_regex.match (str); } private void report_unexpected_token (Token got, string expected) { report_warning (got, "Unexpected Token: %s (Expected: %s)".printf (got.to_string (), expected)); } private void report_warning (Token got, string message) { if (!this.show_warnings) { return ; } int startpos = (got.line == 0)? comment.first_column + got.first_column : got.first_column; int endpos = (got.line == 0)? comment.first_column + got.last_column : got.last_column; if (this.comment_lines == null) { this.comment_lines = this.comment.content.split ("\n"); } this.reporter.warning (this.comment.file.get_name (), comment.first_line + got.line, startpos + 1, endpos + 1, this.comment_lines[got.line], message); } public Parser (Settings settings, ErrorReporter reporter, Api.Tree tree, ModuleLoader modules) { this.factory = new ContentFactory (settings, this, modules); this.reporter = reporter; this.settings = settings; this.tree = tree; try { is_numeric_regex = new Regex ("^[+-]?([0-9]*\\.?[0-9]+|[0-9]+\\.?[0-9]*)([eE][+-]?[0-9]+)?$", RegexCompileFlags.OPTIMIZE); normalize_regex = new Regex ("( |\n|\t)+", RegexCompileFlags.OPTIMIZE); regex_source_lang = new Regex ("^"); } catch (RegexError e) { assert_not_reached (); } } private Note? _parse_note (Api.SourceComment comment) { Comment? cmnt = parse_root_content (comment); if (cmnt == null) { return null; } Note note = factory.create_note (); note.content.add_all (cmnt.content); return note; } private void add_note (ref Comment? comment, Note? note) { if (note == null) { return ; } if (comment == null) { comment = factory.create_comment (); } if (comment.content.size == 0) { comment.content.add (factory.create_paragraph ()); } comment.content.insert (1, note); } private void add_taglet (ref Comment? comment, Taglet? taglet) { if (taglet == null) { return ; } if (comment == null) { comment = factory.create_comment (); } comment.taglets.add (taglet); } public Comment? parse (Api.Node element, Api.GirSourceComment gir_comment, GirMetaData gir_metadata, Importer.InternalIdRegistrar id_registrar) { this.instance_param_name = gir_comment.instance_param_name; this.current_metadata = gir_metadata; this.id_registrar = id_registrar; this.element = element; Comment? cmnt = parse_root_content (gir_comment); if (cmnt != null) { ImporterHelper.extract_short_desc (cmnt, factory); } // deprecated: if (gir_comment.deprecated_comment != null) { Note? note = _parse_note (gir_comment.deprecated_comment); add_note (ref cmnt, note); } // version: if (gir_comment.version_comment != null) { Note? note = _parse_note (gir_comment.version_comment); add_note (ref cmnt, note); } // stability: if (gir_comment.stability_comment != null) { Note? note = _parse_note (gir_comment.stability_comment); add_note (ref cmnt, note); } // return: if (gir_comment.return_comment != null) { Taglet? taglet = parse_block_taglet (gir_comment.return_comment, "return"); add_taglet (ref cmnt, taglet); } // parameters: Vala.MapIterator iter = gir_comment.parameter_iterator (); for (bool has_next = iter.next (); has_next; has_next = iter.next ()) { Taglets.Param? taglet = parse_block_taglet (iter.get_value (), "param") as Taglets.Param; string param_name = iter.get_key (); taglet.is_c_self_param = (param_name == gir_comment.instance_param_name); taglet.parameter_name = param_name; add_taglet (ref cmnt, taglet); } bool first = true; foreach (Vala.List note in this.footnotes) { if (first == true && note.size > 0) { Paragraph p = note.first () as Paragraph; if (p == null) { p = factory.create_paragraph (); cmnt.content.add (p); } p.content.insert (0, factory.create_text ("\n")); } cmnt.content.add_all (note); first = false; } return cmnt; } private Taglet? parse_block_taglet (Api.SourceComment gir_comment, string taglet_name) { this.reset (gir_comment); current = null; next (); parse_docbook_spaces (false); var ic = parse_inline_content (); parse_docbook_spaces (false); if (current.type != TokenType.EOF) { this.report_unexpected_token (current, ""); return null; } BlockContent? taglet = factory.create_taglet (taglet_name) as BlockContent; assert (taglet != null); Paragraph paragraph = factory.create_paragraph (); paragraph.content.add (ic); taglet.content.add (paragraph); return taglet as Taglet; } private Comment? parse_root_content (Api.SourceComment gir_comment) { this.reset (gir_comment); current = null; next (); Token tmp = null; parse_docbook_spaces (false); Comment comment = factory.create_comment (); while (current.type != TokenType.EOF && tmp != current) { tmp = current; var ic = parse_inline_content (); if (ic != null && ic.content.size > 0) { Paragraph p = factory.create_paragraph (); p.content.add (ic); comment.content.add (p); } var bc = parse_block_content (); if (bc != null && bc.size > 0) { comment.content.add_all (bc); } } if (current.type != TokenType.EOF) { this.report_unexpected_token (current, ""); return null; } ImporterHelper.extract_short_desc (comment, factory); return comment; } // // Common: // private Token next () { current = scanner.next (); return current; } private bool ignore_current_xml_close () { if (current.type != TokenType.XML_CLOSE) { return false; } string name = current.content; if ((name in stack) == false) { return true; } return false; } private bool check_xml_open_tag (string tagname) { if ((current.type == TokenType.XML_OPEN && current.content != tagname) || current.type != TokenType.XML_OPEN) { return false; } stack.insert (0, tagname); return true; } private bool check_xml_close_tag (string tagname) { if ((current.type == TokenType.XML_CLOSE && current.content != tagname) || current.type != TokenType.XML_CLOSE) { return false; } assert (stack.remove_at (0) == tagname); return true; } private void parse_docbook_spaces (bool accept_paragraphs = true) { while (true) { if (current.type == TokenType.SPACE) { next (); } else if (current.type == TokenType.NEWLINE) { next (); } else if (accept_paragraphs && current.type == TokenType.GTKDOC_PARAGRAPH) { next (); } else { break; } } } // // Rules, Ground: // private Inline? parse_docbook_link_tempalte (string tagname, bool is_internal) { if (!check_xml_open_tag (tagname)) { this.report_unexpected_token (current, "<%s>".printf (tagname)); return null; } StringBuilder builder = new StringBuilder (); string url = current.attributes.get ("linkend"); next (); // TODO: check xml while (!(current.type == TokenType.XML_CLOSE && current.content == tagname) && current.type != TokenType.EOF) { if (current.type == TokenType.XML_OPEN) { } else if (current.type == TokenType.XML_CLOSE) { } else if (current.type == TokenType.XML_COMMENT) { } else { builder.append (current.content); } next (); } var link = factory.create_link (); if (is_internal) { link.id_registrar = id_registrar; } link.url = url; if (builder.len == 0) { link.content.add (factory.create_text (url)); } else { link.content.add (factory.create_text (normalize (builder.str))); } if (!check_xml_close_tag (tagname)) { this.report_unexpected_token (current, "".printf (tagname)); return link; } next (); return link; } private InlineTaglet? parse_symbol_link (string tagname) { if (!check_xml_open_tag (tagname)) { this.report_unexpected_token (current, "<%s>".printf (tagname)); return null; } if (next ().type == TokenType.SPACE) { next (); } InlineTaglet? taglet = null; if (current.type == TokenType.WORD && current.content == "struct") { next (); if (next ().type == TokenType.SPACE) { next (); } } if (current.type == TokenType.GTKDOC_FUNCTION || current.type == TokenType.GTKDOC_CONST || current.type == TokenType.GTKDOC_TYPE || current.type == TokenType.WORD || current.type == TokenType.GTKDOC_PROPERTY || current.type == TokenType.GTKDOC_SIGNAL) { taglet = this.create_type_link (current.content) as InlineTaglet; assert (taglet != null); } if (next ().type == TokenType.SPACE) { next (); } if (!check_xml_close_tag (tagname)) { this.report_unexpected_token (current, "".printf (tagname)); return taglet; } next (); return taglet; } private void parse_anchor () { if (!check_xml_open_tag ("anchor")) { this.report_unexpected_token (current, ""); return; } string id = current.attributes.get ("id"); if (id != null) { id_registrar.register_symbol (id, element); } next (); if (!check_xml_close_tag ("anchor")) { this.report_unexpected_token (current, ""); return; } next (); } private Link? parse_xref () { if (!check_xml_open_tag ("xref")) { this.report_unexpected_token (current, ""); return null; } string linkend = current.attributes.get ("linkend"); next (); Link link = factory.create_link (); link.content.add (factory.create_text (linkend)); link.id_registrar = id_registrar; link.url = linkend; if (!check_xml_close_tag ("xref")) { this.report_unexpected_token (current, ""); return link; } next (); return link; } private Run? parse_highlighted_template (string tag_name, Run.Style style) { if (!check_xml_open_tag (tag_name)) { this.report_unexpected_token (current, "<%s>".printf (tag_name)); return null; } next (); Run run = parse_inline_content (); if (run.style != Run.Style.NONE && run.style != style) { Run tmp = factory.create_run (style); tmp.content.add (run); run = tmp; } else { run.style = style; } if (!check_xml_close_tag (tag_name)) { this.report_unexpected_token (current, "".printf (tag_name)); return run; } next (); return run; } private ListItem? parse_docbook_listitem () { if (!check_xml_open_tag ("listitem")) { this.report_unexpected_token (current, ""); return null; } next (); ListItem item = factory.create_list_item (); item.content.add_all (parse_mixed_content ()); if (!check_xml_close_tag ("listitem")) { this.report_unexpected_token (current, ""); return item; } next (); return item; } private BlockContent? parse_docbook_information_box_template (string tagname, BlockContent container) { if (!check_xml_open_tag (tagname)) { this.report_unexpected_token (current, "<%s>".printf (tagname)); return null; } next (); parse_docbook_spaces (); Token tmp = null; while (current.type != TokenType.XML_CLOSE && current.type != TokenType.EOF) { tmp = current; var ic = parse_inline_content (); if (ic != null && ic.content.size > 0) { Paragraph p = factory.create_paragraph (); p.content.add (ic); container.content.add (p); } var bc = parse_block_content (); if (bc != null && bc.size > 0) { container.content.add_all (bc); } } parse_docbook_spaces (); if (!check_xml_close_tag (tagname)) { this.report_unexpected_token (current, "".printf (tagname)); return container; } next (); return container; } private Note? parse_docbook_important () { return (Note?) parse_docbook_information_box_template ("important", factory.create_note ()); } private Note? parse_docbook_note () { return (Note?) parse_docbook_information_box_template ("note", factory.create_note ()); } private Warning? parse_docbook_warning () { return (Warning?) parse_docbook_information_box_template ("warning", factory.create_warning ()); } private inline Vala.Collection? parse_docbook_orderedlist () { return parse_docbook_itemizedlist ("orderedlist", Content.List.Bullet.ORDERED); } private Vala.Collection? parse_docbook_itemizedlist (string tag_name = "itemizedlist", Content.List.Bullet bullet_type = Content.List.Bullet.UNORDERED) { if (!check_xml_open_tag (tag_name)) { this.report_unexpected_token (current, "<%s>".printf (tag_name)); return null; } next (); Vala.Collection content = new Vala.ArrayList (); parse_docbook_spaces (); if (current.type == TokenType.XML_OPEN && current.content == "title") { append_block_content_not_null (content, parse_docbook_title ()); parse_docbook_spaces (); } Content.List list = factory.create_list (); list.bullet = bullet_type; content.add (list); while (current.type == TokenType.XML_OPEN) { if (current.content == "listitem") { list.items.add (parse_docbook_listitem ()); } else { break; } parse_docbook_spaces (); } if (!check_xml_close_tag (tag_name)) { this.report_unexpected_token (current, "".printf (tag_name)); return content; } next (); return content; } private Paragraph? parse_gtkdoc_paragraph () { if (current.type != TokenType.GTKDOC_PARAGRAPH) { this.report_unexpected_token (current, ""); return null; } next (); Paragraph p = factory.create_paragraph (); Run? run = parse_inline_content (); p.content.add (run); return p; } private Vala.Collection parse_mixed_content () { Vala.Collection content = new Vala.ArrayList (); Token tmp = null; while (tmp != current) { tmp = current; parse_docbook_spaces (); Run? run = parse_inline_content (); if (run != null && run.content.size > 0) { Paragraph p = factory.create_paragraph (); p.content.add (run); content.add (p); continue; } Vala.Collection lst = parse_block_content (); if (lst != null && lst.size > 0) { content.add_all (lst); continue; } } return content; } private inline Vala.Collection? parse_docbook_simpara () { return parse_docbook_para ("simpara"); } private Vala.Collection? parse_docbook_para (string tag_name = "para") { if (!check_xml_open_tag (tag_name)) { this.report_unexpected_token (current, "<%s>".printf (tag_name)); return null; } next (); Vala.Collection content = parse_mixed_content (); // ignore missing to match gtkdocs behaviour if (!check_xml_close_tag (tag_name) && current.type != TokenType.EOF) { this.report_unexpected_token (current, "".printf (tag_name)); return content; } next (); return content; } private Paragraph? parse_gtkdoc_source () { if (current.type != TokenType.GTKDOC_SOURCE_OPEN) { this.report_unexpected_token (current, "|["); return null; } StringBuilder builder = new StringBuilder (); Token source_token = current; for (next (); current.type != TokenType.EOF && current.type != TokenType.GTKDOC_SOURCE_CLOSE; next ()) { if (current.type == TokenType.WORD) { builder.append (current.content); } else if (current.type != TokenType.XML_COMMENT) { builder.append_len (current.start, current.length); } } SourceCode code = factory.create_source_code (); MatchInfo info; unowned string source = builder.str; if (regex_source_lang.match (source, 0, out info)) { string lang_name = info.fetch (1).down (); SourceCode.Language? lang = SourceCode.Language.from_string (lang_name); code.language = lang; if (lang == null) { report_warning (source_token, "Unknown language `%s' in source code block |[