namespace lsp; include rt, run; //: ============================================================================ //: server.zc: the language server. it is appended to the genome after the //: compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not //: analyze zc with a second implementation of the grammar: it runs the //: compiler itself over the document, in check mode (see chk_unit in //: compiler.zc), and reports what the compiler said. a parse word, an //: operator or a type the document defines changes how the rest of the //: document is read, for the checker and for the highlighter, because both are //: the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not //: a protocol stream but a source file it prints the diagnostics as text. //: ============================================================================ //: the first constant the server declares: the blob cut for a document starts //: here (blob_load, compiler.zc), so this name must stay the first one of the server. const int LSP_SERVER = 1; // the compiler's own functions, declared here so this file checks on its own // in an editor: the build places them in front of this file, so these lines only // promise what is already defined there int run.chk_unit(int src, int buf, int cap, int sets, int lim); int run.hl_begin(int src); int run.hl_end(); //: ---- memory ---- //: nothing here has a fixed address. a document is a heap block of its text, the //: diagnostics and the check's name sets are blocks too, and a check collects //: what the last one left behind (chk_unit). a document compiles into the //: program region from 0 up to CODE_END, which is the map's own. const int DGCAP = 12000; word dgb, sets; int jsize, tcap; int dgbcap = DGCAP; const int MAXDOC = 16, URIW = 160; //: ---- output ---- //: every message is written twice: once to count its bytes for the header, //: once for real. wc is the only thing that touches the terminal. int wmode, wcnt; //: bounded cache of completed analysis replies; document generations and include epochs are part of every key. const int RC_LIMIT = 64, RC_W = 8; word[RC_LIMIT * RC_W] rc_meta; word[RC_LIMIT] rc_payload; int rc_pending = -1, rc_now = -1, rc_epoch, rc_tick, rc_hits, rc_misses, analysis_checks; char* rc_buffer; int rc_cap = 4096; // writes one byte of output. while a message is being sized the byte is only // counted; otherwise it goes to the terminal. int wc(int c) { if (wmode == 2) { if (!rc_buffer || wcnt == rc_cap) { if (rc_buffer) rc_cap = rc_cap * 2; rc_buffer = lsp_grow(rc_buffer, wcnt, rc_cap); } rc_buffer[wcnt] = c; wcnt++; } else if (wmode) wcnt++; else rt.raw_c(c); return 0; } // writes a zero-terminated char* string, one character per word. int ws(char* s) { int i = 0; while (s[i]) { wc(s[i]); i++; } return 0; } // writes n in decimal, with a minus sign when it is negative. int wn(int n) { word[24] b; int i = 0; if (n < 0) { wc('-'); n = 0 - n; } while (1) { b[i] = '0' + n % 10; i++; n = n / 10; if (!n) break; } while (i) { i--; wc(b[i]); } return 0; } // writes byte c inside a json string: quotes and backslashes are escaped, and // the control characters become their escapes (\n, \t, \u00xx) so the text stays one line. int wesc(int c) { if (c == 34) { wc(92); wc(34); return 0; } if (c == 92) { wc(92); wc(92); return 0; } if (c == 10) { wc(92); wc('n'); return 0; } if (c == 13) { wc(92); wc('r'); return 0; } if (c == 9) { wc(92); wc('t'); return 0; } if (c < 32) { ws("\\u00"); wc("0123456789abcdef"[c >> 4]); wc("0123456789abcdef"[c & 15]); return 0; } wc(c); return 0; } // a json string from n chars at p, one per word int wjs(char* p, int n) { int i = 0; wc(34); while (i < n) { wesc(p[i]); i++; } wc(34); return 0; } // sends one message. gen writes its body: it runs once to count the bytes for the // Content-Length header, and once more to write them. int send(int gen) { int n, k; if (rc_pending >= 0 && (gen == gen_hover || gen == gen_tokens || gen == gen_nav || gen == gen_completion || gen == gen_null)) { wmode = 2; wcnt = 0; gen(); n = wcnt; wmode = 0; rc_store(rc_pending, n); rc_pending = -1; ws("Content-Length: "); wn(n); ws("\r\n\r\n"); k = 0; while (k < n) { wc(rc_buffer[k]); k++; } return 0; } wmode = 1; wcnt = 0; gen(); wmode = 0; ws("Content-Length: "); wn(wcnt); ws("\r\n\r\n"); gen(); return 0; } //: ---- documents ---- //: a document is its text, four bytes to a word, in a heap block of its own //: (doff), with a zero word after the last byte so the lexer sees the end. a //: replaced text is a new block, and the old one is garbage for the collector. int nodoc, doccap = MAXDOC; char** duri; word dulen, doff, dsize, dver, dgen, dlive; // Grow a heap buffer while retaining its contents. Globals keep these buffers rooted. word .u lsp_grow(word old, int count, int capacity) { word p = rt.new(capacity); rt.fill_w(p, 0, capacity); if (old && count) rt.copy_w(p, old, count); return p; } // Document columns grow together; each URI has its own string allocation. int docs_grow() { int next = doccap * 2; duri = lsp_grow(duri, doccap, next); dulen = lsp_grow(dulen, doccap, next); doff = lsp_grow(doff, doccap, next); dsize = lsp_grow(dsize, doccap, next); dver = lsp_grow(dver, doccap, next); dgen = lsp_grow(dgen, doccap, next); dlive = lsp_grow(dlive, doccap, next); doccap = next; return 0; } // the byte at offset i of a text. texts are packed four bytes to a word, low byte first. word dc(word base, int i) { return (base[i >> 2] >> ((i & 3) * 8)) & 255; } // the document with the uri p (n bytes long), or -1 when the server has no such document. int dfind(char* p, int n) { int i = 0, k; while (i < doccap) { if (dlive[i] && dulen[i] == n) { k = 0; while (k < n && duri[i][k] == p[k]) k++; if (k == n) return i; } i++; } return 0 - 1; } // takes a free document slot for the uri p (n bytes long), growing the columns // when needed. commit_text sets its text after this. int docnew(char* p, int n) { int i = 0, k; while (1) { if (i == doccap) docs_grow(); if (!dlive[i]) { duri[i] = lsp_grow(0, 0, n + 1); k = 0; while (k < n) { duri[i][k] = p[k]; k++; } dulen[i] = n; dlive[i] = 1; doff[i] = 0; dsize[i] = 0; dver[i] = 0; dgen[i] = 0; return i; } i++; } return 0 - 1; } // the text, its terminator and a spare zero word int dwords(int n) { return (n >> 2) + 2; } //: ---- the check ---- int chk_i, chk_gen; // the document the compiler's tables describe, and its generation int chk_stream, chk_stale, chk_items, chk_next, streamed, unpub = 0 - 1; // see check_progress // checks document i with the compiler in check mode. the diagnostics are left in // dgb, and the types of the untyped lets are recorded while it runs (lt_on). int check_doc(int i) { analysis_checks++; chk_i = i; chk_gen = dgen[i]; rt.outmode = 1; run.lt_on = 1; run.lt_n = 0; run.ltxn = 0; run.ht_n = 0; inc_setup(i); run.chk_unit(doff[i], dgb, dgbcap, sets, rt.CODE_END); dgb = run.dgbuf; dgbcap = run.dgcap; run.lt_on = 0; rt.outmode = 0; return 0; } // a check for diagnostics streams what it has found after each top-level item and is dropped // the moment the host says more input is waiting, which may be the text that replaces this one. // a dropped check leaves the document unpublished (unpub) until the next check of it finishes. // the entry of the untyped local declared at byte offset o, or -1 int lt_find(int o) { int k = 0; while (k < run.lt_n) { if (run.lt_off[k] == o) return k; k++; } return 0 - 1; } int ensure_checked(int i) { if (chk_i != i || chk_gen != dgen[i]) { check_doc(i); if (unpub == i) { unpub = 0 - 1; publish(i); } } return 0; } //: ---- positions ---- //: a cursor over the document that turns byte offsets into the line and the //: utf-16 column the protocol wants. it only moves forward, or starts over. int pbase, plen, poff, pline, pcol; // starts the position cursor at the top of document i. int pc_reset(int i) { pbase = doff[i]; plen = dsize[i]; poff = 0; pline = 0; pcol = 0; return 0; } // moves the position cursor forward to byte offset off, counting lines and the // utf-16 columns the protocol uses (a character of four bytes takes two columns). // a cursor that is past off starts over from the top. int pc_to(int off) { int b; if (off < poff) { poff = 0; pline = 0; pcol = 0; } if (off > plen) off = plen; while (poff < off) { b = dc(pbase, poff); if (b == 10) { pline++; pcol = 0; } else if (b < 128 || b >= 192) { if (b >= 240) pcol = pcol + 2; else pcol++; } poff++; } return 0; } // the byte offset where the 1-based line l starts, or the end of the text when // there is no such line. int line_off(int l) { int o = 0; while (l > 1 && o < plen) { if (dc(pbase, o) == 10) l--; o++; } return o; } //: ---- diagnostics ---- int gd; // the document being reported // writes a json position, {line, character}, for byte offset off. int wpos(int off) { pc_to(off); ws("{\"line\":"); wn(pline); ws(",\"character\":"); wn(pcol); wc('}'); return 0; } // writes the publishDiagnostics message for document gd from dgb. each record // becomes a range (the whole line when the compiler only knew the line), a // severity and its message. int gen_diag() { int at = 0, first = 1, sev, rln, off, rlen, n, o, e; ws("{\"jsonrpc\":\"2.0\",\"method\":\"textDocument/publishDiagnostics\",\"params\":{\"uri\":"); wjs(duri[gd], dulen[gd]); ws(",\"version\":"); wn(dver[gd]); ws(",\"diagnostics\":["); pc_reset(gd); while (at < run.dgpos) { sev = dgb[at]; rln = dgb[at + 1]; off = dgb[at + 2]; rlen = dgb[at + 3]; n = dgb[at + 4]; if (off < 0) { // the compiler only knew the line: mark its text o = line_off(rln); while (o < plen && (dc(pbase, o) == 32 || dc(pbase, o) == 9)) o++; e = o; while (e < plen && dc(pbase, e) != 10) e++; if (e > o && dc(pbase, e - 1) == 13) e--; } else { o = off; e = off + rlen; if (o >= plen) { // the end of the file: the last thing before it o = plen; while (o > 0 && (dc(pbase, o - 1) == 32 || dc(pbase, o - 1) == 10 || dc(pbase, o - 1) == 13 || dc(pbase, o - 1) == 9)) { o--; } if (o > 0) o--; e = o + 1; } if (e <= o) e = o + 1; } if (e > plen) e = plen; if (o > e) o = e; if (!first) wc(','); first = 0; ws("{\"range\":{\"start\":"); wpos(o); ws(",\"end\":"); wpos(e); ws("},\"severity\":"); wn(sev); ws(",\"source\":\"zc\",\"message\":"); wjs(dgb + at + 5, n); wc('}'); at = at + 5 + n; } ws("]}}"); return 0; } // sends the diagnostics of document i. int publish(int i) { gd = i; send(gen_diag); return 0; } const int STREAM_AFTER = 100; // a document of fewer top-level items is checked in one go: dropping it would cost more int check_progress() { int sm, sc, sn, sg; word sb; if (!chk_stream) return 0; chk_items++; if (chk_items < STREAM_AFTER) return 0; if (rt.syscall(42, 0, 0, 0) > 0) { chk_stale = 1; run.chkabort = 1; return 0; } // what has been found goes out when the item count doubles, so the total stays linear if (chk_items < chk_next || run.dgpos == streamed) return 0; chk_next = chk_items * 2; sm = rt.outmode; sb = rt.outbuf; sn = rt.outn; sc = rt.outcap; sg = dgb; rt.outmode = 0; dgb = run.dgbuf; gd = chk_i; send(gen_diag); streamed = run.dgpos; rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; dgb = sg; return 0; } // check document i for publication: streaming, and dropped when its text is replaced. true when it finished int check_publish(int i) { chk_stream = 1; chk_stale = 0; chk_items = 0; chk_next = STREAM_AFTER; streamed = 0; run.chk_hook = check_progress; check_doc(i); run.chk_hook = 0; chk_stream = 0; if (chk_stale) { chk_i = 0 - 1; unpub = i; return 0; } if (unpub == i) unpub = 0 - 1; return 1; } //: ---- semantic tokens ---- //: the legend: keyword type function variable string number operator comment //: parameter property macro enumMember const int TT_KW = 0, TT_TYPE = 1, TT_FN = 2, TT_VAR = 3, TT_STR = 4, TT_NUM = 5, TT_OP = 6, TT_COM = 7, TT_PARAM = 8, TT_PROP = 9, TT_MACRO = 10, TT_ENUM = 11; int tfirst, tprevl, tprevc; // one token on one line int tok_out(int off, int end, int ty) { int l, c, len; pc_to(off); l = pline; c = pcol; pc_to(end); len = pcol - c; if (len <= 0) return 0; if (!tfirst) wc(','); tfirst = 0; if (l == tprevl) { wn(0); wc(','); wn(c - tprevc); } else { wn(l - tprevl); wc(','); wn(c); } wc(','); wn(len); wc(','); wn(ty); ws(",0"); tprevl = l; tprevc = c; return 0; } // a range that may cross lines is cut at the line ends int range_out(int off, int end, int ty) { int e; while (off < end) { e = off; while (e < end && dc(pbase, e) != 10) e++; if (e > off && dc(pbase, e - 1) == 13) tok_out(off, e - 1, ty); else tok_out(off, e, ty); off = e + 1; } return 0; } // between two tokens there is only space and comments int gap_out(int from, int to) { int o = from, e, c; while (o < to) { c = dc(pbase, o); if (c == '/' && dc(pbase, o + 1) == '/') { e = o; while (e < to && dc(pbase, e) != 10) e++; range_out(o, e, TT_COM); o = e; } else if (c == '/' && dc(pbase, o + 1) == '*') { e = o + 2; while (e < plen && !(dc(pbase, e) == '*' && dc(pbase, e + 1) == '/')) e++; e = e + 2; if (e > plen) e = plen; range_out(o, e, TT_COM); o = e; } else o++; } return 0; } // is c an operator character, which the highlighter marks as an operator. int isopc(int c) { char* s = "+-*/%=<>!&|^~?@#$"; int i = 0; while (s[i]) { if (s[i] == c) return 1; i++; } return 0; } // is the name n one of the words the highlighter shows as keywords even when the // compiler has no record for it. int iskeyword(int n) { return n == run.s_fn || n == run.s_var || n == run.s_const || n == run.s_else || n == run.s_as || n == run.s_unchecked || n == run.s_immediate || n == run.s_case || n == run.s_default || n == run.s_do || n == run.s_while || n == run.s_enum || n == run.s_include || n == run.s_memdebug || n == run.s_enhance || n == run.s_micro || n == run.s_vm || n == run.s_struct || n == run.s_type || n == run.s_typecheck || n == run.s_static; } // the record of the name at byte offset o (see rs_note in the compiler), or -1. // the records are sorted by offset, so this is a binary search. int rs_find(int o) { int lo = 0, hi = run.rsn - 1, mid, v, s, k; char* name; word p; while (lo <= hi) { mid = (lo + hi) / 2; v = run.rsrec[mid * run.RS_W]; if (v == o) return mid; if (v < o) lo = mid + 1; else hi = mid - 1; } // qualified module names share one compiler token; the cursor may be on // any component. confirm the complete source spelling before using it. if (hi >= 0 && chk_i >= 0) { p = run.rsrec + hi * run.RS_W; if (p[1] == run.RS_SYM && p[3] >= 0) { s = p[3]; name = run.sname[s]; k = 0; while (name[k] && p[0] + k < dsize[chk_i] && dc(doff[chk_i], p[0] + k) == name[k]) k++; if (!name[k] && o < p[0] + k) return hi; } } return 0 - 1; } //: rs_cur is the cursor of the highlighter's pass over the records, which move //: forward with the tokens. int rs_cur; // the class of an identifier at byte offset o: what the compiler resolved it to // when it checked the document, so a local named like a built-in is a variable. // classify guesses from the name alone, and only serves names with no record. int rs_class(int o, int n, int pt, int pv) { int r = -1, k, v, sk; while (rs_cur < run.rsn && run.rsrec[rs_cur * run.RS_W] < o) rs_cur++; if (rs_cur < run.rsn && run.rsrec[rs_cur * run.RS_W] == o) r = rs_cur; if (r < 0) return classify(n, pt, pv); k = run.rsrec[r * run.RS_W + 1]; if (k == run.RS_LOCAL) return TT_VAR; if (k == run.RS_PARAM) return TT_PARAM; if (k == run.RS_FIELD) return TT_PROP; v = run.rsrec[r * run.RS_W + 2]; sk = v & 15; if (sk == run.K_KW) { if (v & 16) return TT_MACRO; return TT_KW; } if (sk == run.K_TYPE) return TT_TYPE; if (sk == run.K_FN) { if (v & 32) return TT_FN; return TT_VAR; } if (sk == run.K_ENH) return TT_FN; if (sk == run.K_CONST) return TT_ENUM; return TT_VAR; } // the class of an identifier. pt, pv: the token before it int classify(int n, int pt, int pv) { int s, nx = 0; // a name after fn is a declaration; a name after . or -> that is called is a method if (pt == run.T_ID && pv == run.s_fn) return TT_FN; if (pt == run.T_OP && pv == run.s_dot) { if (n == run.s_m || n == run.s_d || n == run.s_u) return TT_KW; run.peek(); if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) return TT_FN; return TT_PROP; } if (pt == run.T_OP && pv == run.s_arrow) { run.peek(); if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) return TT_FN; return TT_PROP; } if (iskeyword(n)) return TT_KW; run.peek(); if (run.pk_tk == run.T_OP && run.pk_tv == run.s_lp) nx = 1; s = run.lookup(n); if (s >= 0) { if (run.skind[s] == run.K_KW) { if (s >= run.ubase) return TT_MACRO; return TT_KW; } if (run.skind[s] == run.K_TYPE) return TT_TYPE; if (run.skind[s] == run.K_FN && !(run.sflag[s] & run.F_DEF)) return TT_VAR; // never defined: the check says so if (run.skind[s] == run.K_FN || run.skind[s] == run.K_ENH) { if (nx) return TT_FN; if (run.lsget(1, n)) return TT_PARAM; if (run.lsget(0, n)) return TT_VAR; return TT_FN; } if (run.skind[s] == run.K_CONST) return TT_ENUM; if (run.lsget(1, n)) return TT_PARAM; return TT_VAR; } if (run.lsget(2, n)) return TT_TYPE; if (run.lsget(1, n)) return TT_PARAM; if (run.lsget(0, n)) return TT_VAR; if (run.lsget(3, n)) return TT_PROP; return TT_VAR; } // writes the semantic tokens of document gd: every token in order, each with the // class the compiler resolved it to (rs_class), and numbers, strings and operators // by their kind. comments are left to the editor's own grammar. int gen_tokens() { int last = 0, ty, pt = 0, pv = 0, o, e, c; ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"data\":["); pc_reset(gd); tfirst = 1; tprevl = 0; tprevc = 0; rs_cur = 0; run.hl_begin(doff[gd]); while (run.tk != run.T_EOF) { o = run.toff; e = run.toff + run.toklen; if (run.toklen > 0 && o >= last) { gap_out(last, o); ty = 0 - 1; if (run.tk == run.T_ID) ty = rs_class(o, run.tv, pt, pv); else if (run.tk == run.T_NUM) { c = dc(pbase, o); if (c == 39) ty = TT_STR; else ty = TT_NUM; } else if (run.tk == run.T_FLT) ty = TT_NUM; else if (run.tk == run.T_STR || run.tk == run.T_BSTR) ty = TT_STR; else if (run.tk == run.T_OP) { if (isopc(dc(pbase, o))) ty = TT_OP; } if (ty >= 0) range_out(o, e, ty); last = e; } pt = run.tk; pv = run.tv; run.next(); } gap_out(last, plen); run.hl_end(); ws("]}}"); return 0; } word tbase; int tn, thave, tover, tuseen; //: ---- files and paths ---- //: the compiler looks an include up next to the file that has it, then in the workspace folder. a client with no //: way for the server to read files (the extension's wasm and vm servers) sends the text of every file a document //: includes (zc/file), keyed by its full path; the compiler asks for them through inc_hook before it reads a file. char* rootbuf; int rootcap = 256, rootn, rootset; // the workspace folder: params.rootUri of initialize char* pathbuf; int pathcap = 256, pathn; // the path of a file the client sends (zc/file) char** vf_path; char** vf_text; int vf_n, vf_cap; // the path inside a file: uri, decoded, with no drive-letter slash char*.u uri_path(char* uri, int n) { char* o; int i = 0, k = 0, c, h1, h2; o = lsp_grow(0, 0, n + 2); if (n >= 7 && uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l' && uri[3] == 'e' && uri[4] == ':') { i = 5; if (uri[i] == '/' && uri[i + 1] == '/') i = 7; } else return 0; while (i < n) { c = uri[i]; if (c == '%' && i + 2 < n) { h1 = uri[i + 1]; h2 = uri[i + 2]; if (h1 >= 'a') h1 = h1 - 32; if (h2 >= 'a') h2 = h2 - 32; if (h1 >= 'A') h1 = h1 - 55; else h1 = h1 - '0'; if (h2 >= 'A') h2 = h2 - 55; else h2 = h2 - '0'; if (h1 >= 0 && h1 < 16 && h2 >= 0 && h2 < 16) { c = h1 * 16 + h2; i = i + 2; } } o[k] = c; k++; i++; } o[k] = 0; // /c:/dir: the slash before a drive letter is not part of the path if (k > 2 && o[0] == '/' && o[2] == ':') { while (k > 0) { o[k - 1] = o[k]; k--; } } return o; } // the directory of document i, or 0 when it is not a file char* doc_dir(int i) { char* path; path = uri_path(duri[i], dulen[i]); if (!path) return 0; return run.inc_dir(path); } // the text the client sent for the file at path, or 0 char* vfs_lookup(char*.m path) { int k = 0; while (k < vf_n) { if (rt.eq_w(vf_path[k], path)) return vf_text[k]; k++; } return 0; } // keep (or with no text, forget) a file: path in pathbuf, its text in tbase / tn int vfs_set(int have) { char* key; char* text; int k = 0, j; key = run.inc_norm(pathbuf); while (k < vf_n && !rt.eq_w(vf_path[k], key)) k++; if (!have) { if (k < vf_n) { vf_n--; vf_path[k] = vf_path[vf_n]; vf_text[k] = vf_text[vf_n]; vf_path[vf_n] = 0; vf_text[vf_n] = 0; } return 0; } text = lsp_grow(0, 0, tn + 2); j = 0; while (j < tn) { text[j] = dc(tbase, j); j++; } text[tn] = 0; if (k == vf_n) { if (vf_n == vf_cap) { if (!vf_cap) vf_cap = 32; else vf_cap = vf_cap * 2; vf_path = lsp_grow(vf_path, vf_n, vf_cap); vf_text = lsp_grow(vf_text, vf_n, vf_cap); } vf_n++; } vf_path[k] = key; vf_text[k] = text; return 0; } // point the compiler's include lookup at document i before it checks it int inc_setup(int i) { run.incdir = doc_dir(i); run.increoot = 0; if (rootset && rootn) run.increoot = uri_path(rootbuf, rootn); run.inc_hook = vfs_lookup; return 0; } //: ---- json in ---- //: the body of a message is read straight off stdin a char at a time and //: never held whole: the fields the server cares about are picked out as they //: go by, and a document's text is decoded into the store as it arrives. int jleft, jpk; int fm_edit_tab, fm_edit_width, fm_edit_set; // the editor's tab settings, from a formatting request int rdecl = 1, hline, hchar, hn, hoff, hcursor, ca_endline = -1, ca_endchar; char* hov; int hovcap = 2300; char* htb; int htbc = 80, htn, ann_name_at; char* hname; int hnamecap = 80; int hdl, hdle, hcs; char* ann_text; int ann_n; char* mbuf, idbuf, jk, kbuf; int mcap = 64, idcap = 64, jkcap = 16, kcap = 40; int mn, idn, idtype; char* ubuf; int ucap = URIW; int un, jver, kn; // the next byte of the json body. the body is read from stdin and stops after // jleft bytes; -1 means the end. junget returns a byte to the next jgetc. int jgetc() { int c; if (jpk != 0 - 2) { c = jpk; jpk = 0 - 2; return c; } if (jleft <= 0) return 0 - 1; jleft--; if (rt.eof()) { jleft = 0; return 0 - 1; } return rt.get_c() & 255; } // return byte c, so that the next jgetc reads it again. int junget(int c) { jpk = c; return 0; } // the next byte of the json body that is not white space. int jws() { int c = jgetc(); while (c == 32 || c == 9 || c == 10 || c == 13) c = jgetc(); return c; } // where the decoded bytes of a string go int jput(int sink, int b) { word at; if (sink == 1) { if (mn + 1 >= mcap) { mbuf = lsp_grow(mbuf, mn, mcap * 2); mcap = mcap * 2; } mbuf[mn] = b; mn++; } else if (sink == 2) { if (idn + 1 >= idcap) { idbuf = lsp_grow(idbuf, idn, idcap * 2); idcap = idcap * 2; } idbuf[idn] = b; idn++; } else if (sink == 3) { if (un + 1 >= ucap) { ubuf = lsp_grow(ubuf, un, ucap * 2); ucap = ucap * 2; } ubuf[un] = b; un++; } else if (sink == 4) { if ((tn >> 2) + 2 >= tcap) { tover = 1; return 0; } at = tbase + (tn >> 2); if ((tn & 3) == 0) at[0] = 0; at[0] = at[0] | (b << ((tn & 3) * 8)); tn++; } else if (sink == 5) { if (kn + 1 >= kcap) { kbuf = lsp_grow(kbuf, kn, kcap * 2); kcap = kcap * 2; } kbuf[kn] = b; kn++; } else if (sink == 6) { if (rootn + 1 >= rootcap) { rootbuf = lsp_grow(rootbuf, rootn, rootcap * 2); rootcap = rootcap * 2; } rootbuf[rootn] = b; rootn++; } else if (sink == 7) { if (pathn + 1 >= pathcap) { pathbuf = lsp_grow(pathbuf, pathn, pathcap * 2); pathcap = pathcap * 2; } pathbuf[pathn] = b; pathn++; } return 0; } // writes the code point cp as utf-8 into sink (one byte at a time through jput). int jutf8(int sink, int cp) { if (cp < 128) { jput(sink, cp); return 0; } if (cp < 2048) { jput(sink, 192 | (cp >> 6)); jput(sink, 128 | (cp & 63)); return 0; } if (cp < 65536) { jput(sink, 224 | (cp >> 12)); jput(sink, 128 | ((cp >> 6) & 63)); jput(sink, 128 | (cp & 63)); return 0; } jput(sink, 240 | (cp >> 18)); jput(sink, 128 | ((cp >> 12) & 63)); jput(sink, 128 | ((cp >> 6) & 63)); jput(sink, 128 | (cp & 63)); return 0; } // reads the four hex digits of a \u escape and returns their value. when a byte // is not a hex digit, the digits read so far are returned and that byte goes back. int jhex4() { int v = 0, i = 0, c, h; while (i < 4) { c = jgetc(); h = run.hexv(c); if (h < 0) { junget(c); return v; } v = v * 16 + h; i++; } return v; } // the rest of a string, the opening quote already read int jstr(int sink) { int c, e, cp, lo; while (1) { c = jgetc(); if (c < 0 || c == 34) return 0; if (c != 92) { jput(sink, c); continue; } e = jgetc(); if (e == 'n') jput(sink, 10); else if (e == 't') jput(sink, 9); else if (e == 'r') jput(sink, 13); else if (e == 'b') jput(sink, 8); else if (e == 'f') jput(sink, 12); else if (e == 'u') { cp = jhex4(); if (cp >= 55296 && cp < 56320) { // a high surrogate: the low half follows as another \u c = jgetc(); if (c == 92) { c = jgetc(); if (c == 'u') { lo = jhex4(); cp = 65536 + ((cp - 55296) << 10) + (lo - 56320); } } } jutf8(sink, cp); } else if (e >= 0) jput(sink, e); } return 0; } // the code of the json key in kbuf, for the keys the server reads (0 for the others). int kcode() { if (rt.eq_w(kbuf, "method")) return 1; if (rt.eq_w(kbuf, "id")) return 2; if (rt.eq_w(kbuf, "params")) return 3; if (rt.eq_w(kbuf, "textDocument")) return 4; if (rt.eq_w(kbuf, "uri")) return 5; if (rt.eq_w(kbuf, "text")) return 6; if (rt.eq_w(kbuf, "contentChanges")) return 7; if (rt.eq_w(kbuf, "version")) return 8; if (rt.eq_w(kbuf, "position")) return 9; if (rt.eq_w(kbuf, "line")) return 10; if (rt.eq_w(kbuf, "character")) return 11; if (rt.eq_w(kbuf, "includeDeclaration")) return 12; if (rt.eq_w(kbuf, "rootUri")) return 15; if (rt.eq_w(kbuf, "path")) return 16; if (rt.eq_w(kbuf, "tabSize")) return 13; if (rt.eq_w(kbuf, "insertSpaces")) return 14; if (rt.eq_w(kbuf, "range")) return 17; if (rt.eq_w(kbuf, "start")) return 18; if (rt.eq_w(kbuf, "diagnostics")) return 19; if (rt.eq_w(kbuf, "end")) return 20; return 0; } // is the key code one of the first dep keys on the json path to the value being read. int janc(int dep, int code) { int i = 0; while (i < dep) { if (jk[i] == code) return 1; i++; } return 0; } // reads one json value at nesting depth dep. the keys the server reads (key) go to // their fields, and objects and arrays are walked for the ones inside them. int jval(int key, int dep) { int c; word k; int sink, neg; if (dep >= jkcap) { jk = lsp_grow(jk, jkcap, jkcap * 2); jkcap = jkcap * 2; } jk[dep] = key; c = jws(); if (c == '{') { while (1) { c = jws(); if (c < 0 || c == '}') return 0; if (c == ',') continue; if (c != 34) continue; kn = 0; jstr(5); kbuf[kn] = 0; k = kcode(); c = jws(); // the colon jval(k, dep + 1); } } if (c == '[') { while (1) { c = jws(); if (c < 0 || c == ']') return 0; // a '}' inside an array is malformed: stop here and leave it for the enclosing object, // since a bare value stops at '}' without taking it, and looping would never end if (c == '}') { junget(c); return 0; } if (c == ',') continue; junget(c); jval(key, dep + 1); } } if (c == 34) { sink = 0; if (key == 1 && dep == 1) { sink = 1; mn = 0; } else if (key == 2 && dep == 1) { sink = 2; idn = 0; idtype = 2; } else if (key == 5 && janc(dep, 4)) { sink = 3; un = 0; tuseen = 1; } else if (key == 6 && (janc(dep, 7) || janc(dep, 4) || (dep == 2 && janc(dep, 3)))) { sink = 4; thave = 1; tover = 0; tbase = text_start(); tn = 0; } else if (key == 15 && dep == 2) { sink = 6; rootn = 0; rootset = 1; } else if (key == 16 && dep == 2) { sink = 7; pathn = 0; } jstr(sink); if (sink == 1) mbuf[mn] = 0; if (sink == 2) idbuf[idn] = 0; if (sink == 3) ubuf[un] = 0; if (sink == 6) rootbuf[rootn] = 0; if (sink == 7) pathbuf[pathn] = 0; if (sink == 4) { k = tbase + (tn >> 2); // zero the rest of the last word and the one after it if ((tn & 3) == 0) k[0] = 0; k[1] = 0; } return 0; } if (c < 0) return 0; // a number, true, false or null sink = 0; if (key == 2 && dep == 1) { sink = 2; idn = 0; idtype = 1; } k = 0; neg = 0; if (key == 12) rdecl = c == 't'; if (key == 14) { fm_edit_set = 1; fm_edit_tab = c != 't'; } while (c >= 0 && c != ',' && c != '}' && c != ']' && c != 32 && c != 10 && c != 13 && c != 9) { if (sink == 2) jput(2, c); if ((key == 8 && janc(dep, 4)) || key == 13 || ((key == 10 || key == 11) && (janc(dep, 9) || janc(dep, 17)))) { if (c == '-') { neg = 1; } else if (run.isdig(c)) k = k * 10 + c - '0'; } c = jgetc(); } junget(c); if (sink == 2) { idbuf[idn] = 0; if (rt.eq_w(idbuf, "null")) idtype = 0; } if (key == 8 && janc(dep, 4)) { if (neg) k = 0 - k; jver = k; } if (key == 13) { fm_edit_set = 1; fm_edit_width = k; } if (key == 10 && (janc(dep, 9) || (janc(dep, 17) && janc(dep, 18) && !janc(dep, 19)))) hline = k; if (key == 11 && (janc(dep, 9) || (janc(dep, 17) && janc(dep, 18) && !janc(dep, 19)))) hchar = k; if (key == 10 && janc(dep, 17) && janc(dep, 20) && !janc(dep, 19)) ca_endline = k; if (key == 11 && janc(dep, 17) && janc(dep, 20) && !janc(dep, 19)) ca_endchar = k; return 0; } // the text of a document being read goes into a block that holds the whole // message: decoded text is never longer than the message it came in word text_start() { tcap = dwords(jsize); tbase = rt.new(tcap); return tbase; } //: ---- messages in ---- char* lbuf; int lcap = 160; int rfirst = 0 - 1; // one header line, without the line end. returns its length, or -1 at the end of the input int rl() { int n = 0, c; while (1) { if (rfirst >= 0) { c = rfirst; rfirst = 0 - 1; } else { if (rt.eof()) { if (!n) return 0 - 1; return n; } c = rt.get_c() & 255; } if (c == 10) return n; if (c != 13) { if (n + 1 >= lcap) { lbuf = lsp_grow(lbuf, n, lcap * 2); lcap = lcap * 2; } lbuf[n] = c; n++; } } return n; } // the lower case of an ascii letter. other bytes are unchanged. int lower(int c) { if (c >= 'A' && c <= 'Z') return c + 32; return c; } // does the header line in lbuf start with content-length: (in any case). int is_clen(int n) { if (n < 15) return 0; char* s = "content-length:"; int i = 0; while (s[i]) { if (lower(lbuf[i]) != s[i]) return 0; i++; } return 1; } int inited, shut, quit; // read one message into the field buffers. returns 0 at the end of the input int read_message() { int n, size = 0 - 1, i; while (1) { n = rl(); if (n < 0) return 0; if (n == 0) { if (size >= 0) break; else continue; } if (is_clen(n)) { i = 15; size = 0; while (i < n && (lbuf[i] == 32 || lbuf[i] == 9)) i++; while (i < n && run.isdig(lbuf[i])) { size = size * 10 + lbuf[i] - '0'; i++; } } } jleft = size; jpk = 0 - 2; jsize = size; mn = 0; mbuf[0] = 0; idn = 0; idbuf[0] = 0; idtype = 0; un = 0; ubuf[0] = 0; jver = 0; thave = 0; tover = 0; tuseen = 0; tn = 0; tbase = 0; hline = 0; hchar = 0; rdecl = 1; fm_edit_set = 0; pathn = 0; ca_endline = -1; ca_endchar = 0; jval(0, 0); while (jleft > 0) jgetc(); // whatever the parser left behind return 1; } //: ---- messages out ---- int wid() { if (idtype == 2) { wjs(idbuf, idn); } else if (idtype == 1) ws(idbuf); else ws("null"); return 0; } // the reply to initialize: the server's capabilities, which are text sync, hover, // and semantic tokens with their legend. int gen_init() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"capabilities\":{\"textDocumentSync\":{\"openClose\":true,\"change\":1,\"save\":{\"includeText\":true}},"); ws("\"completionProvider\":{\"triggerCharacters\":[\".\",\">\"]},\"hoverProvider\":true,\"definitionProvider\":true,\"documentFormattingProvider\":true,\"referencesProvider\":true,\"codeActionProvider\":{\"codeActionKinds\":[\"quickfix\"]},\"semanticTokensProvider\":{\"legend\":{\"tokenTypes\":[\"keyword\",\"type\",\"function\",\"variable\",\"string\",\"number\",\"operator\",\"comment\",\"parameter\",\"property\",\"macro\",\"enumMember\"],\"tokenModifiers\":[]},\"full\":true}},"); ws("\"serverInfo\":{\"name\":\"zc\",\"version\":\"2\"}}}"); return 0; } // a reply with a null result, for the request id in the message. int gen_null() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":null}"); return 0; } //: the error code the next error reply carries. int errcode; // an error reply with the code errcode and a matching message. int gen_error() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"error\":{\"code\":"); wn(errcode); ws(",\"message\":"); if (errcode == 0 - 32601) ws("\"method not found\""); else ws("\"server not initialized\""); ws("}}"); return 0; } // a semantic tokens reply with no tokens, for a document the server does not have. int gen_empty() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"data\":[]}}"); return 0; } // sends an error reply with the code given. int reply_error(int code) { errcode = code; send(gen_error); return 0; } int hs_name = -1, hs_first = -1, hs_const; int bs_k, bs_off; // the embedded source and name offset found by bs_find //: ---- hover ---- //: the documentation of the name under the cursor: the comment lines directly //: above its declaration in the document, or, for what the compiler and the //: standard library define, the docs stored in the image // reports whether a character can occur in an identifier. int hid(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; } // the identifier at hline, hchar of document i, into hname. returns its length int hover_word(int i) { int base = doff[i], size = dsize[i], o = 0, ln = 0, col = 0, c, step, width, b, e, n = 0; hcursor = 0 - 1; hname[0] = 0; if (hline < 0 || hchar < 0) return 0; while (ln < hline && o < size) { if (dc(base, o) == 10) ln++; o++; } if (ln != hline) return 0; // the protocol counts utf-16 units; the document stores utf-8 bytes. while (col < hchar && o < size) { c = dc(base, o); if (c == 10 || c == 13) return 0; step = 1; width = 1; if (c >= 192 && c < 224) step = 2; else if (c >= 224 && c < 240) step = 3; else if (c >= 240) { step = 4; width = 2; } o = o + step; col = col + width; } if (col != hchar) return 0; hcursor = o; if (o >= size || !hid(dc(base, o))) return 0; b = o; while (b > 0 && hid(dc(base, b - 1))) b--; e = o; while (e < size && hid(dc(base, e))) e++; if (e - b + 1 > hnamecap) { hnamecap = e - b + 1; hname = lsp_grow(0, 0, hnamecap); } hoff = b; while (b < e) { hname[n] = dc(base, b); n++; b++; } hname[n] = 0; return n; } // is c a delimiter that ends the name in a top-level declaration int hdelim(int c) { return c == '(' || c == '=' || c == ';' || c == ',' || c == '[' || c == '<' || c == '{'; } // does the line at ls (ending before le) declare hname. only a top-level line // counts (hover_scan has already checked it starts in column 0). the name is // the word just before the line's first delimiter, so a keyword or type on the // line (int, fixed, fn) is never the declared name, and a use of the name // further along the line is not a declaration word hover_decl(int base, int ls, int le) { int dl = ls, e, b, k = 0; while (dl < le && !hdelim(dc(base, dl))) dl++; if (dl >= le) return 0; e = dl; while (e > ls && (dc(base, e - 1) == 32 || dc(base, e - 1) == 9)) e--; b = e; while (b > ls && hid(dc(base, b - 1))) b--; if (b == e) return 0; while (hname[k] && b + k < e && dc(base, b + k) == hname[k]) k++; return !hname[k] && b + k == e; } // the top-level declaration of hname, found by its line: hdl and hdle are the // line's start and end, and hcs is the start of the comment block directly above // it (hcs is -1 when none) which runs to the line at hdl. a declaration needs // no comment, so the signature still shows without one. int hover_scan(int i) { hs_name = -1; hs_first = -1; hs_const = 0; if (hover_source_decl(i) || hover_source_function(i)) return 1; int base = doff[i], size = dsize[i], ls = 0, le, cs = 0 - 1, c0; while (ls < size) { le = ls; while (le < size && dc(base, le) != 10) le++; c0 = dc(base, ls); if (c0 == '/' && ls + 1 < le && dc(base, ls + 1) == '/') { if (cs < 0) cs = ls; } else { if (le > ls && c0 != 32 && c0 != 9 && hover_decl(base, ls, le)) { hdl = ls; hdle = le; hcs = cs; return 1; } cs = 0 - 1; } ls = le + 1; } return 0; } // one character of the hover text, up to its cap int hov_put(int c) { if (hn == hovcap) { hov = lsp_grow(hov, hn, hovcap * 2); hovcap = hovcap * 2; } hov[hn] = c; hn++; return 0; } //: comment ranges are scanned once per document version, outside strings and chars. int hc_i = -1, hc_gen = -1, hc_n, hc_cap = 64; word hc_start, hc_end, hc_kind; int hover_comments(int i) { int o = 0, size = dsize[i], base = doff[i], c, run.start, kind, bol = 1, quote; if (hc_i == i && hc_gen == dgen[i]) return 0; hc_i = i; hc_gen = dgen[i]; hc_n = 0; while (o < size) { c = dc(base, o); if (c == 10) { bol = 1; o++; continue; } if (c == 32 || c == 9 || c == 13) { o++; continue; } if (c == 34 || c == 39) { quote = c; o++; while (o < size && dc(base, o) != quote) { if (dc(base, o) == 92) o++; o++; } o++; bol = 0; continue; } kind = 0; run.start = o; if (c == '/' && dc(base, o + 1) == '/') { kind = 1; if (bol) kind = 2; if (dc(base, o + 2) == ':') kind = 4; o = o + 2; while (o < size && dc(base, o) != 10) o++; } else if (c == '/' && dc(base, o + 1) == '*') { kind = 3; o = o + 2; while (o < size && !(dc(base, o) == '*' && dc(base, o + 1) == '/')) { if (dc(base, o) == 10) bol = 1; o++; } if (o < size) o = o + 2; } else { o++; bol = 0; continue; } if (!hc_start || hc_n == hc_cap) { if (hc_start) hc_cap = hc_cap * 2; hc_start = lsp_grow(hc_start, hc_n, hc_cap); hc_end = lsp_grow(hc_end, hc_n, hc_cap); hc_kind = lsp_grow(hc_kind, hc_n, hc_cap); } hc_start[hc_n] = run.start; hc_end[hc_n] = o; hc_kind[hc_n] = kind; hc_n++; } return 0; } // whitespace between documentation and its target may cross one line, not a blank line. int doc_gap(int base, int from, int to) { int c, lines = 0; while (from < to) { c = dc(base, from); from++; if (c == 10) { lines++; if (lines > 1) return 0; } else if (c != 32 && c != 9 && c != 13) return 0; } return 1; } // remove comment delimiters and optional line-leading stars; retain markdown. int hover_comment_text(int i, int index) { int base = doff[i], o = hc_start[index] + 2, end = hc_end[index], bol = 1, c, before = hn; if (hc_kind[index] == 3) { if (end >= o + 2 && dc(base, end - 2) == '*' && dc(base, end - 1) == '/') end = end - 2; if (o < end && dc(base, o) == '*') o++; } while (o < end && (dc(base, o) == 32 || dc(base, o) == 9 || dc(base, o) == 10 || dc(base, o) == 13)) o++; while (end > o && (dc(base, end - 1) == 32 || dc(base, end - 1) == 9 || dc(base, end - 1) == 10 || dc(base, end - 1) == 13)) end--; if (o == end) return 0; hov_text("\n\n"); while (o < end) { if (bol && hc_kind[index] == 3) { while (o < end && (dc(base, o) == 32 || dc(base, o) == 9)) o++; if (o < end && dc(base, o) == '*') { o++; if (dc(base, o) == 32) o++; } bol = 0; } if (o >= end) break; c = dc(base, o); o++; if (c != 13) hov_put(c); if (c == 10) bol = 1; } return hn - before; } // a trailing declaration comment belongs to its last name, not the next line. // commas in calls and subscripts do not separate declarations. int doc_trailing(int base, int from, int to) { int c, quote, paren = 0, bracket = 0, ended = 0; while (from < to) { c = dc(base, from); from++; if (c == 10 || c == 13) return 0; if (c == 32 || c == 9) continue; if (ended) return 0; if (c == 34 || c == 39) { quote = c; while (from < to && dc(base, from) != quote) { if (dc(base, from) == 92) from++; from++; } if (from >= to) return 0; from++; continue; } if (c == '(') paren++; else if (c == ')') { if (!paren) return 0; paren--; } else if (c == '[') bracket++; else if (c == ']') { if (!bracket) return 0; bracket--; } else if (c == '{' || c == '}') return 0; else if (!paren && !bracket && c == ',') return 0; else if (!paren && !bracket && c == ';') ended = 1; } return !paren && !bracket; } // the comment from..to sits on one line with its declaration: no line break before it, only white space after it int doc_same_line(int base, int from, int to) { while (from < to) { if (dc(base, from) == 10) return 0; from++; } return 1; } int doc_line_end(int base, int from, int size) { int c; while (from < size) { c = dc(base, from); from++; if (c == 10) return 1; if (c != 32 && c != 9 && c != 13) return 0; } return 1; } int hover_comment_section(int i, int k) { if (hc_kind[k] == 4) return 1; int base = doff[i], from = hc_start[k] + 2, end = hc_end[k], a, b; if (hc_kind[k] == 3) end = end - 2; while (from < end) { b = from; while (b < end && dc(base, b) != 10) b++; a = from; while (a < b && (dc(base, a) == 32 || dc(base, a) == 9 || dc(base, a) == '*')) a++; while (b > a && (dc(base, b - 1) == 32 || dc(base, b - 1) == 9 || dc(base, b - 1) == 13)) b--; if (b - a >= 4 && dc(base, a) == '-' && dc(base, a + 1) == '-' && dc(base, b - 1) == '-' && dc(base, b - 2) == '-') return 1; while (from < end && dc(base, from) != 10) from++; from++; } return 0; } // attach leading docs and adjacent or trailing docs to this exact declaration name. int hover_docs(int i, int head, int name, int length, int tail) { int k, first = -1, last = -1, pos = head, base = doff[i], section = 0; hover_comments(i); k = hc_n - 1; while (k >= 0 && hc_end[k] > head) k--; // a comment block that holds a section heading is section text, not documentation while (k >= 0 && hc_kind[k] != 1 && doc_gap(base, hc_end[k], pos)) { if (last < 0) last = k; if (hover_comment_section(i, k)) section = 1; first = k; pos = hc_start[k]; k--; } if (first >= 0 && !section) { k = first; while (k <= last) { hover_comment_text(i, k); k++; } } k = 0; while (k < hc_n) { if ((k < first || k > last) && !hover_comment_section(i, k)) { if (hc_kind[k] == 3 && hc_start[k] >= head && hc_end[k] <= name && doc_gap(base, hc_end[k], name)) hover_comment_text(i, k); else if (length > 0 && hc_start[k] >= name + length && (hc_kind[k] == 1 || hc_kind[k] == 3) && doc_trailing(base, name + length, hc_start[k])) hover_comment_text(i, k); // a one-line declaration owns the comment that ends its line else if (length < 0 && hc_start[k] >= tail && (hc_kind[k] == 1 || hc_kind[k] == 3) && doc_same_line(base, tail, hc_start[k]) && doc_line_end(base, hc_end[k], dsize[i])) hover_comment_text(i, k); } k++; } return 0; } // the markdown for a declaration: its signature as a code block, then the // comment block above it, without the slashes, if there is one int hover_fill(int i) { int base = doff[i], o = hdl, c, cs, ls, typed = 0, k, run.start, end; hn = 0; if (hs_name >= 0) { hov_text("```zc\n"); c = hdl; while (c < hs_first) { hov_put(dc(base, c)); c++; } c = hs_name; while (c < hdle) { hov_put(dc(base, c)); c++; } hov_text("\n```"); hover_docs(i, hdl, hs_name, rt.len_w(hname), hdle); if (bs_is(i) && hs_const) hover_blob_value(); return hn; } while (o < hdle && dc(base, o) != '{' && dc(base, o) != ';' && dc(base, o) != '=') o++; while (o > hdl && (dc(base, o - 1) == 32 || dc(base, o - 1) == 9)) o--; hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10); c = hdl; // an untyped fn: the return type the body gives it replaces the fn if ((dc(base, hdl) == 'f' && dc(base, hdl + 1) == 'n' && dc(base, hdl + 2) == 32) || (dc(base, hdl) == '.' && dc(base, hdl + 1) == 'u')) { ensure_annot(i); k = hdl; while (k < hdle && !hid(dc(base, k))) k++; while (k < hdle && hid(dc(base, k))) k++; while (k < hdle && (dc(base, k) == 32 || dc(base, k) == 9)) k++; ann_name_at = -1; ann_type(i, k); if (ann_name_at >= 0) { run.start = ann_name_at; while (run.start > 0 && dc(ann_text, run.start - 1) != 10 && dc(ann_text, run.start - 1) != ';' && dc(ann_text, run.start - 1) != '}') run.start--; while (run.start < ann_name_at && (dc(ann_text, run.start) == 32 || dc(ann_text, run.start) == 9)) run.start++; end = ann_name_at; while (end < ann_n && dc(ann_text, end) != '{' && dc(ann_text, end) != ';') end++; while (end > run.start && (dc(ann_text, end - 1) == 32 || dc(ann_text, end - 1) == 9 || dc(ann_text, end - 1) == 10 || dc(ann_text, end - 1) == 13)) end--; k = run.start; while (k < end) { hov_put(dc(ann_text, k)); k++; } hov_text("\n```"); htn = 0; hover_docs(i, hdl, hdl, 0 - 1, o); return hn; } htn = 0; } while (c < o) { hov_put(dc(base, c)); c++; } hov_put(10); hov_put('`'); hov_put('`'); hov_put('`'); hover_docs(i, hdl, hdl, 0 - 1, o); return hn; } // the hover reply: the markdown in hov, or null when there is nothing to show. int gen_hover() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); if (!hn) { ws(",\"result\":null}"); return 0; } ws(",\"result\":{\"contents\":{\"kind\":\"markdown\",\"value\":"); wjs(hov, hn); ws("}}}"); return 0; } // the doc of a name from the image's own symbol blob, or 0. the checks reset the // symbol table (zc_init, chk_mark), so the docs are read from the blob itself, // which stays in the image bstr blobdoc(char* nm) { word p = rt.GORG[5]; int k; while (*p) { if (p[0] == run.K_DOC && p[3] == 'd' && p[4] == 'o' && p[5] == 'c' && p[6] == ' ') { k = 0; while (nm[k] && p[7 + k] == nm[k]) k++; if (!nm[k] && !p[7 + k]) return p[1]; } p = p + 3 + rt.len_w(p + 3) + 1; } return 0; } // the signature of a name from the image's own symbol blob, or 0 bstr blobsig(char* nm) { word p = rt.GORG[5]; int k; while (*p) { if (p[0] == run.K_DOC && p[3] == 's' && p[4] == 'i' && p[5] == 'g' && p[6] == ' ') { k = 0; while (nm[k] && p[7 + k] == nm[k]) k++; if (!nm[k] && !p[7 + k]) return p[1]; } p = p + 3 + rt.len_w(p + 3) + 1; } return 0; } // the start of the line before the one starting at ls, or -1 at the top int hl_prev(int base, int ls) { int q; if (ls <= 0) return 0 - 1; q = ls - 1; while (q > 0 && dc(base, q - 1) != 10) q--; return q; } // the last declaration of hname above the hovered line, inside the top-level // item that holds it: a parameter of its header, or a local (a line of the form // type name = ..., so a plain use of the name does not count). the text, marked // as a parameter or a local, goes into hov as a code block. returns 1 when found. int hover_local(int i) { int base = doff[i], size = dsize[i], hs = 0, ln = 0, o = 0, ts = 0 - 1, q, c, hl, le, e, b, k, a, found = 0, kind = 0, sb = 0, se = 0, da = 0 - 1; while (ln < hline && o < size) { if (dc(base, o) == 10) ln++; o++; } hs = o; // the top-level line: the hovered line if it starts in column 0, else the // nearest such line above it (a line that starts in column 0, not a comment) q = hs; c = dc(base, q); if (c != 10 && c != 32 && c != 9 && !(c == '/' && dc(base, q + 1) == '/')) ts = q; while (ts < 0) { q = hl_prev(base, q); if (q < 0) break; c = dc(base, q); if (c == 10 || c == 32 || c == 9) continue; if (c == '/' && dc(base, q + 1) == '/') continue; ts = q; } if (ts < 0) return 0; // the header line: a parameter named hname is the last word of its segment hl = ts; le = hl; while (le < size && dc(base, le) != 10 && dc(base, le) != '{' && dc(base, le) != ';') le++; o = hl; // the segments of the header, one at a time o = hl; while (o < le && dc(base, o) != '(') o++; o++; while (o < le) { while (o < le && (dc(base, o) == 32 || dc(base, o) == 9)) o++; sb = o; while (o < le && dc(base, o) != ',' && dc(base, o) != ')') o++; se = o; while (se > sb && (dc(base, se - 1) == 32 || dc(base, se - 1) == 9)) se--; e = se; b = e; while (b > sb && hid(dc(base, b - 1))) b--; if (b < e) { // a typed parameter has its type before the name, an untyped one is the name alone k = 0; while (hname[k] && b + k < e && dc(base, b + k) == hname[k]) k++; if (!hname[k] && b + k == e) { found = 1; kind = 1; break; } } if (o < le && dc(base, o) == ')') break; o++; } if (found) { hover_put_sig(base, sb, se, kind, 0 - 1); return 1; } // the body: the last declaration of hname from the item's next line through the // hovered line (a declaration on the hovered line itself counts) o = ts; while (o <= hs) { le = o; while (le < hs && dc(base, le) != 10) le++; if (le == hs) while (le < size && dc(base, le) != 10) le++; // le is the end of this line; skip the blank and comment lines q = o; while (q < le && (dc(base, q) == 32 || dc(base, q) == 9)) q++; if (q < le && !(dc(base, q) == '/' && dc(base, q + 1) == '/') && q > o && !hl_kw(base, q)) { e = hdeclend(base, q, le); if (e < le) { b = e; while (b > q && (dc(base, b - 1) == 32 || dc(base, b - 1) == 9)) b--; a = b; while (a > q && hid(dc(base, a - 1))) a--; if (a < b && a > q) { k = 0; while (hname[k] && a + k < b && dc(base, a + k) == hname[k]) k++; if (!hname[k] && a + k == b && (dc(base, a - 1) == 32 || dc(base, a - 1) == 9)) { found = 1; kind = 2; da = a; sb = q; se = e; while (se > sb && (dc(base, se - 1) == 32 || dc(base, se - 1) == 9)) se--; } } } } if (le >= hs) break; o = le + 1; } if (found) { k = 0 - 1; if (kind == 2) k = lt_find(da); hover_put_sig(base, sb, se, kind, k); return 1; } return 0; } // does the word from q to e equal w word hl_is(int base, int q, int e, char* w) { int k = 0; while (w[k] && q + k < e && dc(base, q + k) == w[k]) k++; return !w[k] && q + k == e; } // does a statement start at q with a keyword that cannot begin a declaration, // so that "return i;" is not taken for a declaration of i word hl_kw(int base, int q) { int e = q; while (hid(dc(base, e))) e++; return hl_is(base, q, e, "return") || hl_is(base, q, e, "if") || hl_is(base, q, e, "while") || hl_is(base, q, e, "else") || hl_is(base, q, e, "break") || hl_is(base, q, e, "continue"); } // where a declaration's name ends on the line from q: at = ; , { ( or a [ that // follows a name (let b[24]); the [ of a type (fixed int[?]) is passed over int hdeclend(int base, int q, int le) { int e = q, c; while (e < le) { c = dc(base, e); if (c == '=' || c == ';' || c == ',' || c == '{' || c == '(' || c == ')') return e; if (c == '[' && e > q && hid(dc(base, e - 1))) return e; e++; } return le; } const int AROUNDS = 6; word abase, aerr; int nbase, aerrcap; word afx, aft, amask; int anf, afcap, atcap; word awp; int awn; word ann_half; //: the inferred types of the untyped declarations. annotate_doc runs the annotate //: rounds on a document (in doc slot 0, which is put back afterwards), and the //: text it ends with is kept in ann_text. ann_type then finds, for a name the //: document declares untyped, the type annotate gave that name: the text between //: the token before it in the annotated text and the name, after the last ( , { ; or =. int ann_i = -1, ann_gen = -1, ai_end; // runs the annotate rounds on document i. the rounds run in doc slot 0, and the // slots they use are put back afterwards. the text they end with is kept in // ann_text, with its length in ann_n. int annotate_doc(int i) { int round = 0, n, k, sv_size, sv_live, sv_ulen, sv_gen, sv_slot1 = 0, sv_live1, sv_ulen1, sv_gen1; word sv_base, sv_base1; sv_base = doff[0]; sv_size = dsize[0]; sv_live = dlive[0]; sv_ulen = dulen[0]; sv_gen = dgen[0]; sv_base1 = doff[1]; sv_slot1 = dsize[1]; sv_live1 = dlive[1]; sv_ulen1 = dulen[1]; sv_gen1 = dgen[1]; dlive[0] = 1; dulen[0] = 0; doff[0] = doff[i]; dsize[0] = dsize[i]; dgen[0] = sv_gen + 1; run.ifstrict = 1; check_doc(0); nbase = err_lines(abase); while (round < AROUNDS) { run.ifon = 1; check_doc(0); run.ifon = 0; save_facts(); if (!anf) break; k = 0; while (k < anf) { amask[k] = 0; k++; } fresh_half(); try_range(0, anf); n = apply_masked(ann_half); if (!n) break; doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1; round++; } run.ifstrict = 0; run.ifon = 0; ann_text = doff[0]; ann_n = dsize[0]; doff[0] = sv_base; dsize[0] = sv_size; dlive[0] = sv_live; dulen[0] = sv_ulen; dgen[0] = sv_gen; doff[1] = sv_base1; dsize[1] = sv_slot1; dlive[1] = sv_live1; dulen[1] = sv_ulen1; dgen[1] = sv_gen1; chk_i = -1; return 0; } // annotate for the current state of document i, once per state of it int ensure_annot(int i) { if (ann_i == i && ann_gen == dgen[i]) return 0; annotate_doc(i); ann_i = i; ann_gen = dgen[i]; return 0; } // the next identifier of base at or after pos: its start, with its end in ai_end. // comments, strings and numbers are passed over. -1 when there is none. int ai_next(word base, int size, int pos) { int c, st, q; while (pos < size) { c = dc(base, pos); if (c == '/' && pos + 1 < size && dc(base, pos + 1) == '/') { while (pos < size && dc(base, pos) != 10) pos++; } else if (c == '/' && pos + 1 < size && dc(base, pos + 1) == '*') { pos = pos + 2; while (pos < size && !(dc(base, pos) == '*' && dc(base, pos + 1) == '/')) pos++; if (pos < size) pos = pos + 2; } else if (c == '"' || c == 39) { q = c; pos++; while (pos < size && dc(base, pos) != q && dc(base, pos) != 10) { if (dc(base, pos) == 92) pos++; pos++; } pos++; } else if (c >= '0' && c <= '9') { while (pos < size && hid(dc(base, pos))) pos++; } else if (hid(c)) { st = pos; while (pos < size && hid(dc(base, pos))) pos++; ai_end = pos; return st; } else pos++; } return 0 - 1; } // do the words a[as..ae) of base a and b[bs..be) of base b read the same int ai_same(word a, int as, int ae, word b, int bs, int be) { int k = 0; if (ae - as != be - bs) return 0; while (as + k < ae) { if (dc(a, as + k) != dc(b, bs + k)) return 0; k++; } return 1; } // is the word at s..e fn or let, which annotate replaces with a type int ai_kw(word base, int s, int e) { int k = 0; if (e - s == 2 && dc(base, s) == 'f' && dc(base, s + 1) == 'n') return 1; if (e - s == 3 && dc(base, s) == 'l' && dc(base, s + 1) == 'e' && dc(base, s + 2) == 't') return 1; return k; } // is the name at offset o of document i declared untyped: a parameter after ( or // , and a local after let or a function after fn. only those can gain a type int ann_untyped(int i, int o) { word base = doff[i]; int p = o - 1; while (p >= 0) { while (p >= 0 && (dc(base, p) == 32 || dc(base, p) == 9 || dc(base, p) == 10 || dc(base, p) == 13)) p--; if (p >= 1 && dc(base, p) == '/' && dc(base, p - 1) == '*') { p = p - 2; while (p >= 1 && !(dc(base, p - 1) == '/' && dc(base, p) == '*')) p--; p = p - 2; } else break; } if (p < 0) return 0; if (dc(base, p) == '(' || dc(base, p) == ',') return 1; if (p >= 2 && dc(base, p) == 't' && dc(base, p - 1) == 'e' && dc(base, p - 2) == 'l') return 1; if (p >= 1 && dc(base, p) == 'n' && dc(base, p - 1) == 'f') return 1; return 0; } // the type annotate gave the name declared at offset o of document i, in htb // (htn bytes), or none. the names of the two texts are walked in step: the // annotated text has the same names in the same order, plus the types it put in. int ann_type(int i, int o) { word ob = doff[i], ab = ann_text; int osz = dsize[i], asz = ann_n, op = 0, ap = 0, prev = 0, os, oe, as, ae, cut, k, n = 0, c; htn = 0; while (1) { os = ai_next(ob, osz, op); if (os < 0) return 0; oe = ai_end; op = oe; if (ai_kw(ob, os, oe)) continue; while (1) { as = ai_next(ab, asz, ap); if (as < 0) return 0; ae = ai_end; ap = ae; if (ai_same(ob, os, oe, ab, as, ae)) break; } if (os == o) { ann_name_at = as; cut = prev; k = prev; while (k < as) { c = dc(ab, k); if (c == '(' || c == ',' || c == '{' || c == ';' || c == '=' || c == '}' || c == 10) cut = k + 1; k++; } while (cut < as && (dc(ab, cut) == 32 || dc(ab, cut) == 9 || dc(ab, cut) == 10)) cut++; k = cut; while (k < as) { if (dc(ab, k) == '/' && dc(ab, k + 1) == '*') { k = k + 2; while (k < as && !(dc(ab, k) == '*' && dc(ab, k + 1) == '/')) k++; k = k + 2; continue; } if (n == htbc) { htb = lsp_grow(htb, n, htbc * 2); htbc = htbc * 2; } htb[n] = dc(ab, k); n++; k++; } while (n > 0 && (htb[n - 1] == 32 || htb[n - 1] == 9 || htb[n - 1] == 10)) n--; htn = n; if (htn == 2 && htb[0] == 'f' && htb[1] == 'n') htn = 0; // annotate left a fn alone if (htn == 3 && htb[0] == 'l' && htb[1] == 'e' && htb[2] == 't') htn = 0; return htn; } prev = ae; } return 0; } // the markdown of a local or parameter: its declaration text, then what it is int hover_put_sig(int base, int sb, int se, int kind, int lk) { int k, type_end = se - rt.len_w(hname); while (type_end > sb && (dc(base, type_end - 1) == 32 || dc(base, type_end - 1) == 9)) type_end--; hn = 0; hov_text("```zc\n("); if (lk >= 0) hov_type(run.lt_type[lk]); else if (htn) { k = 0; while (k < htn) { hov_put(htb[k]); k++; } } else if (type_end > sb && !hl_is(base, sb, type_end, "let")) { k = sb; while (k < type_end) { hov_put(dc(base, k)); k++; } } else hov_text("word"); hov_text(") let "); hov_text(hname); if (kind == 1) hov_text(" // parameter;"); else hov_text("; // local"); hov_text("\n```"); return hn; } // a genome name: its signature as a code block, then its doc if the image has one int hover_blob(int i) { bstr ss, ds; int k = 0, n = 0; ss = blobsig(hname); ds = blobdoc(hname); hn = 0; if (ss) { hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10); while (k < rt.len_s(ss)) { hov_put(rt.at_s(ss, k)); k++; } hov_put(10); hov_put('`'); hov_put('`'); hov_put('`'); } if (ds) { if (ss) { hov_put(10); hov_put(10); } k = 0; while (k < rt.len_s(ds)) { hov_put(rt.at_s(ds, k)); k++; } } return hn; } // does the hovered word equal w word hname_is(char* w) { int k = 0; while (w[k] && hname[k] == w[k]) k++; return !w[k] && !hname[k]; } // append a char* string or a compiler type to the markdown buffer. int hov_text(char* s) { int k = 0; while (s[k]) { hov_put(s[k]); k++; } return 0; } int hov_type(word t) { int sm = rt.outmode, sb = rt.outbuf, sn = rt.outn, sc = rt.outcap, need; rt.outmode = 3; rt.outn = 0; run.tprint(t); need = hn + rt.outn; if (need > hovcap) { while (need > hovcap) hovcap = hovcap * 2; hov = lsp_grow(hov, hn, hovcap); } rt.outmode = 2; rt.outbuf = hov; rt.outn = hn; rt.outcap = hovcap; run.tprint(t); hn = rt.outn; rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; return 0; } // explain composite types without relying on their source spelling. int hover_type_info(word t) { int kind, k; if (!t) return 0; kind = t[0]; if (kind == run.TY_PTR) { hov_text("\n\na pointer to `"); hov_type(t[4]); hov_text("`. the pointer occupies one word; the pointed-to storage is separate."); } else if (kind == run.TY_ARR) { hov_text("\n\nan array stored in place, with a fixed length of "); k = t[3]; if (k < 0) hov_text("determined by its initializer; elements of type `"); else { hov_number(k); hov_text(" elements of type `"); } hov_type(t[4]); hov_text("`. storage: "); hov_number(run.tsz(t)); hov_text(" words."); } else if (kind == run.TY_DYN) { if (t[3]) hov_text("\n\nan array handle with a fixed runtime length. "); else hov_text("\n\na growable array handle. "); hov_text("element type: `"); hov_type(t[4]); hov_text("`. `len` gives the current length and `cap` gives capacity."); if (t[3]) hov_text(" `push`, `pop`, and `resize` cannot change its length."); } else if (kind == run.TY_STRUCT) { hov_text("\n\na struct stored in place. storage: "); hov_number(run.tsz(t)); hov_text(" words."); if (t[3] > 0) { hov_text("\n\nfields:\n\n```zc\n"); k = 0; while (k < t[3]) { hov_type(t[4][k * 3 + 1]); hov_put(' '); hov_text(t[4][k * 3]); hov_text(";\n"); k++; } hov_text("```"); } } else if (kind == run.TY_BSTR) hov_text("\n\na packed byte string. its header stores the byte length; data follows in little-endian packed words."); else if (kind == run.TY_FN) hov_text("\n\na callable value. the type lists its result and parameter types."); return 0; } // decimal numbers in markdown use the ordinary printer through the capture buffer. int hov_number(int n) { int sm = rt.outmode, sb = rt.outbuf, sn = rt.outn, sc = rt.outcap; while (hn + 24 > hovcap) { hov = lsp_grow(hov, hn, hovcap * 2); hovcap = hovcap * 2; } rt.outmode = 2; rt.outbuf = hov; rt.outn = hn; rt.outcap = hovcap; rt.put_n(n); hn = rt.outn; rt.outmode = sm; rt.outbuf = sb; rt.outn = sn; rt.outcap = sc; return 0; } // a parsed type includes its brackets and stars, so those characters hover too. int hover_type_span(int i) { int k = 0, best = 0 - 1, width = 0, end; word t; while (k < run.ht_n) { end = run.ht_end[k]; while (end > run.ht_start[k] && (dc(doff[i], end - 1) == 32 || dc(doff[i], end - 1) == 9 || dc(doff[i], end - 1) == 10 || dc(doff[i], end - 1) == 13)) end--; if (hcursor >= run.ht_start[k] && hcursor < end && end - run.ht_start[k] > width) { best = k; width = end - run.ht_start[k]; } k++; } if (best < 0) return 0; t = run.ht_type[best]; hov_text("```zc\n"); hov_type(t); hov_text("\n```"); hover_type_info(t); k = kwtext(); if (k && (hname_is("word") || hname_is("int") || hname_is("char") || hname_is("bool") || hname_is("void") || hname_is("float") || hname_is("f32") || hname_is("f64") || hname_is("byte") || hname_is("u8") || hname_is("i8") || hname_is("u16") || hname_is("i16") || hname_is("u32") || hname_is("i32"))) { while (*k && *k != '~') k++; if (*k) { hov_text("\n\n"); hov_text(k + 1); } } return 1; } // the text of a keyword or a built-in word, from the language guide, or 0. this // one table holds every keyword the compiler registers and the built-ins that // have their own form. the form comes first, then a ~, then what it does (the ~ // separates the two). a name the table does not hold gets the image's docs. char* kwtext() { if (hname_is("bstr")) return "bstr~packed byte string with a length header; indexing yields a byte and slicing returns a packed string"; if (hname_is("word")) return "word~one 64-bit vm word; the default type for untyped integer values and word-sized storage"; if (hname_is("int")) return "int~one signed, word-sized integer; arrays of int use one word per element"; if (hname_is("char")) return "char~one word holding a character code; arrays of char use one word per element"; if (hname_is("bool")) return "bool~one word for a boolean value; comparison and logical operators produce 0 or 1"; if (hname_is("void")) return "void~marks a function with no meaningful result; the vm still uses a one-word return convention"; if (hname_is("float")) return "float~one-word ieee binary64 value; arrays of float use one word per element"; if (hname_is("f64")) return "f64~alias for float, a one-word ieee binary64 value"; if (hname_is("f32")) return "f32~binary32 value rounded when stored; arrays and pointers pack two elements per word"; if (hname_is("u8")) return "u8~unsigned 8-bit integer, range 0..255; values narrow on store and arrays or pointers pack eight elements per word"; if (hname_is("i8")) return "i8~signed 8-bit integer, range -128..127; values narrow on store and arrays or pointers pack eight elements per word"; if (hname_is("u16")) return "u16~unsigned 16-bit integer, range 0..65535; values narrow on store and arrays or pointers pack four elements per word"; if (hname_is("i16")) return "i16~signed 16-bit integer, range -32768..32767; values narrow on store and arrays or pointers pack four elements per word"; if (hname_is("u32")) return "u32~unsigned 32-bit integer, range 0..4294967295; values narrow on store and arrays or pointers pack two elements per word"; if (hname_is("i32")) return "i32~signed 32-bit integer, range -2147483648..2147483647; values narrow on store and arrays or pointers pack two elements per word"; if (hname_is("byte")) return "byte~alias for u8, an unsigned 8-bit integer packed in arrays and pointers"; if (hname_is("fixed")) return "fixed~marks an array whose length cannot change; fixed arrays cannot grow or shrink"; if (hname_is("new")) return "new(T) or new(T, n)~returns a fresh heap object. a local owns it and the compiler releases it when its block ends; pass it to a .d parameter or call free(p) to transfer it explicitly."; if (hname_is("free")) return "free(p)~releases a heap object. a checked call consumes an owned object automatically; the block is kept until collection can safely reuse it, and a repeated free does nothing."; if (hname_is("len_s")) return "len_s(s)~the number of bytes in a packed byte string. unlike len_w, it reads the stored byte length and does not scan for a zero terminator."; if (hname_is("sizeof")) return "sizeof(e) or sizeof(T)~the size in words, found by compiling the expression dry, so it is not run. a packed array counts (n * bytes + 7) / 8 words."; if (hname_is("typeof")) return "typeof(e) or typeof(T)~the type record of an expression or a type, found by compiling it dry. tkind, tsize, tname and the other t functions read it at runtime."; if (hname_is("syscall")) return "syscall(op, a, b, c)~a call to the host through the gate word. the ops are SYS_OPEN, SYS_CLOSE, SYS_READ, SYS_WRITE, SYS_GETENV, SYS_TIME and the rest of the named ones, and SYS_RAW (40) runs any linux syscall on the host. o3 code does not go through the gate, so call syscall from ordinary code."; if (hname_is("len")) return "len(a)~the number of items in an array, growable or fixed."; if (hname_is("cap")) return "cap(a)~the number of items a growable array can hold before it has to grow."; if (hname_is("push")) return "push(a, x)~adds x to the end of a growable array, growing it when needed. a growable array owns its items."; if (hname_is("pop")) return "pop(a)~removes the last item of a growable array and gives it back."; if (hname_is("resize")) return "resize(a, n)~sets the length of a growable array to n, growing or shrinking it."; if (hname_is("o3")) return "o3~marks code for the optimizing tier. it goes on a function, a block or a single variable, and its variables live in registers."; if (hname_is("fn")) return "fn name(a, b) { }~declares a function whose return is a word. a typed function is written like c, int f(int x) { }."; if (hname_is("static")) return "static fn name(args) { }~declares a function inside a struct without a hidden this parameter. call it as Struct.name(args)."; if (hname_is("let")) return "let name = e~declares a local whose type comes from its value. a number stays a plain word, and a pointer or a struct keeps its type."; if (hname_is("const")) return "const [type] NAME = e / const fixed T[N] name~declares a compile-time constant or read-only storage. fixed and const may appear in either order; read-only storage cannot be modified or converted to a mutable reference."; if (hname_is("else")) return "else~the other branch of an if. after an enhance's body, else names the fallback function that runs where the enhancement can't."; if (hname_is("as")) return "e as T~changes the type of an expression without changing its bits."; if (hname_is("unchecked")) return "unchecked fn f() { } / unchecked { } / unchecked;~turns the lifetime analyzer off for a function, a block, or the rest of the unit. the code is not checked, so the analyzer's rules don't apply to it."; if (hname_is("immediate")) return "immediate fn name() { }~a parse word. it runs during compilation whenever its name shows up as a statement, inside an expression or at the top level."; if (hname_is("case")) return "case v:~a label in a switch. each case ends where the next one begins, so cases don't fall through."; if (hname_is("default")) return "default:~the arm of a switch that runs when no case matches."; if (hname_is("do")) return "do { } while (cond);~a loop that runs its body once before the test."; if (hname_is("while")) return "while (cond) { }~a loop that tests the condition before each pass."; if (hname_is("for")) return "for (init; cond; step) { }~a loop with its start, test and step written on one line."; if (hname_is("if")) return "if (cond) { } else { }~runs one branch or the other, by the condition."; if (hname_is("switch")) return "switch (e) { case 1, 2: ... default: ... }~a branch on constant labels. the cases don't fall through, and break leaves early."; if (hname_is("break")) return "break;~leaves the innermost loop or switch."; if (hname_is("continue")) return "continue;~goes on to the next pass of the innermost loop."; if (hname_is("return")) return "return e; or return;~leaves the function with a value, or leaves a void function. returning an owned local transfers it to the caller automatically."; if (hname_is("enum")) return "enum name { A, B = 5 };~global constants. under typecheck on they are a type of their own, and a member can carry fields to make a constructor."; if (hname_is("struct")) return "struct name { fields };~a record type. struct name { } makes it generic, one copy for each set of type arguments."; if (hname_is("type")) return "type name = T;~an alias for a type, such as type row = int[3]; for a pointer to an array."; if (hname_is("include")) return "include \"path\"; / include std;~compiles a file or the embedded standard library at that point, its signatures first."; if (hname_is("memdebug")) return "memdebug on; / memdebug off; / memdebug scrub;~header checks, allocation sites and leak reports for the rest of the file. scrub also fills a freed payload with 0xDEAD."; if (hname_is("typecheck")) return "typecheck on; / typecheck off;~turns on type checks for the code that follows, enums included."; if (hname_is("hints")) return "hints on; / hints small; / hints off;~controls subsequent hint diagnostics: informational ranges by default, compact markers with small, or no hints with off."; if (hname_is("docs")) return "docs on; / docs off;~keeps the doc comments above declarations in the image, so the language server can show them on hover."; if (hname_is("micro")) return "enhance name(args) micro \"recipe\";~the body of an enhancement, written as micro-ops on registers r0 to r15. the inputs arrive in r0 and up, and the outputs leave from r0."; if (hname_is("vm")) return "enhance name(args) vm { ... };~the body of an enhancement, written as stack machine asm. it becomes one opcode when the core lifts it."; if (hname_is("enhance")) return "enhance name(args) micro \"...\" or vm { ... };~installs a custom opcode at compile time, with up to 8 inputs and 8 outputs. an enhancement with one output can be called like a function."; if (hname_is("asm")) return "asm { ... }~a block of stack machine asm, and asm operands can be used inside expressions."; if (hname_is("quote")) return "quote { tokens }~a token list as a fresh object. a parse word can read it and build code from it."; if (hname_is("extern")) return "extern type name(params);~declares a c function on the host and calls it through the native call."; if (hname_is("cfunc")) return "cfunc(f)~a native function pointer that runs the zc function f when c calls it."; return 0; } // the markdown of a keyword: its form as a code block, then what it does int kw_put(char* s) { int k = 0; hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10); while (s[k] && s[k] != '~') { hov_put(s[k]); k++; } hov_put(10); hov_put('`'); hov_put('`'); hov_put('`'); if (s[k]) { hov_put(10); hov_put(10); k++; while (s[k]) { hov_put(s[k]); k++; } } return hn; } // does the line from q declare hname as a method: the name is followed by (, and // what comes before it is a type, fn or static, and nothing that makes it a call int hm_decl(int base, int q, int le) { int po = q, ns, e, k = 0, c; while (po < le && dc(base, po) != '(') po++; if (po >= le) return 0; e = po; ns = e; while (ns > q && hid(dc(base, ns - 1))) ns--; if (ns == e || ns == q) return 0; while (hname[k] && ns + k < e && dc(base, ns + k) == hname[k]) k++; if (hname[k] || ns + k != e) return 0; if (dc(base, ns - 1) != 32 && dc(base, ns - 1) != 9) return 0; k = q; while (k < ns) { c = dc(base, k); if (c == '=' || c == '.' || c == ';' || c == '(' || c == ',' || c == '{' || c == '}') return 0; k++; } if (hl_kw(base, q)) return 0; return 1; } // the markdown of a method or a static function of a struct: its line as the // signature, then the comment lines right above it. the methods are indented in // the struct body, so hover_scan (which reads column 0) does not find them. // returns 1 when the hovered word is such a name. int hover_method(int i) { int base = doff[i], size = dsize[i], ls = 0, le, q, p, e, k, cs, pv, found = 0, sg, se, n; while (ls < size && !found) { le = ls; while (le < size && dc(base, le) != 10) le++; q = ls; while (q < le && (dc(base, q) == 32 || dc(base, q) == 9)) q++; if (q > ls && hm_decl(base, q, le)) { found = 1; sg = q; se = q; } if (!found) ls = le + 1; } if (!found) return 0; // the signature: up to the body, a ; or the end of the line se = sg; while (se < size && dc(base, se) != 10 && dc(base, se) != '{' && dc(base, se) != ';') se++; while (se > sg && (dc(base, se - 1) == 32 || dc(base, se - 1) == 9)) se--; hn = 0; hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10); k = sg; while (k < se) { hov_put(dc(base, k)); k++; } hov_put(10); hov_put('`'); hov_put('`'); hov_put('`'); hover_docs(i, sg, sg, 0, 0); return 1; } // the markdown of a local at its declaration: the name's declaration line, with // the type an untyped let was given. o is the byte offset of the declared name. int hover_named(int i, int o, int kind) { int base = doff[i], end = o, head = o, k, entry, untyped, r, st = 0; word t = 0; while (end < dsize[i] && hid(dc(base, end))) end++; if (end == o) return 0; untyped = ann_untyped(i, o); htn = 0; if (untyped) { ensure_annot(i); ann_type(i, o); ensure_checked(i); } entry = lt_find(o); if (entry >= 0) t = run.lt_type[entry]; if (t && (t[0] == run.TY_ARR || t[0] == run.TY_STRUCT)) htn = 0; k = 0; while (k < run.ht_n) { if (run.ht_end[k] == o && run.ht_start[k] < head) head = run.ht_start[k]; k++; } if (untyped && kind != 1) { k = o - 1; while (k >= 0 && (dc(base, k) == 32 || dc(base, k) == 9)) k--; if (k >= 2 && dc(base, k) == 't' && dc(base, k - 1) == 'e' && dc(base, k - 2) == 'l') head = k - 2; } hn = 0; hov_text("```zc\n"); hov_put('('); if (htn) { k = 0; while (k < htn) { hov_put(htb[k]); k++; } } else if (t) hov_type(t); else hov_text("word"); hov_text(") let"); r = rs_find(hoff); if (kind == 2 && r >= 0) st = (run.rsrec[r * run.RS_W + 11] & 15) - 1; if (st == run.S_M || st == run.S_BORROW || st == run.S_CHILD) hov_text(".m"); else if (st == run.S_U) hov_text(".u"); else if (st == run.S_D) hov_text(".d"); else if (st == run.S_DEAD) hov_text(".expired"); hov_put(' '); k = o; while (k < end) { hov_put(dc(base, k)); k++; } if (kind == 1) hov_text(" // parameter;"); else if (kind == 3) hov_text(" // global;"); else if (kind == 4) hov_text(" // field;"); else hov_text("; // local"); hov_text("\n```"); hover_type_info(t); hover_docs(i, head, o, end - o, 0); if (hoff != o) hover_docs(i, hoff, hoff, rt.len_w(hname), 0); htn = 0; return 1; } int hover_at_decl(int i, int o) { return hover_named(i, o, 2); } // storage facts are copied at the reference's position, before later statements // can change them. unknown facts stay unknown in the preview. int hover_storage(int r) { word p, t = 0; int entry, kind; if (r < 0 || !hn) return 0; p = run.rsrec + r * run.RS_W; kind = p[6]; if (!kind) { if (p[1] == run.RS_LOCAL || p[1] == run.RS_PARAM || p[1] == run.RS_FIELD) { entry = lt_find(p[2]); if (entry >= 0) t = run.lt_type[entry]; } else if (p[1] == run.RS_SYM && p[3] >= 0) t = run.stype[p[3]]; if (!t || (t[0] != run.TY_PTR && t[0] != run.TY_ARR && t[0] != run.TY_DYN)) return 0; hov_text("\n\nsize: "); if (t[0] == run.TY_ARR) { hov_number(t[3]); hov_text(" elements"); } else hov_text("unknown"); hov_text(". lifetime: unknown at this position."); return 0; } hov_text("\n\nsize: "); if (p[4] >= 0) { hov_number(p[4]); hov_text(" elements"); } else hov_text("unknown"); hov_text(". lifetime: "); if (p[5] == 2) { hov_text("ended on line "); hov_number(p[8] & 0xFFFFFF); } else if (p[5] == 1) { if (kind == 1) hov_text("stack storage; ends when its declaring scope exits"); else hov_text("heap storage; live here, ends when freed or its owner releases it"); } else hov_text("unknown after a call or control-flow change"); if (p[7]) { hov_text(". storage begins on line "); hov_number(p[7] & 0xFFFFFF); } hov_text("."); return 0; } // the line (from 1) of a byte offset in document i int off_line(int i, int off) { int base = doff[i], o = 0, ln = 1; while (o < off && o < dsize[i]) { if (dc(base, o) == 10) ln++; o++; } return ln; } // the name written at a byte offset, in backquotes int hover_ident(int i, int off) { int base = doff[i]; hov_put('`'); while (off < dsize[i] && hid(dc(base, off))) { hov_put(dc(base, off)); off++; } hov_put('`'); return 0; } // what the analyzer knows of a local's ownership at this reference, and where its object // goes: up through a return, block value or assignment to an enclosing block, down to a // d parameter or a container, to the collector through a global, or freed when its block ends. int hover_ownership(int i, int r, int decl) { word p; int st, k, n = 0, kind; if (r < 0 || !hn) return 0; p = run.rsrec + r * run.RS_W; st = p[11] & 15; if (st > 1 && st - 1 == run.S_CHILD && p[12]) { hov_text("\n\nborrows from "); hover_ident(i, p[12] - 1); hov_text(", declared on line "); hov_number(off_line(i, p[12] - 1)); hov_text("; valid while that owner holds the object."); } k = 0; while (k < run.evn) { if (run.evrec[k * run.EV_W] == decl) { kind = run.evrec[k * run.EV_W + 1]; if (!n) hov_text("\n\n"); else hov_text(" "); n++; if (kind == run.EV_RETURN) hov_text("passed up: returned"); else if (kind == run.EV_VALUE) hov_text("passed up: the value of its block"); else if (kind == run.EV_UP) hov_text("passed up: assigned to a variable of the enclosing block"); else if (kind == run.EV_DOWN) hov_text("passed down: given to a d parameter"); else if (kind == run.EV_STORE) hov_text("passed down: stored in a container"); else if (kind == run.EV_GLOBAL) hov_text("handed to the collector through a global"); else if (kind == run.EV_MOVE) hov_text("moved: replaces a released owner in"); else if (kind == run.EV_CAPTURE) hov_text("captured: shares storage with a closure"); else hov_text("freed where its block ends"); if (run.evrec[k * run.EV_W + 3]) { hov_text(" "); hover_ident(i, run.evrec[k * run.EV_W + 3] - 1); } hov_text(" on line "); hov_number(run.evrec[k * run.EV_W + 2]); hov_text("."); } k++; } return 0; } // values are saved per reference, so later stores cannot rewrite earlier tips. int hover_value(int r) { word p; char* text; int kind, n, k = 0, c; if (r < 0 || !hn) return 0; p = run.rsrec + r * run.RS_W; kind = p[9]; if (!kind) return 0; hov_text("\n\nvalue at this position:\n```zc\n"); if (kind == 1) hov_number(p[10]); else if (kind == 2) { char[64] buf; n = rt.syscall(41, p[10], buf, 64); if (n >= 0) hov_text(buf); else hov_text("unknown"); } else { text = p[10]; if (kind == 4) hov_put('b'); hov_put('"'); while (text && text[k]) { c = text[k]; k++; if (c == 10) hov_text("\\n"); else if (c == 13) hov_text("\\r"); else if (c == 9) hov_text("\\t"); else { if (c == 34 || c == 92) hov_put(92); hov_put(c); } } hov_put('"'); } hov_text("\n```"); return 0; } // the hover of a name that is not a local. kind is 1 for a built-in (the guide's // text, then the image's docs), 2 for a symbol the document declares (its // declaration, a method, then the image's docs), and 0 when no record says (the // guide, then the text of the document, as before) int hover_global(int i, int kind) { int kt, r, s; r = rs_find(hoff); if (r >= 0 && run.rsrec[r * run.RS_W + 1] == run.RS_SYM && run.rsrec[r * run.RS_W + 3] >= 0 && kind != 2) { s = run.rsrec[r * run.RS_W + 3]; if (run.skind[s] != run.K_KW && bs_find(run.sname[s])) { hover_fill(bs_load(bs_k)); return hn; } } if (kind != 2) { kt = kwtext(); if (kt) return kw_put(kt); } if (kind == 1) { if (bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; } hover_blob(i); return hn; } if (hover_scan(i)) { hover_fill(i); return hn; } if (hover_method(i)) return hn; if (kind == 0 && hover_local(i)) return hn; if (kind != 2 && bs_find(hname)) { hover_fill(bs_load(bs_k)); return hn; } hover_blob(i); return hn; } // the hover at the cursor. the compiler's record of the name decides what it // is: a local is its declaration, a parameter the header that names it, and a // symbol is looked up as that kind of symbol. a name with no record is found by // its text, as before. int do_hover(int i) { int r, k = 0, v = 0, has_word, s, q, o; hn = 0; ensure_checked(i); has_word = hover_word(i); if (has_word && hover_module(i)) { send(gen_hover); return 0; } if (hover_instruction(i)) { send(gen_hover); return 0; } if (hover_type_span(i)) { send(gen_hover); return 0; } if (has_word) { r = rs_find(hoff); if (r >= 0) { k = run.rsrec[r * run.RS_W + 1]; v = run.rsrec[r * run.RS_W + 2]; } if (k == run.RS_LOCAL && v >= 0) hover_at_decl(i, v); else if (k == run.RS_PARAM && v >= 0) hover_named(i, v, 1); else if (k == run.RS_FIELD && v >= 0) hover_named(i, v, 4); else if (k == run.RS_LOCAL) hover_local(i); else if (k == run.RS_SYM) { if ((v & 15) == run.K_GVAR || (v & 15) == run.K_GARR) { s = run.rsrec[r * run.RS_W + 3]; q = 0; o = 0 - 1; while (q < run.rsn) { if (run.rsrec[q * run.RS_W + 3] == s && lt_find(run.rsrec[q * run.RS_W]) >= 0) { o = run.rsrec[q * run.RS_W]; break; } q++; } if (o >= 0) hover_named(i, o, 3); else if (v & 16) hover_global(i, 2); else hover_global(i, 1); } else if (v & 16) hover_global(i, 2); else hover_global(i, 1); } else hover_global(i, 0); r = rs_find(hoff); hover_storage(r); hover_value(r); if (r >= 0 && hn && (run.rsrec[r * run.RS_W + 1] == run.RS_LOCAL || run.rsrec[r * run.RS_W + 1] == run.RS_PARAM) && run.rsrec[r * run.RS_W + 2] >= 0) hover_ownership(i, r, run.rsrec[r * run.RS_W + 2]); } send(gen_hover); return 0; } //: ---- definition and references ---- //: the compiler's records say what the name under the cursor is: a local, a //: parameter or a field names its declaration by offset, a symbol names itself //: and the compiler kept where it was defined (sdoff, and sdsrc for a name from //: an include). definitions and references come from those, not from text. int bs_doc_now; word bs_idx; int nav_i, nav_decl, nav_sym, nav_kind, nav_refs, nav_bdoc; char* nav_file_buf; int nav_file_n; char* navp; int navpn, navcap = 256; int nav_put(int c) { if (navpn + 2 >= navcap) { navp = lsp_grow(navp, navpn, navcap * 2); navcap = navcap * 2; } navp[navpn] = c; navpn++; navp[navpn] = 0; return 0; } // shorten a path in place to its parent directory; 0 means there is no parent. int nav_parent(char*.m path) { int n = rt.len_w(path); if (n == 1 && path[0] == '/') return 1; while (n > 0 && path[n - 1] != '/') n--; if (n > 1) n--; path[n] = 0; return n; } // resolve navigation with the same paths and client-provided files as the compiler. // copy the text because navigation releases its buffer, while a virtual file stays cached. char* nav_find(int i, char* path) { char* buf, copy, dir, other; int k = 0, n; inc_setup(i); buf = run.inc_open(path); if (!buf) return 0; // a path read as written can be relative. find its absolute spelling through the // document's ancestors, checking the contents so a different file is never selected. dir = doc_dir(i); if (run.incres[0] != '/' && run.incres[1] != ':') { while (dir) { char* combined = run.inc_cat(dir, run.incres); char* candidate = run.inc_norm(combined); other = m*run.inc_try(candidate); if (other && rt.eq_w(other, buf)) { run.incres = candidate; break; } if (rt.eq_w(dir, "/")) break; if (!nav_parent(dir)) break; } } navpn = 0; if (!navp) navp = lsp_grow(0, 0, navcap); while (run.incres[k]) { nav_put(run.incres[k]); k++; } n = rt.len_w(buf); copy = lsp_grow(0, 0, n + 1); rt.copy_w(copy, buf, n); return copy; } // {"line", "character"} of byte offset off in a file read into buf int nav_pos_file(char* buf, int off) { int o = 0, run.line = 0, col = 0, b; while (o < off && buf[o]) { b = buf[o]; if (b == 10) { run.line++; col = 0; } else if (b < 128 || b >= 192) { if (b >= 240) col = col + 2; else col++; } o++; } ws("{\"line\":"); wn(run.line); ws(",\"character\":"); wn(col); wc('}'); return 0; } // a location in the document: the name written at offset off int nav_loc_doc(int i, int off) { int e = off, base = doff[i], r, k = 0, dot = -1; char* name; if (i == chk_i) { r = rs_find(off); if (r >= 0 && run.rsrec[r * run.RS_W + 1] == run.RS_SYM && run.rsrec[r * run.RS_W + 3] >= 0) { name = run.sname[run.rsrec[r * run.RS_W + 3]]; while (name[k] && off + k < dsize[i] && dc(base, off + k) == name[k]) { if (name[k] == '.') dot = k; k++; } if (!name[k] && dot >= 0) off += dot + 1; } } e = off; while (e < dsize[i] && hid(dc(base, e))) e++; if (e == off) e = off + 1; pc_reset(i); ws("{\"uri\":"); wjs(duri[i], dulen[i]); ws(",\"range\":{\"start\":"); wpos(off); ws(",\"end\":"); wpos(e); ws("}}"); return 0; } // a location in the include file nav_file_buf, whose path is in navp int nav_loc_file(int off) { int e = off, k; while (nav_file_buf[e] && hid(nav_file_buf[e])) e++; if (e == off) e = off + 1; ws("{\"uri\":\"file://"); k = 0; while (k < navpn) { if (navp[k] == 32) ws("%20"); else wesc(navp[k]); k++; } ws("\",\"range\":{\"start\":"); nav_pos_file(nav_file_buf, off); ws(",\"end\":"); nav_pos_file(nav_file_buf, e); ws("}}"); return 0; } // does record r name what the cursor is on int nav_match(int r) { word p = run.rsrec + r * run.RS_W; if (nav_sym >= 0) return p[1] == run.RS_SYM && p[3] == nav_sym; return (p[1] == run.RS_LOCAL || p[1] == run.RS_PARAM || p[1] == run.RS_FIELD) && p[2] == nav_decl; } // the reply: the definition, or with references every use of the name in the document int gen_nav() { int r = 0, first = 1, declared = 0; ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":["); if (nav_kind) { while (r < run.rsn) { if (nav_match(r)) { if (run.rsrec[r * run.RS_W] == nav_decl) { if (!rdecl) { r++; continue; } declared = 1; } if (!first) wc(','); first = 0; nav_loc_doc(nav_i, run.rsrec[r * run.RS_W]); } r++; } } if ((!nav_kind || (rdecl && !declared)) && (nav_decl >= 0 || nav_refs == 1)) { if (!first) wc(','); first = 0; if (nav_refs == 1) nav_loc_file(nav_decl); else if (nav_refs == 2) nav_loc_doc(nav_bdoc, nav_decl); else nav_loc_doc(nav_i, nav_decl); } ws("]}"); return 0; } // textDocument/definition (refs = 0) and textDocument/references (refs = 1) int do_nav(int i, int refs) { int r, k, v, s; ensure_checked(i); nav_i = i; nav_decl = 0 - 1; nav_sym = 0 - 1; nav_kind = refs; nav_refs = 0; nav_file_buf = 0; if (!hover_word(i)) { send(gen_null); return 0; } r = rs_find(hoff); if (r < 0) { // the compiler has no record of the name (a built-in source is not checked): look for its declaration as text if (hover_scan(i)) { nav_decl = decl_off(doff[i], hdl, hdle); send(gen_nav); return 0; } if (!refs && bs_find(hname)) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); send(gen_nav); return 0; } send(gen_null); return 0; } k = run.rsrec[r * run.RS_W + 1]; v = run.rsrec[r * run.RS_W + 2]; s = run.rsrec[r * run.RS_W + 3]; if (k == run.RS_SYM && s >= 0) { nav_sym = s; if (run.sdoff && run.sdoff[s]) { nav_decl = run.sdoff[s] - 1; if (run.sdsrc[s] && bs_name(run.sdsrc[s])) { k = bs_source(run.sdsrc[s]); if (k >= 0) { nav_refs = 2; nav_bdoc = bs_load(k); } else if (bs_find(run.sname[s])) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); } else nav_decl = 0 - 1; } else if (run.sdsrc[s]) { nav_file_buf = nav_find(i, run.sdsrc[s]); if (nav_file_buf) nav_refs = 1; else nav_decl = 0 - 1; } } else if (bs_find(run.sname[s])) { nav_decl = bs_off; nav_refs = 2; nav_bdoc = bs_load(bs_k); } } else if ((k == run.RS_LOCAL || k == run.RS_PARAM || k == run.RS_FIELD) && v >= 0) nav_decl = v; else { send(gen_null); return 0; } send(gen_nav); if (nav_file_buf) rt.free(nav_file_buf); return 0; } //: ---- the sources of this image ---- //: the image carries the text of rt.zc, compiler.zc, server.zc, std.zc, icon.zc and core.zc (the //: pieces src/tools/embed.sh made), so the definition of a built-in has somewhere to go. each source is a document //: of its own at tinyvm:/NAME.zc, which the editor shows read only and can ask for with zc/source const int BS_COUNT = 6; bstr rt_part(int i); bstr compiler_part(int i); bstr server_part(int i); bstr core_part(int i); bstr bs_part(int k, int i) { if (k == 0) return rt_part(i); if (k == 1) return compiler_part(i); if (k == 2) return server_part(i); if (k == 3) return run.std_part(i); if (k == 4) return run.icon_part(i); return core_part(i); } char* bs_uri(int k) { if (k == 0) return "tinyvm:/rt.zc"; if (k == 1) return "tinyvm:/compiler.zc"; if (k == 2) return "tinyvm:/server.zc"; if (k == 3) return "tinyvm:/std.zc"; if (k == 4) return "tinyvm:/icon.zc"; return "tinyvm:/core.zc"; } // the document of built-in source k, loaded the first time it is asked for int bs_load(int k) { int i, n = 0, j = 0, b; bstr p; word text; if (!bs_idx) bs_idx = lsp_grow(0, 0, BS_COUNT); if (bs_idx[k]) return bs_idx[k] - 1; while (1) { p = bs_part(k, j); if (!p) break; n = n + rt.len_s(p); j++; } text = lsp_grow(0, 0, (n >> 2) + 3); n = 0; j = 0; while (1) { p = bs_part(k, j); if (!p) break; b = 0; while (b < rt.len_s(p)) { if ((n & 3) == 0) text[n >> 2] = 0; text[n >> 2] = text[n >> 2] | (rt.at_s(p, b) << ((n & 3) * 8)); n++; b++; } j++; } i = docnew(bs_uri(k), rt.len_w(bs_uri(k))); doff[i] = text; dsize[i] = n; dver[i] = 1; dgen[i]++; bs_idx[k] = i + 1; return i; } // is document i one of those sources, or anything else with a tinyvm: address int bs_is(int i) { return dulen[i] > 7 && bs_name(duri[i]); } // is the include name a path like tinyvm:std.zc int bs_name(char* path) { return path[0] == 't' && path[1] == 'i' && path[2] == 'n' && path[3] == 'y' && path[4] == 'v' && path[5] == 'm' && path[6] == ':'; } // the offset of the name a declaration line declares: the word before its first delimiter int decl_off(int base, int ls, int le) { if (hs_name >= 0) return hs_name; int dl = ls, e, b; while (dl < le && !hdelim(dc(base, dl))) dl++; e = dl; while (e > ls && (dc(base, e - 1) == 32 || dc(base, e - 1) == 9)) e--; b = e; while (b > ls && hid(dc(base, b - 1))) b--; return b; } // hname becomes the string s int set_hname(char* s) { int n = rt.len_w(s), k = 0; if (n + 1 > hnamecap) { hnamecap = n + 1; hname = lsp_grow(0, 0, hnamecap); } while (k < n) { hname[k] = s[k]; k++; } hname[k] = 0; return 0; } // the offset of method m of struct st in document d (name is Struct.method, the dot at dot), or -1 int bs_method(int dd, char* name, int dot) { int base = doff[dd], size = dsize[dd], ls = 0, le, inside = 0, q, k, ns, e, po, ok; while (ls < size) { le = ls; while (le < size && dc(base, le) != 10) le++; if (!inside) { if (dc(base, ls) == 's' && dc(base, ls + 1) == 't' && dc(base, ls + 2) == 'r' && dc(base, ls + 3) == 'u' && dc(base, ls + 4) == 'c' && dc(base, ls + 5) == 't' && dc(base, ls + 6) == 32) { k = 0; while (k < dot && ls + 7 + k < le && dc(base, ls + 7 + k) == name[k]) k++; if (k == dot && !hid(dc(base, ls + 7 + k))) inside = 1; } } else { if (dc(base, ls) == '}') return 0 - 1; q = ls; while (q < le && (dc(base, q) == 32 || dc(base, q) == 9)) q++; if (q > ls) { po = q; while (po < le && dc(base, po) != '(') po++; if (po < le) { e = po; ns = e; while (ns > q && hid(dc(base, ns - 1))) ns--; k = 0; ok = ns < e; while (ok && ns + k < e) { if (dc(base, ns + k) != name[dot + 1 + k]) ok = 0; k++; } if (ok && !name[dot + 1 + k] && ns > q) return ns; } } } ls = le + 1; } return 0 - 1; } // locate a declaration inside one source, after removing its module prefix. int bs_find_in(int k, char* name) { int n = 0, dot = -1, o, dd = bs_load(k); while (name[n]) { if (name[n] == '.' && dot < 0) dot = n; n++; } if (dot > 0 && name[dot + 1]) { o = bs_method(dd, name, dot); if (o >= 0) { bs_k = k; bs_off = o; return 1; } } else if (dot < 0) { set_hname(name); if (hover_scan(dd)) { bs_k = k; bs_off = decl_off(doff[dd], hdl, hdle); return 1; } } return 0; } char* bs_module_name(int k) { if (k == 0) return "rt"; if (k == 1) return "run"; if (k == 2) return "lsp"; if (k == 3) return "std"; if (k == 4) return "icon"; return "core"; } // qualified symbols stay in their own module, even when another module exports // the same name. unqualified source-only lookups retain the usual search order. int bs_find(char* name) { int k = 0, n, step; char* owner; while (k < BS_COUNT) { owner = bs_module_name(k); n = 0; while (owner[n] && owner[n] == name[n]) n++; if (!owner[n] && name[n] == '.') return bs_find_in(k, name + n + 1); k++; } step = 0; while (step < BS_COUNT) { k = step; if (step == 2) k = 3; if (step == 3) k = 2; if (bs_find_in(k, name)) return 1; step++; } return 0; } // the embedded document for a built-in source path, or -1 for another path. int bs_source(char* path) { if (rt.eq_w(path, "tinyvm:std.zc")) return 3; if (rt.eq_w(path, "tinyvm:icon.zc")) return 4; return 0 - 1; } // zc/source: the text of a built-in source, for the editor to show int gen_source() { int k = 0, dd = bs_doc_now; ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); if (dd < 0) { ws(",\"result\":null}"); return 0; } ws(",\"result\":\""); while (k < dsize[dd]) { wesc(dc(doff[dd], k)); k++; } ws("\"}"); return 0; } // which source a name like /rt.zc or compiler.zc is, as a document, or -1 int bs_named(char* p, int n) { int k = 0, j, ok; char* nm; if (n && p[0] == '/') { p = p + 1; n--; } while (k < BS_COUNT) { nm = bs_uri(k) + 8; j = 0; ok = 1; while (nm[j]) { if (j >= n || p[j] != nm[j]) ok = 0; j++; } if (ok && j == n) return bs_load(k); k++; } return 0 - 1; } //: ---- formatting ---- //: a pretty-printer that keeps the line breaks the author chose and sets everything else: the indent of //: each line from the braces, parentheses and open statements above it, the spaces between the tokens of a //: line, the blank lines between lines and the end of the file. it moves no token to another line, so //: formatting never changes what a program means. the options are the keys of a .zcformat file found from //: the document's directory upward (one "Key: value" per line), then the editor's tab settings, then the //: defaults: //: UseTab: true|false indent with tabs (default true) //: IndentWidth: n the spaces of a level when UseTab is false (default 4) //: MaxEmptyLines: n blank lines kept in a row (default 1) //: ContinuationIndent: n levels added to the lines of an unfinished statement or parenthesis (default 1) //: NormalizeSpacing: bool spaces after commas and around = == && ... (default true) //: CaseIndent: n levels a case label is set in from its switch (default 1) //: FinalNewline: bool end the file with one line break (default true) //: strings, comments, asm and vm blocks and what follows a backtick keep their text. int fm_tab = 1, fm_width = 4, fm_blank = 1, fm_cont = 1, fm_norm = 1, fm_casein = 1, fm_final = 1; char* fm_out; int fm_n, fm_cap; word fm_s, fm_e, fm_k, fm_o, fm_g, fm_l, fm_el, fm_sw; int fm_nt, fm_tcap, fm_swcap; // one character of the output int fm_put(int c) { if (fm_n + 2 >= fm_cap) { fm_out = lsp_grow(fm_out, fm_n, fm_cap * 2); fm_cap = fm_cap * 2; } fm_out[fm_n] = c; fm_n++; return 0; } // start a new line: the spaces at the end of the one before are dropped int fm_newline() { while (fm_n > 0 && (fm_out[fm_n - 1] == 32 || fm_out[fm_n - 1] == 9)) fm_n--; fm_put(10); return 0; } // token kinds (fm_k): 1 word or number, 2 string, 3 character, 4 line comment, 5 block comment, 6 symbol, 7 backtick. // symbol codes (fm_o): 1 comma, 2 semicolon, 3 ( or [, 4 ) or ], 5 {, 6 }, 7 an operator with a space each side, 8 colon, 9 ? int fm_add(int s, int e, int k, int o, int g, int l, int el) { int cap; if (fm_nt >= fm_tcap) { cap = fm_tcap * 2; fm_s = lsp_grow(fm_s, fm_nt, cap); fm_e = lsp_grow(fm_e, fm_nt, cap); fm_k = lsp_grow(fm_k, fm_nt, cap); fm_o = lsp_grow(fm_o, fm_nt, cap); fm_g = lsp_grow(fm_g, fm_nt, cap); fm_l = lsp_grow(fm_l, fm_nt, cap); fm_el = lsp_grow(fm_el, fm_nt, cap); fm_tcap = cap; } fm_s[fm_nt] = s; fm_e[fm_nt] = e; fm_k[fm_nt] = k; fm_o[fm_nt] = o; fm_g[fm_nt] = g; fm_l[fm_nt] = l; fm_el[fm_nt] = el; fm_nt++; return 0; } int fm_wc(int c) { return hid(c) || c >= 128; } // does token t spell the word w int fm_is(int i, int t, char* w) { int base = doff[i], o = fm_s[t], k = 0; if (fm_k[t] != 1) return 0; while (w[k]) { if (o + k >= fm_e[t] || dc(base, o + k) != w[k]) return 0; k++; } return o + k == fm_e[t]; } //: the symbol at o: its length, and its code in fm_sym int fm_sym; int fm_symbol(int base, int o, int size) { int c = dc(base, o), c1 = 0, c2 = 0, n = 1; if (o + 1 < size) c1 = dc(base, o + 1); if (o + 2 < size) c2 = dc(base, o + 2); fm_sym = 0; if (c == ',') fm_sym = 1; else if (c == ';') fm_sym = 2; else if (c == '(' || c == '[') fm_sym = 3; else if (c == ')' || c == ']') fm_sym = 4; else if (c == '{') fm_sym = 5; else if (c == '}') fm_sym = 6; else if (c == ':') fm_sym = 8; else if (c == '?') fm_sym = 9; else if ((c == '<' && c1 == '<' && c2 == '=') || (c == '>' && c1 == '>' && c2 == '=')) { fm_sym = 7; n = 3; } else if (c1 == '=' && (c == '=' || c == '!' || c == '<' || c == '>' || c == '+' || c == '-' || c == '*' || c == '/' || c == '%' || c == '&' || c == '|' || c == '^')) { fm_sym = 7; n = 2; } else if ((c == '&' && c1 == '&') || (c == '|' && c1 == '|')) { fm_sym = 7; n = 2; } else if ((c == '<' && c1 == '<') || (c == '>' && c1 == '>') || (c == '+' && c1 == '+') || (c == '-' && c1 == '-')) n = 2; else if (c == '=') fm_sym = 7; return n; } // split document i into tokens, each with the spaces before it on its line (-1 for the first of a line) int fm_lex(int i) { int base = doff[i], size = dsize[i], o = 0, ln = 0, gap = 0 - 1, c, s, q, e, el, n; fm_nt = 0; if (!fm_s) { fm_tcap = 256; fm_s = lsp_grow(0, 0, 256); fm_e = lsp_grow(0, 0, 256); fm_k = lsp_grow(0, 0, 256); fm_o = lsp_grow(0, 0, 256); fm_g = lsp_grow(0, 0, 256); fm_l = lsp_grow(0, 0, 256); fm_el = lsp_grow(0, 0, 256); } while (o < size) { c = dc(base, o); if (c == 10) { ln++; gap = 0 - 1; o++; continue; } if (c == 13) { o++; continue; } if (c == 32 || c == 9) { if (gap >= 0) gap++; o++; continue; } s = o; el = ln; if (c == '/' && o + 1 < size && dc(base, o + 1) == '/') { while (o < size && dc(base, o) != 10) o++; e = o; while (e > s && (dc(base, e - 1) == 32 || dc(base, e - 1) == 9 || dc(base, e - 1) == 13)) e--; fm_add(s, e, 4, 0, gap, ln, el); } else if (c == '/' && o + 1 < size && dc(base, o + 1) == '*') { o = o + 2; while (o < size && !(dc(base, o) == '*' && o + 1 < size && dc(base, o + 1) == '/')) { if (dc(base, o) == 10) el++; o++; } if (o < size) o = o + 2; fm_add(s, o, 5, 0, gap, ln, el); } else if (c == 34 || c == 39) { q = c; o++; while (o < size && dc(base, o) != q && dc(base, o) != 10) { if (dc(base, o) == 92) o++; o++; } if (o < size && dc(base, o) == q) o++; if (o > size) o = size; if (q == 34) fm_add(s, o, 2, 0, gap, ln, el); else fm_add(s, o, 3, 0, gap, ln, el); } else if (c == 96) { fm_add(s, size, 7, 0, gap, ln, el); o = size; } else if (fm_wc(c)) { while (o < size && fm_wc(dc(base, o))) o++; fm_add(s, o, 1, 0, gap, ln, el); } else { n = fm_symbol(base, o, size); o = o + n; fm_add(s, o, 6, fm_sym, gap, ln, el); } if (el > ln) ln = el; gap = 0; } return 0; } //: ---- source-only hover lookup ---- //: embedded documents have no compiler records. parse declaration boundaries rather than assuming //: there is only one declaration, or one name, on a line. never interpret initializer uses as declarations. int hs_word(int i, int t) { return fm_k[t] == 1 && !run.isdig(dc(doff[i], fm_s[t])); } int hs_equal(int i, int t) { return fm_k[t] == 6 && fm_e[t] == fm_s[t] + 1 && dc(doff[i], fm_s[t]) == '='; } int hs_match(int i, int t) { return fm_is(i, t, hname); } // locate top-level function headers by token boundaries, including adjacent declarations. int hover_source_function(int i) { int t = 0, head = -1, previous = -1, depth = 0, nested = 0, initializer = 0, matched = 0; fm_lex(i); while (t < fm_nt) { if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; } if (fm_k[t] == 7) break; if (depth) { if (fm_k[t] == 6 && fm_o[t] == 5) depth++; else if (fm_k[t] == 6 && fm_o[t] == 6) depth--; if (!depth) { head = -1; previous = -1; initializer = 0; } t++; continue; } if (head < 0) head = t; if (!nested && fm_k[t] == 6 && fm_o[t] == 3 && dc(doff[i], fm_s[t]) == '(' && previous > head && !initializer && hs_match(i, previous)) matched = 1; if (fm_k[t] == 6 && fm_o[t] == 3) nested++; else if (fm_k[t] == 6 && fm_o[t] == 4) nested--; if (!nested && hs_equal(i, t)) initializer = 1; if (!nested && fm_k[t] == 6 && (fm_o[t] == 5 || fm_o[t] == 2)) { if (matched) { hdl = fm_s[head]; hdle = fm_s[t]; hs_name = -1; hs_first = -1; hs_const = 0; hcs = -1; return 1; } if (fm_o[t] == 5) depth = 1; head = -1; previous = -1; initializer = 0; matched = 0; } else previous = t; t++; } return 0; } int hover_source_decl(int i) { int t = 0, head = -1, first = -1, candidate = -1, depth = 0, brackets = 0, init = 0, constant = 0, matched = 0, tail, prev = -1; fm_lex(i); while (t < fm_nt) { if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; } if (fm_k[t] == 7) break; if (depth) { if (fm_k[t] == 6 && fm_o[t] == 5) depth++; if (fm_k[t] == 6 && fm_o[t] == 6) depth--; if (!depth) { head = -1; prev = -1; } t++; continue; } if (head < 0) { head = t; first = -1; candidate = -1; init = 0; brackets = 0; matched = 0; constant = fm_is(i, t, "const"); } if (fm_k[t] == 6 && fm_o[t] == 5) { depth = 1; head = -1; prev = -1; t++; continue; } if (fm_k[t] == 6 && fm_o[t] == 3) { // a function header belongs to the existing function lookup, not this variable lookup. if (!brackets && !init && dc(doff[i], fm_s[t]) == '(') { head = -1; } brackets++; } if (fm_k[t] == 6 && fm_o[t] == 4) brackets--; if (!brackets && head >= 0 && (hs_equal(i, t) || (fm_k[t] == 6 && (fm_o[t] == 1 || fm_o[t] == 2)))) { if (!init && prev >= 0 && hs_word(i, prev) && prev > head) { candidate = prev; if (first < 0) first = prev; // directives and type aliases are not variable declarations. if (!fm_is(i, head, "include") && !fm_is(i, head, "docs") && !fm_is(i, head, "hints") && !fm_is(i, head, "typecheck") && !fm_is(i, head, "type") && !fm_is(i, head, "enhance")) matched = hs_match(i, candidate); } if (hs_equal(i, t)) init = 1; else { if (matched) { hdl = fm_s[head]; hs_first = fm_s[first]; hs_name = fm_s[candidate]; hs_const = constant; tail = fm_s[t]; while (tail > hs_name && (dc(doff[i], tail - 1) == 32 || dc(doff[i], tail - 1) == 9 || dc(doff[i], tail - 1) == 10)) tail--; hdle = tail; return 1; } init = 0; matched = 0; candidate = -1; if (fm_o[t] == 2) { head = -1; first = -1; } } } prev = t; t++; } return 0; } // the image's evaluated constants are safe to read even while viewing its uncompiled source. int hover_blob_value() { word p = rt.GORG[5]; int k; while (*p) { if (p[0] == run.K_CONST) { k = 0; while (hname[k] && p[3 + k] == hname[k]) k++; if (!hname[k] && !p[3 + k]) { hov_text("\n\nvalue at this position:\n```zc\n"); hov_number(p[1]); hov_text("\n```"); return 1; } } p = p + 3 + rt.len_w(p + 3) + 1; } return 0; } char* asm_description(int op) { if (op == 0) return "halt~stops the virtual machine. stack: ( -- )."; if (op == 1) return "load~reads the word at address a. stack: ( a -- value ). addresses are measured in vm words."; if (op == 2) return "store~writes value to the word at address a. stack: ( value a -- )."; if (op == 3) return "pick~duplicates the item n places below the top after consuming n; 0 duplicates the top item. stack: ( ... n -- ... item )."; if (op == 4) return "drop~discards the top item. stack: ( value -- )."; if (op == 5) return "swap~exchanges the top two items. stack: ( a b -- b a )."; if (op == 6) return "call~calls the code at target, saving the next instruction on the return stack. stack: ( target -- )."; if (op == 7) return "jump~continues execution at target. stack: ( target -- )."; if (op == 8) return "jump if zero~jumps to target when condition is zero, consuming both operands. stack: ( condition target -- )."; if (op == 9) return "return~resumes at the address popped from the return stack. leaves the data stack alone."; if (op == 10) return "input~reads one input byte as a signed character and records end-of-input status. stack: ( -- character )."; if (op == 11) return "end of input~pushes 1 at end of input, otherwise 0. stack: ( -- flag )."; if (op == 12) return "output~writes the low byte of character to output. stack: ( character -- )."; if (op == 13) return "add~adds two 64-bit words, wrapping on overflow. stack: ( a b -- a + b )."; if (op == 14) return "subtract~subtracts the top item from the item below it. stack: ( a b -- a - b )."; if (op == 15) return "multiply~multiplies two 64-bit words, wrapping on overflow. stack: ( a b -- a * b )."; if (op == 16) return "bitwise and~stack: ( a b -- a & b )."; if (op == 17) return "bitwise or~stack: ( a b -- a | b )."; if (op == 18) return "bitwise xor~stack: ( a b -- a ^ b )."; if (op == 19) return "arithmetic shift right~shifts a right by b & 63 bits, extending its sign. stack: ( a b -- result )."; if (op == 20) return "signed less than~compares a with b, returning 1 or 0. stack: ( a b -- a < b )."; if (op == 21) return "install enhancement~installs the enhancement descriptor and returns its slot. stack: ( descriptor -- slot )."; if (op == 600) return "float literal~reads the following raw word as ieee binary64 bits and pushes it. stack: ( -- float )."; if (op == 601) return "float add~adds binary64 values. stack: ( a b -- a + b )."; if (op == 602) return "float subtract~subtracts binary64 values. stack: ( a b -- a - b )."; if (op == 603) return "float multiply~multiplies binary64 values. stack: ( a b -- a * b )."; if (op == 604) return "float divide~divides binary64 values. stack: ( a b -- a / b )."; if (op == 605) return "float equal~compares binary64 values for equality. stack: ( a b -- flag )."; if (op == 606) return "float less than~compares binary64 values. stack: ( a b -- flag )."; if (op == 607) return "float less or equal~compares binary64 values. stack: ( a b -- flag )."; if (op == 608) return "integer to float~converts a signed integer to binary64. stack: ( integer -- float )."; if (op == 609) return "float to integer~converts binary64 to a signed integer, truncating toward zero. stack: ( float -- integer )."; if (op == 619) return "data stack pointer~pushes the data stack address before this instruction. stack: ( -- address )."; if (op == 620) return "native call~calls a host function through its descriptor, consuming its arguments and returning one value. stack: ( arguments descriptor -- result )."; if (op == 612) return "load byte~reads an unsigned byte at a byte address. stack: ( address -- value )."; if (op == 613) return "store byte~writes the low 8 bits at a byte address. stack: ( value address -- )."; if (op == 614) return "load 16 bits~reads an unsigned little-endian 16-bit value at a byte address. stack: ( address -- value )."; if (op == 615) return "store 16 bits~writes the low 16 bits at a byte address. stack: ( value address -- )."; if (op == 616) return "load 32 bits~reads an unsigned little-endian 32-bit value at a byte address. stack: ( address -- value )."; if (op == 617) return "store 32 bits~writes the low 32 bits at a byte address. stack: ( value address -- )."; return 0; } // concise overviews for modules supplied by the compiler and language server. char* module_description() { if (hname_is("rt")) return "the runtime module: input and output, strings, allocation, ownership, collection and host operations."; if (hname_is("run")) return "the resident compiler module: compile and evaluate source, inspect tokens and functions, and register syntax or receiver extensions."; if (hname_is("core")) return "the vm host interface: host gates, operand-stack addresses, byte-addressed memory access and program termination."; if (hname_is("lsp")) return "the language server module: document analysis, diagnostics, markdown hovers, completion, navigation and formatting."; if (hname_is("std")) return "the embedded standard library: text and byte strings, numeric helpers, files, buffers, array algorithms and command-line arguments. no file is read and nothing imports it automatically."; if (hname_is("icon")) return "structured runtime syntax transformations: token templates, balanced captures, expression and block edits, function publication and scalar execution inspection."; return 0; } // source modules provide their overview in the first comment after namespace // and include directives. declaration comments further down remain symbol docs. int module_overview(int i) { char* prefix = run.inc_cat("src/lib", hname); int length = rt.len_w(prefix), at = 0, end, start, count = 0, a, b; char* path = rt.new(length + 4), source; rt.copy_w(path, prefix, length); rt.copy_w(path + length, ".zc", 4); inc_setup(i); source = run.inc_open(path); if (!source) return 0; length = rt.len_w(source); while (at < length) { end = at; while (end < length && source[end] != 10) end++; start = at; while (start < end && (source[start] == 32 || source[start] == 9 || source[start] == 13)) start++; if (source[start] == '/' && source[start + 1] == '/') { start += 2; if (source[start] == ':') start++; if (source[start] == 32) start++; if (run.doc_section(source + start, end - start)) break; if (!count) hov_text("\n\n"); else hov_put(10); while (start < end && source[start] != 13) { hov_put(source[start]); start++; } count++; } else if (source[start] == '/' && source[start + 1] == '*') { if (count) break; hov_text("\n\n"); start += 2; while (source[start] && !(source[start] == '*' && source[start + 1] == '/')) { if (source[start] == 10) { hov_put(10); start++; while (source[start] == 32 || source[start] == 9) start++; if (source[start] == '*' && source[start + 1] != '/') { start++; if (source[start] == 32) start++; } } else { hov_put(source[start]); start++; } } return 1; } else { if (count) break; if (start != end) { a = 0; while (a < 9 && source[start + a] == "namespace"[a]) a++; b = 0; while (b < 7 && source[start + b] == "include"[b]) b++; if (!((a == 9 && (source[start + a] == 32 || source[start + a] == ';')) || (b == 7 && source[start + b] == 32))) break; } } at = end + 1; } return count; } // recognize each identifier in include lists without treating module-named // locals, comments or strings as imports. int hover_module(int i) { int t = 0, active = 0; char* description; fm_lex(i); while (t < fm_nt && fm_s[t] <= hcursor) { if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; } if (fm_is(i, t, "include")) active = 1; else if (active && fm_k[t] == 1) { if (hcursor >= fm_s[t] && hcursor < fm_e[t]) { hn = 0; hov_text("```zc\ninclude "); hov_text(hname); hov_text(";\n```\n\n"); description = module_description(); if (description) hov_text(description); else { hov_text("a source library module."); module_overview(i); } hov_text("\n\nexports use the `"); hov_text(hname); hov_text(".name` prefix. the include applies to this source file; dependency imports do not become imports of the caller."); return 1; } active = 2; } else if (active && fm_k[t] == 6 && fm_o[t] == 1) active = 1; else active = 0; t++; } return 0; } // only instruction tokens in asm/vm blocks get instruction descriptions. strings, // comments and ordinary expressions retain their own hovers. int hover_instruction(int i) { int t = 0, depth = 0, raw = 0, pending = 0, prev = -1, c, op, encoded = 0, rawword = 0, k; word desc; if (hcursor < 0) return 0; fm_lex(i); while (t < fm_nt) { if (fm_s[t] > hcursor) break; if (fm_k[t] == 4 || fm_k[t] == 5 || fm_k[t] == 2 || fm_k[t] == 3) { t++; continue; } if (fm_k[t] == 7) break; if (hcursor >= fm_s[t] && hcursor < fm_e[t]) { if (prev >= 0 && fm_is(i, prev, "include") && fm_is(i, t, "std")) { hn = 0; return kw_put("include std;~compiles the embedded standard library here. no file is read and nothing imports it automatically. provides Text, Bytes, Host, Arena, Buf, capture, and command-line helpers."); } if (!raw || (prev >= 0 && fm_k[prev] == 6 && fm_o[prev] == 8)) return 0; op = -1; if (fm_e[t] == fm_s[t] + 1) op = run.mnemidx(dc(doff[i], fm_s[t])); if (dc(doff[i], fm_s[t]) == '#' || (prev >= 0 && dc(doff[i], fm_s[prev]) == '#')) { k = t; if (dc(doff[i], fm_s[t]) == '#') k++; if (k < fm_nt) { c = fm_s[k]; rawword = 0; while (c < fm_e[k] && run.isdig(dc(doff[i], c))) { rawword = rawword * 10 + dc(doff[i], c) - '0'; c++; } if (c == fm_e[k]) { encoded = 1; if (!(rawword & 1)) op = rawword >> 1; } } } desc = asm_description(op); if (!desc && !encoded) return 0; hn = 0; hov_text("```zc\n"); if (encoded) { hov_put('#'); hov_number(rawword); } else hov_put(dc(doff[i], fm_s[t])); hov_text("\n```\n\n"); if (desc) { k = 0; while (desc[k] && desc[k] != '~') { hov_put(desc[k]); k++; } hov_text(" (opcode "); hov_number(op); hov_text(").\n\n"); if (desc[k]) k++; while (desc[k]) { hov_put(desc[k]); k++; } } else if (rawword & 1) { hov_text("encoded literal: pushes "); hov_number(rawword >> 1); hov_put('.'); } else { hov_text("raw encoded opcode "); hov_number(op); hov_put('.'); } return 1; } if (fm_k[t] == 6 && fm_o[t] == 5) { depth++; if (pending) raw = depth; pending = 0; } else if (fm_k[t] == 6 && fm_o[t] == 6) { if (raw == depth) raw = 0; depth--; } else if (fm_is(i, t, "asm") || fm_is(i, t, "vm")) pending = 1; else if (pending) pending = 0; prev = t; t++; } return 0; } int fm_defaults() { fm_tab = 1; fm_width = 4; fm_blank = 1; fm_cont = 1; fm_norm = 1; fm_casein = 1; fm_final = 1; if (fm_edit_set) { fm_tab = fm_edit_tab; if (fm_edit_width > 0 && fm_edit_width < 17) fm_width = fm_edit_width; } return 0; } int fm_bool(char* v, int n) { return n >= 1 && (v[0] == 't' || v[0] == 'T' || v[0] == 'y' || v[0] == 'Y' || v[0] == '1'); } int fm_key(char* buf, int a, int b, char* name) { int k = 0; while (name[k]) { if (a + k >= b || buf[a + k] != name[k]) return 0; k++; } return a + k == b; } // one "Key: value" line of a .zcformat file, at buf[a..b) int fm_option(char* buf, int a, int b) { int k = a, ks, ke, vs, ve, num = 0, j; while (k < b && (buf[k] == 32 || buf[k] == 9)) k++; if (k >= b || buf[k] == '#') return 0; ks = k; while (k < b && buf[k] != ':') k++; if (k >= b) return 0; ke = k; while (ke > ks && (buf[ke - 1] == 32 || buf[ke - 1] == 9)) ke--; k++; while (k < b && (buf[k] == 32 || buf[k] == 9)) k++; vs = k; ve = b; while (ve > vs && (buf[ve - 1] == 32 || buf[ve - 1] == 9 || buf[ve - 1] == 13)) ve--; j = vs; while (j < ve && buf[j] >= '0' && buf[j] <= '9') { num = num * 10 + buf[j] - '0'; j++; } if (fm_key(buf, ks, ke, "UseTab")) fm_tab = fm_bool(buf + vs, ve - vs); else if (fm_key(buf, ks, ke, "IndentWidth")) { if (j > vs && num > 0 && num < 17) fm_width = num; } else if (fm_key(buf, ks, ke, "MaxEmptyLines")) { if (j > vs && num < 100) fm_blank = num; } else if (fm_key(buf, ks, ke, "ContinuationIndent")) { if (j > vs && num < 9) fm_cont = num; } else if (fm_key(buf, ks, ke, "NormalizeSpacing")) fm_norm = fm_bool(buf + vs, ve - vs); else if (fm_key(buf, ks, ke, "CaseIndent")) { if (j > vs && num < 9) fm_casein = num; } else if (fm_key(buf, ks, ke, "FinalNewline")) fm_final = fm_bool(buf + vs, ve - vs); return 0; } // the options for document i: the defaults, the editor's tab settings, then a .zcformat from the // document's directory or the nearest one above it int fm_read_config(char* path) { char* buf = rt.readfile(path); int k = 0, run.line; if (!buf) return 0; while (buf[k]) { run.line = k; while (buf[k] && buf[k] != 10) k++; fm_option(buf, run.line, k); if (buf[k]) k++; } rt.free(buf); return 1; } int fm_config(int i) { char* uri; int n, run.start = 0, end, k; fm_defaults(); uri = duri[i]; n = dulen[i]; if (n > 7 && uri[0] == 'f' && uri[1] == 'i' && uri[2] == 'l' && uri[3] == 'e') run.start = 7; end = n; while (end > run.start && uri[end - 1] != '/') end--; if (end > run.start) end--; while (end >= run.start) { navpn = 0; navp = lsp_grow(navp, 0, navcap); k = run.start; while (k < end) { nav_put(uri[k]); k++; } nav_put('/'); k = 0; while (".zcformat"[k]) { nav_put(".zcformat"[k]); k++; } if (fm_read_config(navp)) return 1; if (end == run.start) break; end--; while (end > run.start && uri[end - 1] != '/') end--; if (end > run.start) end--; } return 0; } // n levels of indent int fm_indent(int levels) { int k = 0, w; if (levels < 0) levels = 0; if (fm_tab) { while (k < levels) { fm_put(9); k++; } } else { w = levels * fm_width; while (k < w) { fm_put(32); k++; } } return 0; } // the spaces between tokens a and b of one line when the source had g; raw: inside an asm or vm block; // qopen: a ? is waiting for its colon int fm_gap(int i, int a, int b, int g, int raw, int qopen) { int ka = fm_k[a], kb = fm_k[b], oa = fm_o[a], ob = fm_o[b], keep = 0; if (g > 0) keep = 1; if (raw) return g; if (kb == 4) { if (g > 0) return g; return 1; } if (!fm_norm) return keep; if (ka == 5 || kb == 5) return keep; if (ka == 6 && oa == 3) return 0; if (kb == 6 && ob == 4) return 0; if (kb == 6 && (ob == 1 || ob == 2)) return 0; if (ka == 6 && (oa == 1 || oa == 2)) return 1; if (ka == 6 && oa == 5 && kb == 6 && ob == 6) return keep; if ((ka == 6 && oa == 5) || (kb == 6 && ob == 6)) return 1; if ((ka == 6 && (oa == 7 || oa == 9)) || (kb == 6 && (ob == 7 || ob == 9))) return 1; if (qopen && ((ka == 6 && oa == 8) || (kb == 6 && ob == 8))) return 1; if (kb == 6 && ob == 3 && dc(doff[i], fm_s[b]) == '(' && ka == 1 && (fm_is(i, a, "if") || fm_is(i, a, "while") || fm_is(i, a, "for") || fm_is(i, a, "switch"))) return 1; if (kb == 6 && ob == 5 && ((ka == 6 && oa == 4) || ka == 1)) return 1; if (ka == 6 && oa == 6 && kb == 1 && (fm_is(i, b, "else") || fm_is(i, b, "while"))) return 1; return keep; } // a block comment at token t, indented to levels: lines that start with a star line up under the first int fm_comment(int i, int t, int levels) { int base = doff[i], o = fm_s[t], e = fm_e[t], c, k; while (o < e) { c = dc(base, o); if (c == 13) { o++; continue; } o++; if (c == 10) { fm_newline(); k = o; while (k < e && (dc(base, k) == 32 || dc(base, k) == 9)) k++; if (k < e && dc(base, k) == '*') { fm_indent(levels); fm_put(32); o = k; } } else fm_put(c); } return 0; } //: the state after token x: braces, parentheses, switch bodies and asm blocks int fm_bd, fm_pd, fm_raw, fm_rawd, fm_pendsw, fm_pendraw, fm_lastk, fm_lasto; int fm_step(int i, int x) { int k = fm_k[x], o = fm_o[x]; if (k == 4 || k == 5) return 0; if (k == 1) { fm_pendraw = 0; if (fm_is(i, x, "switch")) fm_pendsw = 1; if (fm_bd > 0 && fm_sw[fm_bd] && (fm_is(i, x, "case") || fm_is(i, x, "default"))) fm_sw[fm_bd] = 2; if (fm_is(i, x, "asm") || fm_is(i, x, "vm")) fm_pendraw = 1; } else if (k == 6 && o == 5) { fm_bd++; if (fm_bd + 2 >= fm_swcap) { fm_sw = lsp_grow(fm_sw, fm_swcap, fm_swcap * 2); fm_swcap = fm_swcap * 2; } fm_sw[fm_bd] = 0; if (fm_pendsw && fm_pd == 0) fm_sw[fm_bd] = 1; fm_pendsw = 0; if (fm_pendraw && !fm_raw) { fm_raw = 1; fm_rawd = fm_bd; } fm_pendraw = 0; } else { fm_pendraw = 0; if (k == 6 && o == 6) { if (fm_raw && fm_bd == fm_rawd) fm_raw = 0; if (fm_bd > 0) fm_bd--; } else if (k == 6 && o == 3) fm_pd++; else if (k == 6 && o == 4) { if (fm_pd > 0) fm_pd--; } else if (k == 6 && o == 2) fm_pendsw = 0; } fm_lastk = k; fm_lasto = o; return 0; } // format document i into fm_out int fm_run(int i) { int base = doff[i], size = dsize[i], t = 0, t1, x, lead_b, lead_p, k, lvl, cont, blank, endl = 0 - 1, qopen, g, c, first_raw, depth_index, swindent; fm_n = 0; if (!fm_out) { fm_cap = 1024; fm_out = lsp_grow(0, 0, 1024); } if (!fm_sw) { fm_swcap = 64; fm_sw = lsp_grow(0, 0, 64); } fm_lex(i); fm_bd = 0; fm_pd = 0; fm_raw = 0; fm_rawd = 0; fm_pendsw = 0; fm_pendraw = 0; fm_lastk = 0; fm_lasto = 0; while (t < fm_nt) { t1 = t + 1; while (t1 < fm_nt && fm_g[t1] >= 0) t1++; if (fm_n > 0) { blank = fm_l[t] - endl - 1; if (blank > fm_blank) blank = fm_blank; while (blank > 0) { fm_newline(); blank--; } } first_raw = fm_raw; lead_b = 0; lead_p = 0; k = t; while (k < t1 && fm_k[k] == 6 && (fm_o[k] == 6 || fm_o[k] == 4)) { if (fm_o[k] == 6) lead_b++; else lead_p++; k++; } if (first_raw && !(fm_k[t] == 6 && fm_o[t] == 6 && fm_bd == fm_rawd)) { // inside an asm or vm block the line keeps its text fm_indent(fm_bd); x = fm_s[t]; while (x < fm_e[t1 - 1]) { c = dc(base, x); if (c != 13) fm_put(c); x++; } x = t; while (x < t1) { fm_step(i, x); x++; } fm_newline(); endl = fm_el[t1 - 1]; t = t1; continue; } swindent = 0; depth_index = 1; while (depth_index < fm_bd) { if (fm_sw[depth_index] == 2) swindent = swindent + fm_casein; depth_index++; } cont = 0; if (fm_k[t] == 1 && (fm_is(i, t, "case") || fm_is(i, t, "default")) && fm_bd > 0 && fm_sw[fm_bd]) { lvl = fm_bd - 1 + fm_casein + swindent; } else { if (lead_b == 0 && fm_bd > 0 && fm_sw[fm_bd] == 2) swindent = swindent + fm_casein; lvl = fm_bd - lead_b + swindent; if (fm_pd - lead_p > 0) cont = fm_cont; else if (fm_lastk != 0 && !(fm_lastk == 6 && (fm_lasto == 1 || fm_lasto == 2 || fm_lasto == 5 || fm_lasto == 6 || fm_lasto == 8)) && !(fm_k[t] == 6 && (fm_o[t] == 5 || fm_o[t] == 6))) cont = fm_cont; } if (lvl < 0) lvl = 0; fm_indent(lvl + cont); qopen = 0; x = t; while (x < t1) { if (x > t) { g = fm_gap(i, x - 1, x, fm_g[x], fm_raw, qopen); while (g > 0) { fm_put(32); g--; } } if (fm_k[x] == 5) fm_comment(i, x, lvl + cont); else if (fm_k[x] == 7) { // what follows a backtick is data k = fm_s[x]; while (k < size) { c = dc(base, k); if (c != 13) fm_put(c); k++; } return 0; } else { k = fm_s[x]; while (k < fm_e[x]) { fm_put(dc(base, k)); k++; } } if (fm_k[x] == 6 && fm_o[x] == 9) qopen++; else if (fm_k[x] == 6 && fm_o[x] == 8 && qopen > 0) qopen--; fm_step(i, x); x++; } fm_newline(); endl = fm_el[t1 - 1]; t = t1; } // one line break ends the file while (fm_n > 0 && (fm_out[fm_n - 1] == 10 || fm_out[fm_n - 1] == 32 || fm_out[fm_n - 1] == 9)) fm_n--; if (fm_n > 0 && fm_final) fm_put(10); return 0; } // the formatted text of document i is in fm_out int fm_format(int i) { fm_config(i); fm_run(i); return 0; } // is the formatted text the same as the document int fm_same(int i) { int k = 0; if (fm_n != dsize[i]) return 0; while (k < fm_n) { if (fm_out[k] != dc(doff[i], k)) return 0; k++; } return 1; } // textDocument/formatting: one edit that replaces the whole text, or none when it is already formatted int gen_format() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); if (fm_same(nav_i)) { ws(",\"result\":[]}"); return 0; } pc_reset(nav_i); ws(",\"result\":[{\"range\":{\"start\":{\"line\":0,\"character\":0},\"end\":"); wpos(dsize[nav_i]); ws("},\"newText\":"); wjs(fm_out, fm_n); ws("}]}"); return 0; } int do_format(int i) { nav_i = i; fm_format(i); send(gen_format); return 0; } // zclsp --format < file.zc prints the file formatted int format_cli(int first) { int k = 0; load_stdin(first); fm_defaults(); fm_run(0); while (k < fm_n) { rt.put_c(fm_out[k]); k++; } return 0; } //: ---- completion ---- //: candidates own their text: another compiler check can replace all symbol and type tables. char** cp_name, cp_detail, cp_docs; word cp_kind, cp_dead, cp_rank, cp_order, cp_tmp; int cp_n, cp_cap, cp_i, cp_start, cp_end, cp_cursor; char* cp_prefix; char* cp_copy(char* text) { int n = rt.len_w(text); char* p = lsp_grow(0, 0, n + 1); rt.copy_w(p, text, n); return p; } char* cp_hov() { char* p = lsp_grow(0, 0, hn + 1); rt.copy_w(p, hov, hn); return p; } int cp_lower(int c) { if (c >= 'A' && c <= 'Z') return c + 32; return c; } // subsequence matching rewards exact spelling, prefixes, consecutive letters and word boundaries. int cp_score(char* name) { int i = 0, j = 0, score = 0, last = -2, n = rt.len_w(name), exact = 1; if (!cp_prefix[0]) return 100; while (cp_prefix[i]) { while (j < n && cp_lower(name[j]) != cp_lower(cp_prefix[i])) j++; if (j == n) return -1; if (j != i || name[j] != cp_prefix[i]) exact = 0; score = score + j; if (j != last + 1) score = score + 12; if (j == 0 || name[j - 1] == '_' || (name[j] >= 'A' && name[j] <= 'Z')) score = score - 8; last = j; j++; i++; } if (exact) { if (n == i) return 0; return 10 + n - i; } return 100 + score + n - i; } int cp_add(char* name, int kind, int dead, int relevance) { int score = cp_score(name); if (score < 0) return 0; if (cp_n == cp_cap) { if (!cp_cap) cp_cap = 64; else cp_cap = cp_cap * 2; cp_name = lsp_grow(cp_name, cp_n, cp_cap); cp_detail = lsp_grow(cp_detail, cp_n, cp_cap); cp_docs = lsp_grow(cp_docs, cp_n, cp_cap); cp_kind = lsp_grow(cp_kind, cp_n, cp_cap); cp_dead = lsp_grow(cp_dead, cp_n, cp_cap); cp_rank = lsp_grow(cp_rank, cp_n, cp_cap); cp_order = lsp_grow(cp_order, cp_n, cp_cap); cp_tmp = lsp_grow(cp_tmp, 0, cp_cap); } cp_name[cp_n] = cp_copy(name); cp_kind[cp_n] = kind; cp_dead[cp_n] = dead; cp_detail[cp_n] = cp_hov(); cp_docs[cp_n] = 0; cp_rank[cp_n] = dead * 10000000 + score * 10 + relevance; cp_n++; return 1; } bstr cp_signature(char* name) { int k = 0, j = 0, s; run.doc_name_room(rt.len_w(name) + 5); while ("sig "[k]) { run.dnmb[k] = "sig "[k]; k++; } while (name[j]) { run.dnmb[k] = name[j]; k++; j++; } run.dnmb[k] = 0; s = run.lookup(run.intern(run.dnmb)); if (s >= 0 && run.skind[s] == run.K_DOC) return run.sval[s]; return 0; } // signatures and comments collected by the compiler, with types for globals and fields. int cp_symbol(int s, char* label) { if (cp_score(label) < 0) return 0; bstr sig = cp_signature(run.sname[s]), doc; int k = 0, kind = 6; hn = 0; if (sig) { while (k < rt.len_s(sig)) { hov_put(rt.at_s(sig, k)); k++; } } else { if (run.stype[s]) { hov_type(run.stype[s]); hov_put(' '); } hov_text(label); } if (run.skind[s] == run.K_FN || run.skind[s] == run.K_ENH) kind = 3; else if (run.skind[s] == run.K_TYPE) kind = 22; else if (run.skind[s] == run.K_CONST) kind = 21; else if (run.skind[s] == run.K_KW) kind = 14; if (!cp_add(label, kind, 0, 3)) return 0; hn = 0; doc = run.docofs(run.sname[s]); if (doc) { k = 0; while (k < rt.len_s(doc)) { hov_put(rt.at_s(doc, k)); k++; } } if (hn) cp_docs[cp_n - 1] = cp_hov(); return 0; } // array methods expose the receiver's element type in their signatures. int cp_array_method(word result, char* name, word parameter, char* argument, char* docs) { hn = 0; hov_type(result); if (rt.eq_s(name, "pop") && run.isref(result)) hov_text(".u"); hov_put(' '); hov_text(name); hov_put('('); if (parameter) { hov_type(parameter); hov_put(' '); hov_text(argument); } hov_put(')'); if (cp_add(name, 2, 0, 0)) cp_docs[cp_n - 1] = cp_copy(docs); return 0; } int cp_array_members(word t) { word link; cp_array_method(run.tint, "len", 0, "", "returns the number of elements in the array."); cp_array_method(run.tint, "cap", 0, "", "returns the allocated element capacity; for a fixed array, this equals its length."); if (t[0] == run.TY_DYN && !t[3] && !run.treadonly(t)) { if (!run.isagg(t[4])) { cp_array_method(run.tint, "push", t[4], "value", "appends one element. growth may relocate storage and invalidate borrowed element addresses."); cp_array_method(t[4], "pop", 0, "", "removes and returns the last element, transferring ownership for references. an empty array reports an error."); } cp_array_method(run.tint, "resize", run.tint, "length", "sets the array length and zero-fills added elements. resizing invalidates borrowed element addresses."); } link = run.receiver_methods; while (link) { if (!run.array_method(link[1]) && run.receiver_method(t, link[1]) == link[2]) cp_symbol(link[2], link[1]); link = link[0]; } return 0; } int cp_before(int a, int b) { int k = 0; if (cp_rank[a] != cp_rank[b]) return cp_rank[a] < cp_rank[b]; while (cp_name[a][k] && cp_name[a][k] == cp_name[b][k]) k++; return cp_name[a][k] < cp_name[b][k]; } int gen_completion() { int i = 0, j, k, width = 1, lo, mid, hi, a, b, dest; word swap; while (i < cp_n) { cp_order[i] = i; i++; } // bottom-up merge sort keeps large completion lists responsive on the interpreter hosts. while (width < cp_n) { lo = 0; while (lo < cp_n) { mid = lo + width; if (mid > cp_n) mid = cp_n; hi = mid + width; if (hi > cp_n) hi = cp_n; a = lo; b = mid; dest = lo; while (dest < hi) { if (a < mid && (b >= hi || cp_before(cp_order[a], cp_order[b]))) { cp_tmp[dest] = cp_order[a]; a++; } else { cp_tmp[dest] = cp_order[b]; b++; } dest++; } lo = hi; } swap = cp_order; cp_order = cp_tmp; cp_tmp = swap; width = width * 2; } pc_reset(cp_i); ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"isIncomplete\":true,\"items\":["); i = 0; while (i < cp_n) { k = cp_order[i]; if (i) wc(','); ws("{\"label\":"); wjs(cp_name[k], rt.len_w(cp_name[k])); ws(",\"kind\":"); wn(cp_kind[k]); ws(",\"detail\":"); wjs(cp_detail[k], rt.len_w(cp_detail[k])); // the server has already matched the prefix; equal client scores preserve its relevance and lifetime order. if (cp_prefix[0]) { ws(",\"filterText\":"); wjs(cp_prefix, rt.len_w(cp_prefix)); } ws(",\"sortText\":\""); j = 1000000000; while (j > 0) { wc('0' + ((i / j) % 10)); j = j / 10; } wc('"'); if (cp_dead[k]) ws(",\"tags\":[1],\"deprecated\":true"); if (cp_docs[k] || cp_dead[k]) { ws(",\"documentation\":{\"kind\":\"markdown\",\"value\":"); hn = 0; if (cp_docs[k]) hov_text(cp_docs[k]); if (cp_dead[k]) hov_text("\n\nlifetime ended: this variable has been consumed and cannot be used here."); wjs(hov, hn); wc('}'); } ws(",\"textEdit\":{\"range\":{\"start\":"); wpos(cp_start); ws(",\"end\":"); wpos(cp_end); ws("},\"newText\":"); wjs(cp_name[k], rt.len_w(cp_name[k])); ws("}}"); i++; } ws("]}}"); return 0; } int do_completion(int i) { int s, k, j, old, n, head, c; word member, fields; char* nm; char* owner = 0; cp_i = i; cp_n = 0; hover_word(i); if (hcursor < 0) { send(gen_null); return 0; } cp_cursor = hcursor; cp_start = cp_cursor; cp_end = cp_cursor; while (cp_start > 0 && hid(dc(doff[i], cp_start - 1))) cp_start--; while (cp_end < dsize[i] && hid(dc(doff[i], cp_end))) cp_end++; n = cp_cursor - cp_start; cp_prefix = lsp_grow(0, 0, n + 1); k = 0; while (k < n) { cp_prefix[k] = dc(doff[i], cp_start + k); k++; } if (cp_start > 0 && dc(doff[i], cp_start - 1) == '.') { head = cp_start - 1; while (head > 0 && hid(dc(doff[i], head - 1))) head--; n = cp_start - 1 - head; owner = m*lsp_grow(0, 0, n + 1); k = 0; while (k < n) { owner[k] = dc(doff[i], head + k); k++; } owner[n] = 0; if (!run.module_known(owner)) owner = 0; } // the full check supplies functions declared later; the probe supplies locals and their current lifetimes. ensure_checked(i); if (owner) { if (run.module_imported(owner)) { n = rt.len_w(owner); s = 0; while (s < run.nsym) { nm = run.sname[s]; k = 0; while (k < n && nm[k] == owner[k]) k++; if (k == n && nm[k] == '.' && run.skind[s] != run.K_DOC && run.lookup(nm) == s) cp_symbol(s, nm + n + 1); s++; } } send(gen_completion); return 0; } s = 0; while (s < run.nsym) { if (run.skind[s] != run.K_DOC && run.lookup(run.sname[s]) == s && (run.skind[s] != run.K_FN || (run.sflag[s] & (run.F_DEF | run.F_DECL)))) cp_symbol(s, run.sname[s]); s++; } run.probe_off = cp_cursor; old = chk_stream; chk_stream = 0; check_doc(i); chk_stream = old; run.probe_off = -1; member = 0; if (run.probe_has == 2 && run.probe_mem && run.probe_mem[0] == run.TY_STRUCT) member = run.probe_mem[2]; if (run.probe_has == 3) member = run.probe_static; if (run.probe_has == 2 || run.probe_has == 3) { cp_n = 0; if (run.probe_has == 2 && run.probe_mem && (run.probe_mem[0] == run.TY_ARR || run.probe_mem[0] == run.TY_DYN)) cp_array_members(run.probe_mem); if (run.probe_has == 2 && run.probe_mem && run.probe_mem[0] == run.TY_STRUCT) { fields = run.probe_mem[4]; k = 0; while (k < run.probe_mem[3]) { hn = 0; hov_type(fields[k * 3 + 1]); hov_put(' '); hov_text(fields[k * 3]); cp_add(fields[k * 3], 5, 0, 0); k++; } } if (member) { n = rt.len_w(member); s = 0; while (s < run.nsym) { nm = run.sname[s]; k = 0; while (k < n && nm[k] == member[k]) k++; if (k == n && nm[k] == '.' && run.lookup(nm) == s && (run.probe_has != 2 || !run.probe_mem || run.probe_mem[0] != run.TY_STRUCT || run.tfindfield(run.probe_mem, nm + n + 1) < 0) && (run.skind[s] == run.K_FN || run.skind[s] == run.K_CONST) && (run.probe_has != 3 || run.skind[s] == run.K_CONST || (run.sflag[s] & run.F_STATIC))) cp_symbol(s, nm + n + 1); s++; } } } else { k = 0; while (k < run.probe_n) { // a local shadows a global with the same spelling. j = 0; while (j < cp_n) { if (rt.eq_w(cp_name[j], run.probe_name[k])) { cp_n--; cp_name[j] = cp_name[cp_n]; cp_detail[j] = cp_detail[cp_n]; cp_docs[j] = cp_docs[cp_n]; cp_kind[j] = cp_kind[cp_n]; cp_dead[j] = cp_dead[cp_n]; cp_rank[j] = cp_rank[cp_n]; break; } j++; } hn = 0; if (run.probe_type[k]) { hov_type(run.probe_type[k]); hov_put(' '); } hov_text(run.probe_name[k]); if (cp_add(run.probe_name[k], 6, run.probe_state[k], 0)) { hn = 0; j = run.probe_decl[k]; if (j >= 0) { head = j; while (head > 0) { c = dc(doff[i], head - 1); if (c == 10 || c == ';' || c == '{' || c == '}') break; head--; } while (head < j && (dc(doff[i], head) == 32 || dc(doff[i], head) == 9)) head++; hover_docs(i, head, j, rt.len_w(run.probe_name[k]), 0); } if (hn) cp_docs[cp_n - 1] = cp_hov(); } k++; } } // a partial check must never serve subsequent hovers, navigation or diagnostics. chk_i = -1; ann_i = -1; send(gen_completion); return 0; } // import fixes are derived from current compiler errors and qualified exports. // each edit is tied to the document version that was checked. word ca_list; int ca_i, ca_start, ca_end, ca_insert, ca_crlf; int ca_add(char*.d owner, int qualify) { word p = ca_list; while (p) { if (rt.eq_w(p[1], owner)) return 0; p = p[0]; } p = rt.new(3); p[0] = ca_list; p[1] = owner; p[2] = qualify; ca_list = p; return 0; } int ca_import_point(int i) { int t = 0, at = 0, c; fm_lex(i); while (t < fm_nt) { if (fm_k[t] == 4 || fm_k[t] == 5) { t++; continue; } if (!fm_is(i, t, "include") && !fm_is(i, t, "namespace")) break; while (t < fm_nt && !(fm_k[t] == 6 && fm_o[t] == 2)) t++; if (t == fm_nt) break; at = fm_e[t]; t++; } while (at < dsize[i] && (dc(doff[i], at) == 32 || dc(doff[i], at) == 9)) at++; if (at < dsize[i] && dc(doff[i], at) == 13) at++; if (at < dsize[i] && dc(doff[i], at) == 10) at++; ca_crlf = 0; c = 0; while (c < dsize[i] && dc(doff[i], c) != 10) c++; if (c > 0 && dc(doff[i], c - 1) == 13) ca_crlf = 1; return at; } int ca_newline() { if (ca_crlf) hov_put(13); hov_put(10); return 0; } int gen_code_actions() { word p = ca_list; int first = 1, imported; ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":["); while (p) { if (!first) wc(','); first = 0; imported = run.module_imported(p[1]); hn = 0; if (!imported) { hov_text("include "); hov_text(p[1]); hov_put(';'); } if (p[2]) { if (!imported) hov_text(" and "); hov_text("qualify as "); hov_text(p[1]); hov_put('.'); hov_text(hname); } ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":"); wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":["); pc_reset(ca_i); if (!imported) { ws("{\"range\":{\"start\":"); wpos(ca_insert); ws(",\"end\":"); wpos(ca_insert); ws("},\"newText\":"); hn = 0; if (ca_insert && dc(doff[ca_i], ca_insert - 1) != 10) ca_newline(); hov_text("include "); hov_text(p[1]); hov_put(';'); ca_newline(); wjs(hov, hn); wc('}'); } if (p[2]) { if (!imported) wc(','); ws("{\"range\":{\"start\":"); wpos(ca_start); ws(",\"end\":"); wpos(ca_end); ws("},\"newText\":"); hn = 0; hov_text(p[1]); hov_put('.'); hov_text(hname); wjs(hov, hn); wc('}'); } ws("]}]}}"); p = p[0]; } ws("]}"); return 0; } int do_code_actions(int i) { int at = 0, error = -1, s, dot, k, n, base = doff[i], start, end, cursor, limit, sl = hline, sc = hchar; ca_list = 0; ca_i = i; ensure_checked(i); hover_word(i); cursor = hcursor; limit = cursor; if (ca_endline >= 0) { hline = ca_endline; hchar = ca_endchar; hover_word(i); limit = hcursor; hline = sl; hchar = sc; } while (at < run.dgpos) { start = dgb[at + 2]; end = start + dgb[at + 3]; if (dgb[at] == 1 && start >= 0 && cursor >= 0 && start <= limit && end > cursor) { error = at; break; } at += 5 + dgb[at + 4]; } if (error < 0) { send(gen_code_actions); return 0; } pc_reset(i); pc_to(dgb[error + 2] + dgb[error + 3] - 1); hline = pline; hchar = pcol; if (!hover_word(i)) { send(gen_code_actions); return 0; } ca_start = hoff; ca_end = hoff + rt.len_w(hname); ca_insert = ca_import_point(i); start = ca_start; while (start > 0 && (hid(dc(base, start - 1)) || dc(base, start - 1) == '.')) start--; dot = start; while (dot < ca_end && dc(base, dot) != '.') dot++; if (dot < ca_end) { char* owner = rt.new(dot - start + 1); k = start; n = 0; while (k < dot) { owner[n] = dc(base, k); n++; k++; } owner[n] = 0; if (run.module_known(owner) && !run.module_imported(owner)) ca_add(d*owner, 0); else rt.free(owner); } else { s = 0; while (s < run.nsym) { if (run.skind[s] != run.K_DOC) { char* symbol = run.sname[s]; dot = 0; while (symbol[dot] && symbol[dot] != '.') dot++; if (symbol[dot] == '.' && rt.eq_w(symbol + dot + 1, hname)) { char* owner = rt.new(dot + 1); rt.copy_w(owner, symbol, dot); owner[dot] = 0; if (run.module_known(owner)) ca_add(d*owner, 1); else rt.free(owner); } } s++; } } send(gen_code_actions); return 0; } // analysis-result caching avoids reparsing for repeated requests even after another document was checked. // cache entries contain serialized result values, never compiler addresses that a later check can overwrite. int rc_method() { if (rt.eq_w(mbuf, "textDocument/hover")) return 1; if (rt.eq_w(mbuf, "textDocument/semanticTokens/full")) return 2; if (rt.eq_w(mbuf, "textDocument/definition")) return 3; if (rt.eq_w(mbuf, "textDocument/references")) return 4; if (rt.eq_w(mbuf, "textDocument/completion")) return 5; return 0; } int rc_lookup(int i, int method) { int k = 0, slot = 0, oldest = -1; word p; rc_tick++; while (k < RC_LIMIT) { p = rc_meta + k * RC_W; if (p[0] == i + 1 && p[1] == dgen[i] && p[2] == rc_epoch && p[3] == method && p[4] == hline && p[5] == hchar && p[6] == rdecl && rc_payload[k]) { p[7] = rc_tick; rc_now = k; rc_hits++; return 1; } if (!p[0] || oldest < 0 || p[7] < oldest) { slot = k; oldest = p[7]; if (!p[0]) oldest = 0; } k++; } p = rc_meta + slot * RC_W; p[0] = i + 1; p[1] = dgen[i]; p[2] = rc_epoch; p[3] = method; p[4] = hline; p[5] = hchar; p[6] = rdecl; p[7] = rc_tick; rc_payload[slot] = 0; rc_pending = slot; rc_misses++; return 0; } int rc_store(int slot, int n) { int at = 0, k, run.start = -1; char* marker = ",\"result\":"; while (at < n && run.start < 0) { k = 0; while (marker[k] && at + k < n && rc_buffer[at + k] == marker[k]) k++; if (!marker[k]) run.start = at + k; else at++; } if (run.start < 0 || n <= run.start) return 0; rc_payload[slot] = lsp_grow(0, 0, n - run.start); rt.copy_w(rc_payload[slot], rc_buffer + run.start, n - run.start - 1); // omit the response envelope's final brace return 0; } int gen_cached() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":"); ws(rc_payload[rc_now]); wc('}'); return 0; } int rc_forget(int i) { int k = 0; while (k < RC_LIMIT) { if (rc_meta[k * RC_W] == i + 1) { rc_meta[k * RC_W] = 0; rc_payload[k] = 0; } k++; } return 0; } int gen_cache_stats() { ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"hits\":"); wn(rc_hits); ws(",\"misses\":"); wn(rc_misses); ws(",\"checks\":"); wn(analysis_checks); ws("}}"); return 0; } //: ---- dispatch ---- //: the text just read belongs to the document named by the uri int commit_text() { int i; if (!thave || !tuseen) return 0 - 1; i = dfind(ubuf, un); if (i < 0) i = docnew(ubuf, un); if (i < 0) return 0 - 1; doff[i] = tbase; dsize[i] = tn; dver[i] = jver; dgen[i]++; rc_epoch++; return i; } // handles one message: the lifecycle (initialize, shutdown, exit), changes to a // document (which are checked and published), and the requests for semantic // tokens and hovers. int handle() { int i, method; rc_pending = -1; if (rt.eq_w(mbuf, "initialize")) { rc_epoch++; inited = 1; send(gen_init); return 0; } if (rt.eq_w(mbuf, "exit")) { quit = 1; return 0; } if (rt.eq_w(mbuf, "shutdown")) { shut = 1; send(gen_null); return 0; } if (!inited) { if (idtype) reply_error(0 - 32002); return 0; } if (rt.eq_w(mbuf, "textDocument/didOpen") || rt.eq_w(mbuf, "textDocument/didChange") || rt.eq_w(mbuf, "textDocument/didSave")) { if (rt.eq_w(mbuf, "textDocument/didSave")) { rc_epoch++; chk_i = -1; } i = commit_text(); if (i < 0 && tuseen) i = dfind(ubuf, un); if (i >= 0 && bs_is(i)) { run.dgpos = 0; publish(i); } else if (i >= 0 && check_publish(i)) publish(i); return 0; } if (rt.eq_w(mbuf, "zc/source")) { bs_doc_now = bs_named(pathbuf, pathn); send(gen_source); return 0; } if (rt.eq_w(mbuf, "zc/file")) { if (pathn) { vfs_set(thave); rc_epoch++; } chk_i = 0 - 1; // a document that includes it has to be checked again return 0; } if (rt.eq_w(mbuf, "textDocument/didClose")) { i = dfind(ubuf, un); if (i >= 0) { rc_forget(i); rc_epoch++; run.dgpos = 0; publish(i); // an empty list clears its markers dlive[i] = 0; dsize[i] = 0; doff[i] = 0; duri[i] = 0; chk_i = 0 - 1; hc_i = 0 - 1; ann_i = 0 - 1; } return 0; } if (rt.eq_w(mbuf, "zc/cacheStats")) { send(gen_cache_stats); return 0; } method = rc_method(); if (method) { i = dfind(ubuf, un); if (i >= 0 && rc_lookup(i, method)) { send(gen_cached); return 0; } } if (rt.eq_w(mbuf, "textDocument/semanticTokens/full")) { i = dfind(ubuf, un); if (i < 0) { send(gen_empty); return 0; } ensure_checked(i); gd = i; send(gen_tokens); return 0; } if (rt.eq_w(mbuf, "textDocument/definition") || rt.eq_w(mbuf, "textDocument/references")) { i = dfind(ubuf, un); if (i < 0) { send(gen_null); return 0; } do_nav(i, rt.eq_w(mbuf, "textDocument/references")); return 0; } if (rt.eq_w(mbuf, "textDocument/formatting")) { i = dfind(ubuf, un); if (i < 0) { send(gen_null); return 0; } do_format(i); return 0; } if (rt.eq_w(mbuf, "textDocument/completion")) { i = dfind(ubuf, un); if (i < 0) { send(gen_null); return 0; } do_completion(i); return 0; } if (rt.eq_w(mbuf, "textDocument/codeAction")) { i = dfind(ubuf, un); if (i < 0) { ca_list = 0; send(gen_code_actions); return 0; } do_code_actions(i); return 0; } if (rt.eq_w(mbuf, "textDocument/hover")) { i = dfind(ubuf, un); if (i < 0) { send(gen_null); return 0; } do_hover(i); return 0; } if (idtype) reply_error(0 - 32601); return 0; } //: ---- command line ---- //: a source file on stdin instead of a protocol stream: print what the //: compiler found, one line each. with --annotate, print the file with the //: types the compiler can see written in int load_stdin(int first) { int n = 0, c = first, cap = 4096; word nb; int k; tbase = rt.new(cap); while (c >= 0) { if ((n >> 2) + 4 >= cap) { nb = rt.new(cap * 2); k = 0; while (k < cap) { nb[k] = tbase[k]; k++; } tbase = nb; cap = cap * 2; } if ((n & 3) == 0) tbase[n >> 2] = 0; tbase[n >> 2] = tbase[n >> 2] | (c << ((n & 3) * 8)); n++; if (rt.eof()) break; c = rt.get_c() & 255; } if (n & 3) { } else tbase[n >> 2] = 0; tbase[(n >> 2) + 1] = 0; dlive[0] = 1; doff[0] = tbase; dsize[0] = n; dgen[0] = 1; dulen[0] = 0; return n; } // zclsp given a source file on stdin prints its diagnostics as file:line:column // lines, one per error or hint, and says so when there are none. int cli(int first) { int at = 0, i, o; load_stdin(first); check_doc(0); pc_reset(0); while (at < run.dgpos) { o = dgb[at + 2]; if (o < 0) { rt.put_s("line "); rt.put_n(dgb[at + 1]); } else { pc_to(o); rt.put_n(pline + 1); rt.put_c(':'); rt.put_n(pcol + 1); } if (dgb[at] == 1) rt.put_s(": error: "); else if (dgb[at] == 2) rt.put_s(": warning: "); else rt.put_s(": hint: "); i = 0; while (i < dgb[at + 4]) { rt.put_c(dgb[at + 5 + i]); i++; } rt.put_c(10); at = at + 5 + dgb[at + 4]; } if (!run.dgpos) rt.put_s("no errors\n"); return 0; } //: ---- annotate ---- //: zclsp --annotate < file.zc prints the file with the types the compiler can //: see written in: locals from their initializers, parameters from what every //: call passes them or from being used as an address, returns from what the //: function returns (the type facts of compiler.zc). the typechecker is on for //: it, and every edit has to leave the file with no error it didn't have, so //: what comes out compiles wherever the input did. when a batch of edits //: fails, the batch is cut in halves until the bad edits are found and left //: out. run it again and the types it just wrote let the next round see more. int aw_start(int p) { awp = p; awn = 0; return 0; } // appends byte c to the output being built at awp (awn bytes so far). int aw(int c) { if ((awn & 3) == 0) awp[awn >> 2] = 0; awp[awn >> 2] = awp[awn >> 2] | ((c & 255) << ((awn & 3) * 8)); awn++; return 0; } // ends the output being built: a zero word follows the last byte, so it reads as a text. int aw_end() { if ((awn & 3) == 0) awp[awn >> 2] = 0; awp[(awn >> 2) + 1] = 0; return 0; } // the lines of the errors the last check found int err_lines(word dst) { int at = 0, n = 0, need = run.dgpos / 5 + 1, baseline = dst == abase; if (need > aerrcap) { while (need > aerrcap) { if (!aerrcap) aerrcap = 512; else aerrcap = aerrcap * 2; } abase = lsp_grow(abase, nbase, aerrcap); aerr = lsp_grow(aerr, 0, aerrcap); } if (baseline) dst = abase; else dst = aerr; while (at < run.dgpos) { if (dgb[at] == 1) { dst[n] = dgb[at + 1]; n++; } at = at + 5 + dgb[at + 4]; } return n; } // is v one of the first n words of set. int in_set(int v, word set, int n) { int i = 0; while (i < n) { if (set[i] == v) return 1; i++; } return 0; } // the facts, in the order of the text int sort_facts() { int i = 1, j; word a, b; int t0, t1, t2, t3; while (i < run.nifx) { j = i; while (j > 0 && run.ifx[(j - 1) * 4] > run.ifx[j * 4]) { a = run.ifx + (j - 1) * 4; b = run.ifx + j * 4; t0 = a[0]; t1 = a[1]; t2 = a[2]; t3 = a[3]; a[0] = b[0]; a[1] = b[1]; a[2] = b[2]; a[3] = b[3]; b[0] = t0; b[1] = t1; b[2] = t2; b[3] = t3; j--; } i++; } return 0; } // the facts the last check collected, kept across the checks that follow int save_facts() { int i = 0, nt = 0; word f; int k; sort_facts(); if (run.nifx > afcap) { while (run.nifx > afcap) { if (!afcap) afcap = 4096; else afcap = afcap * 2; } afx = lsp_grow(afx, 0, afcap * 4); amask = lsp_grow(amask, 0, afcap); } if (run.nift > atcap) { while (run.nift > atcap) { if (!atcap) atcap = 24000; else atcap = atcap * 2; } aft = lsp_grow(aft, 0, atcap); } anf = 0; while (i < run.nifx) { f = run.ifx + i * 4; i++; afx[anf * 4] = f[0]; afx[anf * 4 + 1] = f[1]; afx[anf * 4 + 2] = nt; afx[anf * 4 + 3] = f[3]; k = 0; while (k < f[3]) { aft[nt + k] = run.ift[f[2] + k]; k++; } nt = nt + f[3]; anf++; } return 0; } // the text of document 0 with the facts in the mask applied, into dst. a fact // that overlaps the one before it is left out. returns how many went in int apply_masked(int dst) { int i = 0, o = 0; word f; int k, n = 0, size = dsize[0], base = doff[0], last = 0 - 1; aw_start(dst); while (i < anf) { f = afx + i * 4; i++; if (!amask[i - 1] || f[0] < o || f[0] == last || f[0] + f[1] > size) continue; while (o < f[0]) { aw(dc(base, o)); o++; } k = 0; while (k < f[3]) { aw(aft[f[2] + k]); k++; } o = f[0] + f[1]; last = f[0]; n++; } while (o < size) { aw(dc(base, o)); o++; } aw_end(); return n; } // the block the masked text is written into: room for the document and every // replacement, so it never runs out word fresh_half() { int n = dsize[0], i = 0; while (i < anf) { n = n + afx[i * 4 + 3]; i++; } ann_half = rt.new(dwords(n)); return ann_half; } // does the document with the masked facts applied check with no new error? int verify() { int ne, i; apply_masked(ann_half); dlive[1] = 1; doff[1] = ann_half; dsize[1] = awn; dgen[1] = dgen[1] + 1; dulen[1] = 0; check_doc(1); ne = err_lines(aerr); i = 0; while (i < ne) { if (!in_set(aerr[i], abase, nbase)) return 0; i++; } return 1; } // take the facts lo .. hi if they fit together, else halve them and try the halves int try_range(int lo, int hi) { int mid, k; if (lo >= hi) return 0; k = lo; while (k < hi) { amask[k] = 1; k++; } if (verify()) return 0; k = lo; while (k < hi) { amask[k] = 0; k++; } if (hi - lo == 1) return 0; mid = (lo + hi) / 2; try_range(lo, mid); try_range(mid, hi); return 0; } // zclsp --annotate reads a source file on stdin and prints it with the types the // compiler can see written in. the rounds run the check with the type facts on, // keep the edits that add no error, and run again on the text they made. int annotate(int first) { int round = 0, n, i; load_stdin(first); run.ifstrict = 1; // the typechecker runs, so a type that doesn't fit how a name is used is an error check_doc(0); nbase = err_lines(abase); while (round < AROUNDS) { run.ifon = 1; check_doc(0); run.ifon = 0; save_facts(); if (!anf) break; i = 0; while (i < anf) { amask[i] = 0; i++; } fresh_half(); try_range(0, anf); n = apply_masked(ann_half); if (!n) break; doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1; round++; } run.ifstrict = 0; i = 0; while (i < dsize[0]) { rt.put_c(dc(doff[0], i)); i++; } return 0; } // the first argument: 1 for --annotate, 2 for --types, which lists the edits // instead of making them int cli_mode() { int n = rt.syscall(rt.SYS_ARGC, 0, 0, 0), len; char* s; int r = 0; if (n < 2) return 0; len = rt.syscall(rt.SYS_ARG, 1, 0, 0); if (len != 10 && len != 7 && len != 8) return 0; s = rt.new(len + 1); rt.syscall(rt.SYS_ARG, 1, s, len + 1); if (s[0] == '-' && s[1] == '-' && s[2] == 'a' && s[3] == 'n' && len == 10) r = 1; if (s[0] == '-' && s[1] == '-' && s[2] == 't' && s[3] == 'y' && len == 7) r = 2; if (s[0] == '-' && s[1] == '-' && s[2] == 'f' && s[3] == 'o' && len == 8) r = 3; rt.free(s); return r; } // zclsp --types < file.zc: one line per edit, line:column, how many characters // it replaces and the text that goes in int list_types(int first) { int i = 0; word f; int k; load_stdin(first); run.ifon = 1; check_doc(0); run.ifon = 0; sort_facts(); pc_reset(0); while (i < run.nifx) { f = run.ifx + i * 4; pc_to(f[0]); rt.put_n(pline + 1); rt.put_c(':'); rt.put_n(pcol + 1); rt.put_c(32); rt.put_n(f[1]); rt.put_c(32); k = 0; while (k < f[3]) { rt.put_c(run.ift[f[2] + k]); k++; } rt.put_c(10); i++; } return 0; } // the entry point of zclsp. the input is either a source file (checked, and its // diagnostics printed), a file to annotate or to list the types of (--annotate, // --types), or the json-rpc stream of a language server (when it starts with a // Content-Length header). int lsp_main() { int c, k, i; run.docs_on = 0; // the image was made with docs on; the documents checked here are not docs run.chk_heap = 1; dgb = rt.new(DGCAP); sets = rt.new(4 * run.LSW); duri = lsp_grow(0, 0, doccap); dulen = lsp_grow(0, 0, doccap); doff = lsp_grow(0, 0, doccap); dsize = lsp_grow(0, 0, doccap); dver = lsp_grow(0, 0, doccap); dgen = lsp_grow(0, 0, doccap); dlive = lsp_grow(0, 0, doccap); mbuf = lsp_grow(0, 0, mcap); idbuf = lsp_grow(0, 0, idcap); jk = lsp_grow(0, 0, jkcap); kbuf = lsp_grow(0, 0, kcap); lbuf = lsp_grow(0, 0, lcap); ubuf = lsp_grow(0, 0, ucap); hov = lsp_grow(0, 0, hovcap); hname = lsp_grow(0, 0, hnamecap); htb = lsp_grow(0, 0, htbc); chk_i = 0 - 1; jpk = 0 - 2; rfirst = 0 - 1; if (rt.eof()) return 0; c = rt.get_c() & 255; if (c != 'C' && c != 'c') { k = cli_mode(); if (k == 1) annotate(c); else if (k == 2) list_types(c); else if (k == 3) format_cli(c); else cli(c); return 0; } rfirst = c; while (!quit) { if (!read_message()) break; handle(); // a check that was dropped for newer input still has to be published once nothing newer is waiting while (unpub >= 0 && !quit && rt.syscall(42, 0, 0, 0) <= 0) { i = unpub; unpub = 0 - 1; if (dlive[i] && check_publish(i)) publish(i); } } return 0; }