aboutsummaryrefslogtreecommitdiff
path: root/src/lsp/server.zc
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
commit1c53304d03414890af0da7432d1aea87e754a14a (patch)
tree97190d86cf548c9bc63f68c86a4d2a4f09809713 /src/lsp/server.zc
parenta (diff)
feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics
Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor<I,O>, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests
Diffstat (limited to 'src/lsp/server.zc')
-rw-r--r--src/lsp/server.zc2400
1 files changed, 2047 insertions, 353 deletions
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index 98e71c3..a07e4d4 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -1,31 +1,34 @@
-// ============================================================================
-// 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.
-// ============================================================================
+namespace lsp;
+include rt, run;
-// 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.
+//: ============================================================================
+//: 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 chk_unit(int src, int buf, int cap, int sets, int lim);
-int hl_begin(int src);
-int hl_end();
+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.
+//: ---- 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;
@@ -33,25 +36,33 @@ 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.
+//: ---- 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) wcnt++; else raw_c(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 word string, one byte per character.
-int ws(word s) {
+// 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) {
- [24]word b; int i = 0;
+ 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]); }
@@ -75,7 +86,7 @@ int wesc(int c) {
return 0;
}
// a json string from n chars at p, one per word
-int wjs(word p, int n) {
+int wjs(char* p, int n) {
int i = 0;
wc(34);
while (i < n) { wesc(p[i]); i++; }
@@ -85,6 +96,13 @@ int wjs(word p, int n) {
// 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");
@@ -92,18 +110,18 @@ int send(int 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.
+//: ---- 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;
-word duri, dulen, doff, dsize, dver, dgen, dlive;
+char** duri; word dulen, doff, dsize, dver, dgen, dlive;
// Grow a heap buffer while retaining its contents. Globals keep these buffers rooted.
-unchecked word lsp_grow(word old, int count, int capacity) {
- word p = new(capacity);
- fill_w(p, 0, capacity);
- if (old && count) copy_w(p, old, count);
+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.
@@ -119,7 +137,7 @@ int docs_grow() {
// 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(word p, int n) {
+int dfind(char* p, int n) {
int i = 0, k;
while (i < doccap) {
if (dlive[i] && dulen[i] == n) {
@@ -133,7 +151,7 @@ int dfind(word p, int n) {
}
// 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(word p, int n) {
+int docnew(char* p, int n) {
int i = 0, k;
while (1) {
if (i == doccap) docs_grow();
@@ -149,34 +167,43 @@ int docnew(word p, int n) {
}
// the text, its terminator and a spare zero word
int dwords(int n) { return (n >> 2) + 2; }
-// ---- the check ----
+//: ---- 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];
- outmode = 1;
- lt_on = 1; lt_n = 0; ltxn = 0; ht_n = 0;
- chk_unit(doff[i], dgb, dgbcap, sets, CODE_END);
- dgb = dgbuf; dgbcap = dgcap;
- lt_on = 0;
- outmode = 0;
+ 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 < lt_n) { if (lt_off[k] == o) return k; k++; }
+ 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 (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.
+//: ---- 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) {
@@ -210,7 +237,7 @@ int line_off(int l) {
return o;
}
-// ---- diagnostics ----
+//: ---- diagnostics ----
int gd; // the document being reported
// writes a json position, {line, character}, for byte offset off.
int wpos(int off) {
@@ -232,7 +259,7 @@ int gen_diag() {
ws(",\"version\":"); wn(dver[gd]);
ws(",\"diagnostics\":[");
pc_reset(gd);
- while (at < dgpos) {
+ 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);
@@ -271,10 +298,36 @@ int publish(int 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
+//: ---- 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
@@ -320,7 +373,7 @@ int gap_out(int from, int to) {
}
// is c an operator character, which the highlighter marks as an operator.
int isopc(int c) {
- word s = "+-*/%=<>!&|^~?@#$";
+ char* s = "+-*/%=<>!&|^~?@#$";
int i = 0;
while (s[i]) { if (s[i] == c) return 1; i++; }
return 0;
@@ -328,84 +381,94 @@ int isopc(int c) {
// 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 == s_fn || n == s_var || n == s_const || n == s_else || n == s_as || n == s_unchecked || n == s_immediate
- || n == s_case || n == s_default || n == s_do || n == s_while || n == s_enum || n == s_include || n == s_memdebug
- || n == s_enhance || n == s_micro || n == s_vm || n == s_struct || n == s_type || n == s_typecheck;
+ 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 = rsn - 1, mid, v;
+ int lo = 0, hi = run.rsn - 1, mid, v, s, k; char* name; word p;
while (lo <= hi) {
mid = (lo + hi) / 2;
- v = rsrec[mid * RS_W];
+ 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.
+//: 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 < rsn && rsrec[rs_cur * RS_W] < o) rs_cur++;
- if (rs_cur < rsn && rsrec[rs_cur * RS_W] == o) r = rs_cur;
+ 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 = rsrec[r * RS_W + 1];
- if (k == RS_LOCAL) return TT_VAR;
- if (k == RS_PARAM) return TT_PARAM;
- if (k == RS_FIELD) return TT_PROP;
- v = rsrec[r * RS_W + 2];
+ 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 == K_KW) { if (v & 16) return TT_MACRO; return TT_KW; }
- if (sk == K_TYPE) return TT_TYPE;
- if (sk == K_FN) { if (v & 32) return TT_FN; return TT_VAR; }
- if (sk == K_ENH) return TT_FN;
- if (sk == K_CONST) return TT_ENUM;
+ 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 == T_ID && pv == s_fn) return TT_FN;
- if (pt == T_OP && pv == s_dot) {
- if (n == s_m || n == s_d || n == s_u) return TT_KW;
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) return TT_FN;
+ 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 == T_OP && pv == s_arrow) {
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) return TT_FN;
+ 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;
- peek();
- if (pk_tk == T_OP && pk_tv == s_lp) nx = 1;
- s = lookup(n);
+ 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 (skind[s] == K_KW) { if (s >= ubase) return TT_MACRO; return TT_KW; }
- if (skind[s] == K_TYPE) return TT_TYPE;
- if (skind[s] == K_FN && !(sflag[s] & F_DEF)) return TT_VAR; // never defined: the check says so
- if (skind[s] == K_FN || skind[s] == K_ENH) {
+ 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 (lsget(1, n)) return TT_PARAM;
- if (lsget(0, n)) return TT_VAR;
+ if (run.lsget(1, n)) return TT_PARAM;
+ if (run.lsget(0, n)) return TT_VAR;
return TT_FN;
}
- if (skind[s] == K_CONST) return TT_ENUM;
- if (lsget(1, n)) return TT_PARAM;
+ if (run.skind[s] == run.K_CONST) return TT_ENUM;
+ if (run.lsget(1, n)) return TT_PARAM;
return TT_VAR;
}
- if (lsget(2, n)) return TT_TYPE;
- if (lsget(1, n)) return TT_PARAM;
- if (lsget(0, n)) return TT_VAR;
- if (lsget(3, n)) return TT_PROP;
+ 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
@@ -416,40 +479,119 @@ int gen_tokens() {
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":{\"data\":[");
pc_reset(gd);
tfirst = 1; tprevl = 0; tprevc = 0; rs_cur = 0;
- hl_begin(doff[gd]);
- while (tk != T_EOF) {
- o = toff; e = toff + toklen;
- if (toklen > 0 && o >= last) {
+ 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 (tk == T_ID) ty = rs_class(o, tv, pt, pv);
- else if (tk == T_NUM) {
+ 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 (tk == T_FLT) ty = TT_NUM;
- else if (tk == T_STR || tk == T_BSTR) ty = TT_STR;
- else if (tk == T_OP) { if (isopc(dc(pbase, o))) ty = TT_OP; }
+ } 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 = tk; pv = tv;
- next();
+ pt = run.tk; pv = run.tv;
+ run.next();
}
gap_out(last, plen);
- hl_end();
+ run.hl_end();
ws("]}}");
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 hline, hchar, hn, hoff, hcursor; word hov; int hovcap = 2300; word htb; int htbc = 80, htn, ann_name_at; word hname; int hnamecap = 80; int hdl, hdle, hcs;
-word ann_text; int ann_n;
-word mbuf, idbuf, jk, kbuf; int mcap = 64, idcap = 64, jkcap = 16, kcap = 40; int mn, idn, idtype; word ubuf; int ucap = URIW; int un, jver, kn;
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() {
@@ -457,8 +599,8 @@ int jgetc() {
if (jpk != 0 - 2) { c = jpk; jpk = 0 - 2; return c; }
if (jleft <= 0) return 0 - 1;
jleft--;
- if (eof()) { jleft = 0; return 0 - 1; }
- return get_c() & 255;
+ 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; }
@@ -492,6 +634,12 @@ int jput(int sink, int b) {
} 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;
}
@@ -516,7 +664,7 @@ int jutf8(int sink, int cp) {
int jhex4() {
int v = 0, i = 0, c, h;
while (i < 4) {
- c = jgetc(); h = hexv(c);
+ c = jgetc(); h = run.hexv(c);
if (h < 0) { junget(c); return v; }
v = v * 16 + h; i++;
}
@@ -551,17 +699,26 @@ int jstr(int sink) {
}
// the code of the json key in kbuf, for the keys the server reads (0 for the others).
int kcode() {
- if (eq_w(kbuf, "method")) return 1;
- if (eq_w(kbuf, "id")) return 2;
- if (eq_w(kbuf, "params")) return 3;
- if (eq_w(kbuf, "textDocument")) return 4;
- if (eq_w(kbuf, "uri")) return 5;
- if (eq_w(kbuf, "text")) return 6;
- if (eq_w(kbuf, "contentChanges")) return 7;
- if (eq_w(kbuf, "version")) return 8;
- if (eq_w(kbuf, "position")) return 9;
- if (eq_w(kbuf, "line")) return 10;
- if (eq_w(kbuf, "character")) return 11;
+ 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.
@@ -609,11 +766,15 @@ int jval(int key, int dep) {
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))) { sink = 4; thave = 1; tover = 0; tbase = text_start(); tn = 0; }
+ 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;
@@ -626,43 +787,48 @@ int jval(int key, int dep) {
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 == 10 || key == 11) && janc(dep, 9))) {
+ if ((key == 8 && janc(dep, 4)) || key == 13 || ((key == 10 || key == 11) && (janc(dep, 9) || janc(dep, 17)))) {
if (c == '-') {
neg = 1;
- } else if (isdig(c)) k = k * 10 + c - '0';
+ } else if (run.isdig(c)) k = k * 10 + c - '0';
}
c = jgetc();
}
junget(c);
if (sink == 2) {
idbuf[idn] = 0;
- if (eq_w(idbuf, "null")) idtype = 0;
+ if (rt.eq_w(idbuf, "null")) idtype = 0;
}
if (key == 8 && janc(dep, 4)) { if (neg) k = 0 - k; jver = k; }
- if (key == 10 && janc(dep, 9)) hline = k;
- if (key == 11 && janc(dep, 9)) hchar = 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 = new(tcap);
+ tbase = rt.new(tcap);
return tbase;
}
-// ---- messages in ----
-word lbuf; int lcap = 160; int rfirst = 0 - 1;
+//: ---- 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 (eof()) { if (!n) return 0 - 1; return n; }
- c = get_c() & 255;
+ if (rt.eof()) { if (!n) return 0 - 1; return n; }
+ c = rt.get_c() & 255;
}
if (c == 10) return n;
if (c != 13) {
@@ -680,7 +846,7 @@ int lower(int c) {
// does the header line in lbuf start with content-length: (in any case).
int is_clen(int n) {
if (n < 15) return 0;
- word s = "content-length:"; int i = 0;
+ char* s = "content-length:"; int i = 0;
while (s[i]) { if (lower(lbuf[i]) != s[i]) return 0; i++; }
return 1;
}
@@ -695,18 +861,19 @@ int read_message() {
if (is_clen(n)) {
i = 15; size = 0;
while (i < n && (lbuf[i] == 32 || lbuf[i] == 9)) i++;
- while (i < n && isdig(lbuf[i])) { size = size * 10 + lbuf[i] - '0'; 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;
+ 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 ----
+//: ---- messages out ----
int wid() {
if (idtype == 2) {
wjs(idbuf, idn);
@@ -718,7 +885,7 @@ int wid() {
int gen_init() {
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid();
ws(",\"result\":{\"capabilities\":{\"textDocumentSync\":{\"openClose\":true,\"change\":1,\"save\":{\"includeText\":true}},");
- ws("\"hoverProvider\":true,\"semanticTokensProvider\":{\"legend\":{\"tokenTypes\":[\"keyword\",\"type\",\"function\",\"variable\",\"string\",\"number\",\"operator\",\"comment\",\"parameter\",\"property\",\"macro\",\"enumMember\"],\"tokenModifiers\":[]},\"full\":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;
}
@@ -729,7 +896,7 @@ int gen_null() {
ws(",\"result\":null}");
return 0;
}
-// the error code the next error reply carries.
+//: the error code the next error reply carries.
int errcode;
// an error reply with the code errcode and a matching message.
int gen_error() {
@@ -756,10 +923,15 @@ int reply_error(int code) {
return 0;
}
-// ---- 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
+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) {
@@ -815,6 +987,8 @@ word hover_decl(int base, int ls, int le) {
// 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;
@@ -839,11 +1013,11 @@ int hov_put(int c) {
hov[hn] = c; hn++;
return 0;
}
-// comment ranges are scanned once per document version, outside strings and chars.
+//: 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, start, kind, bol = 1, quote;
+ 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) {
@@ -855,9 +1029,9 @@ int hover_comments(int i) {
while (o < size && dc(base, o) != quote) { if (dc(base, o) == 92) o++; o++; }
o++; bol = 0; continue;
}
- kind = 0; start = o;
+ kind = 0; run.start = o;
if (c == '/' && dc(base, o + 1) == '/') {
- kind = 1; if (bol) kind = 2;
+ 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;
@@ -868,7 +1042,7 @@ int hover_comments(int i) {
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] = start; hc_end[hc_n] = o; hc_kind[hc_n] = kind; hc_n++;
+ hc_start[hc_n] = run.start; hc_end[hc_n] = o; hc_kind[hc_n] = kind; hc_n++;
}
return 0;
}
@@ -906,22 +1080,77 @@ int hover_comment_text(int i, int index) {
}
return hn - before;
}
-// attach leading docs and adjacent block docs to this exact declaration name.
-int hover_docs(int i, int head, int name, int length) {
- int k, first = -1, last = -1, pos = head, base = doff[i];
+// 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) { k = first; while (k <= last) { hover_comment_text(i, k); k++; } }
+ if (first >= 0 && !section) { k = first; while (k <= last) { hover_comment_text(i, k); k++; } }
k = 0;
while (k < hc_n) {
- if (hc_kind[k] == 3 && (k < first || k > last)) {
- if (hc_start[k] >= head && hc_end[k] <= name && doc_gap(base, hc_end[k], name)) hover_comment_text(i, k);
- else if (hc_start[k] >= name + length && doc_gap(base, name + length, hc_start[k])) hover_comment_text(i, k);
+ 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++;
}
@@ -930,33 +1159,43 @@ int hover_docs(int i, int head, int name, int length) {
// 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, start, end;
+ 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) {
+ 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 + 3;
+ 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++;
- if (ann_type(i, k)) {
- start = ann_name_at;
- while (start > 0 && dc(ann_text, start - 1) != 10 && dc(ann_text, start - 1) != ';' && dc(ann_text, start - 1) != '}') start--;
- while (start < ann_name_at && (dc(ann_text, start) == 32 || dc(ann_text, start) == 9)) start++;
+ 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 > 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 = start; while (k < end) { hov_put(dc(ann_text, k)); k++; }
- hov_text("\n```"); htn = 0; hover_docs(i, hdl, hdl, 0);
+ 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);
+ 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.
@@ -971,28 +1210,28 @@ int gen_hover() {
// 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
-word blobdoc(word nm) {
- word p = GORG[5]; int k;
+bstr blobdoc(char* nm) {
+ word p = rt.GORG[5]; int k;
while (*p) {
- if (p[0] == K_DOC && p[3] == 'd' && p[4] == 'o' && p[5] == 'c' && p[6] == ' ') {
+ 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 + len_w(p + 3) + 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
-word blobsig(word nm) {
- word p = GORG[5]; int k;
+bstr blobsig(char* nm) {
+ word p = rt.GORG[5]; int k;
while (*p) {
- if (p[0] == K_DOC && p[3] == 's' && p[4] == 'i' && p[5] == 'g' && p[6] == ' ') {
+ 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 + len_w(p + 3) + 1;
+ p = p + 3 + rt.len_w(p + 3) + 1;
}
return 0;
}
@@ -1089,7 +1328,7 @@ int hover_local(int i) {
return 0;
}
// does the word from q to e equal w
-word hl_is(int base, int q, int e, word 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;
@@ -1102,7 +1341,7 @@ word hl_kw(int base, int q) {
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
+// 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) {
@@ -1118,11 +1357,11 @@ 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 =.
+//: 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
@@ -1134,11 +1373,11 @@ int annotate_doc(int i) {
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;
- ifstrict = 1;
+ run.ifstrict = 1;
check_doc(0);
nbase = err_lines(abase);
while (round < AROUNDS) {
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
save_facts();
if (!anf) break;
k = 0; while (k < anf) { amask[k] = 0; k++; }
@@ -1149,7 +1388,7 @@ int annotate_doc(int i) {
doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1;
round++;
}
- ifstrict = 0; ifon = 0;
+ 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;
@@ -1269,57 +1508,53 @@ int ann_type(int i, int o) {
}
// 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 = sb;
- hn = 0;
- hov_put('`'); hov_put('`'); hov_put('`'); hov_put('z'); hov_put('c'); hov_put(10);
- while (k < se) { hov_put(dc(base, k)); k++; }
- if (kind == 1) {
- hov_put(' '); hov_put(' '); hov_put('/'); hov_put('/'); hov_put(' '); k = 0; while ("parameter"[k]) { hov_put("parameter"[k]); k++; }
- if (htn > 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- }
- else {
- hov_put(' '); hov_put(' '); hov_put('/'); hov_put('/'); hov_put(' '); k = 0; while ("local"[k]) { hov_put("local"[k]); k++; }
- if (lk >= 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = lt_at[lk]; while (k < lt_at[lk] + lt_len[lk]) { hov_put(ltxt[k]); k++; } }
- else if (htn > 0) { k = 0; while ("; type "[k]) { hov_put("; type "[k]); k++; } k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- }
- hov_put(10); hov_put('`'); hov_put('`'); hov_put('`');
+ 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) {
- int ss, ds, k = 0, n = 0;
+ 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 < len_s(ss)) { hov_put(at_s(ss, k)); k++; }
+ 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 < len_s(ds)) { hov_put(at_s(ds, k)); k++; }
+ 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(word w) {
+word hname_is(char* w) {
int k = 0;
while (w[k] && hname[k] == w[k]) k++;
return !w[k] && !hname[k];
}
-// append a word string, or a compiler type, to the markdown buffer.
-int hov_text(word s) { int k = 0; while (s[k]) { hov_put(s[k]); k++; } return 0; }
+// 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 = outmode, sb = outbuf, sn = outn, sc = outcap, need;
- outmode = 3; outn = 0; tprint(t); need = hn + outn;
+ 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);
}
- outmode = 2; outbuf = hov; outn = hn; outcap = hovcap; tprint(t); hn = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ 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.
@@ -1327,46 +1562,47 @@ int hover_type_info(word t) {
int kind, k;
if (!t) return 0;
kind = t[0];
- if (kind == TY_PTR) {
+ 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 == TY_ARR) {
+ } else if (kind == run.TY_ARR) {
hov_text("\n\nan array stored in place, with a fixed length of ");
- k = t[3]; hov_number(k); hov_text(" elements of type `"); hov_type(t[4]); hov_text("`. storage: "); hov_number(tsz(t)); hov_text(" words.");
- } else if (kind == TY_DYN) {
+ 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 == TY_STRUCT) {
- hov_text("\n\na struct stored in place. storage: "); hov_number(tsz(t)); hov_text(" words.");
+ } 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 == TY_FN) hov_text("\n\na callable value. the type lists its result and parameter types.");
+ } 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 = outmode, sb = outbuf, sn = outn, sc = outcap;
+ 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; }
- outmode = 2; outbuf = hov; outn = hn; outcap = hovcap; put_n(n); hn = outn;
- outmode = sm; outbuf = sb; outn = sn; outcap = sc;
+ 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 < ht_n) {
- end = ht_end[k];
- while (end > 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 >= ht_start[k] && hcursor < end && end - ht_start[k] > width) { best = k; width = end - ht_start[k]; }
+ 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 = ht_type[best];
+ 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"))) {
@@ -1379,7 +1615,8 @@ int hover_type_span(int i) {
// 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.
-int kwtext() {
+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";
@@ -1396,8 +1633,9 @@ int kwtext() {
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 object on the heap. bound to a local, the local owns it (tagged m) and the block frees it when it ends, so you don't write free yourself.";
- if (hname_is("free")) return "free(d*p)~frees a heap object. the block is kept until the next collection finds nothing pointing at it, and a second free does nothing. free takes an object tagged d, so a checked call is free(d*p).";
+ 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.";
@@ -1408,8 +1646,9 @@ int kwtext() {
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~a named constant. its value is worked out while compiling, so it can size an array or label a case.";
+ 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.";
@@ -1423,13 +1662,14 @@ int kwtext() {
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;~leaves the function with a value. a u object leaves with u*x, and the function's return is then owned.";
+ 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<T> { } 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 = [3]int; for a pointer to an array.";
- if (hname_is("include")) return "include \"path\";~reads a file and compiles it at that point, its signatures first.";
+ 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.";
@@ -1441,7 +1681,7 @@ int kwtext() {
return 0;
}
// the markdown of a keyword: its form as a code block, then what it does
-int kw_put(word s) {
+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++; }
@@ -1498,41 +1738,42 @@ int hover_method(int i) {
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);
+ 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; word t = 0;
+ 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 = lt_type[entry];
+ 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 < ht_n) { if (ht_end[k] == o && ht_start[k] < head) head = ht_start[k]; k++; }
+ 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");
- if (untyped && kind != 1) hov_text("let ");
- else if (!untyped && t) { hov_type(t); 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");
- if (htn || entry >= 0) {
- hov_text("; type ");
+ hov_put('(');
if (htn) { k = 0; while (k < htn) { hov_put(htb[k]); k++; } }
- else { k = lt_at[entry]; while (k < lt_at[entry] + lt_len[entry]) { hov_put(ltxt[k]); k++; } }
- }
- hov_text("\n```"); hover_type_info(t); hover_docs(i, head, o, end - o);
- if (hoff != o) hover_docs(i, hoff, hoff, len_w(hname));
+ 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;
}
@@ -1542,14 +1783,14 @@ int hover_at_decl(int i, int o) { return hover_named(i, o, 2); }
int hover_storage(int r) {
word p, t = 0; int entry, kind;
if (r < 0 || !hn) return 0;
- p = rsrec + r * RS_W; kind = p[6];
+ p = run.rsrec + r * run.RS_W; kind = p[6];
if (!kind) {
- if (p[1] == RS_LOCAL || p[1] == RS_PARAM || p[1] == RS_FIELD) {
- entry = lt_find(p[2]); if (entry >= 0) t = lt_type[entry];
- } else if (p[1] == RS_SYM && p[3] >= 0) t = stype[p[3]];
- if (!t || (t[0] != TY_PTR && t[0] != TY_ARR && t[0] != TY_DYN)) return 0;
+ 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] == TY_ARR) { hov_number(t[3]); hov_text(" elements"); }
+ 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;
}
@@ -1565,17 +1806,95 @@ int hover_storage(int r) {
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;
+ 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) { hover_blob(i); return hn; }
+ 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;
}
@@ -1588,72 +1907,1441 @@ int do_hover(int i) {
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 = rsrec[r * RS_W + 1]; v = rsrec[r * RS_W + 2]; }
- if (k == RS_LOCAL && v >= 0) hover_at_decl(i, v);
- else if (k == RS_PARAM && v >= 0) hover_named(i, v, 1);
- else if (k == RS_FIELD && v >= 0) hover_named(i, v, 4);
- else if (k == RS_LOCAL) hover_local(i);
- else if (k == RS_SYM) {
- if ((v & 15) == K_GVAR || (v & 15) == K_GARR) {
- s = rsrec[r * RS_W + 3]; q = 0; o = 0 - 1;
- while (q < rsn) { if (rsrec[q * RS_W + 3] == s && lt_find(rsrec[q * RS_W]) >= 0) { o = rsrec[q * RS_W]; break; } q++; }
+ 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);
+ 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;
}
-// ---- dispatch ----
-// the text just read belongs to the document named by the uri
+
+//: ---- 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]++;
+ 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;
- if (eq_w(mbuf, "initialize")) { inited = 1; send(gen_init); return 0; }
- if (eq_w(mbuf, "exit")) { quit = 1; return 0; }
- if (eq_w(mbuf, "shutdown")) { shut = 1; send(gen_null); return 0; }
+ 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 (eq_w(mbuf, "textDocument/didOpen") || eq_w(mbuf, "textDocument/didChange") || eq_w(mbuf, "textDocument/didSave")) {
+ 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) { check_doc(i); publish(i); }
+ 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 (eq_w(mbuf, "textDocument/didClose")) {
+ if (rt.eq_w(mbuf, "textDocument/didClose")) {
i = dfind(ubuf, un);
if (i >= 0) {
- dgpos = 0; publish(i); // an empty list clears its markers
+ 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 (eq_w(mbuf, "textDocument/semanticTokens/full")) {
+ 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 (eq_w(mbuf, "textDocument/hover")) {
+ 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);
@@ -1663,24 +3351,24 @@ int handle() {
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
-unchecked int load_stdin(int first) {
+//: ---- 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 = new(cap);
+ tbase = rt.new(cap);
while (c >= 0) {
if ((n >> 2) + 4 >= cap) {
- nb = new(cap * 2); k = 0;
+ 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 (eof()) break;
- c = get_c() & 255;
+ if (rt.eof()) break;
+ c = rt.get_c() & 255;
}
if (n & 3) { } else tbase[n >> 2] = 0;
tbase[(n >> 2) + 1] = 0;
@@ -1694,28 +3382,28 @@ int cli(int first) {
load_stdin(first);
check_doc(0);
pc_reset(0);
- while (at < dgpos) {
+ while (at < run.dgpos) {
o = dgb[at + 2];
- if (o < 0) { put_s("line "); put_n(dgb[at + 1]); }
- else { pc_to(o); put_n(pline + 1); put_c(':'); put_n(pcol + 1); }
- if (dgb[at] == 1) put_s(": error: "); else if (dgb[at] == 2) put_s(": warning: "); else put_s(": hint: ");
- i = 0; while (i < dgb[at + 4]) { put_c(dgb[at + 5 + i]); i++; }
- put_c(10);
+ 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 (!dgpos) put_s("no errors\n");
+ 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.
+//: ---- 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) {
@@ -1732,13 +3420,13 @@ int aw_end() {
}
// the lines of the errors the last check found
int err_lines(word dst) {
- int at = 0, n = 0, need = dgpos / 5 + 1, baseline = dst == abase;
+ 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 < dgpos) {
+ while (at < run.dgpos) {
if (dgb[at] == 1) { dst[n] = dgb[at + 1]; n++; }
at = at + 5 + dgb[at + 4];
}
@@ -1753,10 +3441,10 @@ int in_set(int v, word set, int n) {
// 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 < nifx) {
+ while (i < run.nifx) {
j = i;
- while (j > 0 && ifx[(j - 1) * 4] > ifx[j * 4]) {
- a = ifx + (j - 1) * 4; b = ifx + j * 4;
+ 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;
@@ -1770,20 +3458,20 @@ int sort_facts() {
int save_facts() {
int i = 0, nt = 0; word f; int k;
sort_facts();
- if (nifx > afcap) {
- while (nifx > afcap) { if (!afcap) afcap = 4096; else afcap = afcap * 2; }
+ 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 (nift > atcap) {
- while (nift > atcap) { if (!atcap) atcap = 24000; else atcap = atcap * 2; }
+ 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 < nifx) {
- f = ifx + i * 4;
+ 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] = ift[f[2] + k]; k++; }
+ k = 0; while (k < f[3]) { aft[nt + k] = run.ift[f[2] + k]; k++; }
nt = nt + f[3];
anf++;
}
@@ -1812,7 +3500,7 @@ int apply_masked(int dst) {
word fresh_half() {
int n = dsize[0], i = 0;
while (i < anf) { n = n + afx[i * 4 + 3]; i++; }
- ann_half = new(dwords(n));
+ ann_half = rt.new(dwords(n));
return ann_half;
}
// does the document with the masked facts applied check with no new error?
@@ -1845,11 +3533,11 @@ int try_range(int lo, int hi) {
int annotate(int first) {
int round = 0, n, i;
load_stdin(first);
- ifstrict = 1; // the typechecker runs, so a type that doesn't fit how a name is used is an error
+ 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) {
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
save_facts();
if (!anf) break;
i = 0; while (i < anf) { amask[i] = 0; i++; }
@@ -1860,23 +3548,24 @@ int annotate(int first) {
doff[0] = ann_half; dsize[0] = awn; dgen[0] = dgen[0] + 1;
round++;
}
- ifstrict = 0;
+ run.ifstrict = 0;
i = 0;
- while (i < dsize[0]) { put_c(dc(doff[0], i)); i++; }
+ 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
-unchecked int cli_mode() {
- int n = syscall(SYS_ARGC, 0, 0, 0), len; word s; int r = 0;
+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 = syscall(SYS_ARG, 1, 0, 0);
- if (len != 10 && len != 7) return 0;
- s = new(len + 1);
- syscall(SYS_ARG, 1, s, len + 1);
+ 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;
- free(s);
+ 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
@@ -1884,15 +3573,15 @@ unchecked int cli_mode() {
int list_types(int first) {
int i = 0; word f; int k;
load_stdin(first);
- ifon = 1; check_doc(0); ifon = 0;
+ run.ifon = 1; check_doc(0); run.ifon = 0;
sort_facts();
pc_reset(0);
- while (i < nifx) {
- f = ifx + i * 4;
+ while (i < run.nifx) {
+ f = run.ifx + i * 4;
pc_to(f[0]);
- put_n(pline + 1); put_c(':'); put_n(pcol + 1); put_c(32); put_n(f[1]); put_c(32);
- k = 0; while (k < f[3]) { put_c(ift[f[2] + k]); k++; }
- put_c(10);
+ 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;
@@ -1903,27 +3592,32 @@ int list_types(int first) {
// --types), or the json-rpc stream of a language server (when it starts with a
// Content-Length header).
int lsp_main() {
- int c, k;
- docs_on = 0; // the image was made with docs on; the documents checked here are not docs
- chk_heap = 1;
- dgb = new(DGCAP); sets = new(4 * LSW);
+ 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 (eof()) return 0;
- c = get_c() & 255;
+ 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 cli(c);
+ 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;
}