#ifndef _WIN32 #include #ifndef EELSCRIPT_NO_LICE #include "../swell/swell.h" #endif #endif #include "../wdltypes.h" #include "../ptrlist.h" #include "../wdlstring.h" #include "../assocarray.h" #include "../queue.h" #include "../mutex.h" #include "../win32_utf8.h" #include "ns-eel.h" #ifndef EELSCRIPT_MAX_FILE_HANDLES #define EELSCRIPT_MAX_FILE_HANDLES 512 #endif #ifndef EELSCRIPT_FILE_HANDLE_INDEX_BASE #define EELSCRIPT_FILE_HANDLE_INDEX_BASE 1000000 #endif #ifndef EEL_STRING_MAXUSERSTRING_LENGTH_HINT #define EEL_STRING_MAXUSERSTRING_LENGTH_HINT (1<<16) // 64KB per string max #endif #ifndef EEL_STRING_MAX_USER_STRINGS #define EEL_STRING_MAX_USER_STRINGS 32768 #endif #ifndef EEL_STRING_LITERAL_BASE #define EEL_STRING_LITERAL_BASE 2000000 #endif #ifndef EELSCRIPT_LICE_MAX_IMAGES #define EELSCRIPT_LICE_MAX_IMAGES 1024 #endif #ifndef EELSCRIPT_LICE_MAX_FONTS #define EELSCRIPT_LICE_MAX_FONTS 128 #endif #ifndef EELSCRIPT_NET_MAXCON #define EELSCRIPT_NET_MAXCON 4096 #endif #ifndef EELSCRIPT_LICE_CLASSNAME #define EELSCRIPT_LICE_CLASSNAME "eelscript_gfx" #endif // #define EELSCRIPT_NO_NET // #define EELSCRIPT_NO_LICE // #define EELSCRIPT_NO_FILE // #define EELSCRIPT_NO_FFT // #define EELSCRIPT_NO_MDCT // #define EELSCRIPT_NO_EVAL class eel_string_context_state; #ifndef EELSCRIPT_NO_NET class eel_net_state; #endif #ifndef EELSCRIPT_NO_LICE class eel_lice_state; #endif #ifndef EELSCRIPT_NO_PREPROC #include "eel_pproc.h" #endif class eelScriptInst { public: static int init(); eelScriptInst(); virtual ~eelScriptInst(); NSEEL_CODEHANDLE compile_code(const char *code, const char **err); int runcode(const char *code, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec); int loadfile(const char *fn, const char *callerfn, bool allowstdin); NSEEL_VMCTX m_vm; WDL_PtrList m_code_freelist; #ifndef EELSCRIPT_NO_FILE FILE *m_handles[EELSCRIPT_MAX_FILE_HANDLES]; virtual EEL_F OpenFile(const char *fn, const char *mode) { if (!*fn || !*mode) return 0.0; #ifndef EELSCRIPT_NO_STDIO if (!strcmp(fn,"stdin")) return 1; if (!strcmp(fn,"stdout")) return 2; if (!strcmp(fn,"stderr")) return 3; #endif WDL_FastString fnstr(fn); if (!translateFilename(&fnstr,mode)) return 0.0; int x; for (x=0;x= EELSCRIPT_MAX_FILE_HANDLES) return 0.0; FILE *fp = fopenUTF8(fnstr.Get(),mode); if (!fp) return 0.0; m_handles[x]=fp; return x + EELSCRIPT_FILE_HANDLE_INDEX_BASE; } virtual EEL_F CloseFile(int fp_idx) { fp_idx-=EELSCRIPT_FILE_HANDLE_INDEX_BASE; if (fp_idx>=0 && fp_idx=0 && fp_idx m_eval_cache; virtual char *evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch); virtual void evalCacheDispose(char *key, NSEEL_CODEHANDLE ch); WDL_Queue m_defer_eval, m_atexit_eval; void runCodeQ(WDL_Queue *q, const char *fname); void runAtExitCode() { runCodeQ(&m_atexit_eval,"atexit"); m_atexit_eval.Clear(); // make sure nothing gets added in atexit(), in case the user called runAtExitCode before destroying } #endif virtual bool run_deferred(); // requires eval support to be useful virtual bool has_deferred(); WDL_StringKeyedArray m_loaded_fnlist; // imported file list (to avoid repeats) #ifndef EELSCRIPT_NO_PREPROC EEL2_PreProcessor m_preproc; #endif }; //#define EEL_STRINGS_MUTABLE_LITERALS //#define EEL_STRING_WANT_MUTEX #define EEL_STRING_GET_CONTEXT_POINTER(opaque) (((eelScriptInst *)opaque)->m_string_context) #ifndef EEL_STRING_STDOUT_WRITE #ifndef EELSCRIPT_NO_STDIO #define EEL_STRING_STDOUT_WRITE(x,len) { fwrite(x,len,1,stdout); fflush(stdout); } #endif #endif #include "eel_strings.h" #include "eel_misc.h" #ifndef EELSCRIPT_NO_FILE #define EEL_FILE_OPEN(fn,mode) ((eelScriptInst*)opaque)->OpenFile(fn,mode) #define EEL_FILE_GETFP(fp) ((eelScriptInst*)opaque)->GetFileFP(fp) #define EEL_FILE_CLOSE(fpindex) ((eelScriptInst*)opaque)->CloseFile(fpindex) #include "eel_files.h" #endif #ifndef EELSCRIPT_NO_FFT #include "eel_fft.h" #endif #ifndef EELSCRIPT_NO_MDCT #include "eel_mdct.h" #endif #ifndef EELSCRIPT_NO_NET #define EEL_NET_GET_CONTEXT(opaque) (((eelScriptInst *)opaque)->m_net_state) #include "eel_net.h" #endif #ifndef EELSCRIPT_NO_LICE #ifndef EEL_LICE_WANT_STANDALONE #define EEL_LICE_WANT_STANDALONE #endif #ifndef EELSCRIPT_LICE_NOUPDATE #define EEL_LICE_WANT_STANDALONE_UPDATE // gfx_update() which runs message pump and updates screen etc #endif #define EEL_LICE_GET_FILENAME_FOR_STRING(idx, fs, p) (((eelScriptInst*)opaque)->GetFilenameForParameter(idx,fs,p)) #define EEL_LICE_GET_CONTEXT(opaque) ((opaque) ? (((eelScriptInst *)opaque)->m_gfx_state) : NULL) #include "eel_lice.h" #endif #ifndef EELSCRIPT_NO_EVAL #define EEL_EVAL_GET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheGet(str,&(ch)) #define EEL_EVAL_SET_CACHED(str, ch) ((eelScriptInst *)opaque)->evalCacheDispose(str,ch) #define EEL_EVAL_GET_VMCTX(opaque) (((eelScriptInst *)opaque)->m_vm) #define EEL_EVAL_SCOPE_ENTER (((eelScriptInst *)opaque)->m_eval_depth < 3 ? \ ++((eelScriptInst *)opaque)->m_eval_depth : 0) #define EEL_EVAL_SCOPE_LEAVE ((eelScriptInst *)opaque)->m_eval_depth--; #include "eel_eval.h" static EEL_F NSEEL_CGEN_CALL _eel_defer(void *opaque, EEL_F *s) { EEL_STRING_MUTEXLOCK_SCOPE const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow defer(0) etc { eelScriptInst *inst = (eelScriptInst *)opaque; if (inst->m_defer_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { inst->m_defer_eval.Add(str,strlen(str)+1); return 1.0; } #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("defer(): too much defer() code already added, ignoring"); #endif } #ifdef EEL_STRING_DEBUGOUT else if (!str) { EEL_STRING_DEBUGOUT("defer(): invalid string identifier specified %f",*s); } else if (*s < EEL_STRING_MAX_USER_STRINGS) { EEL_STRING_DEBUGOUT("defer(): user string identifier %f specified but not allowed",*s); } #endif return 0.0; } static EEL_F NSEEL_CGEN_CALL _eel_atexit(void *opaque, EEL_F *s) { EEL_STRING_MUTEXLOCK_SCOPE const char *str=EEL_STRING_GET_FOR_INDEX(*s,NULL); if (str && *str && *s >= EEL_STRING_MAX_USER_STRINGS) // don't allow atexit(0) etc { eelScriptInst *inst = (eelScriptInst *)opaque; if (inst->m_atexit_eval.Available() < EEL_STRING_MAXUSERSTRING_LENGTH_HINT) { inst->m_atexit_eval.Add(str,strlen(str)+1); return 1.0; } #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("atexit(): too much atexit() code already added, ignoring"); #endif } #ifdef EEL_STRING_DEBUGOUT else if (!str) { EEL_STRING_DEBUGOUT("atexit(): invalid string identifier specified %f",*s); } else if (*s < EEL_STRING_MAX_USER_STRINGS) { EEL_STRING_DEBUGOUT("atexit(): user string identifier %f specified but not allowed",*s); } #endif return 0.0; } #endif #define opaque ((void *)this) eelScriptInst::eelScriptInst() : m_loaded_fnlist(false) { #ifndef EELSCRIPT_NO_FILE memset(m_handles,0,sizeof(m_handles)); #endif m_vm = NSEEL_VM_alloc(); #ifdef EEL_STRING_DEBUGOUT if (!m_vm) EEL_STRING_DEBUGOUT("NSEEL_VM_alloc(): failed"); #endif NSEEL_VM_SetCustomFuncThis(m_vm,this); #ifdef NSEEL_ADDFUNC_DESTINATION NSEEL_VM_SetFunctionTable(m_vm,NSEEL_ADDFUNC_DESTINATION); #endif m_string_context = new eel_string_context_state; eel_string_initvm(m_vm); #ifndef EELSCRIPT_NO_NET m_net_state = new eel_net_state(EELSCRIPT_NET_MAXCON,NULL); #endif #ifndef EELSCRIPT_NO_LICE m_gfx_state = new eel_lice_state(m_vm,this,EELSCRIPT_LICE_MAX_IMAGES,EELSCRIPT_LICE_MAX_FONTS); m_gfx_state->resetVarsToStock(); #endif #ifndef EELSCRIPT_NO_EVAL m_eval_depth=0; #endif } eelScriptInst::~eelScriptInst() { #ifndef EELSCRIPT_NO_EVAL if (m_atexit_eval.GetSize()>0) runAtExitCode(); #endif int x; m_code_freelist.Empty((void (*)(void *))NSEEL_code_free); #ifndef EELSCRIPT_NO_EVAL for (x=0;xSet(fmt); return translateFilename(fs,iswrite?"w":"r"); } NSEEL_CODEHANDLE eelScriptInst::compile_code(const char *code, const char **err) { if (!m_vm) { *err = "EEL VM not initialized"; return NULL; } #ifndef EELSCRIPT_NO_PREPROC WDL_FastString str; if (strstr(code,EEL2_PREPROCESS_OPEN_TOKEN)) { const char *pperr = m_preproc.preprocess(code,&str); if (pperr) { *err = pperr; return NULL; } code = str.Get(); } else m_preproc.clear_line_info(); #endif NSEEL_CODEHANDLE ch = NSEEL_code_compile_ex(m_vm, code, 0, NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); if (ch) { m_string_context->update_named_vars(m_vm); m_code_freelist.Add((void*)ch); return ch; } *err = NSEEL_code_getcodeerror(m_vm); #ifndef EELSCRIPT_NO_PREPROC if (*err) *err = m_preproc.translate_error_line(*err); #endif return NULL; } int eelScriptInst::runcode(const char *codeptr, int showerr, const char *showerrfn, bool canfree, bool ignoreEndOfInputChk, bool doExec) { if (m_vm) { const char *err = NULL; NSEEL_CODEHANDLE code = NULL; #ifndef EELSCRIPT_NO_PREPROC WDL_FastString str; if (strstr(codeptr,EEL2_PREPROCESS_OPEN_TOKEN)) { err = m_preproc.preprocess(codeptr,&str); if (err) goto on_preproc_error; codeptr = str.Get(); } else m_preproc.clear_line_info(); #endif code = NSEEL_code_compile_ex(m_vm,codeptr,0,canfree ? 0 : NSEEL_CODE_COMPILE_FLAG_COMMONFUNCS); if (code) m_string_context->update_named_vars(m_vm); if (!code && (err=NSEEL_code_getcodeerror(m_vm))) { if (!ignoreEndOfInputChk && (NSEEL_code_geterror_flag(m_vm)&1)) return 1; #ifndef EELSCRIPT_NO_PREPROC err = m_preproc.translate_error_line(err); on_preproc_error: #endif if (showerr) { #ifdef EEL_STRING_DEBUGOUT if (showerr==2) { EEL_STRING_DEBUGOUT("Warning: %s:%s",WDL_get_filepart(showerrfn),err); } else { EEL_STRING_DEBUGOUT("%s:%s",WDL_get_filepart(showerrfn),err); } #endif } return -1; } else { if (code) { #ifdef EELSCRIPT_DO_DISASSEMBLE codeHandleType *p = (codeHandleType*)code; char buf[512]; buf[0]=0; #ifdef _WIN32 GetTempPath(sizeof(buf)-64,buf); lstrcatn(buf,"jsfx-out",sizeof(buf)); #else lstrcpyn_safe(buf,"/tmp/jsfx-out",sizeof(buf)); #endif FILE *fp = fopenUTF8(buf,"wb"); if (fp) { fwrite(p->code,1,p->code_size,fp); fclose(fp); char buf2[2048]; #ifdef _WIN32 snprintf(buf2,sizeof(buf2),"disasm \"%s\"",buf); #else #ifdef __aarch64__ snprintf(buf2,sizeof(buf2), "objdump -D -b binary -maarch64 \"%s\"",buf); #elif defined(__arm__) snprintf(buf2,sizeof(buf2), "objdump -D -b binary -m arm \"%s\"",buf); #elif defined(__LP64__) #ifdef __APPLE__ snprintf(buf2,sizeof(buf2),"distorm3 --b64 \"%s\"",buf); #else snprintf(buf2,sizeof(buf2),"objdump -D -b binary -m i386:x86-64 \"%s\"",buf); #endif #else snprintf(buf2,sizeof(buf2),"distorm3 --b32 \"%s\"",buf); #endif #endif system(buf2); } #endif if (doExec) NSEEL_code_execute(code); if (canfree) NSEEL_code_free(code); else m_code_freelist.Add((void*)code); } return 0; } } return -1; } FILE *eelscript_resolvePath(WDL_FastString &usefn, const char *fn, const char *callerfn) { // resolve path relative to current int x; bool had_abs=false; for (x=0;x<2; x ++) { #ifdef _WIN32 if (!x && ((fn[0] == '\\' && fn[1] == '\\') || (fn[0] && fn[1] == ':'))) #else if (!x && fn[0] == '/') #endif { usefn.Set(fn); had_abs=true; } else { const char *fnu = fn; if (x) { while (*fnu) fnu++; while (fnu >= fn && *fnu != '\\' && *fnu != '/') fnu--; if (fnu < fn) break; fnu++; } usefn.Set(callerfn); int l=usefn.GetLength(); while (l > 0 && usefn.Get()[l-1] != '\\' && usefn.Get()[l-1] != '/') l--; if (l > 0) { usefn.SetLen(l); usefn.Append(fnu); } else { usefn.Set(fnu); } int last_slash_pos=-1; for (l = 0; l < usefn.GetLength(); l ++) { if (usefn.Get()[l] == '/' || usefn.Get()[l] == '\\') { if (usefn.Get()[l+1] == '.' && usefn.Get()[l+2] == '.' && (usefn.Get()[l+3] == '/' || usefn.Get()[l+3] == '\\')) { if (last_slash_pos >= 0) usefn.DeleteSub(last_slash_pos, l+3-last_slash_pos); else usefn.DeleteSub(0,l+3+1); } else { last_slash_pos=l; } } // take currentfn, remove filename part, add fnu } } FILE *fp = fopenUTF8(usefn.Get(),"r"); if (fp) return fp; } if (had_abs) usefn.Set(fn); return NULL; } int eelScriptInst::loadfile(const char *fn, const char *callerfn, bool allowstdin) { WDL_FastString usefn; FILE *fp = NULL; if (!strcmp(fn,"-")) { if (callerfn) { #ifdef EEL_STRING_DEBUGOUT EEL_STRING_DEBUGOUT("@import: can't import \"-\" (stdin)"); #endif return -1; } if (allowstdin) { fp = stdin; fn = "(stdin)"; } } else if (!callerfn) { fp = fopenUTF8(fn,"r"); if (fp) m_loaded_fnlist.Insert(fn,true); } else { fp = eelscript_resolvePath(usefn,fn,callerfn); if (fp) { if (m_loaded_fnlist.Get(usefn.Get())) { fclose(fp); return 0; } m_loaded_fnlist.Insert(usefn.Get(),true); fn = usefn.Get(); } } if (!fp) { #ifdef EEL_STRING_DEBUGOUT if (callerfn) EEL_STRING_DEBUGOUT("Warning: @import could not open '%s'",fn); else EEL_STRING_DEBUGOUT("Error opening %s",fn); #endif return -1; } #ifndef EELSCRIPT_NO_PREPROC WDL_FastString incpath(fn); incpath.remove_filepart(); m_preproc.m_include_paths.Add(incpath.Get()); #endif WDL_FastString code; char line[4096]; for (;;) { line[0]=0; fgets(line,sizeof(line),fp); if (!line[0]) break; if (!strnicmp(line,"@import",7) && isspace((unsigned char)line[7])) { char *p=line+7; while (isspace((unsigned char)*p)) p++; char *ep=p; while (*ep) ep++; while (ep>p && isspace((unsigned char)ep[-1])) ep--; *ep=0; if (*p) loadfile(p,fn,false); } else { code.Append(line); } } if (fp != stdin) fclose(fp); int rv = runcode(code.Get(),callerfn ? 2 : 1, fn,false,true,!callerfn); #ifndef EELSCRIPT_NO_PREPROC m_preproc.m_include_paths.Delete(m_preproc.m_include_paths.GetSize()-1); #endif return rv; } char *eelScriptInst::evalCacheGet(const char *str, NSEEL_CODEHANDLE *ch) { // should mutex protect if multiple threads access this eelScriptInst context int x=m_eval_cache.GetSize(); while (--x >= 0) { char *ret; if (!strcmp(ret=m_eval_cache.Get()[x].str, str)) { *ch = m_eval_cache.Get()[x].ch; m_eval_cache.Delete(x); return ret; } } return NULL; } void eelScriptInst::evalCacheDispose(char *key, NSEEL_CODEHANDLE ch) { // should mutex protect if multiple threads access this eelScriptInst context evalCacheEnt ecc; ecc.str= key; ecc.ch = ch; if (m_eval_cache.GetSize() > 1024) { NSEEL_code_free(m_eval_cache.Get()->ch); free(m_eval_cache.Get()->str); m_eval_cache.Delete(0); } m_eval_cache.Add(ecc); } int eelScriptInst::init() { EEL_string_register(); #ifndef EELSCRIPT_NO_FILE EEL_file_register(); #endif #ifndef EELSCRIPT_NO_FFT EEL_fft_register(); #endif #ifndef EELSCRIPT_NO_MDCT EEL_mdct_register(); #endif EEL_misc_register(); #ifndef EELSCRIPT_NO_EVAL EEL_eval_register(); NSEEL_addfunc_retval("defer",1,NSEEL_PProc_THIS,&_eel_defer); NSEEL_addfunc_retval("runloop", 1, NSEEL_PProc_THIS, &_eel_defer); NSEEL_addfunc_retval("atexit",1,NSEEL_PProc_THIS,&_eel_atexit); #endif #ifndef EELSCRIPT_NO_NET EEL_tcp_register(); #endif #ifndef EELSCRIPT_NO_LICE eel_lice_register(); #ifdef _WIN32 eel_lice_register_standalone(GetModuleHandle(NULL),EELSCRIPT_LICE_CLASSNAME,NULL,NULL); #else eel_lice_register_standalone(NULL,EELSCRIPT_LICE_CLASSNAME,NULL,NULL); #endif #endif return 0; } bool eelScriptInst::has_deferred() { #ifndef EELSCRIPT_NO_EVAL return m_defer_eval.Available() && m_vm; #else return false; #endif } #ifndef EELSCRIPT_NO_EVAL void eelScriptInst::runCodeQ(WDL_Queue *q, const char *callername) { const int endptr = q->Available(); int offs = 0; while (offs < endptr) { if (q->Available() < endptr) break; // should never happen, but safety first! const char *ptr = (const char *)q->Get() + offs; offs += strlen(ptr)+1; NSEEL_CODEHANDLE ch=NULL; char *sv=evalCacheGet(ptr,&ch); if (!sv) sv=strdup(ptr); if (!ch) ch=NSEEL_code_compile(m_vm,sv,0); if (!ch) { free(sv); #ifdef EEL_STRING_DEBUGOUT const char *err = NSEEL_code_getcodeerror(m_vm); if (err) EEL_STRING_DEBUGOUT("%s: error in code: %s",callername,err); #endif } else { NSEEL_code_execute(ch); evalCacheDispose(sv,ch); } } q->Advance(endptr); } #endif bool eelScriptInst::run_deferred() { #ifndef EELSCRIPT_NO_EVAL if (!m_defer_eval.Available()||!m_vm) return false; runCodeQ(&m_defer_eval,"defer"); m_defer_eval.Compact(); return m_defer_eval.Available()>0; #else return false; #endif } #ifdef EEL_WANT_DOCUMENTATION #include "ns-eel-func-ref.h" void EELScript_GenerateFunctionList(WDL_PtrList *fs) { const char *p = nseel_builtin_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } p = eel_strings_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } p = eel_misc_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #ifndef EELSCRIPT_NO_EVAL fs->Add("atexit\t\"code\"\t" #ifndef EELSCRIPT_HELP_NO_DEFER_DESC "Adds code to be executed when the script finishes." #endif ); fs->Add("defer\t\"code\"\t" #ifndef EELSCRIPT_HELP_NO_DEFER_DESC "Adds code which will be executed some small amount of time after the current code finishes. Identical to runloop()" #endif ); fs->Add("runloop\t\"code\"\t" #ifndef EELSCRIPT_HELP_NO_DEFER_DESC "Adds code which will be executed some small amount of time after the current code finishes. Identical to defer()" #endif ); p = eel_eval_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif #ifndef EELSCRIPT_NO_NET p = eel_net_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif #ifndef EELSCRIPT_NO_FFT p = eel_fft_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif #ifndef EELSCRIPT_NO_FILE p = eel_file_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif #ifndef EELSCRIPT_NO_MDCT p = eel_mdct_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif #ifndef EELSCRIPT_NO_LICE p = eel_lice_function_reference; while (*p) { fs->Add(p); p += strlen(p) + 1; } #endif } #endif #undef opaque