js8call/jsc.cpp
2019-11-25 20:13:00 -05:00

239 lines
6.0 KiB
C++

/**
* This file is part of JS8Call.
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
*
* (C) 2018 Jordan Sherer <kn4crd@gmail.com> - All Rights Reserved
*
**/
#include "jsc.h"
#include "varicode.h"
#include <cmath>
#include <QDebug>
#include <QCache>
QMap<QString, quint32> LOOKUP_CACHE;
Codeword JSC::codeword(quint32 index, bool separate, quint32 bytesize, quint32 s, quint32 c){
QList<Codeword> out;
quint32 v = ((index % s) << 1) + (quint32)separate;
out.prepend(Varicode::intToBits(v, bytesize + 1));
quint32 x = index / s;
while(x > 0){
x -= 1;
out.prepend(Varicode::intToBits((x % c) + s, bytesize));
x /= c;
}
Codeword word;
foreach(auto w, out){
word.append(w);
}
return word;
}
QList<CodewordPair> JSC::compress(QString text){
QList<CodewordPair> out;
const quint32 b = 4;
const quint32 s = 7;
const quint32 c = pow(2, 4) - s;
QString space(" ");
QStringList words = text.split(" ", QString::KeepEmptyParts);
for(int i = 0, len = words.length(); i < len; i++){
QString w = words[i];
bool isLastWord = (i == len - 1);
bool ok = false;
bool isSpaceCharacter = false;
// if this is an empty part, it should be a space, unless its the last word.
if(w.isEmpty() && !isLastWord){
w = space;
isSpaceCharacter = true;
}
while(!w.isEmpty()){
// this does both prefix and full match lookup
auto index = lookup(w, &ok);
if(!ok){
break;
}
auto t = JSC::map[index];
w = QString(w).mid(t.size);
bool isLast = w.isEmpty();
bool shouldAppendSpace = isLast && !isSpaceCharacter && !isLastWord;
out.append({ codeword(index, shouldAppendSpace, b, s, c), (quint32)t.size + (shouldAppendSpace ? 1 : 0) /* for the space that follows */});
}
}
return out;
}
QString JSC::decompress(Codeword const& bitvec){
const quint32 b = 4;
const quint32 s = 7;
const quint32 c = pow(2, b) - s;
QStringList out;
quint32 base[8];
base[0] = 0;
base[1] = s;
base[2] = base[1] + s*c;
base[3] = base[2] + s*c*c;
base[4] = base[3] + s*c*c*c;
base[5] = base[4] + s*c*c*c*c;
base[6] = base[5] + s*c*c*c*c*c;
base[7] = base[6] + s*c*c*c*c*c*c;
QList<quint64> bytes;
QList<quint32> separators;
int i = 0;
int count = bitvec.count();
while(i < count){
auto b = bitvec.mid(i, 4);
if(b.length() != 4){
break;
}
quint64 byte = Varicode::bitsToInt(b);
bytes.append(byte);
i += 4;
if(byte < s){
if(count - i > 0 && bitvec.at(i)){
separators.append(bytes.length()-1);
}
i += 1;
}
}
quint32 start = 0;
while(start < (quint32)bytes.length()){
quint32 k = 0;
quint32 j = 0;
while(start + k < (quint32)bytes.length() && bytes[start + k] >= s){
j = j*c + (bytes[start + k] - s);
k++;
}
if(j >= JSC::size){
break;
}
if(start + k >= (quint32)bytes.length()){
break;
}
j = j*s + bytes[start + k] + base[k];
if(j >= JSC::size){
break;
}
// map is in latin1 format, not utf-8
auto word = QLatin1String(JSC::map[j].str);
out.append(word);
if(!separators.isEmpty() && separators.first() == start + k){
out.append(" ");
separators.removeFirst();
}
start = start + (k + 1);
}
return out.join("");
}
bool JSC::exists(QString w, quint32 *pIndex){
bool found = false;
quint32 index = lookup(w, &found);
if(pIndex) *pIndex = index;
return found && JSC::map[index].size == w.length();
}
quint32 JSC::lookup(QString w, bool * ok){
if(LOOKUP_CACHE.contains(w)){
if(ok) *ok = true;
return LOOKUP_CACHE[w];
}
bool found = false;
quint32 result = lookup(w.toLatin1().data(), &found);
if(found){
LOOKUP_CACHE[w] = result;
}
if(ok) *ok = found;
return result;
}
quint32 JSC::lookup(char const* b, bool *ok){
quint32 index = 0;
quint32 count = 0;
bool found = false;
// first find prefix match to jump into the list faster
for(quint32 i = 0; i < JSC::prefixSize; i++){
// skip obvious non-prefixes...
if(b[0] != JSC::prefix[i].str[0]){
continue;
}
// ok, we found one... let's end early for single char strings.
if(JSC::prefix[i].size == 1){
if(ok) *ok = true;
return JSC::list[JSC::prefix[i].index].index;
}
// otherwise, keep track of the first index in the list and the number of elements
index = JSC::prefix[i].index;
count = JSC::prefix[i].size;
found = true;
break;
}
// no prefix found... no lookup
if(!found){
if(ok) *ok = false;
return 0;
}
// now that we have the first index in the list, let's just iterate through the list, comparing words along the way
for(quint32 i = index; i < index + count; i++){
quint32 len = JSC::list[i].size;
if(strncmp(b, JSC::list[i].str, len) == 0){
if(ok) *ok = true;
return JSC::list[i].index;
}
}
if(ok) *ok = false;
return 0;
}