xwords/xwords4/common/dictnry.c

1282 lines
37 KiB
C
Raw Normal View History

/* -*- compile-command: "cd ../linux && make MEMDEBUG=TRUE -j3"; -*- */
2003-11-01 06:35:29 +01:00
/*
* Copyright 1997 - 2022 by Eric House (xwords@eehouse.org). All rights
* reserved.
2003-11-01 06:35:29 +01:00
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef USE_STDIO
# include <stdio.h>
# include <stdlib.h>
#endif
#include "comtypes.h"
#include "dictnryp.h"
#include "dictiter.h"
2003-11-01 06:35:29 +01:00
#include "xwstream.h"
#include "strutils.h"
#include "dictiter.h"
#include "game.h"
#include "dbgutil.h"
2003-11-01 06:35:29 +01:00
#ifdef CPLUS
extern "C" {
#endif
/*****************************************************************************
*
****************************************************************************/
static XP_Bool makeBitmap( XP_U8 const** ptrp, const XP_U8* end );
static XP_U8* getCountsFor( const DictionaryCtxt* dict, XP_U16 nCols );
const DictionaryCtxt*
p_dict_ref( const DictionaryCtxt* dict, XWEnv XP_UNUSED(xwe)
#ifdef DEBUG_REF
,const char* func, const char* file, int line
#endif
)
{
if ( !!dict ) {
DictionaryCtxt* _dict = (DictionaryCtxt*)dict;
pthread_mutex_lock( &_dict->mutex );
++_dict->refCount;
2014-08-21 16:15:20 +02:00
#ifdef DEBUG_REF
XP_LOGFF( "(dict=%p): refCount now %d (from line %d of %s() in %s)",
dict, dict->refCount, line, func, file );
2014-08-21 16:15:20 +02:00
#endif
pthread_mutex_unlock( &_dict->mutex );
}
return dict;
}
void
p_dict_unref( const DictionaryCtxt* dict, XWEnv xwe
#ifdef DEBUG_REF
,const char* func, const char* file, int line
#endif
)
{
if ( !!dict ) {
DictionaryCtxt* _dict = (DictionaryCtxt*)dict;
pthread_mutex_lock( &_dict->mutex );
XP_ASSERT( 0 != _dict->refCount );
--_dict->refCount;
XP_ASSERT( 0 <= _dict->refCount );
2014-08-21 16:15:20 +02:00
#ifdef DEBUG_REF
2021-05-14 20:32:08 +02:00
XP_LOGFF( "(dict=%p): refCount now %d (from line %d of %s() in %s)",
dict, dict->refCount, line, func, file );
2014-08-21 16:15:20 +02:00
#endif
pthread_mutex_unlock( &_dict->mutex );
if ( 0 == _dict->refCount ) {
2020-08-17 04:29:08 +02:00
/* There's a race here. If another thread locks the mutex we'll
still destroy the dict (and the locked mutex!!!) PENDING */
pthread_mutex_destroy( &_dict->mutex );
(*dict->destructor)( _dict, xwe );
}
}
}
void
dict_unref_all( PlayerDicts* pd, XWEnv xwe )
{
XP_U16 ii;
for ( ii = 0; ii < MAX_NUM_PLAYERS; ++ii ) {
dict_unref( pd->dicts[ii], xwe );
}
}
2022-04-21 06:00:51 +02:00
static XP_Bool
getNullTermParam( DictionaryCtxt* XP_UNUSED_DBG(dctx), XP_UCHAR** out,
const XP_U8** ptrp, const XP_U8* end )
{
2022-04-21 06:00:51 +02:00
const XP_U8* ptr = *ptrp;
XP_Bool success = ptr < end;
if ( success ) {
XP_U16 len = 1 + XP_STRLEN( (XP_UCHAR*)ptr );
success = ptr + len <= end;
if ( success ) {
*out = XP_MALLOC( dctx->mpool, len );
XP_MEMCPY( *out, ptr, len );
*ptrp += len;
}
}
XP_ASSERT( success || NULL == *out );
return success;
}
static XP_Bool
loadSpecialData( DictionaryCtxt* ctxt, XP_U8 const** ptrp,
const XP_U8* end )
{
LOG_FUNC();
XP_Bool success = XP_TRUE;
XP_U16 nSpecials = countSpecials( ctxt );
XP_U8 const* ptr = *ptrp;
XP_UCHAR** texts;
XP_UCHAR** textEnds;
SpecialBitmaps* bitmaps;
texts = (XP_UCHAR**)XP_MALLOC( ctxt->mpool,
nSpecials * sizeof(*texts) );
textEnds = (XP_UCHAR**)XP_MALLOC( ctxt->mpool,
nSpecials * sizeof(*textEnds) );
bitmaps = (SpecialBitmaps*)
XP_CALLOC( ctxt->mpool, nSpecials * sizeof(*bitmaps) );
for ( Tile ii = 0; ii < ctxt->nFaces; ++ii ) {
const XP_UCHAR* facep = ctxt->facePtrs[(short)ii];
if ( IS_SPECIAL(*facep) ) {
/* get the string */
CHECK_PTR( ptr, 1, end, error );
XP_U8 txtlen = *ptr++;
CHECK_PTR( ptr, txtlen, end, error );
XP_UCHAR* text = (XP_UCHAR*)XP_MALLOC(ctxt->mpool, txtlen+1);
texts[(int)*facep] = text;
textEnds[(int)*facep] = text + txtlen + 1;
XP_MEMCPY( text, ptr, txtlen );
ptr += txtlen;
text[txtlen] = '\0';
XP_ASSERT( *facep < nSpecials ); /* firing */
/* This little hack is safe because all bytes but the first in a
multi-byte utf-8 char have the high bit set. SYNONYM_DELIM
does not have its high bit set */
XP_ASSERT( 0 == (SYNONYM_DELIM & 0x80) );
for ( ; '\0' != *text; ++text ) {
if ( *text == SYNONYM_DELIM ) {
*text = '\0';
}
}
if ( !makeBitmap( &ptr, end ) ) {
goto error;
}
if ( !makeBitmap( &ptr, end ) ) {
goto error;
}
}
}
goto done;
error:
success = XP_FALSE;
done:
ctxt->chars = texts;
ctxt->charEnds = textEnds;
ctxt->bitmaps = bitmaps;
*ptrp = ptr;
return success;
} /* loadSpecialData */
XP_Bool
parseCommon( DictionaryCtxt* dctx, XWEnv xwe, const XP_U8** ptrp, const XP_U8* end )
{
const XP_U8* ptr = *ptrp;
XP_Bool hasHeader = XP_FALSE;
XP_Bool isUTF8 = XP_FALSE;
XP_U16 charSize;
XP_U16 flags;
XP_Bool formatOk = sizeof(flags) <= end - ptr;
if ( formatOk ) {
XP_MEMCPY( &flags, ptr, sizeof(flags) );
ptr += sizeof( flags );
flags = XP_NTOHS(flags);
XP_LOGFF( "flags=0X%X", flags );
hasHeader = 0 != (DICT_HEADER_MASK & flags);
/* if ( hasHeader ) { */
/* flags &= ~DICT_HEADER_MASK; */
/* } */
XP_U8 nodeSize = 4;
switch ( flags & 0x0007 ) {
case 0x0001:
nodeSize = 3;
charSize = 1;
dctx->is_4_byte = XP_FALSE;
break;
case 0x0002:
nodeSize = 3;
charSize = 2;
dctx->is_4_byte = XP_FALSE;
break;
case 0x0003:
charSize = 2;
dctx->is_4_byte = XP_TRUE;
break;
case 0x0004:
nodeSize = 3;
isUTF8 = XP_TRUE;
dctx->is_4_byte = XP_FALSE;
break;
case 0x0005:
isUTF8 = XP_TRUE;
dctx->is_4_byte = XP_TRUE;
break;
default:
formatOk = XP_FALSE;
break;
}
dctx->isUTF8 = isUTF8;
dctx->nodeSize = nodeSize;
}
if ( formatOk ) {
XP_U8 numFaceBytes, numFaces;
if ( hasHeader ) {
const XP_U8* headerEnd;
{
XP_U16 headerLen;
XP_MEMCPY( &headerLen, ptr, sizeof(headerLen) );
ptr += sizeof(headerLen);
headerLen = XP_NTOHS( headerLen );
headerEnd = ptr + headerLen;
}
XP_U32 wordCount;
XP_MEMCPY( &wordCount, ptr, sizeof(wordCount) );
ptr += sizeof(wordCount);
dctx->nWords = XP_NTOHL( wordCount );
XP_DEBUGF( "dict contains %d words", dctx->nWords );
2022-04-21 22:26:26 +02:00
XP_ASSERT( ptr <= headerEnd );
if ( !getNullTermParam( dctx, &dctx->desc, &ptr, headerEnd ) ) {
XP_LOGFF( "no note" );
goto done;
}
XP_ASSERT( ptr < headerEnd );
2022-04-21 06:00:51 +02:00
if ( !getNullTermParam( dctx, &dctx->md5Sum, &ptr, headerEnd ) ) {
2022-04-21 22:26:26 +02:00
XP_LOGFF( "no md5Sum" );
2022-04-21 06:00:51 +02:00
goto done;
}
XP_U16 headerFlags = 0;
if ( ptr + sizeof(headerFlags) > headerEnd ) {
goto done;
}
XP_MEMCPY( &headerFlags, ptr, sizeof(headerFlags) );
headerFlags = XP_NTOHS( headerFlags );
ptr += sizeof(headerFlags);
XP_LOGFF( "setting headerFlags: 0x%x", headerFlags );
dctx->headerFlags = headerFlags;
if ( ptr == headerEnd ) {
goto done;
}
if ( getNullTermParam( dctx, &dctx->isoCode, &ptr, headerEnd )
&& getNullTermParam( dctx, &dctx->langName, &ptr, headerEnd ) ) {
XP_LOGFF( "got langName: %s; isoCode: %s", dctx->langName,
dctx->isoCode );
2022-04-21 06:00:51 +02:00
} else {
goto done;
}
2022-04-21 06:00:51 +02:00
XP_U8 othersLen = *ptr++;
if ( 0 < othersLen ) {
XP_ASSERT( ptr + othersLen <= headerEnd );
if ( ptr + othersLen <= headerEnd ) {
dctx->otherCounts = XP_MALLOC( dctx->mpool, othersLen );
dctx->otherCountsEnd = dctx->otherCounts + othersLen;
XP_MEMCPY( dctx->otherCounts, ptr, othersLen );
ptr += othersLen;
}
}
done:
if ( ptr < headerEnd ) {
XP_LOGFF( "skipping %zu bytes of header", headerEnd - ptr );
}
ptr = headerEnd;
}
if ( isUTF8 ) {
numFaceBytes = *ptr++;
}
numFaces = *ptr++;
if ( !isUTF8 ) {
numFaceBytes = numFaces * charSize;
}
if ( NULL == dctx->md5Sum
#ifdef DEBUG
|| XP_TRUE
#endif
) {
XP_UCHAR checksum[256];
// XP_LOGFF( "figuring checksum with len: %uz", end - ptr );
computeChecksum( dctx, xwe, ptr, end - ptr, checksum );
if ( NULL == dctx->md5Sum ) {
dctx->md5Sum = copyString( dctx->mpool, checksum );
} else {
2021-02-03 17:47:23 +01:00
#ifndef PLATFORM_WASM
XP_ASSERT( 0 == XP_STRCMP( dctx->md5Sum, checksum ) );
2021-02-03 17:47:23 +01:00
#endif
}
}
dctx->nFaces = numFaces;
dctx->values = XP_MALLOC( dctx->mpool, numFaces);
2022-04-22 16:59:50 +02:00
XP_U8* counts15 = XP_MALLOC( dctx->mpool, numFaces);
dctx->counts[15>>1] = counts15;
XP_U16 facesSize = numFaceBytes;
if ( !isUTF8 ) {
facesSize /= 2;
}
XP_U8 tmp[numFaceBytes];
XP_MEMCPY( tmp, ptr, numFaceBytes );
ptr += numFaceBytes;
dict_splitFaces( dctx, xwe, tmp, numFaceBytes, numFaces );
unsigned short xloc;
XP_MEMCPY( &xloc, ptr, sizeof(xloc) );
ptr += sizeof(xloc);
for ( int ii = 0; ii < numFaces; ++ii ) {
counts15[ii] = *ptr++;
dctx->values[ii] = *ptr++;
}
if ( NULL == dctx->isoCode ) {
XP_LangCode langCode = xloc & 0x7F;
const XP_UCHAR* isoCode = lcToLocale( langCode );
XP_ASSERT( !!isoCode );
dctx->isoCode = copyString( dctx->mpool, isoCode );
2022-05-29 18:15:03 +02:00
XP_LOGFF( "looked up isoCode %s for langCode %d", isoCode,
langCode );
}
2022-05-29 18:15:03 +02:00
#ifdef DEBUG
const XP_UCHAR* locale = lcToLocale( xloc & 0x7F );
2022-05-29 18:15:03 +02:00
XP_ASSERT( !locale || !XP_STRCMP( locale, dctx->isoCode ) );
#endif
}
if ( formatOk ) {
formatOk = loadSpecialData( dctx, &ptr, end );
}
if ( formatOk ) {
XP_ASSERT( ptr < end );
*ptrp = ptr;
}
LOG_RETURNF( "%s", boolToStr(formatOk) );
return formatOk;
2022-04-21 06:00:51 +02:00
} /* parseCommon */
static XP_Bool
makeBitmap( XP_U8 const** ptrp, const XP_U8* end )
{
XP_Bool success = XP_TRUE;
XP_U8 const* ptr = *ptrp;
CHECK_PTR( ptr, 1, end, error );
XP_U8 nCols = *ptr++;
if ( nCols > 0 ) {
CHECK_PTR( ptr, 1, end, error );
XP_U8 nRows = *ptr++;
CHECK_PTR( ptr, ((nRows*nCols)+7) / 8, end, error );
#ifdef DROP_BITMAPS
ptr += ((nRows*nCols)+7) / 8;
#else
do not compile
#endif
}
goto done;
error:
success = XP_FALSE;
done:
*ptrp = ptr;
return success;
}
XP_U16
countSpecials( DictionaryCtxt* ctxt )
{
XP_U16 result = 0;
for ( int ii = 0; ii < ctxt->nFaces; ++ii ) {
if ( IS_SPECIAL( ctxt->facePtrs[ii][0] ) ) {
++result;
}
}
return result;
} /* countSpecials */
2003-11-01 06:35:29 +01:00
void
setBlankTile( DictionaryCtxt* dict )
2003-11-01 06:35:29 +01:00
{
dict->blankTile = -1; /* no known blank */
XP_U16 maxLen = 0;
for ( int tile = 0; tile < dict->nFaces; ++tile ) {
const XP_UCHAR* facePtr = dict->facePtrs[tile];
if ( facePtr[0] == 0 ) {
XP_ASSERT( dict->blankTile == -1 ); /* only one passes test? */
dict->blankTile = (XP_S8)tile;
}
if ( IS_SPECIAL( *facePtr ) ) {
facePtr = dict_getTileString( dict, tile );
2003-11-01 06:35:29 +01:00
}
XP_U16 thisLen = XP_STRLEN( facePtr );
if ( thisLen > maxLen ) {
maxLen = thisLen;
}
}
dict->maxChars = maxLen;
2003-11-01 06:35:29 +01:00
} /* setBlankTile */
/* #if defined BLANKS_FIRST || defined DEBUG */
2003-11-01 06:35:29 +01:00
XP_Bool
dict_hasBlankTile( const DictionaryCtxt* dict )
2003-11-01 06:35:29 +01:00
{
return dict->blankTile >= 0;
} /* dict_hasBlankTile */
/* #endif */
2003-11-01 06:35:29 +01:00
Tile
dict_getBlankTile( const DictionaryCtxt* dict )
2003-11-01 06:35:29 +01:00
{
XP_ASSERT( dict_hasBlankTile(dict) );
return (Tile)dict->blankTile;
} /* dict_getBlankTile */
XP_U16
dict_getTileValue( const DictionaryCtxt* dict, const Tile tile )
2003-11-01 06:35:29 +01:00
{
2013-11-29 19:15:22 +01:00
XP_ASSERT( !!dict );
if ( (tile & TILE_VALUE_MASK) != tile ) {
XP_ASSERT( tile == 32 &&
tile == dict_getBlankTile( dict ) );
}
2003-11-01 06:35:29 +01:00
XP_ASSERT( tile < dict->nFaces );
XP_ASSERT( !!dict->values );
return dict->values[tile];
2003-11-01 06:35:29 +01:00
} /* dict_getTileValue */
static const XP_UCHAR*
dict_getTileStringRaw( const DictionaryCtxt* dict, Tile tile )
{
XP_ASSERT( tile < dict->nFaces );
return dict->facePtrs[tile];
}
const XP_UCHAR*
dict_getTileString( const DictionaryCtxt* dict, Tile tile )
2003-11-01 06:35:29 +01:00
{
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
if ( IS_SPECIAL(*facep) ) {
facep = dict->chars[(XP_U16)*facep];
}
return facep;
}
2003-11-01 06:35:29 +01:00
const XP_UCHAR*
dict_getNextTileString( const DictionaryCtxt* dict, Tile tile,
const XP_UCHAR* cur )
{
const XP_UCHAR* result = NULL;
if ( NULL == cur ) {
result = dict_getTileString( dict, tile );
} else {
cur += XP_STRLEN( cur ) + 1;
XP_Bool isSpecial = dict_faceIsBitmap( dict, tile );
if ( isSpecial || tile == dict->blankTile ) {
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
if ( cur < dict->charEnds[(XP_U16)*facep] ) {
result = cur;
}
} else {
/* use cur only if it is is not now off the end or pointing to to the
next tile */
if ( ++tile == dict->nFaces ) {
if ( cur < dict->facesEnd ) {
result = cur;
}
} else {
const XP_UCHAR* nxt = dict_getTileStringRaw( dict, tile );
if ( nxt != cur ) {
result = cur;
}
}
}
}
return result;
}
2003-11-01 06:35:29 +01:00
XP_U16
dict_numTilesForSize( const DictionaryCtxt* dict, Tile tile, XP_U16 nCols )
2003-11-01 06:35:29 +01:00
{
XP_U8* counts = getCountsFor( dict, nCols );
return counts[tile];
}
/* Older wordlists are built assuming 15x15 boards. Different sized boards
need different numbers of tiles. The wordlist might provide for the size we
have, in which case we use it. Otherwise we extrapolate.
*/
static XP_U8*
getCountsFor( const DictionaryCtxt* dict, XP_U16 nCols )
{
int offset = nCols >> 1;
XP_U8* counts = dict->counts[offset];
if ( !counts ) {
counts = XP_MALLOC( dict->mpool, dict->nFaces );
((DictionaryCtxt*)dict)->counts[offset] = counts;
XP_U8* ptr = dict->otherCounts;
XP_Bool found = XP_FALSE;
while ( !found && !!ptr && ptr < dict->otherCountsEnd ) {
XP_U8 siz = *ptr++;
found = siz == nCols;
if ( found ) {
XP_MEMCPY( counts, ptr, dict->nFaces );
} else {
ptr += dict->nFaces;
}
}
/* don't have it in the wordlist? Extrapolate */
if ( !found ) {
const Tile blank = dict_getBlankTile( dict );
XP_U16 pct = (nCols * nCols * 100) / (15 * 15);
XP_U8* src15 = dict->counts[15>>1];
XP_ASSERT( !!src15 );
for ( Tile tile = 0; tile < dict->nFaces; ++tile ) {
XP_U16 count = src15[tile];
XP_U16 newCount = count * pct / 100;
if ( 50 < (count * pct) % 100 ) {
++newCount;
}
if ( tile == blank && newCount > MAX_NUM_BLANKS ) {
newCount = MAX_NUM_BLANKS;
}
XP_ASSERT( tile != blank || newCount <= MAX_NUM_BLANKS );
counts[tile] = newCount;
}
}
}
// XP_LOGFF( "(tile=%d, ncols=%d) => %d", tile, nCols, count );
return counts;
} /* getCountsFor */
2003-11-01 06:35:29 +01:00
XP_U16
dict_numTileFaces( const DictionaryCtxt* dict )
2003-11-01 06:35:29 +01:00
{
return dict->nFaces;
} /* dict_numTileFaces */
XP_U16
dict_getMaxTileChars( const DictionaryCtxt* ctxt )
{
XP_ASSERT( 0 != ctxt->maxChars );
return ctxt->maxChars;
}
static void
appendIfSpace( XP_UCHAR** bufp, const XP_UCHAR* end, const XP_UCHAR* newtxt )
{
XP_U16 len = XP_STRLEN( newtxt );
if ( *bufp + len < end ) {
XP_MEMCPY( *bufp, newtxt, len );
*bufp += len;
} else {
*bufp = NULL;
}
}
2003-11-01 06:35:29 +01:00
XP_U16
dict_tilesToString( const DictionaryCtxt* dict, const Tile* tiles,
XP_U16 nTiles, XP_UCHAR* buf, XP_U16 bufSize,
const XP_UCHAR* delim )
2003-11-01 06:35:29 +01:00
{
XP_UCHAR* bufp = buf;
const XP_UCHAR* end = bufp + bufSize;
XP_U16 delimLen = NULL == delim ? 0 : XP_STRLEN(delim);
for ( int ii = 0; ii < nTiles && !!bufp; ++ii ) {
if ( 0 < delimLen && 0 < ii ) {
appendIfSpace( &bufp, end, delim );
}
Tile tile = tiles[ii];
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
if ( IS_SPECIAL(*facep) ) {
XP_UCHAR* chars = dict->chars[(XP_U16)*facep];
appendIfSpace( &bufp, end, chars );
} else {
XP_ASSERT ( tile != dict->blankTile ); /* printing blank should be
handled by specials
mechanism */
appendIfSpace( &bufp, end, facep );
2003-11-01 06:35:29 +01:00
}
}
XP_U16 result = 0;
if ( !!bufp && bufp < end ) {
*bufp = '\0';
result = bufp - buf;
}
return result;
2003-11-01 06:35:29 +01:00
} /* dict_tilesToString */
/* Convert str to an array of tiles, continuing until we fail to match or we
* run out of room in which to return tiles. Failure to match means return of
* XP_FALSE, but if we run out of room before failing we return XP_TRUE.
*/
static XP_Bool
tilesForStringImpl( const DictionaryCtxt* dict,
const XP_UCHAR* str, XP_U16 strLen,
Tile* tiles, XP_U16 nTiles, XP_U16 nFound,
OnFoundTiles proc, void* closure )
2003-11-01 06:35:29 +01:00
{
XP_Bool goOn;
if ( nFound == nTiles || 0 == strLen ) {
/* We've recursed to the end and have found a tile! */
goOn = (*proc)( closure, tiles, nFound );
} else {
goOn = XP_TRUE;
XP_U16 nFaces = dict_numTileFaces( dict );
for ( Tile tile = 0; goOn && tile < nFaces; ++tile ) {
for ( const XP_UCHAR* facep = NULL; ; ) {
facep = dict_getNextTileString( dict, tile, facep );
if ( !facep ) {
break;
}
XP_U16 faceLen = XP_STRLEN( facep );
if ( 0 == XP_STRNCMP( facep, str, faceLen ) ) {
tiles[nFound] = tile;
goOn = tilesForStringImpl( dict, str + faceLen,
strLen - faceLen,
tiles, nTiles, nFound + 1,
proc, closure );
break; /* impossible to have than one match per tile */
}
2003-11-01 06:35:29 +01:00
}
}
}
return goOn;
} /* tilesForStringImpl */
void
dict_tilesForString( const DictionaryCtxt* dict, const XP_UCHAR* str,
XP_U16 strLen, OnFoundTiles proc, void* closure )
{
Tile tiles[32];
if ( 0 == strLen ) {
strLen = XP_STRLEN( str );
}
tilesForStringImpl( dict, str, strLen, tiles, VSIZE(tiles), 0, proc, closure );
} /* dict_tilesForString */
2003-11-01 06:35:29 +01:00
XP_Bool
dict_tilesAreSame( const DictionaryCtxt* dict1, const DictionaryCtxt* dict2 )
2003-11-01 06:35:29 +01:00
{
XP_Bool result = XP_FALSE;
2011-04-26 05:17:54 +02:00
XP_ASSERT( !!dict1 );
XP_ASSERT( !!dict2 );
Tile ii;
2003-11-01 06:35:29 +01:00
XP_U16 nTileFaces = dict_numTileFaces( dict1 );
if ( nTileFaces == dict_numTileFaces( dict2 ) ) {
for ( ii = 0; ii < nTileFaces; ++ii ) {
2003-11-01 06:35:29 +01:00
const XP_UCHAR* face1;
const XP_UCHAR* face2;
2003-11-01 06:35:29 +01:00
if ( dict_getTileValue( dict1, ii )
!= dict_getTileValue( dict2, ii ) ){
2003-11-01 06:35:29 +01:00
break;
}
face1 = dict_getTileStringRaw( dict1, ii );
face2 = dict_getTileStringRaw( dict2, ii );
if ( IS_SPECIAL(*face1) != IS_SPECIAL(*face2) ) {
2003-11-01 06:35:29 +01:00
break;
}
if ( IS_SPECIAL(*face1) ) {
XP_UCHAR* chars1 = dict1->chars[(int)*face1];
XP_UCHAR* chars2 = dict2->chars[(int)*face2];
2003-11-01 06:35:29 +01:00
XP_U16 len = XP_STRLEN(chars1);
if ( 0 != XP_STRNCMP( chars1, chars2, len ) ) {
break;
}
} else if ( 0 != XP_STRCMP( face1, face2 ) ) {
2003-11-01 06:35:29 +01:00
break;
}
if ( dict_numTilesForSize( dict1, ii, 15 ) != dict_numTilesForSize( dict2, ii, 15 ) ) {
2003-11-01 06:35:29 +01:00
break;
}
}
result = ii == nTileFaces; /* did we get that far */
2003-11-01 06:35:29 +01:00
}
return result;
} /* dict_tilesAreSame */
/* Summarize tile info in a way it can be presented to users */
void
dict_writeTilesInfo( const DictionaryCtxt* dict, XP_U16 boardSize, XWStreamCtxt* stream )
{
XP_U16 nFaces = dict_numTileFaces( dict );
for ( Tile tile = 0; tile < nFaces; ++tile ) {
XP_U16 val = dict_getTileValue( dict, tile );
XP_U16 count = dict_numTilesForSize( dict, tile, boardSize );
const XP_UCHAR* face = dict_getTileString( dict, tile );
XP_UCHAR buf[32];
XP_SNPRINTF( buf, VSIZE(buf), "%s\t%d\t%d\n", face, count, val );
stream_catString( stream, buf );
}
}
2003-11-01 06:35:29 +01:00
void
dict_writeToStream( const DictionaryCtxt* XP_UNUSED(dict),
XWStreamCtxt* XP_UNUSED(stream) )
2003-11-01 06:35:29 +01:00
{
XP_ASSERT(0);
/* XP_U16 maxCount = 0; */
/* XP_U16 maxValue = 0; */
/* XP_U16 ii, nSpecials; */
/* XP_U16 maxCountBits, maxValueBits; */
/* XP_UCHAR buf[64]; */
/* XP_U16 nBytes; */
/* stream_putBits( stream, 6, dict->nFaces ); */
/* XP_ASSERT(0); /\* if this fires, need to fix for per-boardSize counts *\/ */
/* for ( ii = 0; ii < dict->nFaces*2; ii+=2 ) { */
/* XP_U16 count, value; */
/* count = dict->countsAndValues[ii]; */
/* if ( maxCount < count ) { */
/* maxCount = count; */
/* } */
/* value = dict->countsAndValues[ii+1]; */
/* if ( maxValue < value ) { */
/* maxValue = value; */
/* } */
/* } */
/* maxCountBits = bitsForMax( maxCount ); */
/* maxValueBits = bitsForMax( maxValue ); */
/* stream_putBits( stream, 3, maxCountBits ); /\* won't be bigger than 5 *\/ */
/* stream_putBits( stream, 3, maxValueBits ); */
/* for ( ii = 0; ii < dict->nFaces; ++ii ) { */
/* stream_putBits( stream, maxCountBits, counts[ii] ); */
/* stream_putBits( stream, maxValueBits, dict->values[ii] ); */
/* } */
/* /\* Stream format of the faces is unchanged: chars run together, which */
/* * happens to equal utf-8 for ascii. But now there may be more than one */
/* * byte per face. Old code assumes that, but compatibility is ensured by */
/* * the caller which will not accept an incoming message if the version's */
/* * too new. And utf-8 dicts are flagged as newer by the sender. */
/* *\/ */
/* nBytes = sizeof(buf); */
/* ucharsToNarrow( dict, buf, &nBytes ); */
/* stream_putU8( stream, nBytes ); */
/* stream_putBytes( stream, buf, nBytes ); */
/* for ( nSpecials = ii = 0; ii < dict->nFaces; ++ii ) { */
/* const XP_UCHAR* facep = dict_getTileStringRaw( dict, (Tile)ii ); */
/* if ( IS_SPECIAL( *facep ) ) { */
/* stringToStream( stream, dict->chars[nSpecials++] ); */
/* } */
/* } */
2003-11-01 06:35:29 +01:00
} /* dict_writeToStream */
static void
freeSpecials( DictionaryCtxt* dict )
{
Tile tt;
2003-11-01 06:35:29 +01:00
XP_U16 nSpecials;
for ( nSpecials = tt = 0; tt < dict->nFaces; ++tt ) {
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tt );
if ( IS_SPECIAL( *facep ) ) {
2003-11-01 06:35:29 +01:00
XP_ASSERT( !!dict->chars[nSpecials] );
XP_FREE( dict->mpool, dict->chars[nSpecials] );
XP_FREEP( dict->mpool, &dict->bitmaps[nSpecials].largeBM );
XP_FREEP( dict->mpool, &dict->bitmaps[nSpecials].smallBM );
2003-11-01 06:35:29 +01:00
++nSpecials;
}
}
if ( nSpecials > 0 ) {
XP_FREE( dict->mpool, dict->chars );
XP_FREE( dict->mpool, dict->bitmaps );
}
} /* freeSpecials */
static void
common_destructor( DictionaryCtxt* dict, XWEnv XP_UNUSED(xwe) )
2003-11-01 06:35:29 +01:00
{
freeSpecials( dict );
XP_FREE( dict->mpool, dict->values );
for ( int ii = 0; ii < VSIZE(dict->counts); ++ii ) {
XP_FREEP( dict->mpool, &dict->counts[ii] );
}
XP_FREE( dict->mpool, dict->otherCounts );
XP_FREE( dict->mpool, dict->faces );
XP_FREE( dict->mpool, dict->facePtrs );
2003-11-01 06:35:29 +01:00
XP_FREE( dict->mpool, dict );
} /* common_destructor */
2003-11-01 06:35:29 +01:00
void
dict_loadFromStream( DictionaryCtxt* dict, XWEnv xwe, XWStreamCtxt* stream )
2003-11-01 06:35:29 +01:00
{
XP_ASSERT(0); /* if this fires, need to fix for per-boardSize counts */
XP_U8 nFaces, nFaceBytes;
2003-11-01 06:35:29 +01:00
XP_U16 maxCountBits, maxValueBits;
XP_U16 ii, nSpecials;
2003-11-01 06:35:29 +01:00
XP_UCHAR* localTexts[32];
XP_U8 utf8[MAX_UNIQUE_TILES];
2003-11-01 06:35:29 +01:00
2020-01-29 19:59:57 +01:00
if ( !!dict->destructor ) {
XP_LOGF( "%s(): replacing destructor!!", __func__ );
}
2003-11-01 06:35:29 +01:00
dict->destructor = common_destructor;
dict->func_dict_getShortName = dict_getName; /* default */
2003-11-01 06:35:29 +01:00
nFaces = (XP_U8)stream_getBits( stream, 6 );
maxCountBits = (XP_U16)stream_getBits( stream, 3 );
maxValueBits = (XP_U16)stream_getBits( stream, 3 );
dict->nFaces = nFaces;
dict->values = (XP_U8*)XP_MALLOC( dict->mpool,
sizeof(dict->values[0]) * nFaces );
XP_U8* counts = dict->counts[15>>1]
= (XP_U8*)XP_MALLOC( dict->mpool, sizeof(dict->values[0]) * nFaces );
2003-11-01 06:35:29 +01:00
for ( ii = 0; ii < dict->nFaces; ++ii ) {
counts[ii] = (XP_U8)stream_getBits( stream, maxCountBits );
dict->values[ii] = (XP_U8)stream_getBits( stream, maxValueBits );
2003-11-01 06:35:29 +01:00
}
nFaceBytes = (XP_U8)stream_getU8( stream );
XP_ASSERT( nFaceBytes < VSIZE(utf8) );
stream_getBytes( stream, utf8, nFaceBytes );
dict->isUTF8 = XP_TRUE; /* need to communicate this in stream */
dict_splitFaces( dict, xwe, utf8, nFaceBytes, nFaces );
for ( nSpecials = ii = 0; ii < nFaces; ++ii ) {
const XP_UCHAR* facep = dict_getTileStringRaw( dict, (Tile)ii );
if ( IS_SPECIAL( *facep ) ) {
XP_UCHAR* txt = stringFromStream( dict->mpool, stream );
2003-11-01 06:35:29 +01:00
XP_ASSERT( !!txt );
localTexts[nSpecials] = txt;
++nSpecials;
}
}
if ( nSpecials > 0 ) {
2022-04-22 16:59:50 +02:00
dict->bitmaps =
2003-11-01 06:35:29 +01:00
(SpecialBitmaps*)XP_MALLOC( dict->mpool,
nSpecials * sizeof(*dict->bitmaps) );
XP_MEMSET( dict->bitmaps, 0, nSpecials * sizeof(*dict->bitmaps) );
dict->chars = (XP_UCHAR**)XP_MALLOC( dict->mpool,
nSpecials * sizeof(*dict->chars) );
XP_MEMCPY(dict->chars, localTexts, nSpecials * sizeof(*dict->chars));
}
setBlankTile( dict );
} /* dict_loadFromStream */
#ifdef TEXT_MODEL
/* Return the strlen of the longest face, e.g. 1 for English and Italian;
2 for Spanish; 3 for Catalan */
XP_U16
dict_getMaxWidth( const DictionaryCtxt* dict )
{
XP_U16 result = 0;
Tile tile;
XP_U16 nFaces = dict_numTileFaces( dict );
for ( tile = 0; tile < nFaces; ++tile ) {
const XP_UCHAR* face = dict_getTileString( dict, tile );
XP_U16 len = XP_STRLEN( face );
if ( len > result ) {
result = len;
}
}
return result;
}
#endif
2006-09-24 17:28:15 +02:00
const XP_UCHAR*
dict_getName( const DictionaryCtxt* dict )
2003-11-01 06:35:29 +01:00
{
XP_ASSERT( !!dict );
XP_ASSERT( !!dict->name );
2003-11-01 06:35:29 +01:00
return dict->name;
} /* dict_getName */
XP_Bool
dict_isUTF8( const DictionaryCtxt* dict )
{
XP_ASSERT( !!dict );
return dict->isUTF8;
}
2003-11-01 06:35:29 +01:00
XP_Bool
dict_faceIsBitmap( const DictionaryCtxt* dict, Tile tile )
2003-11-01 06:35:29 +01:00
{
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
return IS_SPECIAL(*facep) && (tile != dict->blankTile);
2003-11-01 06:35:29 +01:00
} /* dict_faceIsBitmap */
void
dict_getFaceBitmaps( const DictionaryCtxt* dict, Tile tile, XP_Bitmaps* bmps )
2003-11-01 06:35:29 +01:00
{
SpecialBitmaps* bitmaps;
const XP_UCHAR* facep = dict_getTileStringRaw( dict, tile );
2003-11-01 06:35:29 +01:00
XP_ASSERT( dict_faceIsBitmap( dict, tile ) );
XP_ASSERT( !!dict->bitmaps );
bitmaps = &dict->bitmaps[(XP_U16)*facep];
bmps->nBitmaps = 2;
bmps->bmps[0] = bitmaps->smallBM;
bmps->bmps[1] = bitmaps->largeBM;
2009-01-25 21:31:13 +01:00
} /* dict_getFaceBitmaps */
2003-11-01 06:35:29 +01:00
2010-12-07 03:24:31 +01:00
XP_U32
dict_getWordCount( const DictionaryCtxt* dict, XWEnv xwe )
2010-12-07 03:24:31 +01:00
{
XP_U32 nWords = dict->nWords;
#ifdef XWFEATURE_WALKDICT
if ( 0 == nWords ) {
DictIter* iter = di_makeIter( dict, xwe, NULL, NULL, 0, NULL, 0 );
nWords = di_countWords( iter, NULL );
di_freeIter( iter, xwe );
}
#endif
return nWords;
2010-12-07 03:24:31 +01:00
}
const XP_UCHAR*
dict_getDesc( const DictionaryCtxt* dict )
{
return dict->desc;
}
2022-06-04 23:02:28 +02:00
const XP_UCHAR*
dict_getISOCode( const DictionaryCtxt* dict )
{
return dict->isoCode;
}
2012-09-08 19:09:31 +02:00
const XP_UCHAR*
dict_getMd5Sum( const DictionaryCtxt* dict )
{
return dict->md5Sum;
}
XP_Bool
dict_hasDuplicates( const DictionaryCtxt* dict )
{
return 0 != (dict->headerFlags & HEADERFLAGS_DUPS_SUPPORTED_BIT);
}
2003-11-01 06:35:29 +01:00
#ifdef STUBBED_DICT
#define BLANK_FACE '\0'
static XP_U8 stub_english_data[] = {
/* count value face */
2008-05-31 05:26:16 +02:00
9, 1, 'A',
2, 3, 'B',
2, 3, 'C',
4, 2, 'D',
12, 1, 'E',
2, 4, 'F',
3, 2, 'G',
2, 4, 'H',
9, 1, 'I',
1, 8, 'J',
1, 5, 'K',
4, 1, 'L',
2, 3, 'M',
6, 1, 'N',
8, 1, 'O',
2, 3, 'P',
1, 10, 'Q',
6, 1, 'R',
4, 1, 'S',
6, 1, 'T',
4, 1, 'U',
2, 4, 'V',
2, 4, 'W',
1, 8, 'X',
2, 4, 'Y',
1, 10, 'Z',
2, 0, BLANK_FACE, /* BLANK1 */
2003-11-01 06:35:29 +01:00
};
void
setStubbedSpecials( DictionaryCtxt* dict )
{
2005-01-04 05:06:37 +01:00
dict->chars = (XP_UCHAR**)XP_MALLOC( dict->mpool, sizeof(char*) );
2003-11-01 06:35:29 +01:00
dict->chars[0] = "_";
} /* setStubbedSpecials */
void
destroy_stubbed_dict( DictionaryCtxt* dict )
{
XP_FREE( dict->mpool, dict->countsAndValues );
2010-01-02 02:40:25 +01:00
XP_FREE( dict->mpool, dict->faces );
2003-11-01 06:35:29 +01:00
XP_FREE( dict->mpool, dict->chars );
XP_FREE( dict->mpool, dict->name );
XP_FREE( dict->mpool, dict->langName );
XP_FREE( dict->mpool, dict->isoCode );
2003-11-01 06:35:29 +01:00
XP_FREE( dict->mpool, dict->bitmaps );
XP_FREE( dict->mpool, dict );
} /* destroy_stubbed_dict */
DictionaryCtxt*
make_stubbed_dict( MPFORMAL_NOCOMMA )
{
2005-01-04 05:06:37 +01:00
DictionaryCtxt* dict = (DictionaryCtxt*)XP_MALLOC( mpool, sizeof(*dict) );
2003-11-01 06:35:29 +01:00
XP_U8* data = stub_english_data;
XP_U16 datasize = sizeof(stub_english_data);
2010-01-02 02:40:25 +01:00
XP_U16 ii;
2003-11-01 06:35:29 +01:00
XP_MEMSET( dict, 0, sizeof(*dict) );
MPASSIGN( dict->mpool, mpool );
2006-09-15 09:32:39 +02:00
dict->name = copyString( mpool, "Stub dictionary" );
2003-11-01 06:35:29 +01:00
dict->nFaces = datasize/3;
dict->destructor = destroy_stubbed_dict;
2010-01-02 02:40:25 +01:00
dict->faces = (XP_UCHAR*)
XP_MALLOC( mpool, 2 * dict->nFaces * sizeof(dict->faces[0]) );
dict->facePtrs = (XP_UCHAR**)
XP_MALLOC( mpool, dict->nFaces * sizeof(dict->facePtrs[0]) );
XP_UCHAR* nextChar = dict->faces;
XP_UCHAR** nextPtr = dict->facePtrs;
for ( ii = 0; ii < datasize/3; ++ii ) {
*nextPtr++ = nextChar;
*nextChar++ = (XP_UCHAR)data[(ii*3)+2];
*nextChar++ = '\0';
2003-11-01 06:35:29 +01:00
}
2005-01-04 05:06:37 +01:00
dict->countsAndValues = (XP_U8*)XP_MALLOC( mpool, dict->nFaces*2 );
2010-01-02 02:40:25 +01:00
for ( ii = 0; ii < datasize/3; ++ii ) {
dict->countsAndValues[ii*2] = data[(ii*3)];
dict->countsAndValues[(ii*2)+1] = data[(ii*3)+1];
2003-11-01 06:35:29 +01:00
}
2005-01-04 05:06:37 +01:00
dict->bitmaps = (SpecialBitmaps*)XP_MALLOC( mpool, sizeof(SpecialBitmaps) );
2003-11-01 06:35:29 +01:00
dict->bitmaps->largeBM = dict->bitmaps->largeBM = NULL;
setStubbedSpecials( dict );
setBlankTile( dict );
return dict;
} /* make_stubbed_dict */
2003-11-01 06:35:29 +01:00
#endif /* STUBBED_DICT */
static array_edge*
dict_super_edge_for_index( const DictionaryCtxt* dict, XP_U32 index )
2003-11-01 06:35:29 +01:00
{
array_edge* result;
if ( index == 0 ) {
result = NULL;
} else {
XP_ASSERT( index < dict->numEdges );
/* avoid long-multiplication lib call on Palm... */
if ( dict->nodeSize == 3 ) {
index += (index << 1);
} else {
XP_ASSERT( dict->nodeSize == 4 );
index <<= 2;
}
2003-11-01 06:35:29 +01:00
result = &dict->base[index];
}
return result;
} /* dict_edge_for_index */
static array_edge*
dict_super_getTopEdge( const DictionaryCtxt* dict )
{
return dict->topEdge;
} /* dict_super_getTopEdge */
static XP_U32
dict_super_index_from( const DictionaryCtxt* dict, array_edge* p_edge )
{
unsigned long result;
array_edge_new* edge = (array_edge_new*)p_edge;
result = ((edge->highByte << 8) | edge->lowByte) & 0x0000FFFF;
if ( dict->is_4_byte ) {
result |= ((XP_U32)edge->moreBits) << 16;
} else {
XP_ASSERT( dict->nodeSize == 3 );
if ( (edge->bits & EXTRABITMASK_NEW) != 0 ) {
result |= 0x00010000; /* using | instead of + saves 4 bytes */
}
}
return result;
} /* dict_super_index_from */
static array_edge*
dict_super_follow( const DictionaryCtxt* dict, array_edge* in )
{
XP_U32 index = dict_index_from( dict, in );
array_edge* result = index > 0?
dict_edge_for_index( dict, index ): (array_edge*)NULL;
return result;
} /* dict_super_follow */
static array_edge*
dict_super_edge_with_tile( const DictionaryCtxt* dict, array_edge* from,
Tile tile )
{
for ( ; ; ) {
Tile candidate = EDGETILE(dict,from);
if ( candidate == tile ) {
break;
}
if ( IS_LAST_EDGE(dict, from ) ) {
from = NULL;
break;
}
from += dict->nodeSize;
}
return from;
} /* edge_with_tile */
void
2021-02-02 05:13:25 +01:00
dict_super_init( MPFORMAL DictionaryCtxt* dict )
{
MPASSIGN(dict->mpool, mpool);
/* subclass may change these later.... */
dict->func_edge_for_index = dict_super_edge_for_index;
dict->func_dict_getTopEdge = dict_super_getTopEdge;
dict->func_dict_index_from = dict_super_index_from;
dict->func_dict_follow = dict_super_follow;
dict->func_dict_edge_with_tile = dict_super_edge_with_tile;
dict->func_dict_getShortName = dict_getName;
2014-03-12 06:01:11 +01:00
pthread_mutex_init( &dict->mutex, NULL );
} /* dict_super_init */
2003-11-01 06:35:29 +01:00
void
dict_super_destroy( DictionaryCtxt* dict )
{
XP_FREEP( dict->mpool, &dict->desc );
XP_FREEP( dict->mpool, &dict->md5Sum );
XP_FREEP( dict->mpool, &dict->values );
for ( int ii = 0; ii < VSIZE(dict->counts); ++ii ) {
XP_FREEP( dict->mpool, &dict->counts[ii] );
}
XP_FREEP( dict->mpool, &dict->otherCounts );
XP_FREEP( dict->mpool, &dict->faces );
XP_FREEP( dict->mpool, &dict->facePtrs );
XP_FREEP( dict->mpool, &dict->name );
XP_FREEP( dict->mpool, &dict->isoCode );
XP_FREEP( dict->mpool, &dict->langName );
}
const XP_UCHAR*
dict_getLangName( const DictionaryCtxt* ctxt )
{
return ctxt->langName;
}
#ifdef XWFEATURE_DICTSANITY
XP_Bool
checkSanity( DictionaryCtxt* dict, const XP_U32 numEdges )
{
XP_U32 ii;
XP_Bool passed = XP_TRUE;
array_edge* edge = dict->base;
if ( NULL != edge ) { /* not empty dict */
XP_U16 nFaces = dict_numTileFaces( dict );
Tile prevTile = 0;
for ( ii = 0; ii < numEdges && passed; ++ii ) {
Tile tile = EDGETILE( dict, edge );
if ( tile < prevTile || tile >= nFaces ) {
XP_LOGF( "%s: node %d (out of %d) has too-large or "
"out-of-order tile", __func__, ii, numEdges );
passed = XP_FALSE;
break;
}
prevTile = tile;
XP_U32 index = dict_index_from( dict, edge );
if ( index >= numEdges ) {
XP_LOGF( "%s: node %d (out of %d) has too-high index %d",
__func__, ii, numEdges, index );
passed = XP_FALSE;
break;
}
if ( IS_LAST_EDGE( dict, edge ) ) {
prevTile = 0;
}
edge += dict->nodeSize;
}
if ( passed ) {
passed = 0 == prevTile; /* last edge seen was a LAST_EDGE */
}
}
return passed;
} /* checkSanity */
#endif
2003-11-01 06:35:29 +01:00
#ifdef CPLUS
}
#endif