1321 lines
29 KiB
C++
1321 lines
29 KiB
C++
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#endif
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "projectcontext.h"
|
|
|
|
#include "fileread.h"
|
|
#include "filewrite.h"
|
|
#include "heapbuf.h"
|
|
#include "wdlstring.h"
|
|
#include "wdlcstring.h"
|
|
#include "fastqueue.h"
|
|
#include "lineparse.h"
|
|
|
|
|
|
//#define WDL_MEMPROJECTCONTEXT_USE_ZLIB 1
|
|
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
|
|
#define WDL_MEMPROJECTCONTEXT_ZLIB_CHUNKSIZE 65536
|
|
#include "zlib/zlib.h"
|
|
|
|
#endif
|
|
|
|
#include "denormal.h"
|
|
|
|
|
|
char *projectcontext_fastDoubleToString(double value, char *bufOut, int prec_digits)
|
|
{
|
|
value = denormal_filter_double2(value);
|
|
|
|
if (value<0.0)
|
|
{
|
|
value=-value;
|
|
*bufOut++ = '-';
|
|
}
|
|
|
|
if (value < 1e-20)
|
|
{
|
|
*bufOut++ = '0';
|
|
*bufOut = 0;
|
|
return bufOut;
|
|
}
|
|
|
|
if (value > 2147483647.0)
|
|
{
|
|
if (value >= 1.0e40) sprintf(bufOut, "%e", value);
|
|
else sprintf(bufOut, "%.*f", wdl_min(prec_digits,8), value);
|
|
while (*bufOut) bufOut++;
|
|
return bufOut;
|
|
}
|
|
|
|
unsigned int value_i, frac, frac2;
|
|
int prec_digits2 = 0;
|
|
|
|
if (prec_digits>0)
|
|
{
|
|
static const unsigned int scales[9] =
|
|
{
|
|
10,
|
|
100,
|
|
1000,
|
|
10000,
|
|
100000,
|
|
1000000,
|
|
10000000,
|
|
100000000,
|
|
1000000000
|
|
};
|
|
|
|
value_i = (unsigned int)value;
|
|
const int value_digits =
|
|
value_i >= 10000 ? (
|
|
value_i >= 1000000 ?
|
|
(value_i >= 100000000 ?
|
|
(value_i >= 1000000000 ? 10 : 9) :
|
|
(value_i >= 10000000 ? 8 : 7)) :
|
|
(value_i >= 100000 ? 6 : 5)
|
|
)
|
|
:
|
|
(
|
|
value_i >= 100 ?
|
|
(value_i >= 1000 ? 4 : 3) :
|
|
(value_i >= 10 ? 2 : 1)
|
|
);
|
|
|
|
// double precision is limited to about 17 decimal digits of meaningful values
|
|
if (prec_digits + value_digits > 17) prec_digits = 17-value_digits;
|
|
|
|
if (prec_digits > 9)
|
|
{
|
|
prec_digits2 = prec_digits - 9;
|
|
prec_digits = 9;
|
|
if (prec_digits2 > 9) prec_digits2 = 9;
|
|
}
|
|
|
|
const unsigned int prec_scale = scales[prec_digits-1];
|
|
const double dfrac = (value - value_i) * prec_scale;
|
|
if (prec_digits2 > 0)
|
|
{
|
|
const unsigned int prec_scale2 = scales[prec_digits2-1];
|
|
frac = (unsigned int) dfrac;
|
|
|
|
double dfrac2 = (dfrac - frac) * prec_scale2;
|
|
frac2 = (unsigned int) (dfrac2 + 0.5);
|
|
|
|
const int prec_scale2_small = wdl_min(prec_scale2/1024,10);
|
|
|
|
if (frac2 <= prec_scale2_small) frac2=0;
|
|
else if (frac2 >= prec_scale2 - prec_scale2_small - 1) frac2=prec_scale2;
|
|
|
|
if (frac2 >= prec_scale2)
|
|
{
|
|
frac2 -= prec_scale2;
|
|
frac++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
frac = (unsigned int) (dfrac + 0.5);
|
|
frac2 = 0;
|
|
const int prec_scale_small = wdl_min(prec_scale/1024,10);
|
|
if (frac <= prec_scale_small) frac=0;
|
|
else if (frac>=prec_scale-prec_scale_small - 1) frac=prec_scale;
|
|
}
|
|
|
|
if (frac >= prec_scale) // round up to next integer
|
|
{
|
|
frac -= prec_scale;
|
|
value_i++;
|
|
}
|
|
}
|
|
else // round to int
|
|
{
|
|
value_i = (unsigned int)(value+0.5);
|
|
frac2 = frac = 0;
|
|
}
|
|
|
|
char digs[32];
|
|
|
|
if (value_i)
|
|
{
|
|
int tmp=value_i;
|
|
int x = 0;
|
|
do {
|
|
const int a = (tmp%10);
|
|
tmp/=10;
|
|
digs[x++]='0' + a;
|
|
} while (tmp);
|
|
|
|
while (x>0) *bufOut++ = digs[--x];
|
|
}
|
|
else
|
|
{
|
|
*bufOut++ = '0';
|
|
}
|
|
|
|
|
|
if (frac || frac2)
|
|
{
|
|
int x = 0;
|
|
int tmp=frac;
|
|
int dleft = prec_digits;
|
|
*bufOut++='.';
|
|
|
|
if (frac) do
|
|
{
|
|
const int a = (tmp%10);
|
|
tmp /= 10;
|
|
if (x || a || frac2) digs[x++] = '0'+a;
|
|
} while (dleft-- > 0 && tmp);
|
|
|
|
while (dleft-->0) *bufOut++ = '0';
|
|
while (x>0) *bufOut++ = digs[--x];
|
|
// x is 0
|
|
|
|
if (frac2)
|
|
{
|
|
tmp=frac2;
|
|
dleft = prec_digits2;
|
|
do
|
|
{
|
|
const int a = (tmp%10);
|
|
tmp /= 10;
|
|
if (x || a) digs[x++] = '0'+a;
|
|
} while (dleft-- > 0 && tmp);
|
|
|
|
while (dleft-->0) *bufOut++ = '0';
|
|
while (x>0) *bufOut++ = digs[--x];
|
|
}
|
|
}
|
|
|
|
*bufOut = 0;
|
|
return bufOut;
|
|
}
|
|
|
|
int ProjectContextFormatString(char *outbuf, size_t outbuf_size, const char *fmt, va_list va)
|
|
{
|
|
int wroffs=0;
|
|
|
|
while (*fmt && outbuf_size > 1)
|
|
{
|
|
char c = *fmt++;
|
|
if (c != '%')
|
|
{
|
|
outbuf[wroffs++] = c != '\n' ? c : ' ';
|
|
outbuf_size--;
|
|
continue;
|
|
}
|
|
|
|
if (*fmt == '%')
|
|
{
|
|
outbuf[wroffs++] = '%';
|
|
outbuf_size--;
|
|
fmt++;
|
|
continue;
|
|
}
|
|
|
|
|
|
const char *ofmt = fmt-1;
|
|
bool want_abort=false;
|
|
|
|
int has_prec=0;
|
|
int prec=0;
|
|
if (*fmt == '.')
|
|
{
|
|
has_prec=1;
|
|
fmt++;
|
|
while (*fmt >= '0' && *fmt <= '9') prec = prec*10 + (*fmt++-'0');
|
|
if (*fmt != 'f' || prec < 0 || prec>20)
|
|
{
|
|
want_abort=true;
|
|
}
|
|
}
|
|
else if (*fmt == '0')
|
|
{
|
|
has_prec=2;
|
|
fmt++;
|
|
while (*fmt >= '0' && *fmt <= '9') prec = prec*10 + (*fmt++-'0');
|
|
if (*fmt != 'x' && *fmt != 'X' && *fmt != 'd' && *fmt != 'u')
|
|
{
|
|
want_abort=true;
|
|
}
|
|
}
|
|
|
|
c = *fmt++;
|
|
if (!want_abort) switch (c)
|
|
{
|
|
case '@':
|
|
case 'p':
|
|
case 's':
|
|
{
|
|
const char *str=va_arg(va,const char *);
|
|
bool prefer_quoteless = true;
|
|
if (c == 'p' && *fmt == '~') // %p~ to bias towards "string" (legacy)
|
|
{
|
|
prefer_quoteless = false;
|
|
fmt++;
|
|
}
|
|
const char qc = outbuf_size >= 3 && c != 's' ? getConfigStringQuoteChar(str, prefer_quoteless) : ' ';
|
|
|
|
if (qc != ' ')
|
|
{
|
|
outbuf[wroffs++] = qc ? qc : '`';
|
|
outbuf_size-=2; // will add trailing quote below
|
|
}
|
|
|
|
if (str) while (outbuf_size > 1 && *str)
|
|
{
|
|
char v = *str++;
|
|
if (!qc && v == '`') v = '\'';
|
|
outbuf[wroffs++] = v != '\n' && v != '\r' ? v : ' ';
|
|
outbuf_size--;
|
|
}
|
|
|
|
if (qc != ' ')
|
|
{
|
|
outbuf[wroffs++] = qc ? qc : '`';
|
|
// outbuf_size already decreased above
|
|
}
|
|
}
|
|
break;
|
|
case 'c':
|
|
{
|
|
int v = (va_arg(va,int)) & 0xff;
|
|
outbuf[wroffs++] = v != '\n' ? v : ' ';
|
|
outbuf_size--;
|
|
}
|
|
break;
|
|
case 'd':
|
|
{
|
|
int v = va_arg(va,int);
|
|
if (v<0)
|
|
{
|
|
outbuf[wroffs++] = '-';
|
|
outbuf_size--;
|
|
v=-v; // this won't handle -2147483648 right, todo special case?
|
|
}
|
|
|
|
char tab[32];
|
|
int x=0;
|
|
do
|
|
{
|
|
tab[x++] = v%10;
|
|
v/=10;
|
|
}
|
|
while (v);
|
|
if (has_prec == 2) while (x<prec) { tab[x++] = 0; }
|
|
|
|
while (--x >= 0 && outbuf_size>1)
|
|
{
|
|
outbuf[wroffs++] = '0' + tab[x];
|
|
outbuf_size--;
|
|
}
|
|
}
|
|
break;
|
|
case 'u':
|
|
{
|
|
unsigned int v = va_arg(va,unsigned int);
|
|
|
|
char tab[32];
|
|
int x=0;
|
|
do
|
|
{
|
|
tab[x++] = v%10;
|
|
v/=10;
|
|
}
|
|
while (v);
|
|
if (has_prec == 2) while (x<prec) { tab[x++] = 0; }
|
|
|
|
while (--x >= 0 && outbuf_size>1)
|
|
{
|
|
outbuf[wroffs++] = '0' + tab[x];
|
|
outbuf_size--;
|
|
}
|
|
}
|
|
break;
|
|
case 'x':
|
|
case 'X':
|
|
{
|
|
const char base = (c - 'x') + 'a';
|
|
unsigned int v = va_arg(va,unsigned int);
|
|
|
|
char tab[32];
|
|
int x=0;
|
|
do
|
|
{
|
|
tab[x++] = v&0xf;
|
|
v>>=4;
|
|
}
|
|
while (v);
|
|
|
|
if (has_prec == 2) while (x<prec) { tab[x++] = 0; }
|
|
|
|
while (--x >= 0 && outbuf_size>1)
|
|
{
|
|
if (tab[x] < 10)
|
|
outbuf[wroffs++] = '0' + tab[x];
|
|
else
|
|
outbuf[wroffs++] = base + tab[x] - 10;
|
|
|
|
outbuf_size--;
|
|
}
|
|
}
|
|
break;
|
|
case 'f':
|
|
{
|
|
double v = va_arg(va,double);
|
|
if (outbuf_size<64)
|
|
{
|
|
char tmp[64];
|
|
projectcontext_fastDoubleToString(v,tmp,has_prec?prec:6);
|
|
const char *str = tmp;
|
|
while (outbuf_size > 1 && *str)
|
|
{
|
|
outbuf[wroffs++] = *str++;
|
|
outbuf_size--;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const char *p=projectcontext_fastDoubleToString(v,outbuf+wroffs,has_prec?prec:6);
|
|
int amt = (int) (p-(outbuf+wroffs));
|
|
wroffs += amt;
|
|
outbuf_size-=amt;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
want_abort=true;
|
|
break;
|
|
}
|
|
if (want_abort)
|
|
{
|
|
fmt=ofmt;
|
|
break;
|
|
}
|
|
}
|
|
|
|
outbuf += wroffs;
|
|
outbuf[0] = 0;
|
|
if (outbuf_size<2||!*fmt)
|
|
return wroffs;
|
|
|
|
#if defined(_WIN32) && defined(_MSC_VER)
|
|
// _vsnprintf() does not always null terminate (see below)
|
|
_vsnprintf(outbuf,outbuf_size,fmt,va);
|
|
#else
|
|
// vsnprintf() on non-win32, always null terminates
|
|
vsnprintf(outbuf,outbuf_size,fmt,va);
|
|
#endif
|
|
|
|
int l;
|
|
outbuf_size--;
|
|
for (l = 0; l < outbuf_size && outbuf[l]; l ++) if (outbuf[l] == '\n') outbuf[l] = ' ';
|
|
|
|
#if defined(_WIN32) && defined(_MSC_VER)
|
|
// nul terminate for _vsnprintf()
|
|
outbuf[l]=0;
|
|
#endif
|
|
|
|
return wroffs+l;
|
|
}
|
|
|
|
|
|
|
|
class ProjectStateContext_Mem : public ProjectStateContext
|
|
{
|
|
public:
|
|
|
|
ProjectStateContext_Mem(WDL_HeapBuf *hb, int rwflags)
|
|
{
|
|
m_rwflags=rwflags;
|
|
m_heapbuf=hb;
|
|
m_pos=0;
|
|
m_tmpflag=0;
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
memset(&m_compstream,0,sizeof(m_compstream));
|
|
m_usecomp=0;
|
|
#endif
|
|
}
|
|
|
|
virtual ~ProjectStateContext_Mem()
|
|
{
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
if (m_usecomp==1)
|
|
{
|
|
FlushComp(true);
|
|
deflateEnd(&m_compstream);
|
|
}
|
|
else if (m_usecomp==2)
|
|
{
|
|
inflateEnd(&m_compstream);
|
|
}
|
|
#endif
|
|
};
|
|
|
|
virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...);
|
|
virtual int GetLine(char *buf, int buflen); // returns -1 on eof
|
|
|
|
virtual WDL_INT64 GetOutputSize() { return m_heapbuf ? m_heapbuf->GetSize() : 0; }
|
|
|
|
virtual int GetTempFlag() { return m_tmpflag; }
|
|
virtual void SetTempFlag(int flag) { m_tmpflag=flag; }
|
|
|
|
int m_pos;
|
|
WDL_HeapBuf *m_heapbuf;
|
|
int m_tmpflag;
|
|
int m_rwflags;
|
|
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
int DecompressData()
|
|
{
|
|
if (m_pos >= m_heapbuf->GetSize()) return 1;
|
|
|
|
m_compstream.next_in = (unsigned char *)m_heapbuf->Get() + m_pos;
|
|
m_compstream.avail_in = m_heapbuf->GetSize()-m_pos;
|
|
m_compstream.total_in = 0;
|
|
|
|
int outchunk = 65536;
|
|
m_compstream.next_out = (unsigned char *)m_compdatabuf.Add(NULL,outchunk);
|
|
m_compstream.avail_out = outchunk;
|
|
m_compstream.total_out = 0;
|
|
|
|
int e = inflate(&m_compstream,Z_NO_FLUSH);
|
|
|
|
m_pos += m_compstream.total_in;
|
|
m_compdatabuf.Add(NULL,m_compstream.total_out - outchunk); // rewind
|
|
|
|
return e != Z_OK;
|
|
}
|
|
|
|
void FlushComp(bool eof)
|
|
{
|
|
while (m_compdatabuf.Available()>=WDL_MEMPROJECTCONTEXT_ZLIB_CHUNKSIZE || eof)
|
|
{
|
|
if (!m_heapbuf->GetSize()) m_heapbuf->SetGranul(256*1024);
|
|
m_compstream.next_in = (unsigned char *)m_compdatabuf.Get();
|
|
m_compstream.avail_in = m_compdatabuf.Available();
|
|
m_compstream.total_in = 0;
|
|
|
|
int osz = m_heapbuf->GetSize();
|
|
|
|
int newsz=osz + wdl_max(m_compstream.avail_in,8192) + 256;
|
|
m_compstream.next_out = (unsigned char *)m_heapbuf->Resize(newsz, false) + osz;
|
|
if (m_heapbuf->GetSize()!=newsz) return; // ERROR
|
|
m_compstream.avail_out = newsz-osz;
|
|
m_compstream.total_out=0;
|
|
|
|
deflate(&m_compstream,eof?Z_SYNC_FLUSH:Z_NO_FLUSH);
|
|
|
|
m_heapbuf->Resize(osz+m_compstream.total_out,false);
|
|
m_compdatabuf.Advance(m_compstream.total_in);
|
|
if (m_compstream.avail_out) break; // no need to process anymore
|
|
|
|
}
|
|
m_compdatabuf.Compact();
|
|
}
|
|
|
|
// these will be used for either decompression or compression, fear
|
|
int m_usecomp; // 0=?, -1 = fail, 1=yes
|
|
WDL_Queue m_compdatabuf;
|
|
z_stream m_compstream;
|
|
#endif
|
|
|
|
};
|
|
|
|
// returns length, modifies ptr to point to tmp if newline needed to be filtered
|
|
static int filter_newline_buf(const char **ptr, char *tmp, int tmpsz)
|
|
{
|
|
const char *use_buf = *ptr;
|
|
if (!use_buf) return -1;
|
|
|
|
int l;
|
|
for (l=0; use_buf[l] && use_buf[l] != '\n'; l++);
|
|
|
|
if (!use_buf[l]) return l;
|
|
|
|
lstrcpyn_safe(tmp,use_buf,tmpsz);
|
|
*ptr=tmp;
|
|
|
|
if (l >= tmpsz) return tmpsz-1;
|
|
|
|
for (;tmp[l]; l++) if (tmp[l] == '\n') tmp[l] = ' '; // replace any newlines with spaces
|
|
return l;
|
|
}
|
|
|
|
|
|
void ProjectStateContext_Mem::AddLine(const char *fmt, ...)
|
|
{
|
|
if (!m_heapbuf || !(m_rwflags&2)) return;
|
|
|
|
char tmp[8192];
|
|
|
|
const char *use_buf;
|
|
int l;
|
|
|
|
va_list va;
|
|
va_start(va,fmt);
|
|
|
|
if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2])
|
|
{
|
|
use_buf = va_arg(va,const char *);
|
|
l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp)) + 1;
|
|
}
|
|
else
|
|
{
|
|
l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va) + 1;
|
|
use_buf = tmp;
|
|
}
|
|
va_end(va);
|
|
|
|
if (l < 1) return;
|
|
|
|
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
if (!m_usecomp)
|
|
{
|
|
if (deflateInit(&m_compstream,WDL_MEMPROJECTCONTEXT_USE_ZLIB)==Z_OK) m_usecomp=1;
|
|
else m_usecomp=-1;
|
|
}
|
|
|
|
if (m_usecomp==1)
|
|
{
|
|
m_compdatabuf.Add(use_buf,(int)l);
|
|
FlushComp(false);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
|
|
const int sz=m_heapbuf->GetSize();
|
|
if (!sz && m_heapbuf->GetGranul() < 256*1024)
|
|
{
|
|
m_heapbuf->SetGranul(256*1024);
|
|
}
|
|
|
|
char *p = (char *)m_heapbuf->ResizeOK(sz+l);
|
|
if (!p)
|
|
{
|
|
// ERROR, resize to 0 and return
|
|
m_heapbuf->Resize(0);
|
|
m_heapbuf=0;
|
|
return;
|
|
}
|
|
memcpy(p+sz,use_buf,l);
|
|
}
|
|
|
|
int ProjectStateContext_Mem::GetLine(char *buf, int buflen) // returns -1 on eof
|
|
{
|
|
if (!m_heapbuf || !(m_rwflags&1)) return -1;
|
|
|
|
buf[0]=0;
|
|
|
|
|
|
#ifdef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
if (!m_usecomp)
|
|
{
|
|
unsigned char hdr[]={0x78,0x01};
|
|
if (m_heapbuf->GetSize()>2 && !memcmp(hdr,m_heapbuf->Get(),4) && inflateInit(&m_compstream)==Z_OK) m_usecomp=2;
|
|
else m_usecomp=-1;
|
|
}
|
|
if (m_usecomp==2)
|
|
{
|
|
int x=0;
|
|
for (;;)
|
|
{
|
|
const char *p = (const char*) m_compdatabuf.Get();
|
|
for (x = 0; x < m_compdatabuf.Available() && p[x] && p[x] != '\r' && p[x] != '\n'; x ++);
|
|
while (x >= m_compdatabuf.Available())
|
|
{
|
|
int err = DecompressData();
|
|
p = (const char *)m_compdatabuf.Get();
|
|
for (; x < m_compdatabuf.Available() && p[x] && p[x] != '\r' && p[x] != '\n'; x ++);
|
|
|
|
if (err) break;
|
|
}
|
|
|
|
if (x||!m_compdatabuf.Available()) break;
|
|
|
|
m_compdatabuf.Advance(1); // skip over nul or newline char
|
|
}
|
|
|
|
if (!x) return -1;
|
|
|
|
if (buflen > 0 && buf)
|
|
{
|
|
int l = wdl_min(buflen-1,x);
|
|
memcpy(buf,m_compdatabuf.Get(),l);
|
|
buf[l]=0;
|
|
}
|
|
|
|
m_compdatabuf.Advance(x+1);
|
|
m_compdatabuf.Compact();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
int avail = m_heapbuf->GetSize() - m_pos;
|
|
const char *p=(const char *)m_heapbuf->Get() + m_pos;
|
|
while (avail > 0 && (p[0] =='\r'||p[0]=='\n'||!p[0]||p[0] == ' ' || p[0] == '\t'))
|
|
{
|
|
p++;
|
|
m_pos++;
|
|
avail--;
|
|
}
|
|
if (avail <= 0) return -1;
|
|
|
|
int x;
|
|
for (x = 0; x < avail && p[x] && p[x] != '\n'; x ++);
|
|
m_pos += x+1;
|
|
|
|
if (buflen > 0&&buf)
|
|
{
|
|
int l = buflen-1;
|
|
if (l>x) l=x;
|
|
memcpy(buf,p,l);
|
|
if (l>0 && buf[l-1]=='\r') l--;
|
|
buf[l]=0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
class ProjectStateContext_File : public ProjectStateContext
|
|
{
|
|
public:
|
|
|
|
ProjectStateContext_File(WDL_FileRead *rd, WDL_FileWrite *wr)
|
|
{
|
|
m_rd=rd;
|
|
m_wr=wr;
|
|
m_indent=0;
|
|
m_bytesOut=0;
|
|
m_errcnt=false;
|
|
m_tmpflag=0;
|
|
_rdbuf_pos = _rdbuf_valid = 0;
|
|
}
|
|
virtual ~ProjectStateContext_File(){ delete m_rd; delete m_wr; };
|
|
|
|
virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...);
|
|
virtual int GetLine(char *buf, int buflen); // returns -1 on eof
|
|
|
|
virtual WDL_INT64 GetOutputSize() { return m_bytesOut; }
|
|
|
|
virtual int GetTempFlag() { return m_tmpflag; }
|
|
virtual void SetTempFlag(int flag) { m_tmpflag=flag; }
|
|
|
|
bool HasError() { return m_errcnt; }
|
|
|
|
WDL_INT64 m_bytesOut WDL_FIXALIGN;
|
|
|
|
WDL_FileRead *m_rd;
|
|
WDL_FileWrite *m_wr;
|
|
|
|
char rdbuf[4096];
|
|
int _rdbuf_pos, _rdbuf_valid;
|
|
|
|
int m_indent;
|
|
int m_tmpflag;
|
|
bool m_errcnt;
|
|
|
|
};
|
|
|
|
int ProjectStateContext_File::GetLine(char *buf, int buflen)
|
|
{
|
|
if (!m_rd||buflen<3) return -1;
|
|
|
|
char * const buf_orig=buf;
|
|
int rdpos = _rdbuf_pos;
|
|
int rdvalid = _rdbuf_valid;
|
|
buflen -= 2;
|
|
|
|
for (;;)
|
|
{
|
|
while (rdpos < rdvalid)
|
|
{
|
|
char c=rdbuf[rdpos++];
|
|
switch (c)
|
|
{
|
|
case ' ': case '\r': case '\n': case '\t': break;
|
|
default:
|
|
*buf++=c;
|
|
|
|
do
|
|
{
|
|
int mxl = rdvalid - rdpos;
|
|
if (mxl > buflen) mxl=buflen;
|
|
while (mxl-->0)
|
|
{
|
|
char c2 = rdbuf[rdpos++];
|
|
if (c2=='\n') goto finished;
|
|
|
|
*buf++ = c2;
|
|
buflen--;
|
|
}
|
|
if (rdpos>=rdvalid)
|
|
{
|
|
rdpos = 0;
|
|
rdvalid = m_rd->Read(rdbuf, sizeof(rdbuf));
|
|
if (rdvalid<1) break;
|
|
}
|
|
}
|
|
while (buflen > 0);
|
|
|
|
finished:
|
|
_rdbuf_pos=rdpos;
|
|
_rdbuf_valid=rdvalid;
|
|
|
|
if (buf > buf_orig && buf[-1] == '\r') buf--;
|
|
*buf=0;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
rdpos = 0;
|
|
rdvalid = m_rd->Read(rdbuf, sizeof(rdbuf));
|
|
if (rdvalid<1)
|
|
{
|
|
buf[0]=0;
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ProjectStateContext_File::AddLine(const char *fmt, ...)
|
|
{
|
|
if (m_wr && !m_errcnt)
|
|
{
|
|
int err=0;
|
|
|
|
char tmp[8192];
|
|
const char *use_buf;
|
|
va_list va;
|
|
va_start(va,fmt);
|
|
int l;
|
|
|
|
if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2])
|
|
{
|
|
// special case "%s" passed, directly use it
|
|
use_buf = va_arg(va,const char *);
|
|
l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp));
|
|
}
|
|
else
|
|
{
|
|
l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va);
|
|
use_buf = tmp;
|
|
}
|
|
|
|
va_end(va);
|
|
if (l < 0) return;
|
|
|
|
|
|
int a=m_indent;
|
|
if (use_buf[0] == '<') m_indent+=2;
|
|
else if (use_buf[0] == '>') a=(m_indent-=2);
|
|
|
|
if (a>0)
|
|
{
|
|
m_bytesOut+=a;
|
|
char tb[128];
|
|
memset(tb,' ',a < (int)sizeof(tb) ? a : (int)sizeof(tb));
|
|
while (a>0)
|
|
{
|
|
const int tl = a < (int)sizeof(tb) ? a : (int)sizeof(tb);
|
|
a-=tl;
|
|
m_wr->Write(tb,tl);
|
|
}
|
|
}
|
|
|
|
err |= m_wr->Write(use_buf,l) != l;
|
|
err |= m_wr->Write("\r\n",2) != 2;
|
|
m_bytesOut += 2 + l;
|
|
|
|
if (err) m_errcnt=true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
ProjectStateContext *ProjectCreateFileRead(const char *fn)
|
|
{
|
|
WDL_FileRead *rd = new WDL_FileRead(fn,0,65536,1);
|
|
if (!rd || !rd->IsOpen())
|
|
{
|
|
delete rd;
|
|
return NULL;
|
|
}
|
|
return new ProjectStateContext_File(rd,NULL);
|
|
}
|
|
ProjectStateContext *ProjectCreateFileWrite(const char *fn)
|
|
{
|
|
WDL_FileWrite *wr = new WDL_FileWrite(fn);
|
|
if (!wr || !wr->IsOpen())
|
|
{
|
|
delete wr;
|
|
return NULL;
|
|
}
|
|
return new ProjectStateContext_File(NULL,wr);
|
|
}
|
|
|
|
|
|
ProjectStateContext *ProjectCreateMemCtx(WDL_HeapBuf *hb)
|
|
{
|
|
return new ProjectStateContext_Mem(hb,3);
|
|
}
|
|
|
|
ProjectStateContext *ProjectCreateMemCtx_Read(const WDL_HeapBuf *hb)
|
|
{
|
|
return new ProjectStateContext_Mem((WDL_HeapBuf *)hb,1);
|
|
}
|
|
ProjectStateContext *ProjectCreateMemCtx_Write(WDL_HeapBuf *hb)
|
|
{
|
|
return new ProjectStateContext_Mem(hb,2);
|
|
}
|
|
|
|
|
|
|
|
|
|
class ProjectStateContext_FastQueue : public ProjectStateContext
|
|
{
|
|
public:
|
|
|
|
ProjectStateContext_FastQueue(WDL_FastQueue *fq)
|
|
{
|
|
m_fq = fq;
|
|
m_tmpflag=0;
|
|
}
|
|
|
|
virtual ~ProjectStateContext_FastQueue()
|
|
{
|
|
};
|
|
|
|
virtual void WDL_VARARG_WARN(printf,2,3) AddLine(const char *fmt, ...);
|
|
virtual int GetLine(char *buf, int buflen) { return -1; }//unsup
|
|
|
|
virtual WDL_INT64 GetOutputSize() { return m_fq ? m_fq->Available() : 0; }
|
|
|
|
virtual int GetTempFlag() { return m_tmpflag; }
|
|
virtual void SetTempFlag(int flag) { m_tmpflag=flag; }
|
|
|
|
WDL_FastQueue *m_fq;
|
|
int m_tmpflag;
|
|
|
|
|
|
};
|
|
|
|
void ProjectStateContext_FastQueue::AddLine(const char *fmt, ...)
|
|
{
|
|
if (!m_fq) return;
|
|
|
|
va_list va;
|
|
va_start(va,fmt);
|
|
|
|
char tmp[8192];
|
|
if (fmt && fmt[0] == '%' && (fmt[1] == 's' || fmt[1] == 'S') && !fmt[2])
|
|
{
|
|
const char *use_buf = va_arg(va,const char *);
|
|
const int l = filter_newline_buf(&use_buf,tmp,(int)sizeof(tmp));
|
|
if (use_buf) m_fq->Add(use_buf, l + 1);
|
|
}
|
|
else
|
|
{
|
|
const int l = ProjectContextFormatString(tmp,sizeof(tmp),fmt, va);
|
|
if (l>0) m_fq->Add(tmp, l+1);
|
|
}
|
|
va_end(va);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ProjectStateContext *ProjectCreateMemWriteFastQueue(WDL_FastQueue *fq) // only write!
|
|
{
|
|
return new ProjectStateContext_FastQueue(fq);
|
|
}
|
|
|
|
bool ProjectContext_GetNextLine(ProjectStateContext *ctx, LineParser *lpOut)
|
|
{
|
|
for (;;)
|
|
{
|
|
char linebuf[4096];
|
|
if (ctx->GetLine(linebuf,sizeof(linebuf)))
|
|
{
|
|
lpOut->parse("");
|
|
return false;
|
|
}
|
|
|
|
if (lpOut->parse(linebuf)||lpOut->getnumtokens()<=0) continue;
|
|
|
|
return true; // success!
|
|
|
|
}
|
|
}
|
|
|
|
|
|
bool ProjectContext_EatCurrentBlock(ProjectStateContext *ctx, ProjectStateContext *ctxOut)
|
|
{
|
|
int child_count=1;
|
|
if (ctx) for (;;)
|
|
{
|
|
char linebuf[4096];
|
|
if (ctx->GetLine(linebuf,sizeof(linebuf))) break;
|
|
const char *sp = linebuf;
|
|
while (*sp == ' ' || *sp == '\t') sp++;
|
|
|
|
const char *p = sp;
|
|
if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any
|
|
if (p[0] == '>') if (--child_count < 1) return true;
|
|
|
|
if (ctxOut) ctxOut->AddLine("%s",sp);
|
|
|
|
if (p[0] == '<') child_count++;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
#include "wdl_base64.h"
|
|
|
|
int cfg_decode_binary(ProjectStateContext *ctx, WDL_HeapBuf *hb) // 0 on success, doesnt clear hb
|
|
{
|
|
int child_count=1;
|
|
for (;;)
|
|
{
|
|
char linebuf[4096];
|
|
if (ctx->GetLine(linebuf,sizeof(linebuf))) break;
|
|
|
|
const char *p = linebuf;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any
|
|
|
|
if (p[0] == '<') child_count++;
|
|
else if (p[0] == '>') { if (child_count-- == 1) return 0; }
|
|
else if (child_count == 1 && p[0])
|
|
{
|
|
unsigned char buf[3200];
|
|
const int buf_l=wdl_base64decode(p,buf,sizeof(buf));
|
|
if (buf_l)
|
|
{
|
|
const int os=hb->GetSize();
|
|
char *dest = (char*)hb->ResizeOK(os+buf_l);
|
|
if (dest) memcpy(dest+os,buf,buf_l);
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cfg_encode_binary(ProjectStateContext *ctx, const void *ptr, int len)
|
|
{
|
|
if (!ctx || len < 1) return;
|
|
|
|
const unsigned char *p=(const unsigned char *)ptr;
|
|
if (len > 128 && len < (1<<30))
|
|
{
|
|
// we could (probably should) use dynamic_cast<> here, but as we span modules this
|
|
// raises all kinds of questions (especially with VC having the option to disable RTTI).
|
|
// for now, we assume that the first void * in an object is the vtable pointer. with
|
|
// testing, of course.
|
|
WDL_FastQueue *fq = NULL;
|
|
WDL_HeapBuf *hb = NULL;
|
|
#ifndef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
static const ProjectStateContext_Mem hb_def(NULL,0);
|
|
#endif
|
|
static const ProjectStateContext_FastQueue fq_def(NULL);
|
|
if (*(void **)ctx == *(void **)&fq_def)
|
|
{
|
|
fq=((ProjectStateContext_FastQueue*)ctx)->m_fq;
|
|
}
|
|
#ifndef WDL_MEMPROJECTCONTEXT_USE_ZLIB
|
|
else if (*(void **)ctx == *(void **)&hb_def)
|
|
{
|
|
hb=((ProjectStateContext_Mem*)ctx)->m_heapbuf;
|
|
}
|
|
#endif
|
|
|
|
if (fq||hb)
|
|
{
|
|
const int linelen8 = 280/8;
|
|
|
|
const int enc_len = ((len+2)/3)*4; // every 3 characters end up as 4
|
|
const int lines = (enc_len + linelen8*8 - 1) / (linelen8*8);
|
|
|
|
char *wr = NULL;
|
|
if (fq)
|
|
{
|
|
wr = (char*)fq->Add(WDL_FASTQUEUE_ADD_NOZEROBUF,enc_len + lines);
|
|
}
|
|
else if (hb)
|
|
{
|
|
const int oldsz=hb->GetSize();
|
|
wr=(char*)hb->ResizeOK(oldsz + enc_len + lines,false);
|
|
if (wr) wr+=oldsz;
|
|
}
|
|
|
|
if (wr)
|
|
{
|
|
#ifdef _DEBUG
|
|
char * const wr_end=wr + enc_len + lines;
|
|
#endif
|
|
|
|
int lpos = 0;
|
|
|
|
while (len >= 6)
|
|
{
|
|
const int accum = (p[0] << 16) + (p[1] << 8) + p[2];
|
|
const int accum2 = (p[3] << 16) + (p[4] << 8) + p[5];
|
|
wr[0] = wdl_base64_alphabet[(accum >> 18) & 0x3F];
|
|
wr[1] = wdl_base64_alphabet[(accum >> 12) & 0x3F];
|
|
wr[2] = wdl_base64_alphabet[(accum >> 6) & 0x3F];
|
|
wr[3] = wdl_base64_alphabet[accum & 0x3F];
|
|
wr[4] = wdl_base64_alphabet[(accum2 >> 18) & 0x3F];
|
|
wr[5] = wdl_base64_alphabet[(accum2 >> 12) & 0x3F];
|
|
wr[6] = wdl_base64_alphabet[(accum2 >> 6) & 0x3F];
|
|
wr[7] = wdl_base64_alphabet[accum2 & 0x3F];
|
|
wr+=8;
|
|
p+=6;
|
|
len-=6;
|
|
|
|
if (++lpos >= linelen8) { *wr++= 0; lpos=0; }
|
|
}
|
|
|
|
if (len >= 3)
|
|
{
|
|
const int accum = (p[0] << 16) + (p[1] << 8) + p[2];
|
|
wr[0] = wdl_base64_alphabet[(accum >> 18) & 0x3F];
|
|
wr[1] = wdl_base64_alphabet[(accum >> 12) & 0x3F];
|
|
wr[2] = wdl_base64_alphabet[(accum >> 6) & 0x3F];
|
|
wr[3] = wdl_base64_alphabet[accum & 0x3F];
|
|
wr+=4;
|
|
p+=3;
|
|
len-=3;
|
|
lpos+=3;
|
|
}
|
|
|
|
if (len>0)
|
|
{
|
|
lpos += len;
|
|
if (len == 2)
|
|
{
|
|
const int accum = (p[0] << 8) | p[1];
|
|
wr[0] = wdl_base64_alphabet[(accum >> 10) & 0x3F];
|
|
wr[1] = wdl_base64_alphabet[(accum >> 4) & 0x3F];
|
|
wr[2] = wdl_base64_alphabet[(accum & 0xF)<<2];
|
|
}
|
|
else
|
|
{
|
|
const int accum = p[0];
|
|
wr[0] = wdl_base64_alphabet[(accum >> 2) & 0x3F];
|
|
wr[1] = wdl_base64_alphabet[(accum & 0x3)<<4];
|
|
wr[2] = '=';
|
|
}
|
|
wr[3] = '=';
|
|
wr+=4;
|
|
}
|
|
if (lpos>0) *wr++=0;
|
|
|
|
#ifdef _DEBUG
|
|
if (wr != wr_end) wdl_log("cfg_encode_binary: block mode size mismatch %d!\n", (int)(wr-wr_end));
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
do
|
|
{
|
|
char buf[256];
|
|
int thiss=len;
|
|
if (thiss > 96) thiss=96;
|
|
wdl_base64encode(p,buf,thiss);
|
|
|
|
ctx->AddLine("%s",buf);
|
|
p+=thiss;
|
|
len-=thiss;
|
|
}
|
|
while (len>0);
|
|
|
|
}
|
|
|
|
|
|
int cfg_decode_textblock(ProjectStateContext *ctx, WDL_FastString *str) // 0 on success, appends to str
|
|
{
|
|
int child_count=1;
|
|
bool did_firstline=!!str->Get()[0];
|
|
for (;;)
|
|
{
|
|
char linebuf[4096];
|
|
if (ctx->GetLine(linebuf,sizeof(linebuf))) break;
|
|
|
|
const char *p = linebuf;
|
|
while (*p == ' ' || *p == '\t') p++;
|
|
if (*p == '\'' || *p == '"' || *p == '`') p++; // skip a quote if any
|
|
|
|
if (!p[0]) continue;
|
|
else if (p[0] == '<') child_count++;
|
|
else if (p[0] == '>') { if (child_count-- == 1) return 0; }
|
|
else if (child_count == 1)
|
|
{
|
|
const char *prefix = did_firstline ? "\r\n" : "";
|
|
// lines can have a prefix immediately before | to specify the line ending of the previous line
|
|
switch (p[0])
|
|
{
|
|
case 'c': prefix=""; p++; break;
|
|
case 'n': prefix="\n"; p++; break;
|
|
case 'r': prefix="\r"; p++; break;
|
|
case 'R': prefix="\n\r"; p++; break;
|
|
}
|
|
|
|
if (p[0] == '|')
|
|
{
|
|
if (*prefix) str->Append(prefix);
|
|
str->Append(++p);
|
|
did_firstline=true;
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
|
|
}
|
|
|
|
void cfg_encode_textblock(ProjectStateContext *ctx, const char *txt) // splits long lines
|
|
{
|
|
while (*txt)
|
|
{
|
|
int l = 0;
|
|
while (txt[l] && l < 4000 && txt[l] != '\r' && txt[l] != '\n') l++;
|
|
ctx->AddLine("|%.*s",l,txt);
|
|
txt += l;
|
|
if (*txt == '\r')
|
|
{
|
|
if (*++txt== '\n') txt++;
|
|
}
|
|
else if (*txt == '\n')
|
|
{
|
|
if (*++txt == '\r') txt++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void cfg_encode_textblock2(ProjectStateContext *ctx, const char *txt) // preserves long lines, line endings
|
|
{
|
|
char prefix = ' ';
|
|
while (*txt)
|
|
{
|
|
int l = 0;
|
|
while (txt[l] && l < 2000 && txt[l] != '\r' && txt[l] != '\n') l++;
|
|
ctx->AddLine("%c|%.*s",prefix,l,txt);
|
|
txt += l;
|
|
if (*txt == '\r')
|
|
{
|
|
prefix = 'r';
|
|
if (*++txt== '\n')
|
|
{
|
|
txt++;
|
|
prefix = ' ';
|
|
}
|
|
}
|
|
else if (*txt == '\n')
|
|
{
|
|
prefix = 'n';
|
|
if (*++txt == '\r')
|
|
{
|
|
txt++;
|
|
prefix = 'R';
|
|
}
|
|
}
|
|
else if (*txt) prefix = 'c';
|
|
else break;
|
|
|
|
if (!*txt)
|
|
ctx->AddLine("%c|",prefix);
|
|
}
|
|
}
|
|
|
|
char getConfigStringQuoteChar(const char *p, bool prefer_quoteless)
|
|
{
|
|
if (!p || !*p) return '"';
|
|
|
|
char fc = *p;
|
|
int flags=0;
|
|
while (*p && flags!=15)
|
|
{
|
|
char c=*p++;
|
|
if (c=='"') flags|=1;
|
|
else if (c=='\'') flags|=2;
|
|
else if (c=='`') flags|=4;
|
|
else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') flags |= 8;
|
|
}
|
|
if (prefer_quoteless || flags == 7)
|
|
{
|
|
if (!(flags & 8) && fc != '"' && fc != '\'' && fc != '`' && fc != '#' && fc != ';') return ' ';
|
|
}
|
|
|
|
if (!(flags & 1)) return '"';
|
|
if (!(flags & 2)) return '\'';
|
|
if (!(flags & 4)) return '`';
|
|
return 0;
|
|
}
|
|
|
|
bool configStringWantsBlockEncoding(const char *in) // returns true if over 1k long, has newlines, or contains all quote chars
|
|
{
|
|
int maxl = 1024, flags = 0;
|
|
while (--maxl)
|
|
{
|
|
switch (*in++)
|
|
{
|
|
case 0: return false;
|
|
case '\n': return true;
|
|
case '"': if ((flags|=1)==7) return true; break;
|
|
case '`': if ((flags|=2)==7) return true; break;
|
|
case '\'': if ((flags|=4)==7) return true; break;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void makeEscapedConfigString(const char *in, WDL_FastString *out)
|
|
{
|
|
char c;
|
|
if (!in || !*in) out->Set("\"\"");
|
|
else if ((c = getConfigStringQuoteChar(in)))
|
|
{
|
|
if (c == ' ')
|
|
{
|
|
out->Set(in);
|
|
}
|
|
else
|
|
{
|
|
out->Set(&c,1);
|
|
out->Append(in);
|
|
out->Append(&c,1);
|
|
}
|
|
}
|
|
else // ick, change ` into '
|
|
{
|
|
out->Set("`");
|
|
out->Append(in);
|
|
out->Append("`");
|
|
char *p=(char *)out->Get()+1;
|
|
while (*p && p[1])
|
|
{
|
|
if (*p == '`') *p='\'';
|
|
else if (*p == '\r' || *p == '\n') *p=' ';
|
|
p++;
|
|
}
|
|
}
|
|
}
|