using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.Contracts; using Microsoft.Boogie; namespace Microsoft.Dafny { public class Util { public static string Comma(IEnumerable l, Func f) { return Comma(",", l, f); } public static string Comma(string comma, IEnumerable l, Func f) { string res = ""; string c = ""; foreach(var t in l) { res += c + f(t); c = comma; } return res; } public static List Map(IEnumerable xs, Func f) { List ys = new List(); foreach (A x in xs) { ys.Add(f(x)); } return ys; } public static List Nil() { return new List(); } public static List Singleton(A x) { return new List { x }; } public static List Cons(A x, List xs) { return Concat(Singleton(x), xs); } public static List Snoc(List xs, A x) { return Concat(xs, Singleton(x)); } public static List Concat(List xs, List ys) { List cpy = new List(xs); cpy.AddRange(ys); return cpy; } public static Dictionary Dict(IEnumerable xs, IEnumerable ys) { return Dict(xs.Zip(ys)); } public static Dictionary Dict(IEnumerable> xys) { Dictionary res = new Dictionary(); foreach (var p in xys) { res[p.Item1] = p.Item2; } return res; } /// /// Returns s but with all occurrences of '_' removed. /// public static string RemoveUnderscores(string s) { Contract.Requires(s != null); return s.Replace("_", ""); } /// /// For "S" returns S and false. /// For @"S" return S and true. /// Assumes that s has one of these forms. /// public static string RemoveParsedStringQuotes(string s, out bool isVerbatimString) { Contract.Requires(s != null); var len = s.Length; if (s[0] == '@') { isVerbatimString = true; return s.Substring(2, len - 3); } else { isVerbatimString = false; return s.Substring(1, len - 2); } } /// /// Replaced any escaped characters in s by the actual character that the escaping represents. /// Assumes s to be a well-parsed string. /// public static string RemoveEscaping(string s, bool isVerbatimString) { Contract.Requires(s != null); var sb = new StringBuilder(); UnescapedCharacters(s, isVerbatimString).Iter(ch => sb.Append(ch)); return sb.ToString(); } /// /// Returns the characters of the well-parsed string p, replacing any /// escaped characters by the actual characters. /// public static IEnumerable UnescapedCharacters(string p, bool isVerbatimString) { Contract.Requires(p != null); if (isVerbatimString) { var skipNext = false; foreach (var ch in p) { if (skipNext) { skipNext = false; } else { yield return ch; if (ch == '"') { skipNext = true; } } } } else { var i = 0; while (i < p.Length) { char special = ' '; // ' ' indicates not special if (p[i] == '\\') { switch (p[i + 1]) { case '\'': special = '\''; break; case '\"': special = '\"'; break; case '\\': special = '\\'; break; case '\0': special = '\0'; break; case 'n': special = '\n'; break; case 'r': special = '\r'; break; case 't': special = '\t'; break; case 'u': int ch = HexValue(p[i + 2]); ch = 16 * ch + HexValue(p[i + 3]); ch = 16 * ch + HexValue(p[i + 4]); ch = 16 * ch + HexValue(p[i + 5]); yield return (char)ch; i += 6; continue; default: break; } } if (special != ' ') { yield return special; i += 2; } else { yield return p[i]; i++; } } } } /// /// Converts a hexadecimal digit to an integer. /// Assumes ch does represent a hexadecimal digit; alphabetic digits can be in either case. /// public static int HexValue(char ch) { if ('a' <= ch && ch <= 'f') { return ch - 'a' + 10; } else if ('A' <= ch && ch <= 'F') { return ch - 'A' + 10; } else { return ch - '0'; } } /// /// Class dedicated to traversing the function call graph /// class FunctionCallFinder : TopDownVisitor> { protected override bool VisitOneExpr(Expression expr, ref List calls) { if (expr is FunctionCallExpr) { calls.Add(((FunctionCallExpr)expr).Function); } return true; } } static Graph BuildFunctionCallGraph(Dafny.Program program) { Graph functionCallGraph = new Graph(); FunctionCallFinder callFinder = new FunctionCallFinder(); foreach (var module in program.Modules) { foreach (var decl in module.TopLevelDecls) { if (decl is ClassDecl) { var c = (ClassDecl)decl; foreach (var member in c.Members) { if (member is Function) { var f = (Function)member; List calls = new List(); foreach (var e in f.Reads) { if (e != null && e.E != null) { callFinder.Visit(e.E, calls); } } foreach (var e in f.Req) { if (e != null) { callFinder.Visit(e, calls); } } foreach (var e in f.Ens) { if (e != null) { callFinder.Visit(e, calls); } } if (f.Body != null) { callFinder.Visit(f.Body, calls); } foreach (var callee in calls) { functionCallGraph.AddEdge(f, callee); } } } } } } return functionCallGraph; } /// /// Prints the program's function call graph in a format suitable for consumption in other tools /// public static void PrintFunctionCallGraph(Dafny.Program program) { var functionCallGraph = BuildFunctionCallGraph(program); foreach (var vertex in functionCallGraph.GetVertices()) { var func = vertex.N; Console.Write("{0},{1}=", func.CompileName, func.EnclosingClass.Module.CompileName); foreach (var callee in vertex.Successors) { Console.Write("{0} ", callee.N.CompileName); } Console.Write("\n"); } } /// /// Generic statistic counter /// static void IncrementStat(IDictionary stats, string stat) { ulong currentValue; if (stats.TryGetValue(stat, out currentValue)) { stats[stat] += 1; } else { stats.Add(stat, 1); } } /// /// Track the maximum value of some statistic /// static void UpdateMax(IDictionary stats, string stat, ulong val) { ulong currentValue; if (stats.TryGetValue(stat, out currentValue)) { if (val > currentValue) { stats[stat] = val; } } else { stats.Add(stat, val); } } /// /// Compute various interesting statistics about the Dafny program /// public static void PrintStats(Dafny.Program program) { SortedDictionary stats = new SortedDictionary(); foreach (var module in program.Modules) { IncrementStat(stats, "Modules"); UpdateMax(stats, "Module height (max)", (ulong)module.Height); ulong num_scc = (ulong)module.CallGraph.TopologicallySortedComponents().Count; UpdateMax(stats, "Call graph width (max)", num_scc); foreach (var decl in module.TopLevelDecls) { if (decl is DatatypeDecl) { IncrementStat(stats, "Datatypes"); } else if (decl is ClassDecl) { var c = (ClassDecl)decl; if (c.Name != "_default") { IncrementStat(stats, "Classes"); } foreach (var member in c.Members) { if (member is Function) { IncrementStat(stats, "Functions (total)"); var f = (Function)member; if (f.IsRecursive) { IncrementStat(stats, "Functions recursive"); } } else if (member is Method) { IncrementStat(stats, "Methods (total)"); var method = (Method)member; if (method.IsRecursive) { IncrementStat(stats, "Methods recursive"); } if (method.IsGhost) { IncrementStat(stats, "Methods ghost"); } } } } } } // Print out the results, with some nice formatting Console.WriteLine(""); Console.WriteLine("Statistics"); Console.WriteLine("----------"); int max_key_length = 0; foreach (var key in stats.Keys) { if (key.Length > max_key_length) { max_key_length = key.Length; } } foreach (var keypair in stats) { string keyString = keypair.Key.PadRight(max_key_length + 2); Console.WriteLine("{0} {1,4}", keyString, keypair.Value); } } } }