diff --git a/src/kOS.Safe/Encapsulation/EnumerableValue.cs b/src/kOS.Safe/Encapsulation/EnumerableValue.cs index 23a31a207d..66df27a093 100644 --- a/src/kOS.Safe/Encapsulation/EnumerableValue.cs +++ b/src/kOS.Safe/Encapsulation/EnumerableValue.cs @@ -1,5 +1,6 @@ using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Serialization; +using System.Text; using System.Collections; using System.Collections.Generic; using System.Linq; @@ -37,11 +38,53 @@ public bool Contains(T item) return InnerEnumerable.Contains(item); } + public override string ToStringIndented(int level) + { + if (level >= TerminalFormatter.MAX_INDENT_LEVEL) + return "<>"; + + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + + int cnt = this.Count(); + if (cnt == 0) + sb.Append(string.Format("{0} (empty)", KOSName)); + else if (cnt == 1) + sb.Append(string.Format("{0} of 1 item:", KOSName)); + else + sb.Append(string.Format("{0} of {1} items:", KOSName, cnt)); + + sb.Append(string.Format("\n{0}",ToStringItems(level + 1))); + return sb.ToString(); + } + public override string ToString() { - return new SafeSerializationMgr(null).ToString(this); + StringBuilder sb = new StringBuilder(); + + int cnt = this.Count(); + if (cnt == 0) + sb.Append(string.Format("{0} (empty)", KOSName)); + else if (cnt == 1) + sb.Append(string.Format("{0} of 1 item:", KOSName)); + else + sb.Append(string.Format("{0} of {1} items:", KOSName, cnt)); + + sb.Append(string.Format("\n{0}",ToStringItems(1))); + return sb.ToString(); } + /// + /// Print the inner items (not the header) of a container. Override this and this + /// enumerable structure will use it in its ToString() and its ToStringIndented(). + /// IMPORTANT: If your enumerable contains zero things, then return empty string, not even + /// a newline. If your enumerable contains at least one thing, then print + /// a line break at the end of each thing. + /// + /// you must pad all lines with this level of indent*TerminalFormatter.INDENT_SPACES + /// + public abstract string ToStringItems(int level); + public override Dump Dump() { var result = new DumpWithHeader diff --git a/src/kOS.Safe/Encapsulation/Lexicon.cs b/src/kOS.Safe/Encapsulation/Lexicon.cs index e621281051..d959a17c3b 100644 --- a/src/kOS.Safe/Encapsulation/Lexicon.cs +++ b/src/kOS.Safe/Encapsulation/Lexicon.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Text; using kOS.Safe.Utilities; using kOS.Safe.Function; @@ -297,9 +298,49 @@ public void SetIndex(int index, Structure value) internalDictionary[FromPrimitiveWithAssert(index)] = value; } + public override string ToStringIndented(int level) + { + // eraseme - I NOTICED I REPEATED THIS EXACT SNIP OF CODE CUT-N-PASTED A FEW TIMES + // eraseme - IN DIFFERENT PLACES. THIS IS PROBABLY A CANDIDATE FOR MAKING INTO ONE + // eraseme - COMMON UTILITY METHOD (THE PART THAT PRINTS THIS HEADER WHEN A ToStringIndented() + // eraseme - WANTS TO). + if (level >= TerminalFormatter.MAX_INDENT_LEVEL) + return "<>"; + + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + + int cnt = this.Count(); + if (cnt == 0) + sb.Append(string.Format("{0} (empty)", KOSName)); + else if (cnt == 1) + sb.Append(string.Format("{0} of 1 item:", KOSName)); + else + sb.Append(string.Format("{0} of {1} items:", KOSName, cnt)); + + sb.Append(string.Format("\n{0}", ToStringItems(level + 1))); + return sb.ToString(); + } + + public string ToStringItems(int level) + { + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + foreach (Structure key in internalDictionary.Keys) + { + Structure val = internalDictionary[key]; + sb.Append(string.Format("{0}[{1}] = {2}\n", + pad, + key.ToString(), + val.ToStringIndented(level) + )); + } + return sb.ToString(); + } + public override string ToString() { - return new SafeSerializationMgr(null).ToString(this); + return ToStringIndented(0); } // Try to call the normal SetSuffix that all structures do, but if that fails, diff --git a/src/kOS.Safe/Encapsulation/ListValue.cs b/src/kOS.Safe/Encapsulation/ListValue.cs index 5650380282..a204be7757 100644 --- a/src/kOS.Safe/Encapsulation/ListValue.cs +++ b/src/kOS.Safe/Encapsulation/ListValue.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; using kOS.Safe.Properties; @@ -73,6 +74,30 @@ public override void LoadDump(Dump dump) } } + public override string ToStringItems(int level) + { + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + int cnt = this.Count(); + int digitWidth = Utilities.KOSMath.DecimalDigitsIn(cnt); + for (int i = 0; i < cnt; ++i) + { + Structure asStructure = this[i] as Structure; + if (asStructure != null) + { + sb.Append(string.Format("{0}[{1}] = {2}\n", + pad, + i.ToString().PadLeft(digitWidth), + asStructure.ToStringIndented(level) + )); + } + else // Hypothetically this case should not happen, but if we screwed up somewhere so it does, at least you can see something. + { + sb.Append(this[i].ToString()); + } + } + return sb.ToString(); + } private void ListInitializeSuffixes() { AddSuffix("COPY", new NoArgsSuffix> (() => new ListValue(this))); diff --git a/src/kOS.Safe/Encapsulation/QueueValue.cs b/src/kOS.Safe/Encapsulation/QueueValue.cs index 9c91ac7e3e..ee9f252504 100644 --- a/src/kOS.Safe/Encapsulation/QueueValue.cs +++ b/src/kOS.Safe/Encapsulation/QueueValue.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text; using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Serialization; using kOS.Safe.Function; @@ -63,6 +64,33 @@ public static QueueValue CreateQueue(IEnumerable list) { return new QueueValue(list.Cast()); } + + public override string ToStringItems(int level) + { + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + var asArray = InnerEnumerable.ToArray(); + int i = 0; + foreach (object item in asArray) + { + Structure asStructure = item as Structure; + if (asStructure != null) + { + sb.Append(string.Format("{0}[{1}] = {2}\n", + pad, + (i == 0 ? "top ->" : (i == asArray.Count() ? "bottom" : " ")), + asStructure.ToStringIndented(level) + )); + } + else // Hypothetically this case should not happen, but if we screwed up somewhere so it does, at least you can see something. + { + sb.Append(item.ToString()); + } + ++i; + } + return sb.ToString(); + } + } [kOS.Safe.Utilities.KOSNomenclature("Queue", KOSToCSharp = false)] // one-way because the generic templated QueueValue is the canonical one. @@ -110,5 +138,6 @@ private void InitializeSuffixes() { return new QueueValue(toCopy.Select(x => FromPrimitiveWithAssert(x))); } + } } \ No newline at end of file diff --git a/src/kOS.Safe/Encapsulation/RangeValue.cs b/src/kOS.Safe/Encapsulation/RangeValue.cs index 79a621a4fc..49e8c7e76b 100644 --- a/src/kOS.Safe/Encapsulation/RangeValue.cs +++ b/src/kOS.Safe/Encapsulation/RangeValue.cs @@ -85,6 +85,20 @@ public override string ToString() { return "RANGE(" + InnerEnumerable.Start + ", " + InnerEnumerable.Stop + ", " + InnerEnumerable.Step + ")"; } + + public override string ToStringIndented(int level) + { + // By default, an Enumerable's ToStringIndented() would print out a header line, but here it's + // not needed, so override it to just print the content line only: + return ToStringItems(level); + } + public override string ToStringItems(int level) + { + // Indent level is being ignored because this is single-line and + // never contains other things, despite being implemented as + // a EnumerableValue which needs a ToStringItems(). + return ToString(); + } } public class Range : IEnumerable diff --git a/src/kOS.Safe/Encapsulation/StackValue.cs b/src/kOS.Safe/Encapsulation/StackValue.cs index 1eed16dae6..7d2bddcdc3 100644 --- a/src/kOS.Safe/Encapsulation/StackValue.cs +++ b/src/kOS.Safe/Encapsulation/StackValue.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Linq; +using System.Text; using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Serialization; using kOS.Safe.Function; @@ -69,6 +70,31 @@ public static StackValue CreateStack(IEnumerable list) { return new StackValue(list.Cast()); } + public override string ToStringItems(int level) + { + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + var asArray = InnerEnumerable.ToArray(); + int i = 0; + foreach (object item in asArray) + { + Structure asStructure = item as Structure; + if (asStructure != null) + { + sb.Append(string.Format("{0}[{1}] = {2}\n", + pad, + (i == 0 ? "front->" : (i == asArray.Count() ? "back ->" : " ")), + asStructure.ToStringIndented(level) + )); + } + else // Hypothetically this case should not happen, but if we screwed up somewhere so it does, at least you can see something. + { + sb.Append(item.ToString()); + } + ++i; + } + return sb.ToString(); + } } [kOS.Safe.Utilities.KOSNomenclature("Stack", KOSToCSharp = false)] // one-way because the generic templated StackValue is the canonical one. diff --git a/src/kOS.Safe/Encapsulation/Structure.cs b/src/kOS.Safe/Encapsulation/Structure.cs index 3027491585..6d29359d73 100644 --- a/src/kOS.Safe/Encapsulation/Structure.cs +++ b/src/kOS.Safe/Encapsulation/Structure.cs @@ -358,5 +358,29 @@ public static object ToPrimitive(object value) return value; } + + /// + /// A wrapper around Structure.ToString() that will indent the ToString() output + /// to the desired indent level. + /// + public virtual string ToStringIndented(int level) + { + if (level >= TerminalFormatter.MAX_INDENT_LEVEL) + return "<>"; + + StringBuilder returnVal = new StringBuilder(); + string[] lines = ToString().Split('\n'); + string pad = ""; + if (lines.Count() > 1) + { + pad = String.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + returnVal.Append("\n"); + } + foreach (string line in lines) + { + returnVal.AppendFormat("{0}{1}", pad, line); + } + return returnVal.ToString(); + } } } diff --git a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs index 4e0e1c0da9..04d92834b6 100644 --- a/src/kOS.Safe/Encapsulation/UniqueSetValue.cs +++ b/src/kOS.Safe/Encapsulation/UniqueSetValue.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using kOS.Safe.Encapsulation.Suffixes; using kOS.Safe.Exceptions; using kOS.Safe.Serialization; @@ -67,7 +68,30 @@ private void SetInitializeSuffixes() AddSuffix("COPY", new NoArgsSuffix> (() => new UniqueSetValue(this))); AddSuffix("ADD", new OneArgsSuffix (toAdd => Collection.Add(toAdd))); AddSuffix("REMOVE", new OneArgsSuffix (toRemove => Collection.Remove(toRemove))); - } + } + + public override string ToStringItems(int level) + { + StringBuilder sb = new StringBuilder(); + string pad = string.Empty.PadRight(level * TerminalFormatter.INDENT_SPACES, ' '); + var asArray = InnerEnumerable.ToArray(); + foreach (object item in asArray) + { + Structure asStructure = item as Structure; + if (asStructure != null) + { + sb.Append(string.Format("{0}{1}\n", + pad, + asStructure.ToStringIndented(level) + )); + } + else // Hypothetically this case should not happen, but if we screwed up somewhere so it does, at least you can see something. + { + sb.Append(item.ToString()); + } + } + return sb.ToString(); + } } [kOS.Safe.Utilities.KOSNomenclature("UniqueSet", KOSToCSharp = false)] // one-way because the generic templated UniqueSetValue is the canonical one. diff --git a/src/kOS.Safe/Serialization/TerminalFormatter.cs b/src/kOS.Safe/Serialization/TerminalFormatter.cs index b14448b4af..ba4f8ed7a7 100644 --- a/src/kOS.Safe/Serialization/TerminalFormatter.cs +++ b/src/kOS.Safe/Serialization/TerminalFormatter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using kOS.Safe.Encapsulation; using System.Linq; @@ -7,7 +7,17 @@ namespace kOS.Safe.Serialization { public class TerminalFormatter : IFormatWriter { - private static int INDENT_SPACES = 2; + public static int INDENT_SPACES = 2; + + // eraseme - MUST INCREASE THIS VALUE AFTER TESTING IS OVER!!! + public static int MAX_INDENT_LEVEL = 5; // SET LOW DURING TESTING SO IT'S EASY TO TRIGGER IT + + // eraseme - THIS ENTIRE CLASS BELOW THIS POINT IS PROBABLY NOT NEEDED ANYMORE IF THIS PR IS USED. + // eraseme - IT ONLY USES THE ABOVE TWO SETTINGS (TEST THIS BY REMOVING THE REST OF THIS AND + // eraseme - SEEING IF IT COMPILES.) + // eraseme - THE ENTIRE CLASS COULD GO AWAY AND THESE SETTINGS COULD BE MOVED ELSEWHERE, + // eraseme - WHERE A USER SCRIPT COULD ALTER THEM. + private static readonly TerminalFormatter instance; public static TerminalFormatter Instance diff --git a/src/kOS.Safe/Utilities/kosMath.cs b/src/kOS.Safe/Utilities/kosMath.cs index ad47b7301b..658ddbe05a 100644 --- a/src/kOS.Safe/Utilities/kosMath.cs +++ b/src/kOS.Safe/Utilities/kosMath.cs @@ -147,5 +147,27 @@ public static double GetRandom(string key) randomizers.Add(key, new Random()); return randomizers[key].NextDouble(); } + + /// + /// Get the number of decimal digits in a number. i.e. if input = 33333, return a 5. + /// + /// + /// + public static int DecimalDigitsIn(int val) + { + int absVal = val < 0 ? -val : val; + // Believe it or not, a basic hardcoded if-else chain is actually the fastest performance + // when you know the numbers aren't allowed to be large (have to fit in int32): + if (absVal < 10) return 1; + if (absVal < 100) return 2; + if (absVal < 1000) return 3; + if (absVal < 10000) return 4; + if (absVal < 100000) return 5; + if (absVal < 1000000) return 6; + if (absVal < 10000000) return 7; + if (absVal < 100000000) return 8; + if (absVal < 1000000000) return 9; + return 10; + } } }