470 lines
12 KiB
C++
470 lines
12 KiB
C++
#ifndef _EEL2_PREPROC_H_
|
|
#define _EEL2_PREPROC_H_
|
|
#include "ns-eel-int.h"
|
|
#include "../win32_utf8.h"
|
|
|
|
#define EEL2_PREPROCESS_OPEN_TOKEN "<?"
|
|
|
|
class EEL2_PreProcessor
|
|
{
|
|
enum { LITERAL_BASE = 100000 };
|
|
public:
|
|
EEL2_PreProcessor(int max_sz = 64<<20, int max_include_depth=20)
|
|
{
|
|
m_max_sz = max_sz;
|
|
m_fsout = NULL;
|
|
m_vm = NSEEL_VM_alloc();
|
|
m_max_include_depth = max_include_depth;
|
|
m_output_linecnt = 0;
|
|
m_cur_depth = 0;
|
|
NSEEL_VM_SetCustomFuncThis(m_vm, this);
|
|
NSEEL_VM_SetStringFunc(m_vm, addStringCallback, NULL);
|
|
if (!m_ftab.list_size)
|
|
{
|
|
NSEEL_addfunc_varparm_ex("printf",1,0,NSEEL_PProc_THIS,&pp_printf,&m_ftab);
|
|
NSEEL_addfunc_varparm_ex("include",1,1,NSEEL_PProc_THIS,&pp_include,&m_ftab);
|
|
}
|
|
NSEEL_VM_SetFunctionTable(m_vm, &m_ftab);
|
|
m_suppress = NSEEL_VM_regvar(m_vm, "_suppress");
|
|
}
|
|
|
|
void define(const char *name, double val)
|
|
{
|
|
EEL_F *v = NSEEL_VM_regvar(m_vm,name);
|
|
if (v) *v = val;
|
|
}
|
|
|
|
~EEL2_PreProcessor()
|
|
{
|
|
for (int x = 0; x < m_code_handles.GetSize(); x ++)
|
|
NSEEL_code_free((NSEEL_CODEHANDLE) m_code_handles.Get(x));
|
|
m_literal_strings.Empty(true,free);
|
|
if (m_vm) NSEEL_VM_free(m_vm);
|
|
m_suppress = NULL;
|
|
}
|
|
|
|
void clear_line_info()
|
|
{
|
|
m_line_tab.Resize(0);
|
|
}
|
|
const char *preprocess(const char *str, WDL_FastString *fs)
|
|
{
|
|
if (!m_vm || !m_suppress)
|
|
return "preprocessor: memory error";
|
|
if (!m_cur_depth)
|
|
{
|
|
m_line_tab.Resize(0);
|
|
m_output_linecnt = 0;
|
|
*m_suppress = 0.0;
|
|
}
|
|
int input_linecnt = 0;
|
|
for (;;)
|
|
{
|
|
const bool suppress = m_suppress && *m_suppress > 0.0;
|
|
|
|
int lc = 0;
|
|
const char *tag = str;
|
|
while (*tag && strncmp(tag,EEL2_PREPROCESS_OPEN_TOKEN,2)) if (*tag++ == '\n') lc++;
|
|
|
|
if (lc)
|
|
{
|
|
input_linecnt += lc;
|
|
if (suppress)
|
|
add_line_inf(m_output_linecnt,lc);
|
|
else
|
|
m_output_linecnt += lc;
|
|
}
|
|
|
|
if (!*tag)
|
|
{
|
|
if (!suppress) fs->Append(str);
|
|
return NULL;
|
|
}
|
|
if (!suppress && tag > str) fs->Append(str,(int)(tag-str));
|
|
|
|
tag += 2;
|
|
while (*tag == ' ' || *tag == '\t') tag++;
|
|
str = tag;
|
|
lc = 0;
|
|
while (*str && strncmp(str,"?>",2)) if (*str++ == '\n') lc++;
|
|
|
|
if (!*str)
|
|
{
|
|
m_tmp.SetFormatted(512, "%d: unterminated preprocessor " EEL2_PREPROCESS_OPEN_TOKEN " block", input_linecnt+1);
|
|
return m_tmp.Get();
|
|
}
|
|
|
|
if (lc)
|
|
{
|
|
input_linecnt += lc;
|
|
add_line_inf(m_output_linecnt,lc);
|
|
}
|
|
if (str > tag)
|
|
{
|
|
m_tmp.Set(tag,(int)(str-tag));
|
|
NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(m_vm, m_tmp.Get(), 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS);
|
|
if (!ch)
|
|
{
|
|
const char *err = NSEEL_code_getcodeerror(m_vm);
|
|
if (err)
|
|
{
|
|
const int line_ref = atoi(err);
|
|
while (*err >= '0' && *err <= '9') err++;
|
|
m_tmp.SetFormatted(512,"%d: preprocessor%s%s",input_linecnt+line_ref,*err && *err != ':' ? ": ":"",err);
|
|
return m_tmp.Get();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lc = 0;
|
|
const int oldlen = fs->GetLength();
|
|
m_fsout = fs;
|
|
NSEEL_code_execute(ch);
|
|
m_fsout = NULL;
|
|
m_code_handles.Add(ch);
|
|
for (int x = oldlen; x < fs->GetLength(); x ++) if (fs->Get()[x] == '\n') lc++;
|
|
if (lc)
|
|
{
|
|
add_line_inf(m_output_linecnt,-lc);
|
|
m_output_linecnt += lc;
|
|
}
|
|
}
|
|
}
|
|
str += 2;
|
|
}
|
|
}
|
|
|
|
const char *translate_error_line(const char *err_line)
|
|
{
|
|
if (m_line_tab.GetSize()<2) return err_line;
|
|
int l = atoi(err_line)-1;
|
|
if (l<0) return err_line;
|
|
|
|
// tab is a list of pairs
|
|
// [<output position>, delta]
|
|
// delta>0 if input lines were skipped
|
|
// delta<0 if output lines were added
|
|
|
|
int nl = l;
|
|
for (int x = m_line_tab.GetSize()-2; x >= 0; x -= 2)
|
|
{
|
|
int p = m_line_tab.Get()[x];
|
|
if (nl > p)
|
|
{
|
|
int delta = m_line_tab.Get()[x+1];
|
|
nl += delta;
|
|
if (nl < p) nl = p;
|
|
}
|
|
}
|
|
|
|
if (l == nl) return err_line;
|
|
|
|
while (*err_line >= '0' && *err_line <= '9') err_line++;
|
|
if (*err_line == ':') err_line++;
|
|
m_tmp.SetFormatted(512,"%d:%s",1 + nl,err_line);
|
|
return m_tmp.Get();
|
|
}
|
|
|
|
NSEEL_VMCTX m_vm;
|
|
WDL_PtrList<char> m_literal_strings;
|
|
WDL_FastString m_tmp, *m_fsout;
|
|
WDL_TypedBuf<int> m_line_tab; // expose this in case the caller wants to keep copies around
|
|
EEL_F *m_suppress;
|
|
int m_max_sz;
|
|
int m_cur_depth, m_max_include_depth;
|
|
int m_output_linecnt;
|
|
static eel_function_table m_ftab;
|
|
WDL_PtrList<void> m_code_handles;
|
|
WDL_PtrList<const char> m_include_paths;
|
|
|
|
void add_line_inf(int output_linecnt, int lc)
|
|
{
|
|
if (!m_cur_depth)
|
|
{
|
|
m_line_tab.Add(output_linecnt); // log lc lines of input skipped
|
|
m_line_tab.Add(lc);
|
|
}
|
|
}
|
|
|
|
static EEL_F addStringCallback(void *opaque, struct eelStringSegmentRec *list)
|
|
{
|
|
EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque;
|
|
if (!_this) return -1.0;
|
|
|
|
const int sz = nseel_stringsegments_tobuf(NULL,0,list);
|
|
char *ns = (char *)malloc(sz+1);
|
|
if (WDL_NOT_NORMALLY(!ns)) return -1.0;
|
|
nseel_stringsegments_tobuf(ns,sz,list);
|
|
|
|
const int nstr = _this->m_literal_strings.GetSize();
|
|
for (int x=0;x<nstr;x++)
|
|
{
|
|
char *s = _this->m_literal_strings.Get(x);
|
|
if (!strcmp(s,ns))
|
|
{
|
|
free(ns);
|
|
return x + LITERAL_BASE;
|
|
}
|
|
}
|
|
_this->m_literal_strings.Add(ns);
|
|
return nstr + LITERAL_BASE;
|
|
}
|
|
|
|
const char *GetString(EEL_F v)
|
|
{
|
|
if (v >= LITERAL_BASE && v < LITERAL_BASE + m_literal_strings.GetSize())
|
|
return m_literal_strings.Get((int) (v - LITERAL_BASE));
|
|
return NULL;
|
|
}
|
|
|
|
static int eel_validate_format_specifier(const char *fmt_in, char *typeOut,
|
|
char *fmtOut, int fmtOut_sz,
|
|
char *varOut, int varOut_sz,
|
|
int *varOut_used
|
|
)
|
|
{
|
|
const char *fmt = fmt_in+1;
|
|
int state=0;
|
|
if (fmt_in[0] != '%') return 0; // ugh passed a non-specifier
|
|
|
|
*varOut_used = 0;
|
|
*varOut = 0;
|
|
|
|
if (fmtOut_sz-- < 2) return 0;
|
|
*fmtOut++ = '%';
|
|
|
|
while (*fmt)
|
|
{
|
|
const char c = *fmt++;
|
|
if (fmtOut_sz < 2) return 0;
|
|
|
|
if (c == 'f'|| c=='e' || c=='E' || c=='g' || c=='G' || c == 'd' || c == 'u' ||
|
|
c == 'x' || c == 'X' || c == 'c' || c == 'C' || c =='s' || c=='S' || c=='i')
|
|
{
|
|
*typeOut = c;
|
|
fmtOut[0] = c;
|
|
fmtOut[1] = 0;
|
|
return (int) (fmt - fmt_in);
|
|
}
|
|
else if (c == '.')
|
|
{
|
|
*fmtOut++ = c; fmtOut_sz--;
|
|
if (state&(2)) break;
|
|
state |= 2;
|
|
}
|
|
else if (c == '+')
|
|
{
|
|
*fmtOut++ = c; fmtOut_sz--;
|
|
if (state&(32|16|8|4)) break;
|
|
state |= 8;
|
|
}
|
|
else if (c == '-' || c == ' ')
|
|
{
|
|
*fmtOut++ = c; fmtOut_sz--;
|
|
if (state&(32|16|8|4)) break;
|
|
state |= 16;
|
|
}
|
|
else if (c >= '0' && c <= '9')
|
|
{
|
|
*fmtOut++ = c; fmtOut_sz--;
|
|
state|=4;
|
|
}
|
|
else if (c == '{')
|
|
{
|
|
if (state & 64) break;
|
|
state|=64;
|
|
if (*fmt == '.' || (*fmt >= '0' && *fmt <= '9')) return 0; // symbol name can't start with 0-9 or .
|
|
|
|
while (*fmt != '}')
|
|
{
|
|
if ((*fmt >= 'a' && *fmt <= 'z') ||
|
|
(*fmt >= 'A' && *fmt <= 'Z') ||
|
|
(*fmt >= '0' && *fmt <= '9') ||
|
|
*fmt == '_' || *fmt == '.' || *fmt == '#')
|
|
{
|
|
if (varOut_sz < 2) return 0;
|
|
*varOut++ = *fmt++;
|
|
varOut_sz -- ;
|
|
}
|
|
else
|
|
{
|
|
return 0; // bad character in variable name
|
|
}
|
|
}
|
|
fmt++;
|
|
*varOut = 0;
|
|
*varOut_used=1;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int eel_format_strings(void *opaque, const char *fmt, const char *fmt_end, char *buf, int buf_sz, int num_fmt_parms, EEL_F **fmt_parms)
|
|
{
|
|
EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque;
|
|
int fmt_parmpos = 0;
|
|
char *op = buf;
|
|
while ((fmt_end ? fmt < fmt_end : *fmt) && op < buf+buf_sz-128)
|
|
{
|
|
if (fmt[0] == '%' && fmt[1] == '%')
|
|
{
|
|
*op++ = '%';
|
|
fmt+=2;
|
|
}
|
|
else if (fmt[0] == '%')
|
|
{
|
|
char ct=0;
|
|
char fs[128];
|
|
char varname[128];
|
|
int varname_used=0;
|
|
const int l=eel_validate_format_specifier(fmt,&ct,fs,sizeof(fs),varname,sizeof(varname),&varname_used);
|
|
if (!l || !ct)
|
|
{
|
|
*op=0;
|
|
return -1;
|
|
}
|
|
|
|
const EEL_F *varptr = NULL;
|
|
if (!varname_used)
|
|
{
|
|
if (fmt_parmpos < num_fmt_parms) varptr = fmt_parms[fmt_parmpos];
|
|
fmt_parmpos++;
|
|
}
|
|
double v = varptr ? (double)*varptr : 0.0;
|
|
|
|
if (ct == 's' || ct=='S')
|
|
{
|
|
const char *str = _this->GetString(v);
|
|
const int maxl=(int) (buf+buf_sz - 2 - op);
|
|
snprintf(op,maxl,fs,str ? str : "");
|
|
}
|
|
else
|
|
{
|
|
if (ct == 'x' || ct == 'X' || ct == 'd' || ct == 'u' || ct=='i')
|
|
{
|
|
snprintf(op,64,fs,(int) (v));
|
|
}
|
|
else if (ct == 'c')
|
|
{
|
|
*op++=(char) (int)v;
|
|
*op=0;
|
|
}
|
|
else if (ct == 'C')
|
|
{
|
|
const unsigned int iv = (unsigned int) v;
|
|
int bs = 0;
|
|
if (iv & 0xff000000) bs=24;
|
|
else if (iv & 0x00ff0000) bs=16;
|
|
else if (iv & 0x0000ff00) bs=8;
|
|
while (bs>=0)
|
|
{
|
|
const char c=(char) (iv>>bs);
|
|
*op++=c?c:' ';
|
|
bs-=8;
|
|
}
|
|
*op=0;
|
|
}
|
|
else
|
|
{
|
|
snprintf(op,64,fs,v);
|
|
}
|
|
}
|
|
|
|
while (*op) op++;
|
|
|
|
fmt += l;
|
|
}
|
|
else
|
|
{
|
|
*op++ = *fmt++;
|
|
}
|
|
|
|
}
|
|
*op=0;
|
|
return (int) (op - buf);
|
|
}
|
|
|
|
static EEL_F NSEEL_CGEN_CALL pp_printf(void *opaque, INT_PTR num_param, EEL_F **parms)
|
|
{
|
|
if (num_param>0 && opaque)
|
|
{
|
|
EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque;
|
|
const char *fmt = _this->GetString(parms[0][0]);
|
|
if (fmt)
|
|
{
|
|
char buf[16384];
|
|
const int len = eel_format_strings(opaque,fmt,NULL,buf,(int)sizeof(buf), (int)num_param-1, parms+1);
|
|
|
|
if (len >= 0)
|
|
{
|
|
if (_this->m_fsout && _this->m_fsout->GetLength() < _this->m_max_sz)
|
|
{
|
|
_this->m_fsout->Append(buf,len);
|
|
}
|
|
return 1.0;
|
|
}
|
|
}
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
static EEL_F NSEEL_CGEN_CALL pp_include(void *opaque, INT_PTR num_param, EEL_F **parms)
|
|
{
|
|
if (num_param>0 && opaque)
|
|
{
|
|
EEL2_PreProcessor *_this = (EEL2_PreProcessor*)opaque;
|
|
if (_this->m_cur_depth >= _this->m_max_include_depth) return -1.0;
|
|
|
|
const char *fn = _this->GetString(parms[0][0]);
|
|
if (!fn || !*fn) return -2.0;
|
|
|
|
WDL_FastString fullfn;
|
|
for (int x = _this->m_include_paths.GetSize()-1; x >= 0; x--)
|
|
{
|
|
const char *p = _this->m_include_paths.Get(x);
|
|
if (p && *p)
|
|
{
|
|
fullfn.Set(p);
|
|
fullfn.Append(WDL_DIRCHAR_STR);
|
|
fullfn.Append(fn);
|
|
FILE *fp = fopenUTF8(fullfn.Get(),"rb");
|
|
if (fp)
|
|
{
|
|
double rv = 0.0;
|
|
_this->m_cur_depth++;
|
|
fullfn.Set("");
|
|
while (fullfn.GetLength() < (4<<20))
|
|
{
|
|
char buf[512];
|
|
if (!fgets(buf,sizeof(buf),fp)) break;
|
|
fullfn.Append(buf);
|
|
}
|
|
fclose(fp);
|
|
|
|
WDL_FastString *outp = _this->m_fsout;
|
|
if (_this->preprocess(fullfn.Get(),outp))
|
|
{
|
|
rv = -3.0;
|
|
}
|
|
_this->m_fsout = outp;
|
|
|
|
_this->m_cur_depth--;
|
|
return rv;
|
|
}
|
|
}
|
|
}
|
|
return -4.0;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
};
|
|
|
|
eel_function_table EEL2_PreProcessor::m_ftab;
|
|
|
|
#endif
|