Files
filament/libs/filament-matp/src/JsonishParser.cpp
T
yein fa436f1d12 Make JsonishParser use utils::Status (#9302)
* Make JsonishParser use utils::Status

- add unsupported error in utils::Status
- add invalid case in the test

* return a pair of Status and string in resolveEscapes;
use initializer list for JsonishString and move that to the header.
2025-10-09 15:44:03 -07:00

332 lines
9.0 KiB
C++

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "JsonishParser.h"
#include <utils/sstream.h>
#include <utils/string.h>
#include <utils/Status.h>
#include <string.h>
namespace matp {
static std::pair<utils::Status, std::string> resolveEscapes(const std::string& s) {
std::string out;
out.reserve(s.length());
bool inEscape = false;
for (char c : s) {
if (inEscape) {
switch (c) {
case '\\':
out += '\\';
break;
case '/':
out += '\u002F';
break;
case 't':
out += '\t';
break;
case 'r':
out += '\r';
break;
case 'n':
out += '\n';
break;
case 'f':
out += '\f';
break;
case 'b':
out += '\b';
break;
case '"':
out += '"';
break;
case 'u':
// TODO: implement unicode escape sequences
return { utils::Status::unsupported(
"Unicode escape sequences currently not supported."),
out };
break;
default:
utils::io::sstream errorMessage;
errorMessage << "Invalid escape sequence \\" << c << ".";
return { utils::Status::invalidArgument(
errorMessage.c_str()), out };
}
inEscape = false;
} else {
if (c == '\\') {
inEscape = true;
continue;
} else {
out += c;
}
}
}
if (inEscape) {
return { utils::Status::invalidArgument("Incomplete escape sequence."), out };
}
return { utils::Status::ok(), out };
}
std::unique_ptr<JsonishObject> JsonishParser::parse() noexcept {
std::unique_ptr<JsonishObject> p(parseObject());
return p;
}
utils::Status JsonishParser::getParseStatus() const noexcept {
return mStatus;
}
JsonishObject* JsonishParser::parseObject() noexcept {
// Read the {
if (!consumeLexeme(JsonType::BLOCK_START)) {
setInvalidArgumentError("expected token '{'");
return nullptr;
}
// Empty blocks are allowed.
if (consumeLexeme(BLOCK_END)) {
return new JsonishObject();
}
// Read members.
JsonishObject* object = parseMembers();
// Read the }
if (!consumeLexeme(JsonType::BLOCK_END)) {
setInvalidArgumentError("expected token '}'");
delete object;
return nullptr;
}
return object;
}
JsonishArray* JsonishParser::parseArray() noexcept {
// Read the [
if (!consumeLexeme(JsonType::ARRAY_START)) {
setInvalidArgumentError("expected token '['");
return nullptr;
}
// Empty arrays are allowed.
if (consumeLexeme(ARRAY_END)) {
return new JsonishArray();
}
// Read elements.
JsonishArray* array = parseElements();
// Read the ]
if (!consumeLexeme(JsonType::ARRAY_END)) {
setInvalidArgumentError("expected token ']'");
delete array;
return nullptr;
}
return array;
}
JsonishPair JsonishParser::parsePair() noexcept {
JsonishPair pair {"" , nullptr};
// Read key string.
const JsonLexeme* keyLexeme = consumeLexeme(JsonType::STRING);
if (!keyLexeme) {
return pair;
}
// Read the :
if (!consumeLexeme(JsonType::COLUMN)) {
return pair;
}
// Read the value.
JsonishValue* value = parseValue();
if (!value) {
return pair;
}
pair = {keyLexeme->getStringValue(), value};
return pair;
}
const JsonLexeme* JsonishParser::consumeLexeme(JsonType type) noexcept {
if (mCursor >= mLexemes.size()) {
return nullptr;
}
const JsonLexeme* lexeme = &mLexemes[mCursor];
if (lexeme->getType() == type) {
mCursor++;
return lexeme;
} else {
return nullptr;
}
}
const JsonLexeme* JsonishParser::peekNextLexemeType() const noexcept {
if (mCursor >= mLexemes.size()) {
return nullptr;
}
return &mLexemes[mCursor];
}
const JsonLexeme* JsonishParser::peekCurrentLexemeType() const noexcept {
if (mCursor <= 0) {
return nullptr;
}
return &mLexemes[mCursor - 1];
}
JsonishObject* JsonishParser::parseMembers() noexcept {
JsonishObject* object = new JsonishObject();
while (true) {
JsonishPair pair = parsePair();
if (!pair.value) {
// Accept a comma after the last element
const JsonLexeme* lexeme = peekNextLexemeType();
if (lexeme && lexeme->getType() == BLOCK_END) {
break;
}
setInvalidArgumentError("unable to parse pair");
delete object;
return nullptr;
}
object->addPair(pair);
// Is there a comma and more pairs?
if (!consumeLexeme(COMMA)) {
break;
}
}
return object;
}
JsonishArray* JsonishParser::parseElements() noexcept {
JsonishArray* array = new JsonishArray();
while (true) {
JsonishValue* value = parseValue();
if (!value) {
setInvalidArgumentError("unable to read value");
delete array;
return nullptr;
}
array->addValue(value);
// Is there a comma and more pairs?
if (!consumeLexeme(COMMA)) {
break;
}
}
return array;
}
// Strings optionally have an array suffix, as in: "float[9]". To handle this we parse the array
// value (which might be multidimensional), discard the parsed value, and append it to the string.
JsonishValue* JsonishParser::parseString() noexcept {
std::string tmp;
const JsonLexeme* strLexeme = consumeLexeme(STRING);
const JsonLexeme* arrLexeme = peekNextLexemeType();
JsonishValue* arrValue;
if (arrLexeme && arrLexeme->getType() == ARRAY_START && (arrValue = parseArray())) {
delete arrValue;
const JsonLexeme* next = peekNextLexemeType();
if (!next) {
return nullptr;
}
size_t length = next->getStart() - strLexeme->getStart();
tmp = std::string(strLexeme->getStart(), length);
} else {
tmp = std::string(strLexeme->getStart(), strLexeme->getSize());
}
auto [status, resolvedStr] = resolveEscapes(tmp);
if (!status.isOk()) {
mStatus = status;
return nullptr;
}
return new JsonishString(resolvedStr);
}
JsonishValue* JsonishParser::parseValue() noexcept {
const JsonLexeme* next = peekNextLexemeType();
if (next == nullptr) {
return nullptr;
}
switch (next->getType()) {
case STRING:
return parseString();
case NUMBER:
consumeLexeme(NUMBER);
return new JsonishNumber(utils::strtof_c(next->getStart(), nullptr));
case BLOCK_START:
return parseObject();
case ARRAY_START:
return parseArray();
case BOOLEAN:
consumeLexeme(BOOLEAN);
return new JsonishBool(!strncmp(next->getStart(), "true", next->getSize()));
case NUll:
consumeLexeme(NUll);
return new JsonishNull();
default:
setInvalidArgumentError("unexpected token");
return nullptr;
}
}
void JsonishParser::setInvalidArgumentError(const char* message) noexcept {
// Already encountered an error.
if (!mStatus.isOk()) {
return;
}
utils::io::sstream errorMessage;
errorMessage << "JsonishParser error" << utils::io::endl;
errorMessage << "Syntax error, " << message;
const JsonLexeme* lexeme = peekNextLexemeType();
if (lexeme == nullptr) {
lexeme = peekCurrentLexemeType();
}
if (lexeme != nullptr) {
errorMessage << " at line:" << lexeme->getLine()
<< " position:" << lexeme->getLinePosition() << utils::io::endl
<< " got lexeme type: "
<< JsonLexeme::getTypeString(lexeme->getType()) << utils::io::endl
<< " got lexeme value: " << lexeme->getStringValue() << utils::io::endl;
} else {
errorMessage << " but reached end of file instead." << utils::io::endl;
}
mStatus = utils::Status::invalidArgument(errorMessage.c_str());
}
} // namespace matp