diff --git a/config/AcediaAliases_Colors.ini b/config/AcediaAliases_Colors.ini index 29732d3..0cb8609 100644 --- a/config/AcediaAliases_Colors.ini +++ b/config/AcediaAliases_Colors.ini @@ -11,6 +11,15 @@ record=(alias="type_boolean",value="rgb(38,139,210)") record=(alias="type_string",value="rgb(211,54,130)") record=(alias="type_literal",value="rgb(42,161,152)") record=(alias="type_class",value="rgb(108,113,196)") +record=(alias="json_propertyName",value="rgb(255,255,255)") +record=(alias="json_objectBraces",value="rgb(128,128,128)") +record=(alias="json_arrayBraces",value="rgb(128,128,128)") +record=(alias="json_comma",value="rgb(128,128,128)") +record=(alias="json_colon",value="rgb(128,128,128)") +record=(alias="json_number",value="rgb(181,137,0)") +record=(alias="json_boolean",value="rgb(38,139,210)") +record=(alias="json_string",value="rgb(211,54,130)") +record=(alias="json_null",value="rgb(42,161,152)") ; Pink colors record=(alias="Pink",value="rgb(255,192,203)") record=(alias="LightPink",value="rgb(255,182,193)") diff --git a/sources/Data/JSON/JArray.uc b/sources/Data/JSON/JArray.uc index 18a6a95..7278ea8 100644 --- a/sources/Data/JSON/JArray.uc +++ b/sources/Data/JSON/JArray.uc @@ -179,6 +179,7 @@ public final function JArray SetNumber( newStorageValue.type = JSON_Number; newStorageValue.numberValue = value; newStorageValue.numberValueAsInt = int(value); + newStorageValue.preferIntegerValue = false; data[index] = newStorageValue; return self; } @@ -206,6 +207,7 @@ public final function JArray SetInteger( newStorageValue.type = JSON_Number; newStorageValue.numberValue = float(value); newStorageValue.numberValueAsInt = value; + newStorageValue.preferIntegerValue = true; data[index] = newStorageValue; return self; } @@ -531,6 +533,45 @@ public function JSON Clone() return clonedArray; } +public function string DisplayWith(JSONDisplaySettings displaySettings) +{ + local int i; + local bool isntFirstElement; + local string contents; + local string openingBraces, closingBraces; + local string elementsSeparator; + local JSONDisplaySettings innerSettings; + if (displaySettings.stackIndentation) { + innerSettings = IndentSettings(displaySettings, true); + } + else { + innerSettings = displaySettings; + } + // Prepare delimiters using appropriate indentation rules + // We only use inner settings for the part right after '[', + // as the rest is supposed to be aligned with outer objects + openingBraces = displaySettings.beforeArrayOpening + $ "[" $ innerSettings.afterArrayOpening; + closingBraces = displaySettings.beforeArrayEnding + $ "]" $ displaySettings.afterArrayEnding; + elementsSeparator = "," $ innerSettings.afterArrayComma; + if (innerSettings.colored) { + elementsSeparator = "{$json_comma" $ elementsSeparator $ "}"; + openingBraces = "{$json_arrayBraces" $ openingBraces $ "}"; + closingBraces = "{$json_arrayBraces" $ closingBraces $ "}"; + } + // Display inner properties + for (i = 0; i < data.length; i += 1) + { + if (isntFirstElement) { + contents $= elementsSeparator; + } + contents $= DisplayAtom(data[i], innerSettings); + isntFirstElement = true; + } + return openingBraces $ contents $ closingBraces; +} + defaultproperties { } \ No newline at end of file diff --git a/sources/Data/JSON/JObject.uc b/sources/Data/JSON/JObject.uc index b657ec5..57e28b4 100644 --- a/sources/Data/JSON/JObject.uc +++ b/sources/Data/JSON/JObject.uc @@ -322,10 +322,11 @@ public final function JObject SetNumber(string name, float value) { local JProperty property; FindProperty(name, property); - property.value.type = JSON_Number; - property.value.numberValue = value; - property.value.numberValueAsInt = int(value); - property.value.complexValue = none; + property.value.type = JSON_Number; + property.value.numberValue = value; + property.value.numberValueAsInt = int(value); + property.value.complexValue = none; + property.value.preferIntegerValue = false; UpdateProperty(property); return self; } @@ -334,10 +335,11 @@ public final function JObject SetInteger(string name, int value) { local JProperty property; FindProperty(name, property); - property.value.type = JSON_Number; - property.value.numberValue = float(value); - property.value.numberValueAsInt = value; - property.value.complexValue = none; + property.value.type = JSON_Number; + property.value.numberValue = float(value); + property.value.numberValueAsInt = value; + property.value.complexValue = none; + property.value.preferIntegerValue = true; UpdateProperty(property); return self; } @@ -527,6 +529,69 @@ public function JSON Clone() return clonedObject; } +public function string DisplayWith(JSONDisplaySettings displaySettings) +{ + local int i, j; + local bool isntFirstProperty; + local string contents; + local string openingBraces, closingBraces; + local string propertiesSeparator; + local array nextProperties; + local JSONDisplaySettings innerSettings; + if (displaySettings.stackIndentation) { + innerSettings = IndentSettings(displaySettings); + } + else { + innerSettings = displaySettings; + } + // Prepare delimiters using appropriate indentation rules + // We only use inner settings for the part right after '{', + // as the rest is supposed to be aligned with outer objects + openingBraces = displaySettings.beforeObjectOpening + $ "{" $ innerSettings.afterObjectOpening; + closingBraces = displaySettings.beforeObjectEnding + $ "}" $ displaySettings.afterObjectEnding; + propertiesSeparator = "," $ innerSettings.afterObjectComma; + if (innerSettings.colored) { + propertiesSeparator = "{$json_comma" @ propertiesSeparator $ "}"; + openingBraces = "{$json_objectBraces &" $ openingBraces $ "}"; + closingBraces = "{$json_objectBraces &" $ closingBraces $ "}"; + } + // Display inner properties + for (i = 0; i < hashTable.length; i += 1) + { + nextProperties = hashTable[i].properties; + for (j = 0; j < nextProperties.length; j += 1) + { + if (isntFirstProperty) { + contents $= propertiesSeparator; + } + contents $= DisplayProperty(nextProperties[j], innerSettings); + isntFirstProperty = true; + } + } + return openingBraces $ contents $ closingBraces; +} + +protected function string DisplayProperty( + JProperty toDisplay, + JSONDisplaySettings displaySettings) +{ + local string result; + result = displaySettings.beforePropertyName + $ DisplayJSONString(toDisplay.name) + $ displaySettings.afterPropertyName; + if (displaySettings.colored) { + result = "{$json_propertyName" @ result $ "}{$json_colon :}"; + } + else { + result $= ":"; + } + return (result $ displaySettings.beforePropertyValue + $ DisplayAtom(toDisplay.value, displaySettings) + $ displaySettings.afterPropertyValue); +} + defaultproperties { ABSOLUTE_LOWER_CAPACITY_LIMIT = 10 diff --git a/sources/Data/JSON/JSON.uc b/sources/Data/JSON/JSON.uc index 36aa5b4..28e9440 100644 --- a/sources/Data/JSON/JSON.uc +++ b/sources/Data/JSON/JSON.uc @@ -68,6 +68,7 @@ struct JStorageAtom // them as both `float` and `integer` and allow user to request any version // of them var protected int numberValueAsInt; + var protected bool preferIntegerValue; // Some `string` values might be actually used to represent classes, // so we will give users an ability to request `string` value as a class. var protected class stringValueAsClass; @@ -84,6 +85,22 @@ enum JComparisonResult JCR_Equal }; +struct JSONDisplaySettings +{ + var bool colored; + var bool stackIndentation; + var string subObjectIndentation, subArrayIndentation; + var string beforeObjectOpening, afterObjectOpening; + var string beforeObjectEnding, afterObjectEnding; + var string beforePropertyName, afterPropertyName; + var string beforePropertyValue, afterPropertyValue; + var string afterObjectComma; + var string beforeArrayOpening, afterArrayOpening; + var string beforeArrayEnding, afterArrayEnding; + var string beforeElement, afterElement; + var string afterArrayComma; +}; + public function JSON Clone() { return none; @@ -158,6 +175,182 @@ protected final function TryLoadingStringAsClass(out JStorageAtom atom) class(DynamicLoadObject(atom.stringValue, class'Class', true)); } +public final function string Display( + optional bool fancyPrinting, + optional bool colorSettings) +{ + local JSONDisplaySettings settingsToUse; + // Settings are minimal by default + if (fancyPrinting) { + settingsToUse = GetFancySettings(); + } + if (colorSettings) { + settingsToUse.colored = true; + } + return DisplayWith(settingsToUse); +} + +public function string DisplayWith(JSONDisplaySettings displaySettings) +{ + return ""; +} + +protected final function string DisplayAtom( + JStorageAtom atom, + JSONDisplaySettings displaySettings) +{ + local string colorTag; + local string result; + if ( atom.complexValue != none + && (atom.type == JSON_Object || atom.type == JSON_Array) ) { + return atom.complexValue.DisplayWith(displaySettings); + } + if (atom.type == JSON_Null) { + result = "null"; + colorTag = "$json_null"; + } + else if (atom.type == JSON_Number) { + if (atom.preferIntegerValue) { + result = string(atom.numberValueAsInt); + } + else { + result = DisplayFloat(atom.numberValue); + } + colorTag = "$json_number"; + } + else if (atom.type == JSON_String) { + result = DisplayJSONString(atom.stringValue); + colorTag = "$json_string"; + } + else if (atom.type == JSON_Boolean) { + if (atom.booleanValue) { + result = "true"; + } + else { + result = "false"; + } + colorTag = "$json_boolean"; + } + if (displaySettings.colored) { + return "{" $ colorTag @ result $ "}"; + } + return result; +} + +protected final function string DisplayFloat(float number) +{ + local int integerPart, fractionalPart; + local int precision; + local int howManyZeroes; + local string zeroes; + local string result; + precision = Clamp(MAX_FLOAT_PRECISION, 0, 10); + if (number < 0) { + number *= -1; + result = "-"; + } + integerPart = number; + result $= string(integerPart); + number = (number - integerPart); + fractionalPart = Round(number * (10 ** precision)); + if (fractionalPart <= 0) { + return result; + } + result $= "."; + howManyZeroes = precision - CountDigits(fractionalPart); + while (fractionalPart > 0 && fractionalPart % 10 == 0) { + fractionalPart /= 10; + } + while (howManyZeroes > 0) { + zeroes $= "0"; + howManyZeroes -= 1; + } + return result $ zeroes $ string(fractionalPart); +} + +protected final function int CountDigits(int number) +{ + local int digitCounter; + while (number > 0) + { + number -= (number % 10); + number /= 10; + digitCounter += 1; + } + return digitCounter; +} + +protected final function JSONDisplaySettings GetFancySettings() +{ + local string lineFeed; + local JSONDisplaySettings fancySettings; + lineFeed = Chr(10); + fancySettings.stackIndentation = true; + fancySettings.subObjectIndentation = " "; + fancySettings.subArrayIndentation = ""; + fancySettings.afterObjectOpening = lineFeed; + fancySettings.beforeObjectEnding = lineFeed; + fancySettings.beforePropertyValue = " "; + fancySettings.afterObjectComma = lineFeed; + fancySettings.beforeElement = " "; + fancySettings.afterArrayComma = " "; + return fancySettings; +} + +protected final function string DisplayJSONString(string input) +{ + // Convert control characters (+ other, specified by JSON) + // into escaped sequences + ReplaceText(input, "\"", "\\\""); + ReplaceText(input, "/", "\\/"); + ReplaceText(input, "\\", "\\\\"); + ReplaceText(input, Chr(0x08), "\\b"); + ReplaceText(input, Chr(0x0c), "\\f"); + ReplaceText(input, Chr(0x0a), "\\n"); + ReplaceText(input, Chr(0x0d), "\\r"); + ReplaceText(input, Chr(0x09), "\\t"); + // TODO: test if there are control characters and render them as "\u...." + return ("\"" $ input $ "\""); +} + +protected final function JSONDisplaySettings IndentSettings( + JSONDisplaySettings inputSettings, + optional bool indentingArray) +{ + local string lineFeed; + local string lineFeedIndent; + local JSONDisplaySettings indentedSettings; + indentedSettings = inputSettings; + lineFeed = Chr(0x0a); + if (indentingArray) { + lineFeedIndent = lineFeed $ inputSettings.subArrayIndentation; + } + else { + lineFeedIndent = lineFeed $ inputSettings.subObjectIndentation; + } + if (lineFeedIndent == lineFeed) { + return indentedSettings; + } + ReplaceText(indentedSettings.afterObjectEnding, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterPropertyName, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterObjectComma, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterArrayOpening, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforeArrayEnding, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterArrayEnding, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforeElement, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterElement, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterArrayComma, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforeObjectOpening, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforePropertyValue, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterObjectOpening, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforeObjectEnding, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforeArrayOpening, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.afterPropertyValue, lineFeed, lineFeedIndent); + ReplaceText(indentedSettings.beforePropertyName, lineFeed, lineFeedIndent); + return indentedSettings; +} + defaultproperties { + MAX_FLOAT_PRECISION = 4 } \ No newline at end of file diff --git a/sources/Text/Parser.uc b/sources/Text/Parser.uc index 0571b7b..ebc87f5 100644 --- a/sources/Text/Parser.uc +++ b/sources/Text/Parser.uc @@ -598,10 +598,10 @@ public final function Parser MUnsignedInteger * * A Unicode code point can also be directly entered with either of the two * commands: - * \U0056 - * \u56 - * The difference is that `\U` allows you to enter two-byte code point, while - * `\u` only allows to define code points that fit into 1 byte, + * \u0056 + * \U56 + * The difference is that `\u` allows you to enter two-byte code point, while + * `\U` only allows to define code points that fit into 1 byte, * but is more compact. * * @param denotedCodePoint If parsing is successful, parameter will contain @@ -633,11 +633,11 @@ public final function Parser MEscapedSequence } } // Escaped character denotes declaration of arbitrary Unicode code point - if (denotedCharacter.codePoint == CODEPOINT_ULARGE) + if (denotedCharacter.codePoint == CODEPOINT_USMALL) { MUnsignedInteger(denotedCharacter.codePoint, 16, 4); } - else if (denotedCharacter.codePoint == CODEPOINT_USMALL) + else if (denotedCharacter.codePoint == CODEPOINT_ULARGE) { MUnsignedInteger(denotedCharacter.codePoint, 16, 2); } diff --git a/sources/Text/Tests/TEST_Parser.uc b/sources/Text/Tests/TEST_Parser.uc index a6f0cab..d460a7a 100644 Binary files a/sources/Text/Tests/TEST_Parser.uc and b/sources/Text/Tests/TEST_Parser.uc differ