/* * Copyright (c) 2003-2008 The University of Wroclaw. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the University may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN * NO EVENT SHALL THE UNIVERSITY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ using Nemerle.Collections; using Nemerle.IO; using Nemerle.Compiler; using Nemerle.Compiler.Parsetree; using Nemerle.Utility; using System; using System.Xml; namespace Nemerle.Compiler { public class XmlDoc { document : XmlDocument; docNode : XmlNode; mutable last_loc : Location; internal this (comments : Map [Location, string], output : string) { this.Comments = comments; this.OutputFileName = output; document = XmlDocument (); _ = document.AppendChild (document.CreateXmlDeclaration ("1.0", null, null)); def d = document.AppendChild (document.CreateElement ("doc")); def name = d.AppendChild (document.CreateElement ("assembly")). AppendChild (document.CreateElement ("name")); _ = name.AppendChild (document.CreateTextNode (this.OutputFileName)); docNode = d.AppendChild (d.AppendChild (document.CreateElement ("members"))); } OutputFileName : string; Comments : Map [Location, string]; internal Save () : void { document.Save (OutputFileName); } CreateMember (name : string) : XmlNode { def mem = document.CreateElement ("member"); def attr = document.CreateAttribute("name"); attr.Value = name; _ = mem.SetAttributeNode(attr); mem } DumpMember (m : IMember) : void { def member = docNode.AppendChild (CreateMember (GetKey(m))); add_comments (member, m); } variant XToken { | EmptyLine | EOF | Text { t : string; } | XmlStart { n : string; v : string; } | XmlEnd { n : string; } | WhiteSpace { w : string } public override ToString () : string { match (this) { | EmptyLine => "[EmptyLine]" | EOF => "[EOF]" | Text (t) => "\"" + t + "\"" | XmlStart (n, v) => "<" + n + " " + v + ">" | XmlEnd (n) => "" | WhiteSpace (w) => "[" + w + "]" } } } static tokenize (comment : string) : array [XToken] { def lines = comment.Split (array ['\n']); mutable all_stars = true; def white_space = array [' ', '\t', '\r', '\n']; def res = System.Collections.Generic.List (); for (mutable i = 0; i < lines.Length; ++i) { lines[i] = lines[i].TrimEnd (white_space); when (i > 0 && lines[i].Trim (white_space) != "" && !lines [i].TrimStart (white_space).StartsWith ("*")) all_stars = false; } when (all_stars) for (mutable i = 1; i < lines.Length; ++i) when (lines [i].Trim (white_space) != "") lines [i] = lines [i].TrimStart (white_space).Substring (1); def buf = string.Join ("\n", lines) + "\n"; mutable i = 0; mutable something_since_newline = false; while (i < buf.Length) { def start = i; def space_tab (ch) { ch == ' ' || ch == '\t' } def scan (pred) { while (i < buf.Length && pred (buf [i])) i++; buf.Substring (start, i - start) } if (space_tab (buf [i])) res.Add (XToken.WhiteSpace (scan (space_tab))); else if (buf [i] == '\n') { match (scan (_ == '\n')) { | "\n" => res.Add (XToken.WhiteSpace ("\n")); | s => res.Add (XToken.WhiteSpace (s)); res.Add (XToken.EmptyLine ()); } something_since_newline = false; } else if (i + 1 < buf.Length && buf [i] == '<' && buf [i+1] == '[') { i += 2; if (something_since_newline) res.Add (XToken.XmlStart ("c", "")); else { def spaces = scan (space_tab).Substring (2); def tag = if (i >= buf.Length || buf [i] == '\n') "code" else "c"; res.Add (XToken.XmlStart (tag, "")); res.Add (XToken.WhiteSpace (spaces)); something_since_newline = true; } } else if (i + 1 < buf.Length && buf [i] == ']' && buf [i+1] == '>') { i += 2; res.Add (XToken.XmlEnd ("")); } else if (buf [i] == ']') { i++; something_since_newline = true; res.Add (XToken.Text ("]")); } else if (buf [i] == '<') { something_since_newline = true; def v = scan (_ != '>'); if (i == buf.Length) // location? Message.Warning ("unfinished XML tag"); else i++; def v = v.Substring (1).Trim (white_space); if (v.StartsWith ("/")) res.Add (XToken.XmlEnd (v.Substring (1))); else { def idx = v.IndexOfAny (array [' ', '\t']); if (idx == -1) if (v.EndsWith ("/")) { def v = v.Substring (0, v.Length - 1); res.Add (XToken.XmlStart (v, "")); res.Add (XToken.XmlEnd (v)); } else res.Add (XToken.XmlStart (v, "")); else { def name = v.Substring (0, idx); def args = v.Substring (idx); if (args.EndsWith ("/")) { def args = args.Substring (0, args.Length - 1); res.Add (XToken.XmlStart (name, args)); res.Add (XToken.XmlEnd (name)); } else res.Add (XToken.XmlStart (name, args)); } } } else { something_since_newline = true; res.Add (XToken.Text (scan (c => c != '<' && c != '\n' && c != ']'))); } } res.Add (XToken.EOF ()); // Message.Debug ("tokens: " + res.ToArray ().ToList().ToString(" ")); res.ToArray (); } /* Top-level tags: , , , , , , , , , Other tags: , , , , , , , , , */ parse_comment (comment : string) : XmlNode { def is_top_tag (_) { | "summary" | "remarks" | "example" | "exception" | "param" | "permission" | "returns" | "seealso" | "include" | "value" => true | _ => false } def is_text_holding (_) { | "para" | "code" | "list" => true | _ => false } def tokens = tokenize (comment).ToList ().GetEnumerator (); // stack of currently active tags, along with information if // they were user-supplied mutable tags = []; def sb = NStringBuilder (""); def skip_ws () { match (tokens.Current) { | WhiteSpace | EmptyLine => _ = tokens.MoveNext (); skip_ws () | _ => {} } } def output (_ : XToken) { | EmptyLine | EOF => {} | WhiteSpace (t) | Text (t) => _ = sb.Append (t) | XmlStart (n, v) => _ = sb.Append ("<" + n + v + ">"); | XmlEnd (n) => _ = sb.Append (""); } def output_and_move () { output (tokens.Current); _ = tokens.MoveNext (); } def possibly_close (pred) { match (tags) { | (name, false) :: xs when pred (name) => tags = xs; output (XToken.XmlEnd (name)); | _ => {} } } def open_fake (name) { tags ::= (name, false); output (XToken.XmlStart (name, "")); } _ = tokens.MoveNext (); skip_ws (); match (tokens.Current) { | XmlStart (n, _) when is_top_tag (n) => output_and_move (); skip_ws (); tags ::= (n, true); | _ => open_fake ("summary") } while (! (tokens.Current is XToken.EOF)) { def t = tokens.Current; match (t) { | EOF => {} | EmptyLine => possibly_close (_ == "para"); possibly_close (_ == "summary"); | Text => // TODO: for we should strip some common prefix when (!tags.Exists ((n, _) => is_top_tag (n))) open_fake ("remarks"); when (!tags.Exists ((n, _) => is_text_holding (n))) open_fake ("para"); output (t); | XmlStart (n, _) => when (is_text_holding (n) || is_top_tag (n)) possibly_close (_ == "para"); when (is_top_tag (n)) // any auto summary/remarks possibly_close (is_top_tag); when (!is_top_tag (n) && !tags.Exists ((n, _) => is_top_tag (n))) open_fake ("remarks"); when (!is_text_holding (n) && !is_top_tag (n) && !tags.Exists ((n, _) => is_text_holding (n))) open_fake ("para"); tags ::= (n, true); output (t); | XmlEnd (n) => // close any auto tags at the top while (tags is (_, false) :: _) possibly_close (_ => true); match (tags) { | (n', true) :: xs when n' == n || n == "" => tags = xs; output (XToken.XmlEnd (n')); | _ => Message.Warning ($"trying to close XML tag <$n> that is not open") } | WhiteSpace => output (t) } _ = tokens.MoveNext (); } while (tags is (_, false) :: _) possibly_close (_ => true); foreach ((n, true) in tags) Message.Warning ($"unclosed XML tag <$n>"); def frag = document.CreateDocumentFragment (); //Message.Debug ($"comment: $sb"); frag.InnerXml = sb.ToString (); frag } add_comments (x : XmlNode, mem : IMember) : void { def curr = mem.Location; def choose (loc : Location, com, acc) { if (loc.File == curr.File && loc.CompareTo (curr) <= 0 && (curr.CompareTo (last_loc) < 0 || loc.CompareTo (last_loc) > 0)) com; else acc } Util.locate (curr, { def comment = Comments.Fold (null, choose); when (comment != null && comment != "") { try { _ = x.AppendChild (parse_comment (comment)); } catch { | e is XmlException => Message.Warning ("Cannot parse XML in comment: " + e.Message); } } }); last_loc = curr; } internal DumpType (t : TypeInfo) : void { def node = docNode.AppendChild (CreateMember (GetKey(t))); add_comments (node, t); def mems = t.GetMembers (BindingFlags.Static %| BindingFlags.Instance %| BindingFlags.Public %| BindingFlags.NonPublic %| BindingFlags.DeclaredOnly); List.Iter (mems, DumpMember); } // Append a method parameter type name. // static AppendParmTypeName (sb : NStringBuilder, p : TyVar, m : IMethod) : NStringBuilder { // Append a generic parameter type name, which is an index actually. // def appendStaticTyVarName (sb, tyvar) { def indexOf (lst, elem) { def loop(l, a, idx) { match (l) { | h :: t => if (h.Equals (a)) idx else loop (t, a, idx + 1) | [] => -1 } } loop(lst, elem, 0) } match (indexOf (m.GetHeader ().typarms, tyvar)) { | -1 => match (indexOf (m.DeclaringType.Typarms, tyvar)) { | -1 => Message.Warning ($"Unknown type parameter $tyvar"); // Should never be happen. sb.Append ("???"); | x => sb.Append ("`").Append (x); // The declaring type generic parameter index. } | x => sb.Append ("``").Append (x); // The method generic parameter index. } } // Append type name and, optional, generic argument names. // def appendTypeNameAndArgs (sb, typeName, args) { sb.Append (typeName) .AppendUnless (args.IsEmpty, sb => sb.Append ("{").AppendList (args, (sb, e) => AppendParmTypeName (sb, e, m), ",").Append ("}")); } // Append function argument types & return type. // def appendFunctionTypeArgs (sb, from, to) { def args = match (from.Fix ()) { | MType.Void => []; | MType.Tuple (args) => args; | _ => [from]; } match (to.Fix ()) { | MType.Void => appendTypeNameAndArgs (sb, "Nemerle.Builtins.FunctionVoid", args); | _ => appendTypeNameAndArgs (sb, "Nemerle.Builtins.Function", args.Append ([to])); } } def appendArrayType (sb, t, rank) { AppendParmTypeName(sb, t, m) .Append ("[") // Seems to be a bug in the c# compiler: // multidimension arrays are 0 based, while unidimension arrays are -1 based. // Since all xml doc tools are aware of this bug, we must be compatible with it too. // .AppendWhen(rank > 1, sb => sb.AppendNTimes (rank, "0:", ",")) .Append ("]"); } match (p.Fix ()) { | Class (tycon, args) => appendTypeNameAndArgs (sb, tycon.FullName, args); | TyVarRef (tyvar) => appendStaticTyVarName (sb, tyvar); | Array (t, rank) => appendArrayType (sb, t, rank); | Tuple (args) => appendTypeNameAndArgs (sb, "Nemerle.Builtins.Tuple", args); | Fun (from, to) => appendFunctionTypeArgs (sb, from, to); | Ref (t) | Out (t) => AppendParmTypeName (sb, t, m).Append("@"); | Void => sb.Append ("System.Void"); | Intersection => Util.ice ("Got MType.Intersection for doc comment"); } } public static GetKey (m : IMember) : string { // Since typical member key has at least four parts // (prefix, declaring type, dot, name) it is good // to use a string builder here. // def sb = NStringBuilder (); def appendTypeName (sb, typeInfo) { def tyParmsCount = typeInfo.Typarms.Length; if (tyParmsCount == 0) sb.Append (typeInfo.FullName) else if (typeInfo.DeclaringType == null) sb.Append (typeInfo.FullName).Append ("`").Append (tyParmsCount); else appendTypeName (sb, typeInfo.DeclaringType) .Append (".").Append (typeInfo.Name) .Append ("`").Append (tyParmsCount - typeInfo.DeclaringType.Typarms.Length); } (match (m.MemberKind) { | MemberKinds.Field with prefix = "F:" | MemberKinds.Event with prefix = "E:" | MemberKinds.Property with prefix = "P:" => appendTypeName (sb.Append (prefix), m.DeclaringType) .Append (".").Append (m.Name.Replace ('.', '#')); | MemberKinds.Constructor | MemberKinds.Method => def method = m :> IMethod; def parms = method.GetParameters (); def tyParmsCount = method.GetHeader ().typarms.Length; appendTypeName (sb.Append ("M:"), method.DeclaringType) .Append (".").Append (m.Name.Replace ('.', '#')) .AppendUnless (tyParmsCount == 0, sb => sb.Append ("``").Append (tyParmsCount)) .AppendUnless (parms.IsEmpty, sb => sb.Append ("(").AppendList (parms, (sb, p) => AppendParmTypeName (sb, p.ty, method), ",").Append (")")) .AppendWhen (method.Name == "op_Explicit" || method.Name == "op_Implicit", sb => AppendParmTypeName (sb.Append("~"), method.ReturnType, method)); | MemberKinds.TypeInfo | MemberKinds.NestedType => appendTypeName(sb.Append ("T:"), m :> TypeInfo); | other => Util.ice ($"Invalid MemberKinds for doc comment: `$other'"); }).ToString (); } } } // end ns