#include <algorithm>
#include <cstdint>
#include <cstring>
#include <memory>
#include "BinaryDict.hpp"
#include "DartsDict.hpp"
#include "Dict.hpp"
#include "Lexicon.hpp"
#include "darts.h"
using namespace opencc;
static const char* OCDHEADER = "OPENCCDARTS1";
namespace {
struct LegacyUnit64 {
uint64_t unit;
bool has_leaf() const { return ((unit >> 8) & 1) == 1; }
int value() const {
return static_cast<int>(unit & ((1U << 31) - 1));
}
uint64_t label() const { return unit & ((1U << 31) | 0xFF); }
uint64_t offset() const {
return (unit >> 10) << ((unit & (1U << 9)) >> 6);
}
};
int Legacy64ExactMatch(const LegacyUnit64* arr, const char* key, size_t len) {
size_t pos = 0;
for (size_t i = 0; i < len; ++i) {
uint64_t c = static_cast<unsigned char>(key[i]);
pos ^= static_cast<size_t>(arr[pos].offset() ^ c);
if (arr[pos].label() != c) return -1;
}
if (!arr[pos].has_leaf()) return -1;
return arr[pos ^ static_cast<size_t>(arr[pos].offset())].value();
}
size_t Legacy64PrefixSearch(const LegacyUnit64* arr, const char* key,
size_t maxLen, int* results, size_t maxResults) {
size_t count = 0;
size_t pos = 0;
for (size_t i = 0; i < maxLen; ++i) {
uint64_t c = static_cast<unsigned char>(key[i]);
pos ^= static_cast<size_t>(arr[pos].offset() ^ c);
if (arr[pos].label() != c) break;
if (arr[pos].has_leaf()) {
if (count < maxResults) {
results[count] = arr[pos ^ static_cast<size_t>(arr[pos].offset())].value();
}
++count;
}
}
return count;
}
bool ValidateLegacy64(const LegacyUnit64* arr, size_t numUnits, int maxValueLimit) {
if (numUnits == 0) return false;
if (arr[0].label() != 0 || arr[0].has_leaf() || arr[0].offset() == 0) return false;
if (((static_cast<size_t>(arr[0].offset())) | 0xFFu) >= numUnits) return false;
for (size_t i = 1; i < numUnits; ++i) {
uint64_t lbl = arr[i].label();
if (lbl <= 0xFF) {
if (((i ^ static_cast<size_t>(arr[i].offset())) | 0xFFu) >= numUnits) return false;
} else if (maxValueLimit >= 0) {
if (arr[i].value() >= maxValueLimit) return false;
}
}
return true;
}
}
class DartsDict::DartsInternal {
public:
BinaryDictPtr binary;
void* buffer;
Darts::DoubleArray* doubleArray; const LegacyUnit64* legacyArray64;
DartsInternal()
: binary(nullptr), buffer(nullptr), doubleArray(nullptr),
legacyArray64(nullptr) {}
~DartsInternal() {
if (buffer != nullptr) {
free(buffer);
}
delete doubleArray;
}
};
DartsDict::DartsDict() { internal = new DartsInternal; }
DartsDict::~DartsDict() { delete internal; }
size_t DartsDict::KeyMaxLength() const { return maxLength; }
Optional<const DictEntry*> DartsDict::Match(const char* word,
size_t len) const {
if (len > maxLength) {
return Optional<const DictEntry*>::Null();
}
if (internal->legacyArray64 != nullptr) {
int val = Legacy64ExactMatch(internal->legacyArray64, word, len);
if (val != -1) {
return Optional<const DictEntry*>(lexicon->At(static_cast<size_t>(val)));
}
return Optional<const DictEntry*>::Null();
}
Darts::DoubleArray& dict = *internal->doubleArray;
Darts::DoubleArray::result_pair_type result;
dict.exactMatchSearch(word, result, len);
if (result.value != -1) {
return Optional<const DictEntry*>(
lexicon->At(static_cast<size_t>(result.value)));
}
return Optional<const DictEntry*>::Null();
}
Optional<const DictEntry*> DartsDict::MatchPrefix(const char* word,
size_t len) const {
const size_t DEFAULT_NUM_ENTRIES = 64;
size_t searchLen = (std::min)(maxLength, len);
if (internal->legacyArray64 != nullptr) {
int results[DEFAULT_NUM_ENTRIES];
size_t numMatched = Legacy64PrefixSearch(
internal->legacyArray64, word, searchLen, results, DEFAULT_NUM_ENTRIES);
if (numMatched == 0) {
return Optional<const DictEntry*>::Null();
}
int maxVal;
if (numMatched < DEFAULT_NUM_ENTRIES) {
maxVal = results[numMatched - 1];
} else {
std::vector<int> rematchedResults(numMatched);
numMatched = Legacy64PrefixSearch(internal->legacyArray64, word,
searchLen, rematchedResults.data(),
rematchedResults.size());
maxVal = rematchedResults[numMatched - 1];
}
if (maxVal >= 0) {
return Optional<const DictEntry*>(
lexicon->At(static_cast<size_t>(maxVal)));
}
return Optional<const DictEntry*>::Null();
}
Darts::DoubleArray& dict = *internal->doubleArray;
Darts::DoubleArray::value_type results[DEFAULT_NUM_ENTRIES];
Darts::DoubleArray::value_type maxMatchedResult;
size_t numMatched =
dict.commonPrefixSearch(word, results, DEFAULT_NUM_ENTRIES, searchLen);
if (numMatched == 0) {
return Optional<const DictEntry*>::Null();
} else if (numMatched < DEFAULT_NUM_ENTRIES) {
maxMatchedResult = results[numMatched - 1];
} else {
Darts::DoubleArray::value_type* rematchedResults =
new Darts::DoubleArray::value_type[numMatched];
numMatched = dict.commonPrefixSearch(word, rematchedResults, numMatched,
searchLen);
maxMatchedResult = rematchedResults[numMatched - 1];
delete[] rematchedResults;
}
if (maxMatchedResult >= 0) {
return Optional<const DictEntry*>(
lexicon->At(static_cast<size_t>(maxMatchedResult)));
}
return Optional<const DictEntry*>::Null();
}
LexiconPtr DartsDict::GetLexicon() const { return lexicon; }
PrefixMatchView DartsDict::MatchPrefixValue(const char* word,
size_t len) const {
Optional<const DictEntry*> matched = MatchPrefix(word, len);
if (matched.IsNull()) {
return PrefixMatchView{};
}
const DictEntry* entry = matched.Get();
const size_t keyLen = entry->KeyLength();
return PrefixMatchView{true, keyLen, std::string_view(word, keyLen),
entry->GetDefaultView()};
}
DartsDictPtr DartsDict::NewFromFile(FILE* fp) {
DartsDictPtr dict(new DartsDict());
auto internal = dict->internal;
size_t headerLen = strlen(OCDHEADER);
std::vector<char> headerBuffer(headerLen);
size_t bytesRead = fread(headerBuffer.data(), sizeof(char), headerLen, fp);
if (bytesRead != headerLen || memcmp(headerBuffer.data(), OCDHEADER, headerLen) != 0) {
throw InvalidFormat("Invalid OpenCC dictionary header");
}
long currentOffset = ftell(fp);
fseek(fp, 0L, SEEK_END);
long fileEnd = ftell(fp);
fseek(fp, currentOffset, SEEK_SET);
size_t remainingSize =
(fileEnd > currentOffset)
? static_cast<size_t>(fileEnd - currentOffset)
: 0;
uint8_t probe[8];
if (fread(probe, 1, 8, fp) != 8) {
throw InvalidFormat("Invalid OpenCC dictionary header (dartsSize)");
}
bool is64bit =
(probe[4] == 0 && probe[5] == 0 && probe[6] == 0 && probe[7] == 0);
if (is64bit) {
uint64_t dartsSize64;
memcpy(&dartsSize64, probe, 8);
size_t dartsSize = static_cast<size_t>(dartsSize64);
if (dartsSize > remainingSize) {
throw InvalidFormat(
"Invalid OpenCC dictionary (dartsSize exceeds file size)");
}
if (dartsSize % sizeof(LegacyUnit64) != 0) {
throw InvalidFormat("Invalid legacy OCD dictionary unit alignment");
}
std::unique_ptr<void, decltype(&free)> buffer(malloc(dartsSize), free);
if (!buffer) {
throw std::bad_alloc();
}
bytesRead = fread(buffer.get(), 1, dartsSize, fp);
if (bytesRead != dartsSize) {
throw InvalidFormat("Invalid legacy OCD dictionary size mismatch");
}
BinaryDictPtr binary = BinaryDict::NewFromFile(fp);
internal->buffer = buffer.release();
internal->binary = std::move(binary);
internal->legacyArray64 = static_cast<const LegacyUnit64*>(internal->buffer);
dict->lexicon = internal->binary->GetLexicon();
dict->maxLength = internal->binary->KeyMaxLength();
size_t numUnits = dartsSize / sizeof(LegacyUnit64);
if (!ValidateLegacy64(internal->legacyArray64, numUnits,
static_cast<int>(dict->lexicon->Length()))) {
throw InvalidFormat("Invalid legacy OCD dictionary darts data");
}
return dict;
}
fseek(fp, -4, SEEK_CUR);
uint32_t dartsSize32;
memcpy(&dartsSize32, probe, 4);
size_t dartsSize = dartsSize32;
if (dartsSize > remainingSize) {
throw InvalidFormat(
"Invalid OpenCC dictionary (dartsSize exceeds file size)");
}
std::unique_ptr<Darts::DoubleArray> doubleArray(new Darts::DoubleArray());
if (dartsSize % doubleArray->unit_size() != 0) {
throw InvalidFormat("Invalid OpenCC dictionary size of darts alignment");
}
std::unique_ptr<void, decltype(&free)> buffer(malloc(dartsSize), free);
if (!buffer) {
throw std::bad_alloc();
}
bytesRead = fread(buffer.get(), 1, dartsSize, fp);
if (bytesRead != dartsSize) {
throw InvalidFormat("Invalid OpenCC dictionary size of darts mismatch");
}
doubleArray->set_array(buffer.get(), dartsSize / doubleArray->unit_size());
BinaryDictPtr binary = BinaryDict::NewFromFile(fp);
internal->buffer = buffer.release();
internal->doubleArray = doubleArray.release();
internal->binary = std::move(binary);
dict->lexicon = internal->binary->GetLexicon();
dict->maxLength = internal->binary->KeyMaxLength();
if (!internal->doubleArray->validate(static_cast<Darts::DoubleArray::value_type>(
dict->lexicon->Length()))) {
throw InvalidFormat("Invalid OpenCC dictionary darts data");
}
return dict;
}
DartsDictPtr DartsDict::NewFromBuffer(const char* data, size_t size) {
DartsDictPtr dict(new DartsDict());
auto internal = dict->internal;
size_t headerLen = strlen(OCDHEADER);
if (size < headerLen || memcmp(data, OCDHEADER, headerLen) != 0) {
throw InvalidFormat("Invalid OpenCC dictionary header");
}
size_t offset = headerLen;
if (size - offset < 8) {
throw InvalidFormat("Invalid OpenCC dictionary header (dartsSize)");
}
uint8_t probe[8];
memcpy(probe, data + offset, 8);
offset += 8;
bool is64bit =
(probe[4] == 0 && probe[5] == 0 && probe[6] == 0 && probe[7] == 0);
if (is64bit) {
uint64_t dartsSize64;
memcpy(&dartsSize64, probe, 8);
size_t dartsSize = static_cast<size_t>(dartsSize64);
if (dartsSize > size - offset) {
throw InvalidFormat(
"Invalid OpenCC dictionary (dartsSize exceeds file size)");
}
if (dartsSize % sizeof(LegacyUnit64) != 0) {
throw InvalidFormat("Invalid legacy OCD dictionary unit alignment");
}
std::unique_ptr<void, decltype(&free)> buffer(malloc(dartsSize), free);
if (!buffer) {
throw std::bad_alloc();
}
memcpy(buffer.get(), data + offset, dartsSize);
offset += dartsSize;
BinaryDictPtr binary = BinaryDict::NewFromBuffer(data + offset, size - offset);
internal->buffer = buffer.release();
internal->binary = std::move(binary);
internal->legacyArray64 = static_cast<const LegacyUnit64*>(internal->buffer);
dict->lexicon = internal->binary->GetLexicon();
dict->maxLength = internal->binary->KeyMaxLength();
size_t numUnits = dartsSize / sizeof(LegacyUnit64);
if (!ValidateLegacy64(internal->legacyArray64, numUnits,
static_cast<int>(dict->lexicon->Length()))) {
throw InvalidFormat("Invalid legacy OCD dictionary darts data");
}
return dict;
}
uint32_t dartsSize32;
memcpy(&dartsSize32, probe, 4);
size_t dartsSize = dartsSize32;
offset -= 4;
if (dartsSize > size - offset) {
throw InvalidFormat(
"Invalid OpenCC dictionary (dartsSize exceeds file size)");
}
std::unique_ptr<Darts::DoubleArray> doubleArray(new Darts::DoubleArray());
if (dartsSize % doubleArray->unit_size() != 0) {
throw InvalidFormat("Invalid OpenCC dictionary size of darts alignment");
}
std::unique_ptr<void, decltype(&free)> buffer(malloc(dartsSize), free);
if (!buffer) {
throw std::bad_alloc();
}
memcpy(buffer.get(), data + offset, dartsSize);
offset += dartsSize;
doubleArray->set_array(buffer.get(), dartsSize / doubleArray->unit_size());
BinaryDictPtr binary = BinaryDict::NewFromBuffer(data + offset, size - offset);
internal->buffer = buffer.release();
internal->doubleArray = doubleArray.release();
internal->binary = std::move(binary);
dict->lexicon = internal->binary->GetLexicon();
dict->maxLength = internal->binary->KeyMaxLength();
if (!internal->doubleArray->validate(static_cast<Darts::DoubleArray::value_type>(
dict->lexicon->Length()))) {
throw InvalidFormat("Invalid OpenCC dictionary darts data");
}
return dict;
}
DartsDictPtr DartsDict::NewFromDict(const Dict& thatDict) {
DartsDictPtr dict(new DartsDict());
auto internal = dict->internal;
std::unique_ptr<Darts::DoubleArray> doubleArray(new Darts::DoubleArray());
std::vector<std::string> keys;
std::vector<const char*> keys_cstr;
size_t maxLength = 0;
const LexiconPtr& lexicon = thatDict.GetLexicon();
size_t lexiconCount = lexicon->Length();
keys.resize(lexiconCount);
keys_cstr.resize(lexiconCount);
for (size_t i = 0; i < lexiconCount; i++) {
const DictEntry* entry = lexicon->At(i);
keys[i] = entry->Key();
keys_cstr[i] = keys[i].c_str();
maxLength = (std::max)(entry->KeyLength(), maxLength);
}
doubleArray->build(lexicon->Length(), &keys_cstr[0]);
dict->lexicon = lexicon;
dict->maxLength = maxLength;
internal->doubleArray = doubleArray.release();
return dict;
}
void DartsDict::SerializeToFile(FILE* fp) const {
if (internal->doubleArray == nullptr) {
throw InvalidFormat(
"Cannot serialize a legacy 64-bit OCD dictionary; "
"rebuild via NewFromDict first");
}
Darts::DoubleArray& dict = *internal->doubleArray;
fwrite(OCDHEADER, sizeof(char), strlen(OCDHEADER), fp);
uint32_t dartsSize = static_cast<uint32_t>(dict.total_size());
fwrite(&dartsSize, sizeof(uint32_t), 1, fp);
fwrite(dict.array(), sizeof(char), dartsSize, fp);
internal->binary.reset(new BinaryDict(lexicon));
internal->binary->SerializeToFile(fp);
}