#include "core.h" #include "vm.h" #include "load.h" #include "ns.h" #include "utils/utf.h" #include "utils/talloc.h" #include "utils/file.h" #include "utils/time.h" #include "utils/interrupt.h" #if defined(_WIN32) || defined(_WIN64) #include "windows/getline.h" #endif #if __GNUC__ && __i386__ && !__clang__ #warning "CBQN is known to miscompile on GCC for 32-bit x86 builds; using clang instead is suggested" #endif #if USE_REPLXX_IO && !USE_REPLXX #error "Cannot use USE_REPLXX_IO without USE_REPLXX" #endif STATIC_GLOBAL B replPath; GLOBAL B replName; // used in vm.c STATIC_GLOBAL Scope* gsc; STATIC_GLOBAL bool repl_initialized = false; static NOINLINE void repl_init() { if (repl_initialized) return; cbqn_init(); replPath = m_c8vec_0("."); gc_add(replPath); replName = m_c8vec_0("(REPL)"); gc_add(replName); Body* body = m_nnsDesc(); B ns = m_nns(body); gsc = ptr_inc(c(NS, ns)->sc); gc_add(tag(gsc,OBJ_TAG)); ptr_dec(v(ns)); repl_initialized = true; } typedef struct { bool r; char* e; } IsCmdTmp; static NOINLINE IsCmdTmp isCmd0(char* s, const char* cmd) { while (*cmd) { if (*cmd==' ' && *s==0) return (IsCmdTmp){.r=true, .e=s}; if (*s!=*cmd || *s==0) return (IsCmdTmp){.r=false, .e=s}; s++; cmd++; } return (IsCmdTmp){.r=true, .e=s}; } static bool isCmd(char* s, char** e, const char* cmd) { IsCmdTmp t = isCmd0(s, cmd); *e = t.e; return t.r; } static NOINLINE i64 readInt(char** p) { char* c = *p; i64 am = 0; while (*c>='0' & *c<='9') { am = am*10 + (*c - '0'); c++; } *p = c; return am; } #if USE_REPLXX #include #include #include "utils/cstr.h" GLOBAL Replxx* global_replxx; INIT_GLOBAL bool replxx_read_only = false; STATIC_GLOBAL char* global_histfile; STATIC_GLOBAL u32 cfg_prefixChar = U'\\'; static i8 const themes[3][12][3] = { // {-1,-1,-1} for default/unchanged color, {-1,-1,n} for grayscale 0…23, else RGB 0…5 { // 0: "none" {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, {-1,-1,-1}, }, { // 1: "dark" [ 0] = {-1,-1,-1}, // default [ 1] = {-1,-1,11}, // comments [ 2] = {4,1,1}, // numbers [ 3] = {1,2,5}, // strings [ 4] = {-1,-1,-1}, // value names [ 5] = {1,4,1}, // functions [ 6] = {5,1,4}, // 1-modifiers [ 7] = {5,4,2}, // 2-modifiers [ 8] = {5,4,0}, // assignment (←↩→⇐), statement separators (,⋄) [ 9] = {4,3,5}, // ⟨⟩, [] ·, @, ‿ [10] = {3,2,4}, // {} [11] = {5,0,0}, // error }, { // 2: "light" [ 0] = {-1,-1,-1}, // default [ 1] = {-1,-1,11}, // comments [ 2] = {1,0,2}, // numbers [ 3] = {0,0,2}, // strings [ 4] = {-1,-1,-1}, // value names [ 5] = {0,1,0}, // functions [ 6] = {2,0,0}, // 1-modifiers [ 7] = {2,1,0}, // 2-modifiers [ 8] = {0,0,5}, // assignment (←↩→⇐), statement separators (,⋄) [ 9] = {0,1,1}, // ⟨⟩, [] ·, @, ‿ [10] = {3,0,4}, // {} [11] = {5,0,0}, // error } }; typedef i8 Theme[12][3]; STATIC_GLOBAL ReplxxColor theme_replxx[12]; STATIC_GLOBAL i32 cfg_theme = 1; STATIC_GLOBAL bool cfg_enableKeyboard = true; STATIC_GLOBAL B cfg_path; static char* const command_completion[] = { ")ex ", ")r ", ")escaped ", ")profile ", ")profile@", ")t ", ")t:", ")time ", ")time:", ")mem", ")mem t", ")mem s", ")mem f", ")mem log", ")erase ", ")clearImportCache", ")kb", ")theme dark", ")theme light", ")theme none", ")exit", ")off", ")vars", ")gc", ")gc off", ")gc on", ")gc log", ")internalPrint ", ")heapdump", #if NATIVE_COMPILER && !ONLY_NATIVE_COMP ")switchCompiler", #endif ")e ", ")explain ", }; NOINLINE void cfg_changed(void) { B s = emptyCVec(); AFMT("theme=%i\nkeyboard=%i\nprefix=%i\n", cfg_theme, cfg_enableKeyboard, cfg_prefixChar); if (CATCH) { freeThrown(); goto end; } path_wChars(incG(cfg_path), s); popCatch(); end: decG(s); } NOINLINE void cfg_set_theme(i32 num, bool writeCfg) { if (num>=3) return; cfg_theme = num; i8 const (*data)[3] = themes[num]; for (int i = 0; i < 12; i++) { i8 v0 = data[i][0]; i8 v1 = data[i][1]; i8 v2 = data[i][2]; theme_replxx[i] = v0>=0? replxx_color_rgb666(v0,v1,v2) : v2>=0? replxx_color_grayscale(v2) : REPLXX_COLOR_DEFAULT; } if (writeCfg) cfg_changed(); } void cfg_set_keyboard(bool enable, bool writeCfg) { cfg_enableKeyboard = enable; if (writeCfg) cfg_changed(); } extern u32* const dsv_text[]; STATIC_GLOBAL B sysvalNames, sysvalNamesNorm; NOINLINE void fill_color(ReplxxColor* cols, int s, int e, ReplxxColor col) { PLAINLOOP for (int i = s; i < e; i++) cols[i] = col; } static bool chr_nl(u32 c) { return c==10 || c==13; } static bool chr_dig(u32 c) { return c>='0' && c<='9'; } static bool chr_low(u32 c) { return c>='a' && c<='z'; } static bool chr_upp(u32 c) { return c>='A' && c<='Z'; } static u32 chr_to_low(u32 c) { return c-'A'+'a'; } static u32 chr_to_upp(u32 c) { return c-'a'+'A'; } static bool chr_num0(u32 c) { return chr_dig(c) || c==U'¯' || c==U'π' || c==U'∞'; } static bool chr_name0(u32 c) { return chr_low(c) || chr_upp(c) || c=='_'; } static bool chr_nameM(u32 c) { return chr_name0(c) || chr_num0(c); } static u32* const chrs_fn = U"!+-×÷⋆*√⌊⌈∧∨¬|=≠≤<>≥≡≢⊣⊢⥊∾≍⋈↑↓↕⌽⍉/⍋⍒⊏⊑⊐⊒∊⍷⊔«»𝔽𝔾𝕎𝕏𝕊"; static u32* const chrs_m1 = U"`˜˘¨⁼⌜´˝˙"; static u32* const chrs_m2 = U"∘⊸⟜○⌾⎉⚇⍟⊘◶⎊"; static u32* const chrs_lit = U"⟨⟩[]·@‿"; static u32* const chrs_dmd = U"←↩,⋄→⇐"; static u32* const chrs_blk = U"{}𝕨𝕩𝕗𝕘𝕣𝕤:?;"; NOINLINE bool chr_in(u32 val, u32* chrs) { while(*chrs) if (val == *(chrs++)) return true; return false; } #define CATCH_OOM(X) if (CATCH) { freeThrown(); X; } void highlighter_replxx(const char* input, ReplxxColor* colors, int size, void* data) { CATCH_OOM(return) B charObj = utf8Decode0(input); if (IA(charObj) != size) goto end; // don't want to kill the REPL if this happens, but gotta do _something_ charObj = toC32Any(vec_addN(charObj, m_c32(0))); u32* chars = c32any_ptr(charObj); u32* badPrefix = U")escaped"; // )escaped has its own parsing setup, highlighting that would be very misleading for (usz i = 0; i < 8; i++) if (chars[i]!=badPrefix[i]) goto not_bad_prefix; goto end; not_bad_prefix:; ReplxxColor* theme = theme_replxx; #define SKIP(V) ({ u32 c; while(true) { c=chars[i]; if (c==0 || !(V)) break; i++; } }) // skips to first character that doesn't match V, or to the final null byte #define FILL(T) ({ assert(i<=size); fill_color(colors, i0, i, theme[T]); }) #define SET1(T) ({ assert(i<=size); colors[i0] = theme[T]; }) #define SKIP1 ({ if (i+1<=size) i++; }) int i = 0; while (i < size) { int i0 = i; u32 c0 = chars[i++]; bool sys = c0==U'•' && istart && chr_nameM(*(c-1))) c--; return c; } NOINLINE void completion_impl(const char* inp, void* res, bool hint, int* dist) { if (!*inp) return; CATCH_OOM(return) B inpB = toC32Any(utf8Decode0(inp)); u32* chars = c32any_ptr(inpB); u32* ws; u32* we; i32 mode; // 0:var 1:sys 2:ns 3:cmd if (chars[0] == ')') { ws = chars; we = chars+IA(inpB); mode = 3; } else { not_cmd:; we = chars+IA(inpB); ws = rskip_name(chars, we); u32 last = ws>chars? *(ws-1) : 0; mode = last=='.'? 2 : last==U'•'? 1 : 0; if (mode==2) { u32* ws2 = ws; while (--ws2 > chars && *ws2=='.') { u32* ws3 = rskip_name(chars, ws2); if (ws2==ws3 || ws3==chars) break; ws2 = ws3; u32 last2 = *(ws2-1); if (last2==U'•') { ws=ws2; mode=1; break; } } } if (mode==1) ws--; } usz wl = *dist = we-ws; bool failedCmd = mode==3; if (wl>0) { usz wo = ws-chars; bool doUpper = false; B norm = mode==3? incG(inpB) : str_norm(ws, wl, &doUpper); B reg = mode==1? vec_slice(inpB, wo, wl) : bi_N; usz normLen = IA(norm); if (normLen>0) { B vars; switch (mode) { case 0: vars = listVars(gsc); break; case 1: vars = incG(sysvalNames); break; case 2: vars = allNsFields(); break; case 3: { usz n = sizeof(command_completion)/sizeof(char*); HArr_p vo = m_harr0v(n); for (ux i = 0; i < n; i++) vo.a[i] = m_c8vec_0(command_completion[i]); vars = vo.b; } } if (q_N(vars)) goto noVars; usz via=IA(vars); SGetU(vars) for (usz i = 0; i < via; i++) { i32 matchState=0; B match=bi_N; usz matchLen=0, skip=0; for (usz j = 0; j < (mode==1? 2 : 1); j++) { match = j? harr_ptr(sysvalNamesNorm)[i] : GetU(vars,i); bool doNorm = mode==1? j : true; matchLen = IA(match); usz wlen = doNorm? normLen : wl; if (wlen>=matchLen) continue; B slc = vec_slice(match, 0, wlen); bool matched = equal(slc, doNorm? norm : reg); decG(slc); if (matched) { matchState=doNorm?1:2; skip=wlen; break; } } if (matchState) { failedCmd = false; usz ext = matchLen-skip; assert(ext<100); u32* rp; B r = m_c32arrv(&rp, wl + ext); COPY_TO(rp, el_c32, 0, inpB, wo, wl); if (doUpper && matchState!=2) { SGetU(match) for (usz i = 0; i < ext; i++) { u32 cc=o2cG(GetU(match,i+skip)); rp[wl+i] = chr_low(cc)? chr_to_upp(cc) : cc; } } else { COPY_TO(rp, el_c32, wl, match, skip, ext); } char* str = toCStr(r); if (hint) replxx_add_hint(res, str); else replxx_add_completion(res, str); freeCStr(str); decG(r); } } decG(vars); noVars:; } decG(norm); if (mode==1) decG(reg); if (failedCmd) goto not_cmd; } decG(inpB); popCatch(); } void complete_replxx(const char* inp, replxx_completions* res, int* dist, void* data) { completion_impl(inp, res, false, dist); } void hint_replxx(const char* inp, replxx_hints* res, int* dist, ReplxxColor* c, void* data) { completion_impl(inp, res, true, dist); } static NOINLINE char* malloc_B(B x) { // toCStr but allocated by malloc; consumes x u64 len1 = utf8lenB(x); char* s1 = malloc(len1+1); toUTF8(x, s1); decG(x); s1[len1] = '\0'; return s1; } typedef struct { B s; u64 pos; } TmpState; static NOINLINE TmpState getState() { ReplxxState st; replxx_get_state(global_replxx, &st); return (TmpState){.s = utf8Decode0(st.text), .pos = st.cursorPosition}; } static NOINLINE void setState(TmpState s) { // consumes s.s char* r = malloc_B(s.s); ReplxxState st = (ReplxxState){.text = r, .cursorPosition = s.pos}; replxx_set_state(global_replxx, &st); free(r); } static NOINLINE TmpState insertChar(u32 p, bool replace) { TmpState t = getState(); u64 pos = t.pos; B s0 = t.s; if (pos==0) replace = false; B s = vec_slice(s0, 0, pos-(replace? 1 : 0)); ACHR(p); AJOIN(vec_slice(s0, pos, IA(s0)-pos)); decG(s0); return (TmpState){.s = s, .pos = replace? pos : pos+1}; } STATIC_GLOBAL B b_pv; STATIC_GLOBAL int b_pp; STATIC_GLOBAL bool inBackslash; static void stopBackslash() { inBackslash = false; } NOINLINE void setPrev(B s, u64 pos) { // consumes decG(b_pv); b_pv = s; b_pp = pos; } NOINLINE void setPrevNow() { ReplxxState st; replxx_get_state(global_replxx, &st); setPrev(utf8Decode0(st.text), st.cursorPosition); } ReplxxActionResult enter_replxx(int code, void* data) { if (inBackslash) { setState(insertChar('\n', false)); stopBackslash(); setPrevNow(); return REPLXX_ACTION_RESULT_CONTINUE; } return replxx_invoke(global_replxx, REPLXX_ACTION_COMMIT_LINE, 0); } static NOINLINE bool slice_equal(B a, usz as, B b, usz bs, usz l) { B ac = vec_slice(a, as, l); B bc = vec_slice(b, bs, l); bool r = equal(ac, bc); decG(ac); decG(bc); return r; } B indexOf_c2(B, B, B); B pick_c1(B, B); STATIC_GLOBAL B b_key, b_val; void modified_replxx(char** s_res, int* p_res, void* userData) { if (!cfg_enableKeyboard) return; CATCH_OOM(return) TmpState t = getState(); B s = t.s; u64 pos = t.pos; if (equal(b_pv, s) && pos==b_pp) goto end_withPrev; // sometimes this is called when nothing actually changed if (IA(b_pv)+1 != IA(s) || b_pp+1 != pos) goto stop; // user did something other than type a single character if (inBackslash) { if (!slice_equal(b_pv, 0, s, 0, pos-1)) goto stop; if (!slice_equal(b_pv, b_pp, s, pos, IA(s)-pos)) goto stop; if (o2cG(IGet(s,pos-1)) == cfg_prefixChar) goto stop; // always make sure that the prefix char typed in twice types in the prefix char usz mapPos = o2i(C1(pick, C2(indexOf, incG(b_key), IGet(s,pos-1)))); if (mapPos==IA(b_key)) goto stop; TmpState t2 = insertChar(o2c(IGetU(b_val, mapPos)), true); *s_res = malloc_B(t2.s); // will be free()'d by replxx *p_res = t2.pos; goto stop; } else { if (o2cG(IGetU(s,b_pp))==cfg_prefixChar) { inBackslash = true; *s_res = malloc_B(incG(b_pv)); // will be free()'d by replxx *p_res = b_pp; // restore state to previous one, and don't call setPrev as that'll set it to the updated one goto end_noPrev; } goto end_withPrev; } stop: stopBackslash(); end_withPrev: setPrevNow(); end_noPrev: decG(s); popCatch(); } void before_exit(void) { if (global_replxx!=NULL && global_histfile!=NULL) { if (!replxx_read_only) replxx_history_save(global_replxx, global_histfile); replxx_end(global_replxx); global_replxx = NULL; } } static NOINLINE B path_rel_dec(B base, B rel) { B res = path_rel(base, rel, "main.c"); dec(base); return res; } static NOINLINE B get_config_path(bool inConfigDir, char* name) { char* history_dir = getenv("XDG_DATA_HOME"); bool addConfig = false; if (!history_dir) { history_dir = getenv("HOME"); if (history_dir) addConfig = inConfigDir; } if (!history_dir) history_dir = "."; B p = utf8Decode0(history_dir); if (addConfig) p = path_rel_dec(p, m_c8vec_0(".config")); return path_rel_dec(p, m_c8vec_0(name)); } static void cbqn_init_replxx() { B cfg = get_config_path(true, "cbqn_repl.txt"); cfg_path = cfg; gc_add(cfg); if (path_type(incG(cfg_path))==0) { cfg_set_theme(1, false); cfg_set_keyboard(true, false); } else { B lns = path_lines(incG(cfg_path)); SGetU(lns) for (usz i = 0; i < IA(lns); i++) { B ln = GetU(lns, i); char* s = toCStr(ln); char* e; if (isCmd(s, &e, "theme=" )) cfg_theme = e[0]-'0'; else if (isCmd(s, &e, "keyboard=")) cfg_enableKeyboard = e[0]-'0'; #if !NO_RYU else if (isCmd(s, &e, "prefix=" )) cfg_prefixChar = readInt(&e); #endif freeCStr(s); } decG(lns); cfg_set_theme(cfg_theme, false); cfg_set_keyboard(cfg_enableKeyboard, false); } gc_add(b_key = m_c32vec_0(U"\"`1234567890-=~!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:|zxcvbnm,./ZXCVBNM<>? ")); gc_add(b_val = m_c32vec_0( U"˙˜˘¨⁼⌜´˝7∞¯•÷׬⎉⚇⍟◶⊘⎊⍎⍕⟨⟩√⋆⌽𝕨∊↑∧y⊔⊏⊐π←→↙𝕎⍷𝕣⍋YU⊑⊒⍳⊣⊢⍉𝕤↕𝕗𝕘⊸∘○⟜⋄↩↖𝕊D𝔽𝔾«J⌾»·|⥊𝕩↓∨⌊n≡∾≍≠⋈𝕏C⍒⌈N≢≤≥⇐‿")); sysvalNames = emptyHVec(); sysvalNamesNorm = emptyHVec(); u32* const* c = dsv_text; while (*c) { bool unused; B str = m_c32vec_0(*(c++)); sysvalNames = vec_addN(sysvalNames, str); sysvalNamesNorm = vec_addN(sysvalNamesNorm, str_norm(c32any_ptr(str), IA(str), &unused)); } gc_add(sysvalNames); gc_add(sysvalNamesNorm); gc_add_ref(&b_pv); global_replxx = replxx_init(); b_pv = emptyCVec(); b_pp = 0; } const char* cbqn_replxx_input(char* prefix) { setPrev(emptyCVec(), 0); return replxx_input(global_replxx, prefix); } #else void before_exit(void) { } #endif B m_state(B path, B name, B args); NOINLINE B gsc_exec_inplace(B src, char* name, B args) { Block* block = bqn_compSc(src, m_state(incG(replPath), m_c8vec_0(name), args), gsc, true); ptr_dec(gsc->body); // redirect new errors to the newly executed code; initial scope had 0 vars, so this is safe gsc->body = ptr_inc(block->bodies[0]); B r = execBlockInplace(block, gsc); ptr_dec(block); return r; } bool profiler_alloc(void); bool profiler_start(i32 mode, i64 hz); bool profiler_stop(void); void profiler_free(void); void profiler_displayResults(void); void clearImportCache(void); #if NATIVE_COMPILER && !ONLY_NATIVE_COMP void switchComp(void); #endif STATIC_GLOBAL B escape_parser; static B simple_unescape(B x) { if (RARE(escape_parser.u==0)) { escape_parser = bqn_exec(utf8Decode0("{m←\"Expected surrounding quotes\" ⋄ m!2≤≠𝕩 ⋄ m!\"\"\"\"\"\"≡0‿¯1⊏𝕩 ⋄ s←¬e←<`'\\'=𝕩 ⋄ i‿o←\"\\\"\"nr\"⋈\"\\\"\"\"∾@+10‿13 ⋄ 1↓¯1↓{n←i⊐𝕩 ⋄ \"Unknown escape\"!∧´n≠≠i ⋄ n⊏o}⌾((s/»e)⊸/) s/𝕩}"), bi_N); gc_add(escape_parser); } return c1(escape_parser, x); } static NOINLINE void printTime(f64 ns) { if (ns<1e3) printf("%.5gns\n", ns); else if (ns<1e6) printf("%.4gus\n", ns/1e3); else if (ns<1e9) printf("%.4gms\n", ns/1e6); else printf("%.5gs\n", ns/1e9); } static NOINLINE f64 timeBlockN(Block* block, i64 rep) { u64 sns = nsTime(); for (i64 i = 0; i < rep; i++) dec(execBlockInplace(block, gsc)); u64 ens = nsTime(); return ens-sns; } #if !NO_RYU bool ryu_s2d_n(u8* buffer, int len, f64* result); #endif void heap_printInfoStr(char* str); extern GLOBAL bool gc_log_enabled, mem_log_enabled; void cbqn_runLine0(char* ln, i64 read) { if (ln[0]==0 || read==0) return; B code; int output; // 0-no; 1-formatter; 2-internal int mode = 0; // 0: regular execution; 1: single timing; 2: many timings; 3: second-limited timing; 4: profile; 4: profile ip i32 timeRep = 0; f64 timeNanos = -1; i64 profile = -1; if (ln[0] == ')') { char* cmdS = ln+1; char* cmdE; if (isCmd(cmdS, &cmdE, "ex ")) { B path = utf8Decode0(cmdE); code = path_chars(path); output = 0; } else if (isCmd(cmdS, &cmdE, "r ")) { code = utf8Decode0(cmdE); output = 0; } else if (isCmd(cmdS, &cmdE, "escaped ")) { B u = simple_unescape(utf8Decode0(cmdE)); u64 len = utf8lenB(u); TALLOC(char, ascii, len+1); toUTF8(u, ascii); dec(u); ascii[len] = '\0'; cbqn_runLine0(ascii, len+1); TFREE(ascii); return; } else if (isCmd(cmdS, &cmdE, "t ") || isCmd(cmdS, &cmdE, "time ")) { code = utf8Decode0(cmdE); mode = 1; output = 0; } else if (isCmd(cmdS, &cmdE, "profile ") || isCmd(cmdS, &cmdE, "profile@")) { mode = 4; goto profile_init; profile_init:; char* cpos = cmdE; profile = '@'==*(cpos-1)? readInt(&cpos) : 5000; if (profile==0) { printf("Cannot profile with 0hz sampling frequency\n"); return; } if (profile>999999) { printf("Cannot profile with >999999hz frequency\n"); return; } code = utf8Decode0(cpos); output = 0; #if PROFILE_IP } else if (isCmd(cmdS, &cmdE, "profileip ") || isCmd(cmdS, &cmdE, "profileip@")) { mode = 5; goto profile_init; #endif } else if (isCmd(cmdS, &cmdE, "t:") || isCmd(cmdS, &cmdE, "time:")) { char* repE = cmdE; mode = 2; #if NO_RYU i64 am = readInt(&repE); #else while ((*repE>='0' & *repE<='9') || *repE=='.' || *repE=='e') repE++; if (repE-cmdE >= 40) { printf("Timing repetition count too large\n"); return; } ux chrs = repE-cmdE; f64 am; if (!ryu_s2d_n((u8*)cmdE, chrs, &am)) { printf("Bad timing repetition count\n"); return; } if (am==0) { printf("Timing repetition count was zero\n"); return; } if (*repE == 's') { repE++; mode=3; timeNanos=am*1e9; } #endif code = utf8Decode0(repE); timeRep = am; output = 0; } else if (isCmd(cmdS, &cmdE, "mem ")) { if (strcmp(cmdE,"log")==0) { mem_log_enabled^= true; printf("Allocation logging %s\n", mem_log_enabled? "enabled" : "disabled"); } else { heap_printInfoStr(cmdE); } return; } else if (isCmd(cmdS, &cmdE, "erase ")) { char* name = cmdE; i64 len = strlen(name); ScopeExt* e = gsc->ext; if (e!=NULL) { i32 am = e->varAm; for (i32 i = 0; i < am; i++) { B c = e->vars[i+am]; if (IA(c) != len) continue; SGetU(c) bool ok = true; for (i32 j = 0; j < len; j++) ok&= o2cG(GetU(c, j))==name[j]; if (ok) { B val = e->vars[i]; e->vars[i] = bi_noVar; dec(val); #if ENABLE_GC if (!gc_depth) gc_forceGC(true); #endif return; } } } printf("No such variable found\n"); return; } else if (isCmd(cmdS, &cmdE, "clearImportCache ")) { clearImportCache(); return; } else if (isCmd(cmdS, &cmdE, "heapdump ")) { cbqn_heapDump(NULL); return; #if USE_REPLXX } else if (isCmd(cmdS, &cmdE, "kb ")) { if (*cmdE == 0) { kb_toggle: cfg_set_keyboard(!cfg_enableKeyboard, true); printf("Backslash input %s\n", cfg_enableKeyboard? "enabled" : "disabled"); } else { B a = utf8Decode0(cmdE); if (IA(a)==0) goto kb_toggle; if (IA(a)!=1) { printf("Invalid )kb usage: expected either no argument, or a single character\n"); } else { u32 c = o2cG(IGetU(a,0)); printf("Backslash input prefix character set to U+%04x\n", c); cfg_prefixChar = c; cfg_changed(); } decG(a); } return; } else if (isCmd(cmdS, &cmdE, "theme ")) { if (strcmp(cmdE,"dark" )==0) cfg_set_theme(1, true); else if (strcmp(cmdE,"light")==0) cfg_set_theme(2, true); else if (strcmp(cmdE,"none" )==0) cfg_set_theme(0, true); else printf("Unknown theme\n"); return; #endif } else if (isCmd(cmdS, &cmdE, "exit") || isCmd(cmdS, &cmdE, "off")) { bqn_exit(0); } else if (isCmd(cmdS, &cmdE, "vars")) { B r = listVars(gsc); if (q_N(r)) { printf("Couldn't list variables\n"); } else { usz ia = IA(r); B* rp = harr_ptr(r); for (usz i = 0; i < ia; i++) { if (i!=0) printf(", "); printsB(rp[i]); } printf("\n"); } return; } else if (isCmd(cmdS, &cmdE, "gc ")) { #if ENABLE_GC bool toplevel = true; if (0==*cmdE) { gc_now:; if (gc_depth!=0) { printf("GC is disabled, but forcibly GCing anyway\n"); u64 stored_depth = gc_depth; gc_depth = 0; gc_forceGC(toplevel); gc_depth = stored_depth; } else { gc_forceGC(toplevel); } } else if (strcmp(cmdE,"nontop")==0) { toplevel = false; goto gc_now; } else if (strcmp(cmdE,"on")==0) { if (gc_depth==0) printf("GC already on\n"); else if (gc_depth>1) printf("GC cannot be enabled\n"); else { gc_enable(); printf("GC enabled\n"); } } else if (strcmp(cmdE,"off")==0) { if (gc_depth>0) printf("GC already off\n"); else { gc_disable(); printf("GC disabled\n"); } } else if (strcmp(cmdE,"log")==0) { gc_log_enabled^= true; printf("GC logging %s\n", gc_log_enabled? "enabled" : "disabled"); } else printf("Unknown GC command\n"); #else printf("Macro ENABLE_GC was false at compile-time, cannot GC\n"); #endif return; } else if (isCmd(cmdS, &cmdE, "internalPrint ")) { code = utf8Decode0(cmdE); output = 2; #if NATIVE_COMPILER && !ONLY_NATIVE_COMP } else if (isCmd(cmdS, &cmdE, "switchCompiler")) { switchComp(); return; #endif } else if (isCmd(cmdS, &cmdE, "e ") || isCmd(cmdS, &cmdE, "explain ")) { HArr* expla = toHArr(bqn_explain(utf8Decode0(cmdE))); usz ia=PIA(expla); for(usz i=0; ia[i]); printf("\n"); } ptr_dec(expla); return; } else { printf("Unknown REPL command\n"); return; } } else { code = utf8Decode0(ln); output = 1; } Block* block = bqn_compSc(code, m_state(incG(replPath), incG(replName), emptySVec()), gsc, true); ptr_dec(gsc->body); gsc->body = ptr_inc(block->bodies[0]); B res; if (mode==1) { u64 sns = nsTime(); res = execBlockInplace(block, gsc); u64 ens = nsTime(); printTime(ens-sns); } else if (mode==2) { printTime(timeBlockN(block, timeRep) / timeRep); res = m_c32(0); } else if (mode==3) { f64 tns = 0; // total nanoseconds timed u64 rt = 0; // count of runs of those nanoseconds u64 r1 = 1; // run count to attempt next while (1) { // printf(N64d"\n", r1); f64 ct = timeBlockN(block, r1); tns+= ct; rt+= r1; timeNanos-= ct; if (timeNanos<=0) break; u64 r2 = timeNanos*r1/ct; if (r2 <= r1/8) { r1/=8; if (!r1) r1=1; } else if (r2 >= r1*2) { r1*=2; } else r1 = r2; } printTime(tns / rt); } else if (mode==4 || mode==5) { if (CATCH) { profiler_stop(); profiler_free(); rethrow(); } if (profiler_alloc() && profiler_start(mode==5? 2 : 1, profile)) { res = execBlockInplace(block, gsc); profiler_stop(); profiler_displayResults(); profiler_free(); } popCatch(); } else { res = execBlockInplace(block, gsc); } ptr_dec(block); if (output) { if (output!=2 && FORMATTER) { B resFmt = bqn_fmt(res); printsB(resFmt); dec(resFmt); printf("\n"); } else { printI(res); printf("\n"); fflush(stdout); dec(res); } } else dec(res); } void cbqn_runLine(char* ln, i64 len) { if(CATCH) { cbqn_takeInterrupts(false); fprintf(stderr, "Error: "); printErrMsg(thrownMsg); fprintf(stderr, "\n"); vm_pst(envCurr+1, envStart+envPrevHeight); freeThrown(); #if HEAP_VERIFY cbqn_heapVerify(); #endif gc_maybeGC(true); return; } cbqn_takeInterrupts(true); cbqn_runLine0(ln, len); #if HEAP_VERIFY cbqn_heapVerify(); #endif gc_maybeGC(true); cbqn_takeInterrupts(false); popCatch(); } #if WASM void cbqn_evalSrc(char* src, i64 len) { B code = utf8Decode(src, len); B resFmt = bqn_fmt(bqn_exec(code, bi_N)); printsB(resFmt); dec(resFmt); printf("\n"); } #endif #if EMCC int main() { repl_init(); } #elif !CBQN_LIB #if HAS_VERSION extern char* const cbqn_versionInfo; #endif int main(int argc, char* argv[]) { #if USE_REPLXX_IO cbqn_init(); cbqn_init_replxx(); #endif bool startREPL = argc==1; bool silentREPL = false; bool execStdin = false; if (!startREPL) { i32 i = 1; while (i!=argc) { char* carg = argv[i]; if (carg[0]!='-') break; if (carg[1]==0) { execStdin=true; i++; break; } i++; if (carg[1]=='-') { if (!strcmp(carg, "--help")) { print_help: printf( "Usage: %s [options] [file.bqn [arguments]]\n" "Options:\n" " -f file execute the contents of the file with all further arguments as •args\n" " -e code execute the argument as BQN\n" " -p code execute the argument as BQN and print its result pretty-printed\n" " -o code execute the argument as BQN and print its raw result\n" " -M num set maximum heap size to num megabytes\n" " -r start the REPL after executing all arguments\n" " -s start a silent REPL\n" " --help show this help text\n" #if HAS_VERSION " --version display CBQN version information\n" #endif , argv[0]); exit(0); } else if (!strcmp(carg, "--version")) { #if HAS_VERSION printf("%s", cbqn_versionInfo); #else printf("CBQN, unknown version\n"); #endif exit(0); #if USE_REPLXX } else if (!strcmp(carg, "--replxx-read-only")) { replxx_read_only = true; continue; #endif } else { printf("%s: Unknown option: %s\n", argv[0], carg); exit(1); } } else { carg++; char c; while ((c = *carg++)) { switch(c) { default: fprintf(stderr, "%s: Unknown option: -%c\n", argv[0], c); exit(1); #define REQARG(X) if(*carg) { fprintf(stderr, "%s: -%s must end the option\n", argv[0], #X); exit(1); } if (i==argc) { fprintf(stderr, "%s: -%s requires an argument\n", argv[0], #X); exit(1); } case 'f': repl_init(); REQARG(f); goto execFile; case 'e': { repl_init(); REQARG(e); dec(gsc_exec_inplace(utf8Decode0(argv[i++]), "(-e)", emptySVec())); break; } case 'L': { repl_init(); break; } // just initialize. mostly for perf testing case 'p': { repl_init(); REQARG(p); B r = gsc_exec_inplace(utf8Decode0(argv[i++]), "(-p)", emptySVec()); if (FORMATTER) { r = bqn_fmt(r); printsB(r); } else { printI(r); } dec(r); printf("\n"); break; } case 'o': { repl_init(); REQARG(o); B r = gsc_exec_inplace(utf8Decode0(argv[i++]), "(-o)", emptySVec()); if (isAtm(r) || RNK(r)!=1) thrM("(-o): Value to print must be a string"); printsB(r); decG(r); printf("\n"); break; } case 'M': { REQARG(M); char* str = argv[i++]; u64 am = 0; while (*str) { char c = *str++; if (c<'0' | c>'9') { printf("%s: -M: Argument not a number\n", argv[0]); exit(1); } if (am>1ULL<<48) { printf("%s: -M: Too large\n", argv[0]); exit(1); } am = am*10 + c-48; } mm_heapMax = am*1024*1024; break; } #ifdef PERF_TEST case 'R': { repl_init(); REQARG(R); B path = utf8Decode0(argv[i++]); B lines = path_lines(path); usz ia = IA(lines); SGet(lines) for (u64 i = 0; i < ia; i++) { dec(gsc_exec_inplace(Get(lines, i), "(-R)", emptySVec())); } break; } #endif case 'h': goto print_help; case 'r': { startREPL=true; break; } case 's': { startREPL=true; silentREPL=true; break; } } } } } execFile: if (i!=argc || execStdin) { repl_init(); B src; if (!execStdin) src = utf8Decode0(argv[i++]); B args; if (i==argc) { args = emptySVec(); } else { M_HARR(ap, argc-i) for (usz j = 0; j < argc-i; j++) HARR_ADD(ap, j, utf8Decode0(argv[i+j])); args = HARR_FV(ap); } B execRes; if (execStdin) { execRes = gsc_exec_inplace(utf8DecodeA(stream_bytes(stdin)), "(stdin)", args); } else { execRes = bqn_execFile(src, args); } dec(execRes); #if HEAP_VERIFY cbqn_heapVerify(); #endif gc_forceGC(true); } } if (startREPL) { repl_init(); #if USE_REPLXX if (!silentREPL) { #if !USE_REPLXX_IO cbqn_init_replxx(); #endif B f = get_config_path(false, ".cbqn_repl_history"); char* histfile = toCStr(f); dec(f); gc_add(tag(TOBJ(histfile), OBJ_TAG)); replxx_history_load(global_replxx, histfile); global_histfile = histfile; replxx_set_ignore_case(global_replxx, true); replxx_set_highlighter_callback(global_replxx, highlighter_replxx, NULL); replxx_set_hint_callback(global_replxx, hint_replxx, NULL); replxx_set_completion_callback(global_replxx, complete_replxx, NULL); replxx_enable_bracketed_paste(global_replxx); replxx_bind_key(global_replxx, REPLXX_KEY_ENTER, enter_replxx, NULL); replxx_set_modify_callback(global_replxx, modified_replxx, NULL); replxx_bind_key_internal(global_replxx, REPLXX_KEY_CONTROL('N'), "history_next"); replxx_bind_key_internal(global_replxx, REPLXX_KEY_CONTROL('P'), "history_previous"); replxx_set_max_history_size(global_replxx, 50000); while(true) { const char* ln = cbqn_replxx_input(" "); if (ln==NULL) { if (errno==0) printf("\n"); break; } replxx_history_add(global_replxx, ln); cbqn_runLine((char*)ln, strlen(ln)); if (!replxx_read_only) replxx_history_save(global_replxx, histfile); } } else #endif { while (true) { if (!silentREPL) { printf(" "); fflush(stdout); } char* ln = NULL; size_t gl = 0; i64 read = getline(&ln, &gl, stdin); if (read<=0 || ln[0]==0) { if(!silentREPL) printf("\n"); break; } if (ln[read-1]==10) ln[--read] = 0; if (ln[read-1]==13) ln[--read] = 0; cbqn_runLine(ln, read); free(ln); } } } #if HEAP_VERIFY cbqn_heapVerify(); #endif bqn_exit(0); } #endif