blob: e6677a596ddc55ff4e3f468d98bd4d89594034d7 [file] [log] [blame]
#include "image_io/base/data_scanner.h"
namespace photos_editing_formats {
namespace image_io {
namespace {
const char kWhitespaceChars[] = " \t\n\r";
/// This function is like strspn but does not assume a null-terminated string.
size_t memspn(const char* s, size_t slen, const char* accept) {
const char* p = s;
const char* spanp;
char c, sc;
cont:
c = *p++;
if (slen-- == 0) return p - 1 - s;
for (spanp = accept; (sc = *spanp++) != '\0';)
if (sc == c) goto cont;
return p - 1 - s;
}
/// @return Whether value is in the range [lo:hi].
bool InRange(char value, char lo, char hi) {
return value >= lo && value <= hi;
}
/// @return Whether the value is the first character of a kName type scanner.
bool IsFirstNameChar(char value) {
return InRange(value, 'A', 'Z') || InRange(value, 'a', 'z') || value == '_' ||
value == ':';
}
/// Scans the characters in the s string, where the characters can be any legal
/// character in the name.
/// @return The number of name characters scanned.
size_t ScanOptionalNameChars(const char* s, size_t slen) {
const char* kOptionalChars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_:";
return memspn(s, slen, kOptionalChars);
}
/// Scans the whitespace characters in the s string.
/// @return The number of whitepace characters scanned.
size_t ScanWhitespaceChars(const char* s, size_t slen) {
return memspn(s, slen, kWhitespaceChars);
}
} // namespace
std::string DataScanner::GetWhitespaceChars() { return kWhitespaceChars; }
DataScanner DataScanner::CreateLiteralScanner(const std::string& literal) {
return DataScanner(DataScanner::kLiteral, literal);
}
DataScanner DataScanner::CreateNameScanner() {
return DataScanner(DataScanner::kName);
}
DataScanner DataScanner::CreateQuotedStringScanner() {
return DataScanner(DataScanner::kQuotedString);
}
DataScanner DataScanner::CreateSentinelScanner(const std::string& sentinels) {
return DataScanner(DataScanner::kSentinel, sentinels);
}
DataScanner DataScanner::CreateThroughLiteralScanner(
const std::string& literal) {
return DataScanner(DataScanner::kThroughLiteral, literal);
}
DataScanner DataScanner::CreateWhitespaceScanner() {
return DataScanner(DataScanner::kWhitespace);
}
DataScanner DataScanner::CreateOptionalWhitespaceScanner() {
return DataScanner(DataScanner::kOptionalWhitespace);
}
size_t DataScanner::ExtendTokenLength(size_t delta_length) {
token_range_ =
DataRange(token_range_.GetBegin(), token_range_.GetEnd() + delta_length);
return token_range_.GetLength();
}
void DataScanner::SetInternalError(const DataContext& context,
const std::string& error_description,
DataMatchResult* result) {
result->SetType(DataMatchResult::kError);
result->SetMessage(
Message::kInternalError,
context.GetErrorText({}, {GetDescription()}, error_description, ""));
}
void DataScanner::SetSyntaxError(const DataContext& context,
const std::string& error_description,
DataMatchResult* result) {
result->SetType(DataMatchResult::kError);
result->SetMessage(Message::kSyntaxError,
context.GetErrorText(error_description, GetDescription()));
}
DataMatchResult DataScanner::ScanLiteral(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
DataMatchResult result;
size_t token_length = token_range_.GetLength();
if (token_length >= literal_or_sentinels_.length()) {
SetInternalError(context, "Literal already scanned", &result);
return result;
}
size_t bytes_still_needed = literal_or_sentinels_.length() - token_length;
size_t bytes_to_compare = std::min(bytes_still_needed, bytes_available);
if (strncmp(&literal_or_sentinels_[token_length], cbytes, bytes_to_compare) ==
0) {
token_length = ExtendTokenLength(bytes_to_compare);
result.SetBytesConsumed(bytes_to_compare);
result.SetType(token_length == literal_or_sentinels_.length()
? DataMatchResult::kFull
: DataMatchResult::kPartialOutOfData);
} else {
SetSyntaxError(context, "Expected literal", &result);
}
return result;
}
DataMatchResult DataScanner::ScanName(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
DataMatchResult result;
size_t token_length = token_range_.GetLength();
if (token_length == 0) {
if (!IsFirstNameChar(*cbytes)) {
SetSyntaxError(context, "Expected first character of a name", &result);
return result;
}
token_length = ExtendTokenLength(1);
result.SetBytesConsumed(1);
bytes_available -= 1;
cbytes += 1;
}
size_t optional_bytes_consumed =
ScanOptionalNameChars(cbytes, bytes_available);
token_length = ExtendTokenLength(optional_bytes_consumed);
result.IncrementBytesConsumed(optional_bytes_consumed);
if (result.GetBytesConsumed() == 0 && token_length > 0) {
result.SetType(DataMatchResult::kFull);
} else if (optional_bytes_consumed < bytes_available) {
result.SetType(DataMatchResult::kFull);
} else {
result.SetType(DataMatchResult::kPartialOutOfData);
}
return result;
}
DataMatchResult DataScanner::ScanQuotedString(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
const size_t kStart = 0;
const size_t kDone = '.';
const size_t kSquote = '\'';
const size_t kDquote = '"';
DataMatchResult result;
size_t token_length = token_range_.GetLength();
if ((data_ == kStart && token_length != 0) ||
(data_ != kStart && data_ != kSquote && data_ != kDquote)) {
SetInternalError(context, "Inconsistent state", &result);
return result;
}
if (data_ == kStart) {
if (*cbytes != kSquote && *cbytes != kDquote) {
SetSyntaxError(context, "Expected start of a quoted string", &result);
return result;
}
data_ = *cbytes++;
bytes_available--;
result.SetBytesConsumed(1);
token_length = ExtendTokenLength(1);
}
const char* ebytes = reinterpret_cast<const char*>(
memchr(cbytes, static_cast<int>(data_), bytes_available));
size_t bytes_scanned = ebytes ? ebytes - cbytes : bytes_available;
result.IncrementBytesConsumed(bytes_scanned);
token_length = ExtendTokenLength(bytes_scanned);
if (bytes_scanned == bytes_available) {
result.SetType(DataMatchResult::kPartialOutOfData);
} else {
result.SetType(DataMatchResult::kFull);
result.IncrementBytesConsumed(1);
ExtendTokenLength(1);
data_ = kDone;
}
return result;
}
DataMatchResult DataScanner::ScanSentinel(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
DataMatchResult result;
if (data_ != 0) {
SetInternalError(context, "Sentinel already scanned", &result);
return result;
}
char cbyte = *cbytes;
for (size_t index = 0; index < literal_or_sentinels_.size(); ++index) {
char sentinel = literal_or_sentinels_[index];
if ((sentinel == '~' && IsFirstNameChar(cbyte)) || cbyte == sentinel) {
ExtendTokenLength(1);
result.SetBytesConsumed(1).SetType(DataMatchResult::kFull);
data_ = sentinel;
break;
}
}
if (result.GetBytesConsumed() == 0) {
SetSyntaxError(context, "Expected sentinal character", &result);
}
return result;
}
DataMatchResult DataScanner::ScanThroughLiteral(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
DataMatchResult result;
size_t& scanned_literal_length = data_;
if (scanned_literal_length >= literal_or_sentinels_.length()) {
SetInternalError(context, "Literal already scanned", &result);
return result;
}
while (bytes_available > 0) {
if (scanned_literal_length == 0) {
// Literal scan not in progress. Find the first char of the literal.
auto* matched_byte = reinterpret_cast<const char*>(
memchr(cbytes, literal_or_sentinels_[0], bytes_available));
if (matched_byte == nullptr) {
// first char not found and chars exhausted.
ExtendTokenLength(bytes_available);
result.IncrementBytesConsumed(bytes_available);
result.SetType(DataMatchResult::kPartialOutOfData);
break;
} else {
// found the first char of the literal.
size_t bytes_scanned = (matched_byte - cbytes) + 1;
result.IncrementBytesConsumed(bytes_scanned);
bytes_available -= bytes_scanned;
cbytes += bytes_scanned;
ExtendTokenLength(bytes_scanned);
scanned_literal_length = 1;
}
}
// check if the rest of the literal is there.
size_t bytes_still_needed =
literal_or_sentinels_.length() - scanned_literal_length;
size_t bytes_to_compare = std::min(bytes_still_needed, bytes_available);
if (strncmp(&literal_or_sentinels_[scanned_literal_length], cbytes,
bytes_to_compare) == 0) {
// Yes, the whole literal is there or chars are exhausted.
ExtendTokenLength(bytes_to_compare);
scanned_literal_length += bytes_to_compare;
result.IncrementBytesConsumed(bytes_to_compare);
result.SetType(scanned_literal_length == literal_or_sentinels_.length()
? DataMatchResult::kFull
: DataMatchResult::kPartialOutOfData);
break;
}
// false alarm, the firsts char of the literal were found, but not the
// whole enchilada. Keep searching at one past the first char of the match.
scanned_literal_length = 0;
}
return result;
}
DataMatchResult DataScanner::ScanWhitespace(const char* cbytes,
size_t bytes_available,
const DataContext& context) {
DataMatchResult result;
size_t token_length = token_range_.GetLength();
result.SetBytesConsumed(ScanWhitespaceChars(cbytes, bytes_available));
token_length = ExtendTokenLength(result.GetBytesConsumed());
if (result.GetBytesConsumed() == 0) {
if (token_length == 0 && type_ == kWhitespace) {
SetSyntaxError(context, "Expected whitespace", &result);
} else {
result.SetType(DataMatchResult::kFull);
}
} else {
result.SetType((result.GetBytesConsumed() < bytes_available)
? DataMatchResult::kFull
: DataMatchResult::kPartialOutOfData);
}
return result;
}
DataMatchResult DataScanner::Scan(const DataContext& context) {
scan_call_count_ += 1;
DataMatchResult result;
if (!context.IsValidLocationAndRange()) {
SetInternalError(context, context.GetInvalidLocationAndRangeErrorText(),
&result);
return result;
}
if (!token_range_.IsValid()) {
token_range_ = DataRange(context.GetLocation(), context.GetLocation());
}
size_t bytes_available = context.GetRange().GetEnd() - context.GetLocation();
const char* cbytes = context.GetCharBytes();
switch (type_) {
case kLiteral:
result = ScanLiteral(cbytes, bytes_available, context);
break;
case kName:
result = ScanName(cbytes, bytes_available, context);
break;
case kQuotedString:
result = ScanQuotedString(cbytes, bytes_available, context);
break;
case kSentinel:
result = ScanSentinel(cbytes, bytes_available, context);
break;
case kThroughLiteral:
result = ScanThroughLiteral(cbytes, bytes_available, context);
break;
case kWhitespace:
case kOptionalWhitespace:
result = ScanWhitespace(cbytes, bytes_available, context);
break;
default:
SetInternalError(context, "Undefined scanner type", &result);
break;
}
return result;
}
void DataScanner::ResetTokenRange() { token_range_ = DataRange(); }
void DataScanner::Reset() {
data_ = 0;
scan_call_count_ = 0;
ResetTokenRange();
}
std::string DataScanner::GetDescription() const {
std::string description;
switch (type_) {
case kLiteral:
description = "Literal:'";
description += literal_or_sentinels_;
description += "'";
break;
case kName:
description = "Name";
break;
case kQuotedString:
description = "QuotedString";
break;
case kSentinel:
description = "OneOf:'";
description += literal_or_sentinels_;
description += "'";
break;
case kThroughLiteral:
description = "ThruLiteral:'";
description += literal_or_sentinels_;
description += "'";
break;
case kWhitespace:
description = "Whitespace";
break;
case kOptionalWhitespace:
description = "OptionalWhitespace";
break;
}
return description;
}
std::string DataScanner::GetLiteral() const {
return type_ == kLiteral || type_ == kThroughLiteral ? literal_or_sentinels_
: "";
}
std::string DataScanner::GetSentenels() const {
return type_ == kSentinel ? literal_or_sentinels_ : "";
}
char DataScanner::GetSentinel() const { return type_ == kSentinel ? data_ : 0; }
} // namespace image_io
} // namespace photos_editing_formats