|
|
|
/**
|
|
|
|
* Provides convenient access to JSON-related functions.
|
|
|
|
* Printing method produce correct JSON.
|
|
|
|
* Parsing methods do not provide validity checks guarantees and will parse
|
|
|
|
* 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 Anton Tarasenko
|
|
|
|
*------------------------------------------------------------------------------
|
|
|
|
* This file is part of Acedia.
|
|
|
|
*
|
|
|
|
* Acedia is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* Acedia is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
class JSONAPI extends AcediaObject
|
|
|
|
config(AcediaSystem);
|
|
|
|
|
|
|
|
// complex value
|
|
|
|
var const int TNULL, TTRUE, TFALSE, TDOT, TEXPONENT;
|
|
|
|
var const int TOPEN_BRACKET, TCLOSE_BRACKET, TOPEN_BRACE, TCLOSE_BRACE;
|
|
|
|
var const int TCOMMA, TCOLON, TQUOTE;
|
|
|
|
|
|
|
|
var const int CODEPOINT_BACKSPACE, CODEPOINT_TAB, CODEPOINT_LINE_FEED;
|
|
|
|
var const int CODEPOINT_FORM_FEED, CODEPOINT_CARRIAGE_RETURN;
|
|
|
|
var const int CODEPOINT_QUOTATION_MARK, CODEPOINT_SOLIDUS;
|
|
|
|
var const int CODEPOINT_REVERSE_SOLIDUS, CODEPOINT_SMALL_B, CODEPOINT_SMALL_F;
|
|
|
|
var const int CODEPOINT_SMALL_N, CODEPOINT_SMALL_R, CODEPOINT_SMALL_T;
|
|
|
|
|
|
|
|
// Max precision that will be used when outputting JSON values as a string.
|
|
|
|
// Hardcoded to force this value between 0 and 10, inclusively.
|
|
|
|
var private const config int MAX_FLOAT_PRECISION;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a null JSON value ("null" in arbitrary case).
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON null term (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value from
|
|
|
|
* it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
*/
|
|
|
|
public final function TryNullWith(Parser parser)
|
|
|
|
{
|
|
|
|
if (parser != none) {
|
|
|
|
parser.Match(T(default.TNULL), SCASE_INSENSITIVE);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Tries to parse null JSON value ("null" in arbitrary case) and reports
|
|
|
|
* whether parsing succeeded.
|
|
|
|
*
|
|
|
|
* `source` must contain precisely a null JSON value and nothing else for this
|
|
|
|
* method to succeed. For example, even having leading/trailing whitespace
|
|
|
|
* symbols (" null" or "null ") is enough to fail parsing.
|
|
|
|
*
|
|
|
|
* @param source `Text` instance to parse JSON null value from.
|
|
|
|
* @return `true` if parsing succeeded and `false` otherwise.
|
|
|
|
*/
|
|
|
|
public final function bool IsNull(Text source)
|
|
|
|
{
|
|
|
|
local bool parsingSucceeded;
|
|
|
|
local Parser parser;
|
|
|
|
if (source == none) return false;
|
|
|
|
|
|
|
|
parser = _.text.Parse(source);
|
|
|
|
parser.Match(T(default.TNULL), SCASE_INSENSITIVE);
|
|
|
|
parsingSucceeded = parser.Ok() && parser.HasFinished();
|
|
|
|
parser.FreeSelf();
|
|
|
|
return parsingSucceeded;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON boolean ("true" or "false" with
|
|
|
|
* arbitrary case).
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON boolean (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value from
|
|
|
|
* it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @return Parsed boolean value if parsing was successful and
|
|
|
|
* `false` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function bool ParseBooleanVariableWith(Parser parser)
|
|
|
|
{
|
|
|
|
local Parser.ParserState initState;
|
|
|
|
if (parser == none) return false;
|
|
|
|
if (!parser.Ok()) return false;
|
|
|
|
|
|
|
|
initState = parser.GetCurrentState();
|
|
|
|
// Check if we should return `true`
|
|
|
|
if (parser.Match(T(default.TTRUE), SCASE_INSENSITIVE).Ok()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
// We need to try parsing "false", so that we can use `parser`'s state
|
|
|
|
// to report about success of parsing; but we return `false` anyway.
|
|
|
|
parser.RestoreState(initState).Match(T(default.TFALSE), SCASE_INSENSITIVE);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON boolean ("true" or "false"
|
|
|
|
* with arbitrary case) into either `BoolBox` or `BoolRef`.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON boolean (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`BoolRef`) and `false` if immutable (`BoolBox`).
|
|
|
|
* @return Parsed boolean value as an `AcediaObject` if parsing was successful
|
|
|
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to
|
|
|
|
* be not `none` and have correct class, determined by
|
|
|
|
* `parseAsMutable` parameter.
|
|
|
|
* Returns `none` iff parsing has failed.
|
|
|
|
*/
|
|
|
|
public final function AcediaObject ParseBooleanWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local bool result;
|
|
|
|
if (parser == none) return none;
|
|
|
|
|
|
|
|
result = ParseBooleanVariableWith(parser);
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.bool(result);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.bool(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a JSON boolean ("true" or "false" with arbitrary case) from
|
|
|
|
* a given `source` into either `BoolBox` or `BoolRef`.
|
|
|
|
*
|
|
|
|
* `source` must contain precisely a boolean value and nothing else for this
|
|
|
|
* method to succeed. For example, even having leading/trailing whitespace
|
|
|
|
* symbols (" true" or "false ") is enough to fail parsing.
|
|
|
|
*
|
|
|
|
* @param source `Text` instance to parse JSON boolean value from.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`BoolRef`) and `false` if immutable (`BoolBox`).
|
|
|
|
* @return Parsed boolean value as an `AcediaObject` if parsing was successful
|
|
|
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to
|
|
|
|
* be not `none` and have correct class, determined by
|
|
|
|
* `parseAsMutable` parameter.
|
|
|
|
* Returns `none` iff parsing has failed.
|
|
|
|
*/
|
|
|
|
public final function AcediaObject ParseBoolean(
|
|
|
|
Text source,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local bool result;
|
|
|
|
local bool parsingFailed;
|
|
|
|
local Parser parser;
|
|
|
|
if (source == none) return none;
|
|
|
|
|
|
|
|
parser = _.text.Parse(source);
|
|
|
|
result = ParseBooleanVariableWith(parser);
|
|
|
|
parsingFailed = !parser.Ok() || !parser.HasFinished();
|
|
|
|
parser.FreeSelf();
|
|
|
|
if (parsingFailed) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.bool(result);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.bool(result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON number into an integer.
|
|
|
|
*
|
|
|
|
* If number is written in an "integer form" (not dot "." or exponent "e"),
|
|
|
|
* then it will be directly be parsed as an `int`. Otherwise it will be
|
|
|
|
* parsed as a `float` and the converted into `int`, with appropriate loss
|
|
|
|
* of precision.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse a JSON number
|
|
|
|
* (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* To parse a JSON number into a `float` use `ParseFloatVariableWith()` method.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value from
|
|
|
|
* it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param integerOnly Setting this parameter to `true` will prevent method
|
|
|
|
* from parsing number as a `float` (and possibly losing precision):
|
|
|
|
* in case it is written in a `float`, parsing will be considered failed.
|
|
|
|
* @return Parsed integer value if parsing was successful and
|
|
|
|
* `0` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function int ParseIntegerVariableWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool integerOnly)
|
|
|
|
{
|
|
|
|
local int integerValue;
|
|
|
|
local bool isInFloatForm;
|
|
|
|
local float floatValue;
|
|
|
|
local Parser.ParserState initState, integerParsedState;
|
|
|
|
if (parser == none) return 0;
|
|
|
|
|
|
|
|
initState = parser.GetCurrentState();
|
|
|
|
if (!parser.MInteger(integerValue, 10).Ok()) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// `integerParsedState` is guaranteed to be a successful state
|
|
|
|
integerParsedState = parser.GetCurrentState();
|
|
|
|
// JSON number recorded as float form will have either dot or exponent
|
|
|
|
// after the integer part.
|
|
|
|
isInFloatForm = parser.Match(T(default.TDOT)).Ok();
|
|
|
|
parser.RestoreState(integerParsedState);
|
|
|
|
if (parser.Match(T(default.TEXPONENT), SCASE_INSENSITIVE).Ok()) {
|
|
|
|
isInFloatForm = true;
|
|
|
|
}
|
|
|
|
// For a number check if it can be parsed as a float specifically.
|
|
|
|
// If not - use parsed integer.
|
|
|
|
parser.RestoreState(initState);
|
|
|
|
if (isInFloatForm && parser.MNumber(floatValue).Ok())
|
|
|
|
{
|
|
|
|
if (integerOnly)
|
|
|
|
{
|
|
|
|
parser.Fail();
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return int(floatValue);
|
|
|
|
}
|
|
|
|
parser.RestoreState(integerParsedState);
|
|
|
|
return integerValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON number.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON number (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* To parse a JSON number into an `int` use `ParseIntegerVariableWith()` method.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value from
|
|
|
|
* it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @return Parsed number value if parsing was successful and
|
|
|
|
* `0.0` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function float ParseFloatVariableWith(Parser parser)
|
|
|
|
{
|
|
|
|
local float floatValue;
|
|
|
|
if (parser == none) return 0.0;
|
|
|
|
if (!parser.MNumber(floatValue).Ok()) return 0.0;
|
|
|
|
|
|
|
|
return floatValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON number into one of the following
|
|
|
|
* object classes: `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, depending on
|
|
|
|
* parameters and how numeric value is recorded.
|
|
|
|
*
|
|
|
|
* To improve precision, this method will try to parse JSON number as
|
|
|
|
* an integer (`IntBox` or `IntRef`) if possible (if number does not include
|
|
|
|
* fractional or exponent parts: "." or "e").
|
|
|
|
* Otherwise it will parse number as a floating point value
|
|
|
|
* (`FloatBox` or `FloatRef`).
|
|
|
|
* The choice between box and reference is made depending on the method's
|
|
|
|
* parameter `parseAsMutable`.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON number (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`IntRef` or `FloatRef`) and `false` if immutable
|
|
|
|
* (`IntBox` or `FloatBox`).
|
|
|
|
* @return Parsed number value as an `AcediaObject` if parsing was successful
|
|
|
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to
|
|
|
|
* be not `none` and have correct class, determined partly by
|
|
|
|
* `parseAsMutable` parameter.
|
|
|
|
* Returns `none` iff parsing has failed.
|
|
|
|
*/
|
|
|
|
public final function AcediaObject ParseNumberWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local int integerResult;
|
|
|
|
local float floatResult;
|
|
|
|
local Parser.ParserState initState;
|
|
|
|
if (parser == none) return none;
|
|
|
|
|
|
|
|
initState = parser.GetCurrentState();
|
|
|
|
// Try parsing into `int`;
|
|
|
|
// this will fail if number recorded in floating format.
|
|
|
|
integerResult = ParseIntegerVariableWith(parser, true);
|
|
|
|
if (parser.Ok())
|
|
|
|
{
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.int(integerResult);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.int(integerResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If simple integer does not work - try to parse it as `float`
|
|
|
|
floatResult = ParseFloatVariableWith(parser.RestoreState(initState));
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.float(floatResult);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.float(floatResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a JSON number from `source` into one of the following
|
|
|
|
* object classes: `IntBox`, `IntRef`, `FloatBox`, `FloatRef`, depending on
|
|
|
|
* parameters and how numeric value is recorded.
|
|
|
|
*
|
|
|
|
* To improve precision, this method will try to parse JSON number as
|
|
|
|
* an integer (`IntBox` or `IntRef`) if possible (if number does not include
|
|
|
|
* fractional or exponent parts: "." or "e").
|
|
|
|
* Otherwise it will parse number as a floating point value
|
|
|
|
* (`FloatBox` or `FloatRef`).
|
|
|
|
* The choice between box and reference is made depending on the method's
|
|
|
|
* parameter `parseAsMutable`.
|
|
|
|
*
|
|
|
|
* `source` must contain precisely a numeric value and nothing else for this
|
|
|
|
* method to succeed. For example, even having leading/trailing whitespace
|
|
|
|
* symbols (" 75.3" or "9 ") is enough to fail parsing.
|
|
|
|
*
|
|
|
|
* @param source `Text` instance to parse JSON number from.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`IntRef` or `FloatRef`) and `false` if immutable
|
|
|
|
* (`IntBox` or `FloatBox`).
|
|
|
|
* @return Parsed number value as an `AcediaObject` if parsing was successful
|
|
|
|
* and `none` otherwise. If parsing succeeded, it is guaranteed to
|
|
|
|
* be not `none` and have correct class, determined partly by
|
|
|
|
* `parseAsMutable` parameter.
|
|
|
|
* Returns `none` iff parsing has failed.
|
|
|
|
*/
|
|
|
|
public final function AcediaObject ParseNumber(
|
|
|
|
Text source,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local int integerResult;
|
|
|
|
local float floatResult;
|
|
|
|
local Parser parser;
|
|
|
|
parser = _.text.Parse(source);
|
|
|
|
// Try parsing into `int`;
|
|
|
|
// this will fail if number recorded in floating format.
|
|
|
|
integerResult = ParseIntegerVariableWith(parser, true);
|
|
|
|
if (parser.Ok() && parser.HasFinished())
|
|
|
|
{
|
|
|
|
parser.FreeSelf();
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.int(integerResult);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.int(integerResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// If simple integer does not work - try to parse it as `float`
|
|
|
|
floatResult = ParseFloatVariableWith(parser.R());
|
|
|
|
if (!parser.Ok() || !parser.HasFinished())
|
|
|
|
{
|
|
|
|
parser.FreeSelf();
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
parser.FreeSelf();
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return _.ref.float(floatResult);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return _.box.float(floatResult);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON string.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON string (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`MutableText`) and `false` if immutable (`Text`).
|
|
|
|
* @return Parsed string value as `Text` or `MutableText` (depending on
|
|
|
|
* `parseAsMutable` parameter) if parsing was successful and
|
|
|
|
* `none` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function Text ParseStringWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local Text immutableTextValue;
|
|
|
|
local MutableText mutableTextValue;
|
|
|
|
if (parser == none) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
parser.MStringLiteral(mutableTextValue);
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
mutableTextValue.FreeSelf();
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return mutableTextValue;
|
|
|
|
}
|
|
|
|
immutableTextValue = mutableTextValue.Copy();
|
|
|
|
mutableTextValue.FreeSelf();
|
|
|
|
return immutableTextValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Parses a JSON string from `source` into either `Text` or `MutableText`,
|
|
|
|
* depending on parameters.
|
|
|
|
*
|
|
|
|
* `source` must contain precisely a JSON string value and nothing else for
|
|
|
|
* this method to succeed. For example, even having leading/trailing whitespace
|
|
|
|
* symbols (" \"string!\"" or "\"another!\" ") is enough to fail parsing.
|
|
|
|
*
|
|
|
|
* @param source `Text` instance to parse JSON string from.
|
|
|
|
* @param parseAsMutable `true` if you want this method to return mutable
|
|
|
|
* object (`MutableText`) and `false` if immutable (`Text`).
|
|
|
|
* @return Parsed string value as `Text` or `MutableText` (depending on
|
|
|
|
* `parseAsMutable` parameter) if parsing was successful and
|
|
|
|
* `none` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function Text ParseString(
|
|
|
|
Text source,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local bool parsingSuccessful;
|
|
|
|
local Parser parser;
|
|
|
|
local Text immutableTextValue;
|
|
|
|
local MutableText mutableTextValue;
|
|
|
|
parser = _.text.Parse(source);
|
|
|
|
parsingSuccessful =
|
|
|
|
parser.MStringLiteral(mutableTextValue).Ok() && parser.HasFinished();
|
|
|
|
parser.FreeSelf();
|
|
|
|
if (!parsingSuccessful) {
|
|
|
|
mutableTextValue.FreeSelf();
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
if (parseAsMutable) {
|
|
|
|
return mutableTextValue;
|
|
|
|
}
|
|
|
|
immutableTextValue = mutableTextValue.Copy();
|
|
|
|
mutableTextValue.FreeSelf();
|
|
|
|
return immutableTextValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON array.
|
|
|
|
*
|
|
|
|
* This method will parse JSON values that are contained in parsed JSON array
|
|
|
|
* according to description given for `ParseWith()` method.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON array (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON array
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to parse array's
|
|
|
|
* items as mutable values and `false` otherwise (as immutable ones).
|
|
|
|
* @return Parsed JSON array as `DynamicArray` if parsing was successful and
|
|
|
|
* `none` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public final function DynamicArray ParseArrayWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local bool parsingSucceeded;
|
|
|
|
local Parser.ParserState confirmedState;
|
|
|
|
local AcediaObject nextValue;
|
|
|
|
local array<AcediaObject> parsedValues;
|
|
|
|
if (parser == none) return none;
|
|
|
|
|
|
|
|
confirmedState =
|
|
|
|
parser.Skip().Match(T(default.TOPEN_BRACKET)).GetCurrentState();
|
|
|
|
while (parser.Ok() && !parser.HasFinished())
|
|
|
|
{
|
|
|
|
confirmedState = parser.Skip().GetCurrentState();
|
|
|
|
// Check for JSON array ending and ONLY THEN declare parsing
|
|
|
|
// is successful, not encountering '}' implies bad JSON format.
|
|
|
|
if (parser.Match(T(default.TCLOSE_BRACKET)).Ok()) {
|
|
|
|
parsingSucceeded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
parser.RestoreState(confirmedState);
|
|
|
|
// Look for comma after each element
|
|
|
|
if (parsedValues.length > 0)
|
|
|
|
{
|
|
|
|
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
confirmedState = parser.GetCurrentState();
|
|
|
|
}
|
|
|
|
// Parse next value
|
|
|
|
nextValue = ParseWith(parser, parseAsMutable);
|
|
|
|
parsedValues[parsedValues.length] = nextValue;
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (parsingSucceeded) {
|
|
|
|
return _.collections.NewDynamicArray(parsedValues, true);
|
|
|
|
}
|
|
|
|
_.memory.FreeMany(parsedValues);
|
|
|
|
parser.Fail();
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON object.
|
|
|
|
*
|
|
|
|
* This method will parse JSON values that are contained in parsed JSON object
|
|
|
|
* according to description given for `ParseWith()` method.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON object (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON object
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to parse object's
|
|
|
|
* items as mutable values and `false` otherwise (as immutable ones).
|
|
|
|
* @return Parsed JSON object as `AssociativeArray` if parsing was successful
|
|
|
|
* and `none` otherwise. To check for parsing success check the state of
|
|
|
|
* the `parser`.
|
|
|
|
*/
|
|
|
|
public function AssociativeArray ParseObjectWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local bool parsingSucceeded;
|
|
|
|
local Parser.ParserState confirmedState;
|
|
|
|
local array<AssociativeArray.Entry> parsedEntries;
|
|
|
|
if (parser == none) return none;
|
|
|
|
|
|
|
|
// Ensure that parser starts pointing at what looks like a JSON object
|
|
|
|
confirmedState =
|
|
|
|
parser.Skip().Match(T(default.TOPEN_BRACE)).GetCurrentState();
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
while (parser.Ok() && !parser.HasFinished())
|
|
|
|
{
|
|
|
|
confirmedState = parser.Skip().GetCurrentState();
|
|
|
|
// Check for JSON object ending and ONLY THEN declare parsing
|
|
|
|
// is successful, not encountering '}' implies bad JSON format.
|
|
|
|
if (parser.Match(T(default.TCLOSE_BRACE)).Ok())
|
|
|
|
{
|
|
|
|
parsingSucceeded = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
parser.RestoreState(confirmedState);
|
|
|
|
// Look for comma after each key-value pair
|
|
|
|
if (parsedEntries.length > 0)
|
|
|
|
{
|
|
|
|
if (!parser.Match(T(default.TCOMMA)).Skip().Ok()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
confirmedState = parser.GetCurrentState();
|
|
|
|
}
|
|
|
|
// Parse property
|
|
|
|
parsedEntries[parsedEntries.length] =
|
|
|
|
ParseProperty(parser, parseAsMutable);
|
|
|
|
if (!parser.Ok()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (parsingSucceeded) {
|
|
|
|
return _.collections.NewAssociativeArray(parsedEntries, true);
|
|
|
|
}
|
|
|
|
FreeEntries(parsedEntries);
|
|
|
|
parser.Fail();
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parses a JSON key-value pair (there must not be any leading spaces).
|
|
|
|
private function AssociativeArray.Entry ParseProperty(
|
|
|
|
Parser parser,
|
|
|
|
bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local MutableText nextKey;
|
|
|
|
local AssociativeArray.Entry entry;
|
|
|
|
parser.MStringLiteral(nextKey).Skip().Match(T(default.TCOLON)).Skip();
|
|
|
|
entry.key = nextKey.Copy();
|
|
|
|
nextKey.FreeSelf();
|
|
|
|
entry.value = ParseWith(parser, parseAsMutable);
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Auxiliary method for deallocating unneeded objects in entry pairs.
|
|
|
|
private function FreeEntries(array<AssociativeArray.Entry> entries)
|
|
|
|
{
|
|
|
|
local int i;
|
|
|
|
for (i = 0; i < entries.length; i += 1)
|
|
|
|
{
|
|
|
|
_.memory.Free(entries[i].key);
|
|
|
|
_.memory.Free(entries[i].value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Uses given parser to parse a JSON value.
|
|
|
|
*
|
|
|
|
* This method will parse JSON values that are contained in parsed JSON object
|
|
|
|
* according to description given for `ParseWith()` method.
|
|
|
|
*
|
|
|
|
* Rules for determining types into which JSON value will be parsed:
|
|
|
|
* 1. Null values will be returned as `none`;
|
|
|
|
* 2. Number values will be return as an `IntBox`/`IntRef` if they consist
|
|
|
|
* of only digits (and optionally a sign) and `FloatBox`/`FloatRef`
|
|
|
|
* otherwise. Choice between box and ref is made based on
|
|
|
|
* `parseAsMutable` parameter (boxes are immutable, refs are mutable);
|
|
|
|
* 3. String values will be parsed as `Text`/`MutableText`, based on
|
|
|
|
* `parseAsMutable` parameter;
|
|
|
|
* 4. Array values will be parsed as a `DynamicArray`, it's items parsed
|
|
|
|
* according to these rules (`parseAsMutable` parameter is propagated).
|
|
|
|
* 5. Object values will be parsed as a `AssociativeArray`, it's items
|
|
|
|
* parsed according to these rules (`parseAsMutable` parameter is
|
|
|
|
* propagated) and recorded under the keys parsed into `Text`.
|
|
|
|
*
|
|
|
|
* It does not matter what content follows parsed value in the `parser`,
|
|
|
|
* method will be successful as long as it manages to parse correct
|
|
|
|
* JSON value (from the current `parser`'s position).
|
|
|
|
*
|
|
|
|
* To check whether parsing have failed, simply check if `parser` is in
|
|
|
|
* a failed state after the method call.
|
|
|
|
*
|
|
|
|
* @param parser Parser that method would use to parse JSON value
|
|
|
|
* from it's current position. It's confirmed state will not be changed.
|
|
|
|
* If parsing was successful it will point at the next available character.
|
|
|
|
* Parser will be in a failed state after this method iff
|
|
|
|
* parsing has failed.
|
|
|
|
* @param parseAsMutable `true` if you want this method to parse value
|
|
|
|
* (and it's sub-items, if applicable) as mutable values and
|
|
|
|
* `false` if you want them to be immutable.
|
|
|
|
* @return Parsed JSON value as `AcediaObject` that has one of the classes
|
|
|
|
* described in parsing rules, `none` otherwise. To check for parsing
|
|
|
|
* success check the state of the `parser`. Note that method can also
|
|
|
|
* return `none` if parsed JSON value was "null".
|
|
|
|
*/
|
|
|
|
public final function AcediaObject ParseWith(
|
|
|
|
Parser parser,
|
|
|
|
optional bool parseAsMutable)
|
|
|
|
{
|
|
|
|
local AcediaObject result;
|
|
|
|
local Parser.ParserState initState;
|
|
|
|
if (parser == none) return none;
|
|
|
|
if (!parser.Ok()) return none;
|
|
|
|
|
|
|
|
initState = parser.GetCurrentState();
|
|
|
|
TryNullWith(parser);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
result = ParseBooleanWith(parser.RestoreState(initState), parseAsMutable);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result = ParseNumberWith(parser.RestoreState(initState), parseAsMutable);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result = ParseStringWith(parser.RestoreState(initState), parseAsMutable);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result = ParseArrayWith(parser.RestoreState(initState), parseAsMutable);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result = ParseObjectWith(parser.RestoreState(initState), parseAsMutable);
|
|
|
|
if (parser.Ok()) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* "Prints" given `AcediaObject` value, saving in in JSON format.
|
|
|
|
*
|
|
|
|
* Only certain classes (the same as the ones that can be parsed from JSON
|
|
|
|
* via this API) are supported:
|
|
|
|
* 1. `none` is printed into "null";
|
|
|
|
* 2. Boolean types (`BoolBox`/`BoolRef`) are printed into JSON bool value;
|
|
|
|
* 3. Integer (`IntBox`/`IntRef`) and float (`FloatBox`/`FloatRef`) types
|
|
|
|
* are printed into JSON number value;
|
|
|
|
* 4. `Text` and `MutableText` are printed into JSON string value;
|
|
|
|
* 5. `DynamicArray` is printed into JSON array with `Print()` method
|
|
|
|
* applied to each of it's items. If some of them have not printable
|
|
|
|
* types - "none" will be used as a fallback.
|
|
|
|
* 6. `AssociativeArray` is printed into JSON object with `Print()` method
|
|
|
|
* applied to each of it's items. Only items with `Text` keys are
|
|
|
|
* printed, the rest is omitted. If some of them have not printable
|
|
|
|
* types - "none" will be used as a fallback.
|
|
|
|
*
|
|
|
|
* @param toPrint Object to "print" into `MutableText`.
|
|
|
|
* @return Text version of given `toDisplay`, if it has one of the printable
|
|
|
|
* classes. Otherwise returns `none`.
|
|
|
|
* Note that `none` is considered printable and will produce "null".
|
|
|
|
*/
|
|
|
|
public final function MutableText Print(AcediaObject toPrint)
|
|
|
|
{
|
|
|
|
if (toPrint == none) {
|
|
|
|
return T(default.TNULL).MutableCopy();
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'IntBox') {
|
|
|
|
return _.text.FromIntM(IntBox(toPrint).Get());
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'IntRef') {
|
|
|
|
return _.text.FromIntM(IntRef(toPrint).Get());
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'BoolBox') {
|
|
|
|
return _.text.FromBoolM(BoolBox(toPrint).Get());
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'BoolRef') {
|
|
|
|
return _.text.FromBoolM(BoolRef(toPrint).Get());
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'FloatBox') {
|
|
|
|
return _.text.FromFloatM(FloatBox(toPrint).Get(), MAX_FLOAT_PRECISION);
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'FloatRef') {
|
|
|
|
return _.text.FromFloatM(FloatRef(toPrint).Get(), MAX_FLOAT_PRECISION);
|
|
|
|
}
|
|
|
|
if ( toPrint.class == class'Text'
|
|
|
|
|| toPrint.class == class'MutableText') {
|
|
|
|
return DisplayText(Text(toPrint));
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'DynamicArray') {
|
|
|
|
return PrintArray(DynamicArray(toPrint));
|
|
|
|
}
|
|
|
|
if (toPrint.class == class'AssociativeArray') {
|
|
|
|
return PrintObject(AssociativeArray(toPrint));
|
|
|
|
}
|
|
|
|
return none;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* "Prints" given `DynamicArray` value, saving it as a JSON array in
|
|
|
|
* `MutableText`.
|
|
|
|
*
|
|
|
|
* It's items must either be equal to `none` or have one of the following
|
|
|
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
|
|
|
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
|
|
|
|
* Otherwise items will be printed as "null" values.
|
|
|
|
* Also see `Print()` method.
|
|
|
|
*
|
|
|
|
* @param toPrint Array to "print" into `MutableText`.
|
|
|
|
* @return Text version of given `toPrint`, if it has one of the printable
|
|
|
|
* classes. Otherwise returns `none`.
|
|
|
|
* Note that `none` is considered printable and will produce "null".
|
|
|
|
*/
|
|
|
|
public final function MutableText PrintArray(DynamicArray toPrint)
|
|
|
|
{
|
|
|
|
local int i, length;
|
|
|
|
local MutableText result, printedItem;
|
|
|
|
if (toPrint == none) return none;
|
|
|
|
|
|
|
|
length = toPrint.GetLength();
|
|
|
|
result = T(default.TOPEN_BRACKET).MutableCopy();
|
|
|
|
for (i = 0; i < length; i += 1)
|
|
|
|
{
|
|
|
|
if (i > 0) {
|
|
|
|
result.Append(T(default.TCOMMA));
|
|
|
|
}
|
|
|
|
printedItem = Print(toPrint.GetItem(i));
|
|
|
|
if (printedItem != none)
|
|
|
|
{
|
|
|
|
result.Append(printedItem);
|
|
|
|
printedItem.FreeSelf();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
result.Append(T(default.TNULL));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.Append(T(default.TCLOSE_BRACKET));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* "Prints" given `AssociativeArray` value, saving it as a JSON object in
|
|
|
|
* `MutableText`.
|
|
|
|
*
|
|
|
|
* Only prints items recorded with `Text` key, the rest is omitted.
|
|
|
|
*
|
|
|
|
* It's items must either be equal to `none` or have one of the following
|
|
|
|
* classes: `BoolBox`, `BoolRef`, `IntBox`, `IntRef`, `FloatBox`, `FloatRef`,
|
|
|
|
* `Text`, `MutableText`, `DynamicArray`, `AssociativeArray`.
|
|
|
|
* Otherwise items will be printed as "null" values.
|
|
|
|
* Also see `Print()` method.
|
|
|
|
*
|
|
|
|
* @param toPrint Array to "print" into `MutableText`.
|
|
|
|
* @return Text version of given `toPrint`, if it has one of the printable
|
|
|
|
* classes. Otherwise returns `none`.
|
|
|
|
* Note that `none` is considered printable and will produce "null".
|
|
|
|
*/
|
|
|
|
public final function MutableText PrintObject(AssociativeArray toPrint)
|
|
|
|
{
|
|
|
|
local bool printedKeyValuePair;
|
|
|
|
local Iter iter;
|
|
|
|
local Text nextKey;
|
|
|
|
local AcediaObject nextValue;
|
|
|
|
local MutableText result, printedKey, printedValue;
|
|
|
|
if (toPrint == none) return none;
|
|
|
|
|
|
|
|
result = T(default.TOPEN_BRACE).MutableCopy();
|
|
|
|
iter = toPrint.Iterate();
|
|
|
|
for (iter = toPrint.Iterate(); !iter.HasFinished(); iter.Next())
|
|
|
|
{
|
|
|
|
if (printedKeyValuePair) {
|
|
|
|
result.Append(T(default.TCOMMA));
|
|
|
|
}
|
|
|
|
nextKey = Text(iter.GetKey());
|
|
|
|
nextValue = iter.Get();
|
|
|
|
if (nextKey == none) continue;
|
|
|
|
if (nextKey.class != class'Text') continue;
|
|
|
|
printedKey = DisplayText(nextKey);
|
|
|
|
printedValue = Print(nextValue);
|
|
|
|
result.Append(printedKey).Append(T(default.TCOLON));
|
|
|
|
printedKey.FreeSelf();
|
|
|
|
if (printedValue != none)
|
|
|
|
{
|
|
|
|
result.Append(printedValue);
|
|
|
|
printedValue.FreeSelf();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
result.Append(T(default.TNULL));
|
|
|
|
}
|
|
|
|
printedKeyValuePair = true;
|
|
|
|
}
|
|
|
|
result.Append(T(default.TCLOSE_BRACE));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Auxiliary method to convert `Text` into it's JSON "string"
|
|
|
|
// representation.
|
|
|
|
// We can't just dump `original`'s contents into JSON output as is,
|
|
|
|
// since we have to replace several special characters with escaped sequences.
|
|
|
|
private final function MutableText DisplayText(Text original)
|
|
|
|
{
|
|
|
|
local int i, length;
|
|
|
|
local MutableText result;
|
|
|
|
local Text.Character nextCharacter;
|
|
|
|
local Text.Character reverseSolidus;
|
|
|
|
reverseSolidus = _.text.CharacterFromCodePoint(CODEPOINT_REVERSE_SOLIDUS);
|
|
|
|
result = T(TQUOTE).MutableCopy();
|
|
|
|
length = original.GetLength();
|
|
|
|
for (i = 0; i < length; i += 1)
|
|
|
|
{
|
|
|
|
nextCharacter = original.GetCharacter(i);
|
|
|
|
if (DoesNeedEscaping(nextCharacter.codePoint))
|
|
|
|
{
|
|
|
|
result.AppendCharacter(reverseSolidus);
|
|
|
|
nextCharacter.codePoint =
|
|
|
|
GetEscapedVersion(nextCharacter.codePoint);
|
|
|
|
result.AppendCharacter(nextCharacter);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
result.AppendCharacter(nextCharacter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result.Append(T(TQUOTE));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks whether a certain character (code point) needs to be replaced for
|
|
|
|
// JSON printing.
|
|
|
|
private final function bool DoesNeedEscaping(int codePoint)
|
|
|
|
{
|
|
|
|
if (codePoint == CODEPOINT_REVERSE_SOLIDUS) return true;
|
|
|
|
if (codePoint == CODEPOINT_CARRIAGE_RETURN) return true;
|
|
|
|
if (codePoint == CODEPOINT_QUOTATION_MARK) return true;
|
|
|
|
if (codePoint == CODEPOINT_BACKSPACE) return true;
|
|
|
|
if (codePoint == CODEPOINT_FORM_FEED) return true;
|
|
|
|
if (codePoint == CODEPOINT_LINE_FEED) return true;
|
|
|
|
if (codePoint == CODEPOINT_SOLIDUS) return true;
|
|
|
|
if (codePoint == CODEPOINT_TAB) return true;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replaces code point with it's escaped letter.
|
|
|
|
// When printing text into JSON some characters need to be escaped
|
|
|
|
// (see `DoesNeedEscaping()`), but while some can use themselves in escaped
|
|
|
|
// sequence ("\""), some need to be replaced with a different character ("\n").
|
|
|
|
private final function int GetEscapedVersion(int codePoint)
|
|
|
|
{
|
|
|
|
if (codePoint == CODEPOINT_BACKSPACE) {
|
|
|
|
return CODEPOINT_SMALL_B;
|
|
|
|
}
|
|
|
|
else if (codePoint == CODEPOINT_FORM_FEED) {
|
|
|
|
return CODEPOINT_SMALL_F;
|
|
|
|
}
|
|
|
|
else if (codePoint == CODEPOINT_LINE_FEED) {
|
|
|
|
return CODEPOINT_SMALL_N;
|
|
|
|
}
|
|
|
|
else if (codePoint == CODEPOINT_CARRIAGE_RETURN) {
|
|
|
|
return CODEPOINT_SMALL_R;
|
|
|
|
}
|
|
|
|
else if (codePoint == CODEPOINT_TAB) {
|
|
|
|
return CODEPOINT_SMALL_T;
|
|
|
|
}
|
|
|
|
return codePoint;
|
|
|
|
}
|
|
|
|
|
|
|
|
defaultproperties
|
|
|
|
{
|
|
|
|
MAX_FLOAT_PRECISION = 4
|
|
|
|
TNULL = 0
|
|
|
|
stringConstants(0) = "null"
|
|
|
|
TTRUE = 1
|
|
|
|
stringConstants(1) = "true"
|
|
|
|
TFALSE = 2
|
|
|
|
stringConstants(2) = "false"
|
|
|
|
TDOT = 3
|
|
|
|
stringConstants(3) = "."
|
|
|
|
TEXPONENT = 4
|
|
|
|
stringConstants(4) = "e"
|
|
|
|
TOPEN_BRACKET = 5
|
|
|
|
stringConstants(5) = "["
|
|
|
|
TCLOSE_BRACKET = 6
|
|
|
|
stringConstants(6) = "]"
|
|
|
|
TOPEN_BRACE = 7
|
|
|
|
stringConstants(7) = "&{"
|
|
|
|
TCLOSE_BRACE = 8
|
|
|
|
stringConstants(8) = "&}"
|
|
|
|
TCOMMA = 9
|
|
|
|
stringConstants(9) = ","
|
|
|
|
TCOLON = 10
|
|
|
|
stringConstants(10) = ":"
|
|
|
|
TQUOTE = 11
|
|
|
|
stringConstants(11) = "\""
|
|
|
|
|
|
|
|
CODEPOINT_BACKSPACE = 8
|
|
|
|
CODEPOINT_TAB = 9
|
|
|
|
CODEPOINT_LINE_FEED = 10
|
|
|
|
CODEPOINT_FORM_FEED = 12
|
|
|
|
CODEPOINT_CARRIAGE_RETURN = 13
|
|
|
|
CODEPOINT_QUOTATION_MARK = 34
|
|
|
|
CODEPOINT_SOLIDUS = 47
|
|
|
|
CODEPOINT_REVERSE_SOLIDUS = 92
|
|
|
|
CODEPOINT_SMALL_B = 98
|
|
|
|
CODEPOINT_SMALL_F = 102
|
|
|
|
CODEPOINT_SMALL_N = 110
|
|
|
|
CODEPOINT_SMALL_R = 114
|
|
|
|
CODEPOINT_SMALL_T = 116
|
|
|
|
}
|