|
|
|
@ -5,7 +5,7 @@
|
|
|
|
|
* both valid and invalid JSON. However only correctly parsing valid JSON |
|
|
|
|
* is guaranteed. This means that you should not rely on these methods to parse |
|
|
|
|
* any JSON extensions or validate JSON for you. |
|
|
|
|
* Copyright 2021-2022 Anton Tarasenko |
|
|
|
|
* Copyright 2021-2023 Anton Tarasenko |
|
|
|
|
*------------------------------------------------------------------------------ |
|
|
|
|
* This file is part of Acedia. |
|
|
|
|
* |
|
|
|
@ -1381,6 +1381,228 @@ private final function int GetEscapedVersion(int codePoint)
|
|
|
|
|
return codePoint; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Increments given value by another value, producing result as a new |
|
|
|
|
* JSON-compatible value. |
|
|
|
|
* |
|
|
|
|
* What "incrementing" actually does depends on the passed JSON values: |
|
|
|
|
* `valueToIncrement` and `increment` parameters. Unless either of them is `none` |
|
|
|
|
* (then "increment" simply acts as a `_.json.Copy()` method for |
|
|
|
|
* the non-`none` one), they must represent the same JSON type and: |
|
|
|
|
* |
|
|
|
|
* 1. JSON bool: performs logical "or" operation on given values; |
|
|
|
|
* 2. JSON number: adds values together; |
|
|
|
|
* 3. JSON string: appends `increment` at the end of `valueToIncrement`; |
|
|
|
|
* 4. JSON array: appends copy of elements of `increment` at the end of |
|
|
|
|
* array of copies of elements of `valueToIncrement` |
|
|
|
|
* (calls `ArrayList::append()` method); |
|
|
|
|
* 5. JSON object: creates new collection, based on `valueToIncrement` |
|
|
|
|
* with added key-value pairs from `increment` (all elements are |
|
|
|
|
* copied). Does not override old values |
|
|
|
|
* (calls `HashTable::append()` method). |
|
|
|
|
* |
|
|
|
|
* In case they represent different JSON types (that aren't "null") - |
|
|
|
|
* incrementing should produce `none`. |
|
|
|
|
* |
|
|
|
|
* @param valueToIncrement Value to increment. Can be any JSON-compatible |
|
|
|
|
* value. |
|
|
|
|
* @param increment Value to increment it by. Can be any |
|
|
|
|
* JSON-compatible value. |
|
|
|
|
* @return Incremented data (guaranteed to contain copies and not actual |
|
|
|
|
* objects from either `valueToIncrement` or `increment`). `none` if |
|
|
|
|
* argument types were incompatible. Whether type of the result will be |
|
|
|
|
* immutable (boxes and `Text`) or mutable (refs and `MutableText`) |
|
|
|
|
* depends on immutability of `valueToIncrement`. When adding two numbers, |
|
|
|
|
* whether result will be boxed (this includes both boxes and refs) `int` |
|
|
|
|
* or `float` depends on both parameters - if either of them if `float`, |
|
|
|
|
* then result will be `float`. |
|
|
|
|
*/ |
|
|
|
|
public final function AcediaObject Increment( |
|
|
|
|
AcediaObject valueToIncrement, |
|
|
|
|
AcediaObject increment) |
|
|
|
|
{ |
|
|
|
|
local AcediaObject result; |
|
|
|
|
|
|
|
|
|
if (valueToIncrement == none) { |
|
|
|
|
result = _.json.Copy(increment); |
|
|
|
|
} |
|
|
|
|
else if (increment == none) { |
|
|
|
|
result = _.json.Copy(valueToIncrement); |
|
|
|
|
} |
|
|
|
|
else if ( valueToIncrement.class == class'IntBox' |
|
|
|
|
|| valueToIncrement.class == class'IntRef' |
|
|
|
|
|| valueToIncrement.class == class'FloatBox' |
|
|
|
|
|| valueToIncrement.class == class'FloatRef') |
|
|
|
|
{ |
|
|
|
|
result = Increment_Number(valueToIncrement, increment); |
|
|
|
|
} |
|
|
|
|
else if ( valueToIncrement.class == class'BoolBox' |
|
|
|
|
|| valueToIncrement.class == class'BoolRef') |
|
|
|
|
{ |
|
|
|
|
result = Increment_Bool(valueToIncrement, increment); |
|
|
|
|
} |
|
|
|
|
else if ( valueToIncrement.class == class'Text' |
|
|
|
|
|| valueToIncrement.class == class'MutableText') |
|
|
|
|
{ |
|
|
|
|
result = Increment_Text(valueToIncrement, increment); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
result = Increment_Collections(valueToIncrement, increment); |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assumes `valueToIncrement` and `increment` aren't `none` |
|
|
|
|
private final function AcediaObject Increment_Collections( |
|
|
|
|
AcediaObject valueToIncrement, |
|
|
|
|
AcediaObject increment) |
|
|
|
|
{ |
|
|
|
|
local AcediaObject result; |
|
|
|
|
local ArrayList arrayListCopy; |
|
|
|
|
local HashTable hashTableCopy; |
|
|
|
|
|
|
|
|
|
if ( valueToIncrement.class == class'ArrayList' |
|
|
|
|
&& increment.class == class'ArrayList') |
|
|
|
|
{ |
|
|
|
|
arrayListCopy = ArrayList(Copy(ArrayList(increment))); |
|
|
|
|
result = CopyArrayList(ArrayList(valueToIncrement)) |
|
|
|
|
.Append(arrayListCopy); |
|
|
|
|
_.memory.Free(arrayListCopy); |
|
|
|
|
} |
|
|
|
|
else if ( valueToIncrement.class == class'HashTable' |
|
|
|
|
&& increment.class == class'HashTable') |
|
|
|
|
{ |
|
|
|
|
hashTableCopy = HashTable(Copy(HashTable(increment))); |
|
|
|
|
result = CopyHashTable(HashTable(valueToIncrement)) |
|
|
|
|
.Append(hashTableCopy); |
|
|
|
|
_.memory.Free(hashTableCopy); |
|
|
|
|
} |
|
|
|
|
return result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assumes `valueToIncrement` and `increment` aren't `none` |
|
|
|
|
private final function AcediaObject Increment_Text( |
|
|
|
|
AcediaObject valueToIncrement, |
|
|
|
|
AcediaObject increment) |
|
|
|
|
{ |
|
|
|
|
local BaseText textIncrement; |
|
|
|
|
local MutableText builder; |
|
|
|
|
|
|
|
|
|
textIncrement = BaseText(increment); |
|
|
|
|
if (BaseText(increment) == none) { |
|
|
|
|
return none; |
|
|
|
|
} |
|
|
|
|
builder = BaseText(valueToIncrement).MutableCopy(); |
|
|
|
|
builder.Append(textIncrement); |
|
|
|
|
if (Text(valueToIncrement) != none) { |
|
|
|
|
return builder.IntoText(); |
|
|
|
|
} |
|
|
|
|
return builder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assumes `valueToIncrement` and `increment` aren't `none` |
|
|
|
|
private final function AcediaObject Increment_Bool( |
|
|
|
|
AcediaObject valueToIncrement, |
|
|
|
|
AcediaObject increment) |
|
|
|
|
{ |
|
|
|
|
local bool value1, value2; |
|
|
|
|
|
|
|
|
|
if (valueToIncrement.class == class'BoolBox') { |
|
|
|
|
value1 = BoolBox(valueToIncrement).Get(); |
|
|
|
|
} |
|
|
|
|
if (valueToIncrement.class == class'BoolRef') { |
|
|
|
|
value1 = BoolRef(valueToIncrement).Get(); |
|
|
|
|
} |
|
|
|
|
if (increment.class == class'BoolBox') { |
|
|
|
|
value2 = BoolBox(increment).Get(); |
|
|
|
|
} |
|
|
|
|
else if (increment.class == class'BoolRef') { |
|
|
|
|
value2 = BoolRef(increment).Get(); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
return none; |
|
|
|
|
} |
|
|
|
|
if (ValueBox(valueToIncrement) != none) { |
|
|
|
|
return _.box.bool(value1 || value2); |
|
|
|
|
} |
|
|
|
|
return _.ref.bool(value1 || value2); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Assumes `valueToIncrement` and `increment` aren't `none` |
|
|
|
|
// Assumes `valueToIncrement` is one of four classes: `IntBox`, `IntRef`, |
|
|
|
|
// `FloatBox` or `FloatRef`. |
|
|
|
|
private final function AcediaObject Increment_Number( |
|
|
|
|
AcediaObject valueToIncrement, |
|
|
|
|
AcediaObject increment) |
|
|
|
|
{ |
|
|
|
|
local bool hasFloats; |
|
|
|
|
local int intSummand1, intSummand2, intSum; |
|
|
|
|
local float floatSummand1, floatSummand2, floatSum; |
|
|
|
|
|
|
|
|
|
hasFloats = valueToIncrement.class == class'FloatBox' |
|
|
|
|
|| valueToIncrement.class == class'FloatRef' |
|
|
|
|
|| increment.class == class'FloatBox' |
|
|
|
|
|| increment.class == class'FloatRef'; |
|
|
|
|
// `valueToIncrement` is guaranteed to have an appropriate type, |
|
|
|
|
// but `increment` might not, so only do check on second call |
|
|
|
|
ExtractBoxedNumericValue(valueToIncrement, intSummand1, floatSummand1); |
|
|
|
|
if (!ExtractBoxedNumericValue(increment, intSummand2, floatSummand2)) { |
|
|
|
|
return none; |
|
|
|
|
} |
|
|
|
|
if (hasFloats) |
|
|
|
|
{ |
|
|
|
|
floatSum = floatSummand1 + floatSummand2 |
|
|
|
|
+ float(intSummand1 + intSummand2); |
|
|
|
|
if (ValueBox(valueToIncrement) != none) { |
|
|
|
|
return _.box.float(floatSum); |
|
|
|
|
} |
|
|
|
|
return _.ref.float(floatSum); |
|
|
|
|
} |
|
|
|
|
intSum = intSummand1 + intSummand2 + int(floatSummand1 + floatSummand2); |
|
|
|
|
if (ValueBox(valueToIncrement) != none) { |
|
|
|
|
return _.box.int(intSum); |
|
|
|
|
} |
|
|
|
|
return _.ref.int(intSum); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Extracts numeric value and records it into one of two out arguments: |
|
|
|
|
// |
|
|
|
|
// * `asInteger` iff `value` is either `IntBox` or `IntRef`; |
|
|
|
|
// * `asFloat` iff `value` is either `FloatBox` or `FloatRef`; |
|
|
|
|
// |
|
|
|
|
// Does not change the value in remaining parameter. |
|
|
|
|
// Returns `true` in case of success (method managed to read the value) and |
|
|
|
|
// `false` otherwise (`value` had non-numeric or unknown type). |
|
|
|
|
private final function bool ExtractBoxedNumericValue( |
|
|
|
|
AcediaObject value, |
|
|
|
|
out int asInteger, |
|
|
|
|
out float asFloat) |
|
|
|
|
{ |
|
|
|
|
local bool success; |
|
|
|
|
|
|
|
|
|
if (value.class == class'IntBox') |
|
|
|
|
{ |
|
|
|
|
asInteger = IntBox(value).Get(); |
|
|
|
|
success = true; |
|
|
|
|
} |
|
|
|
|
else if (value.class == class'IntRef') |
|
|
|
|
{ |
|
|
|
|
asInteger = IntRef(value).Get(); |
|
|
|
|
success = true; |
|
|
|
|
} |
|
|
|
|
else if (value.class == class'FloatBox') |
|
|
|
|
{ |
|
|
|
|
asFloat = FloatBox(value).Get(); |
|
|
|
|
success = true; |
|
|
|
|
} |
|
|
|
|
else if (value.class == class'FloatRef') |
|
|
|
|
{ |
|
|
|
|
asFloat = FloatRef(value).Get(); |
|
|
|
|
success = true; |
|
|
|
|
} |
|
|
|
|
return success; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Performs a deep copy of the `inputData`. This means it copies not only |
|
|
|
|
* `inputData` itself, but (in case it is a container) all of the values |
|
|
|
|