aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 08:09:40 +0000
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 08:09:40 +0000
commita3e9e53e64a1aa5692bca2b60bcbff6a0fcfdba1 (patch)
tree2598c50e36e488935211c2e304b720a58315bacb
parentembed: tests, editor support and docs; js vm file access; test fixes (diff)
lsp: quick fixes for hints and bare library names; comment cleanup
an undefined bare name that a module exports now says which one: "put_c is used but never defined; did you mean to include rt and use rt.put_c?", and the import quick fix is titled the same way and marked preferred when it is the only one, so the editor's auto-fix applies it. hints gain quick fixes that keep the program's meaning: an unused parameter, or a local with no other use, is renamed to _; a constant local with one declarator becomes const; a power-of-two multiply becomes a parenthesized shift (not when a * / or % binds its left operand); and let p = rt.new(n) with a small constant becomes let p[n]. the hints no longer fire for _ discards or for locals already declared const. whole-line /* */ comments in zc sources are // comments now, and //: is gone from the maintained sources: the notes it marked were mostly the documentation of the declaration below, which hovers now show. section headers are separated from the declarations after them.
-rw-r--r--docs/LEARN.md4
-rw-r--r--examples/learn/29-block-comments.zc2
-rw-r--r--src/compiler/compiler.zc562
-rw-r--r--src/lib/std.zc32
-rw-r--r--src/lsp/server.zc394
-rw-r--r--src/runtime/rt.zc218
-rw-r--r--tests/drivers/codeactions_client_test.js2
-rw-r--r--tests/drivers/hint_fixes_test.js66
-rw-r--r--tests/drivers/toolchain_lsp_contract_test.js4
-rwxr-xr-xtests/test.sh3
10 files changed, 780 insertions, 507 deletions
diff --git a/docs/LEARN.md b/docs/LEARN.md
index 8b0e1e6..b649b4f 100644
--- a/docs/LEARN.md
+++ b/docs/LEARN.md
@@ -915,7 +915,7 @@ output:
the language server reports informational hints for likely mistakes. by default, `hints on;` shows blue informational squiggles. `hints small;` keeps the same hints but displays compact dots; it does not reduce their number or kinds. `hints off;` suppresses hints after the directive. each setting applies from its source position.
-an inline block comment immediately before a numeric literal marks that literal as intentional and suppresses its magic-number hint. a comment before another operand does not suppress the hint. a block comment immediately above a declaration provides hover documentation; `//:` marks an implementation note instead.
+an inline block comment immediately before a numeric literal marks that literal as intentional and suppresses its magic-number hint. a comment before another operand does not suppress the hint. block comments are for this kind of place, between code on one line; a comment on lines of its own is written with `//`. the `//` comment immediately above a declaration is its hover documentation.
[example source](../examples/learn/29-block-comments.zc):
@@ -923,7 +923,7 @@ an inline block comment immediately before a numeric literal marks that literal
namespace;
include rt;
-/* adds a memory offset to an address. */
+// adds a memory offset to an address.
int offset_address(int address) {
return address + /* offset in memory */ 0x40ff;
}
diff --git a/examples/learn/29-block-comments.zc b/examples/learn/29-block-comments.zc
index a5088eb..9468cc5 100644
--- a/examples/learn/29-block-comments.zc
+++ b/examples/learn/29-block-comments.zc
@@ -1,7 +1,7 @@
namespace;
include rt;
-/* adds a memory offset to an address. */
+// adds a memory offset to an address.
int offset_address(int address) {
return address + /* offset in memory */ 0x40ff;
}
diff --git a/src/compiler/compiler.zc b/src/compiler/compiler.zc
index c6e1ca4..40d83cf 100644
--- a/src/compiler/compiler.zc
+++ b/src/compiler/compiler.zc
@@ -1,59 +1,59 @@
namespace run;
include rt;
-//: ============================================================================
-//: compiler.zc: the resident zc compiler. it lives in the genome next to the
-//: runtime, compiles source straight to vm words in memory, and is visible to
-//: the programs it compiles: every function and global here can be called or
-//: patched by name, and parse words (immediate fns) run inside it.
-//: ============================================================================
+// ============================================================================
+// compiler.zc: the resident zc compiler. it lives in the genome next to the
+// runtime, compiles source straight to vm words in memory, and is visible to
+// the programs it compiles: every function and global here can be called or
+// patched by name, and parse words (immediate fns) run inside it.
+// ============================================================================
-//: how this file is organized, in the order a source file travels through it:
-//:
-//: 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a
-//: kind (tk), a value (tv: number or interned name) and a
-//: place (line, offset, length). sources nest: stdin, a
-//: token list for macros, a string for eval.
-//: 2 symbols every name is interned once in a string pool; symbols
-//: (functions, globals, constants, keywords, types) hang
-//: off the interned name and shadow one another.
-//: 3 types records in the type area, hash-consed, so comparing two
-//: types is comparing two words. typing is optional: an
-//: untyped program is all "word".
-//: 4 analyzer the ownership/lifetime pass. it runs while the parser
-//: emits code, tracking for every local whether it owns an
-//: object in this block (m), moves one up and out (u), or
-//: moves one down into a lower scope (d). borrowing is
-//: tracked separately. the pass inserts frees, and joins
-//: merge that knowledge at
-//: if/else, loops and switches.
-//: 5 expressions precedence climbing over the operator tables, which a
-//: program can extend at compile time.
-//: 6 statements blocks, control flow, switch, asm blocks, quote/grab.
-//: 7 functions signatures, prologues, closures, methods, generics,
-//: fattening (typed new) and the top level.
-//: 8 setup the keyword table, built-in names, entry point.
-//: 9 check mode the same compiler run over an in-memory document for the
-//: language server: errors and hints become records.
-//:
-//: the compiler emits vm words directly into memory at "here" (there is no
-//: intermediate tree). a forward reference emits a placeholder literal and
-//: remembers the site on a patch chain; defining the name patches the chain.
-//: the compiler is itself written in zc and lives in the genome next to rt.zc,
-//: so a program being compiled can call any function in this file, and
-//: immediate fns (parse words) run in the middle of compilation and can read
-//: tokens, emit code, or add operators. a few conventions that recur:
-//: - "plain word": most values are untyped 64-bit words. a pointer is just a
-//: word that happens to be a heap address.
-//: - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op.
-//: - names ending in an s_ prefix are interned spellings the parser compares
-//: against by pointer, which is why intern exists.
-//: - serr reports and carries on; die reports and skips input to the next
-//: ; or }. nothing in here raises or unwinds.
-//: - a backtick outside a comment or a literal ends the source (srcend). zc0,
-//: the c bootstrap, reads the same way, so a backtick in a comment is fine.
-//:
-//: ---- compiler data region (@map-cc) ----
+// how this file is organized, in the order a source file travels through it:
+//
+// 1 lexer bytes -> tokens (nextch, lex1, next/peek). a token is a
+// kind (tk), a value (tv: number or interned name) and a
+// place (line, offset, length). sources nest: stdin, a
+// token list for macros, a string for eval.
+// 2 symbols every name is interned once in a string pool; symbols
+// (functions, globals, constants, keywords, types) hang
+// off the interned name and shadow one another.
+// 3 types records in the type area, hash-consed, so comparing two
+// types is comparing two words. typing is optional: an
+// untyped program is all "word".
+// 4 analyzer the ownership/lifetime pass. it runs while the parser
+// emits code, tracking for every local whether it owns an
+// object in this block (m), moves one up and out (u), or
+// moves one down into a lower scope (d). borrowing is
+// tracked separately. the pass inserts frees, and joins
+// merge that knowledge at
+// if/else, loops and switches.
+// 5 expressions precedence climbing over the operator tables, which a
+// program can extend at compile time.
+// 6 statements blocks, control flow, switch, asm blocks, quote/grab.
+// 7 functions signatures, prologues, closures, methods, generics,
+// fattening (typed new) and the top level.
+// 8 setup the keyword table, built-in names, entry point.
+// 9 check mode the same compiler run over an in-memory document for the
+// language server: errors and hints become records.
+//
+// the compiler emits vm words directly into memory at "here" (there is no
+// intermediate tree). a forward reference emits a placeholder literal and
+// remembers the site on a patch chain; defining the name patches the chain.
+// the compiler is itself written in zc and lives in the genome next to rt.zc,
+// so a program being compiled can call any function in this file, and
+// immediate fns (parse words) run in the middle of compilation and can read
+// tokens, emit code, or add operators. a few conventions that recur:
+// - "plain word": most values are untyped 64-bit words. a pointer is just a
+// word that happens to be a heap address.
+// - a code word is lit(v) = 2v+1 (odd) or op(k) = 2k (even). see lit and op.
+// - names ending in an s_ prefix are interned spellings the parser compares
+// against by pointer, which is why intern exists.
+// - serr reports and carries on; die reports and skips input to the next
+// ; or }. nothing in here raises or unwinds.
+// - a backtick outside a comment or a literal ends the source (srcend). zc0,
+// the c bootstrap, reads the same way, so a backtick in a comment is fine.
+//
+// ---- compiler data region (@map-cc) ----
// src/tools/genmap computes these addresses from the compiler table sizes for each
// build. later regions (the type area, local types, exit records) are computed
@@ -115,56 +115,56 @@ const JOIN_PATHS = 0x40;
const O3_REGIONS = 0xA;
// @end-map
-//: table bases start in the generated map; growth copies columns without changing indices.
+// table bases start in the generated map; growth copies columns without changing indices.
-/* interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id. */
+// interned spelling of each symbol, indexed by symbol id; use lookup(name) to find an id.
char** sname = sname_BASE;
-/* kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC. */
+// kind of each symbol: K_FN, K_GVAR, K_GARR, K_CONST, K_KW, K_ENH, K_TYPE or K_DOC.
word skind = skind_BASE;
-/* value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind. */
+// value of each symbol, indexed by symbol id: a function code address, variable storage address, constant value, enhancement slot or type record, according to skind.
word sval = sval_BASE;
-/* packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags. */
+// packed function signature for each symbol; includes the owned-return bit, parameter count and parameter lifetime tags.
word ssig = ssig_BASE;
-/* definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC. */
+// definition and compilation flags for each symbol, such as F_DEF, F_DECL, F_GEN and F_STATIC.
word sflag = sflag_BASE;
-/* head of unresolved code references for each symbol; references are patched when the symbol is defined. */
+// head of unresolved code references for each symbol; references are patched when the symbol is defined.
word sfix = sfix_BASE;
-/* previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain. */
+// previous symbol with the same interned name, stored as id plus one; zero ends its shadow chain.
word snext = snext_BASE;
// retained source metadata for functions that can be transformed at runtime.
word fnmeta;
int syntax_host_floor, syntax_host_from; word syntax_host_symbols;
-/* interned name of each currently active local variable or parameter. */
+// interned name of each currently active local variable or parameter.
char** lname = lname_BASE;
-/* frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame. */
+// frame slot of each local; laddr(lslot[i]) emits its address relative to the current frame.
word lslot = lslot_BASE;
-/* scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks. */
+// scope depth where each local was declared; the lifetime analyzer uses it when values move between blocks.
word lhome = lhome_BASE;
-/* ownership state of each local: plain, m, u, d, dead, child or borrowed. */
+// ownership state of each local: plain, m, u, d, dead, child or borrowed.
word lstate = lstate_BASE;
-/* owner local of a borrowed or container-owned local; lroot(i) follows this chain. */
+// owner local of a borrowed or container-owned local; lroot(i) follows this chain.
word lowner = lowner_BASE;
-/* source line of each local's last tracked use; zero means it has not been used. */
+// source line of each local's last tracked use; zero means it has not been used.
word lused = lused_BASE;
-/* nonzero for locals stored in place as fixed arrays or structs; their expression value is their address. */
+// nonzero for locals stored in place as fixed arrays or structs; their expression value is their address.
word larr = larr_BASE;
-/* nonzero when a local is responsible for releasing its owned object. */
+// nonzero when a local is responsible for releasing its owned object.
word lown = lown_BASE;
-/* code location to patch for each unresolved reference record. */
+// code location to patch for each unresolved reference record.
word fsite = fsite_BASE;
-/* next unresolved reference record, stored as index plus one; zero ends a patch chain. */
+// next unresolved reference record, stored as index plus one; zero ends a patch chain.
word fnext = fnext_BASE;
int symcap = NS, loccap = NL, fixcap = NF;
const word R_TY = rt.ZFP - 16384, R_TYEND = rt.ZFP;
-//: interned text and type records never move: only their index vectors grow.
+// interned text and type records never move: only their index vectors grow.
char** namehash = R_IH; word poolend = R_POOLEND;
word poolblocks, typeblocks;
int hashcap = IHN, namecount, poolchunk = R_POOLEND - R_POOL;
word typelist = R_TY, tyend = R_TYEND;
int typecap = 1024;
-//: growing scratch regions retain offsets used by nested parser frames.
+// growing scratch regions retain offsets used by nested parser frames.
word R_SNAP = R_SNAP_BASE; int R_SNAP_cap = R_QBUF_BASE - R_SNAP_BASE;
word R_QBUF = R_QBUF_BASE; int R_QBUF_cap = R_SRC_BASE - R_QBUF_BASE;
word R_SRC = R_SRC_BASE; int R_SRC_cap = R_SEQ_BASE - R_SRC_BASE;
@@ -173,7 +173,7 @@ word R_CTX = R_CTX_BASE; int R_CTX_cap = R_LOOP_BASE - R_CTX_BASE;
word R_LOOP = R_LOOP_BASE; int R_LOOP_cap = R_ASM - R_LOOP_BASE;
word[60] lpfact_initial; word lpfact = lpfact_initial; int loopfacts_cap = 60;
-//: operator columns grow independently of their initial map reservations.
+// operator columns grow independently of their initial map reservations.
char** bname = bname_BASE;
word bprec = bprec_BASE;
word bseq = bseq_BASE;
@@ -185,18 +185,18 @@ char** aname = aname_BASE;
word abin = abin_BASE;
word kops = kops_BASE;
int bincap = bprec_BASE - bname_BASE, uncap = useq_BASE - uname_BASE, asgcap = abin_BASE - aname_BASE, kopcap = R_CTX_BASE - kops_BASE;
-//: ---- token kinds, symbol kinds and flags ----
+// ---- token kinds, symbol kinds and flags ----
const int T_EOF = 0, T_NUM = 1, T_ID = 2, T_STR = 3, T_OP = 4, T_FLT = 5, T_BSTR = 6;
const int K_FN = 1, K_GVAR = 2, K_GARR = 3, K_CONST = 4, K_KW = 5, K_ENH = 6, K_TYPE = 7, K_DOC = 8;
const int F_DEF = 1, F_SIG = 2, F_UNTYPED = 4, F_STMT = 8, F_EXPR = 16, F_TOP = 32, F_FALLBACK = 64, F_GEN = 128, F_PRE = 256, F_O3 = 512, F_EXT = 1024, F_TPL = 2048, F_STATIC = 4096, F_DECL = 8192, F_RETOWN = 16384, F_EARLY = 32768;
-//: variable states for the lifetime analyzer
+// variable states for the lifetime analyzer
const int S_PLAIN = 0, S_M = 1, S_U = 2, S_D = 3, S_DEAD = 4, S_CHILD = 5, S_BORROW = 6;
-//: expression classes: plain word, a new object, or a bare variable (C_VAR + index)
+// expression classes: plain word, a new object, or a bare variable (C_VAR + index)
const int C_PLAIN = 0, C_FRESH = 1, C_VAR = 2;
-//: lvalue kinds
+// lvalue kinds
const int LV_NONE = 0, LV_LOCAL = 1, LV_GLOBAL = 2, LV_ELEM = 3, LV_OTHER = 4;
-//: opcodes
+// opcodes
const int O_Q = 0, O_LD = 1, O_ST = 2, O_PICK = 3, O_DROP = 4, O_SWAP = 5, O_CALL = 6, O_JMP = 7;
const int O_JZ = 8, O_RET = 9, O_ADD = 13, O_SUB = 14, O_LT = 20;
const int XOP0 = 22; // first enhancement opcode
@@ -205,7 +205,7 @@ const int OP_FEQ = 605, OP_FLT = 606, OP_FLE = 607, OP_I2F = 608, OP_F2I = 609;
const int SEQWORD = -4; // in an operator sequence: the next word is a full-width literal
const int SEQREF = -2; // in an operator sequence: the next word names a symbol
-//: ---- state ----
+// ---- state ----
int chk_heap;
int nenh; word pooltop; int nsym, ubase, vislo, nfixu, fixfree;
@@ -214,15 +214,15 @@ int nbin, nun, nasg, nkop, seqtop;
char* mnem = "q@!$_~cjzr,?.+-*&|^><%";
int check_on = 1; // set to 0 to switch the analyzer off for what follows
int tk; word tv; int tglue, tline, line = 1, ch, chkind; word chptr, chbase; int qdone, isp = -1, pendot;
-//: where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended
+// where the current token sits in its source (chars from the start, -1 for a token list), how long it is, and where the one before it ended
int toff = -1, toklen, tpend = -1, tpline;
int ntok; // tokens taken so far, so a parse word that reads nothing can be caught
int qst, qesc, qp; // srcend's reading state: normal, line comment, block comment, string, char literal
char[1100] tbuf_initial; char* tbuf = tbuf_initial; int tbufcap = 1100;
-//: doc comments: the comment lines directly above a declaration (no blank line
-//: between them or before the declaration) are its documentation. the lexer
-//: collects them in dbuf, dlast is the line of the last one, and dline the
-//: line of the token that follows them. only when docs are on.
+// doc comments: the comment lines directly above a declaration (no blank line
+// between them or before the declaration) are its documentation. the lexer
+// collects them in dbuf, dlast is the line of the last one, and dline the
+// line of the token that follows them. only when docs are on.
int docs_on; // docs on; keeps them: a symbol "doc name" holds a byte string
char[2100] dbuf_initial; char* dbuf = dbuf_initial; int docbufcap = 2100; int dn, dlast, dline, docpending, dsect;
char[2100] dsnap_initial; char* dsnap = dsnap_initial; int docsnapcap = 2100; int dsn, dnm; char[300] dnmb_initial; char* dnmb = dnmb_initial; int docnamecap = 300;
@@ -231,13 +231,13 @@ int here, hdelta, hlimit, ptop;
int code_chunk = rt.CODE_END;
word image_buffer, image_words; int image_active, image_org, image_capacity, image_size;
int nloc, lbase, fdepth, fslots, fchk, fret, live, bxdepth, epichain, fframe, infn;
-//: a global that borrows a local owner through g = m*x. each entry is the global's
-//: symbol, the local, and the block depth of the store; the lend ends when the same
-//: global is assigned again in that block, and is an error if the block can end first.
+// a global that borrows a local owner through g = m*x. each entry is the global's
+// symbol, the local, and the block depth of the store; the lend ends when the same
+// global is assigned again in that block, and is an error if the block can end first.
word lendrec; int nlend, lendcap, xlend = -1;
int finsym, finfer; // the function being compiled, and whether its owned return is still to be inferred (no .u)
int fn_return_type; // a consistent return type inferred from the function body
-//: effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative.
+// effects come from the real compiler's uses, stores, returns and call edges; unknown bodies stay conservative.
const int EF_READ = 1, EF_WRITE = 2, EF_RETAIN = 4, EF_CONSUME = 8, EF_UNKNOWN = 16, EF_ALIAS = 32, EF_IDENTITY = 64;
word effect_symbols, effect_edges, effect_requirements, effect_current; int effect_floor, effect_argument_base;
word local_origin, local_origin_mask, local_cell, local_captured, local_ended, local_end_info; word effect_value, argorigin_mask; int capture_enabled; word[1] capture_dummy;
@@ -249,7 +249,7 @@ int xbor = -1; // local a pointer value is borrowed from, or -1
int mdbg, mdbg_flags; // memdebug: compile checks in, and the flags start() turns on
int lsp, loopbase, ctxsp, snaptop;
int lv, lvk, lvi, kw_expr, xcls, fty;
-//: interned spellings the parser compares against
+// interned spellings the parser compares against
int s_lp, s_rp, s_lb, s_rb, s_lk, s_rk, s_comma, s_semi, s_eq, s_arrow, s_inc, s_dec;
int s_star, s_slash, s_amp, s_colon, s_hash, s_minus, s_andand, s_oror;
@@ -259,23 +259,23 @@ int s_frame_floor, s_frame_top, s_frame_node, s_frame_busy, s_frame_guard, s_fra
int s_copy_w;
int s_do, s_while, s_enum, s_include, s_memdebug, s_scrub, s_alloc, s_check, s_memset, s_static;
-//: ---- types: kinds, tables and parser state (see the types section) ----
+// ---- types: kinds, tables and parser state (see the types section) ----
const int TY_WORD = 0, TY_INT = 1, TY_CHAR = 2, TY_BOOL = 3, TY_VOID = 4, TY_PTR = 5, TY_ARR = 6;
-//: dyn: T[?], a = element, n = 1 if fixed
+// dyn: T[?], a = element, n = 1 if fixed
const int TY_FN = 7, TY_STRUCT = 8, TY_VAR = 9, TY_GEN = 10, TY_DYN = 11, TY_FLOAT = 12, TY_BSTR = 13;
const int NTL = 1024; // the first NTL words list every record
-//: type per local, declared tag per local, type per symbol
+// type per local, declared tag per local, type per symbol
const word LTYPE_BASE = R_ASM + ASM_WORDS, LTAG_BASE = LTYPE_BASE + NL, STYPE_BASE = LTAG_BASE + NL;
word ltype = LTYPE_BASE, ltag = LTAG_BASE, stype = STYPE_BASE;
int tytop, ntl; word tword, tint, tchar, tbool, tvoid, tfloat, tf32, tbstr;
int ckst; char* ckn; // extern: the zc struct a by-value parameter names (ckind), and its name
int tc_on; // the optional typechecker, off unless a program says typecheck on;
-//: type, literal 0, float, byte string, dry runs (typeof), unique giveaway
+// type, literal 0, float, byte string, dry runs (typeof), unique giveaway
word xty; int xzero, xfloat, xstr, dry, xgive;
-//: the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts
+// the type facts of annotate mode (see type facts below): the tables, and where the declaration being read starts
const int NIF = 431, IFP = 24, IFW = 80, IFN = 4096, IFT = 32768;
int ifon; word ifs, ifr; int nif; word ifx; int nifx; word ift; int nift; word ifcur; int ifkp, ifkk, ifcall, ifgk, ifstrict;
@@ -283,31 +283,31 @@ word if_edges, if_value, if_local;
int ifrcap = NIF, ifxcap = IFN, iftcap = IFT;
int fldst, fldi; // the one struct a bare address's field name resolved to
char*[8] tpn; int ntp; // type parameters in scope
-//: reading a return type: unknown names are type parameters declared after the function's name
+// reading a return type: unknown names are type parameters declared after the function's name
int tvauto;
word readonly_types;
int dty, dtag; // result of dtype()
int s_lt, s_gt, s_shr, s_le, s_ge, s_eqeq, s_ne, s_plus, s_struct, s_type, s_typecheck, s_docs, s_small, s_std, s_icon, s_on, s_off, s_new, s_new_t;
int s_o3, s_lfa, s_outside, s_new_raw, s_new_oz, s_oob;
int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32;
-//: ---- o3 region state ----
+// ---- o3 region state ----
-//: each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer.
+// each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer.
int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0;
-//: o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
+// o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays.
word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten;
-//: operator names used by numeric type checks.
+// operator names used by numeric type checks.
word[12] numops; int nnumops, s_fixed, s_dlen, s_dcap, s_dnew, s_dat, s_dpush, s_dpop, s_dresize, xconst, xcval;
int xlenloc, arrloc, fortest, forstep, ft_iv, ft_bd, ft_ir, ft_nops, fs_ok, xstamp;
-//: local lower bound / len(array); one proof record per live loop
+// local lower bound / len(array); one proof record per live loop
int nfact, factcap = RANGE_FACTS; word[RANGE_FACTS] floc_initial, flo_initial, flen_initial; word floc = floc_initial, flo = flo_initial, flen = flen_initial;
int s_len_s, s_at_s, s_slice_s, s_cat_s, s_eq_s, s_lt_s, s_le_s, s_put_bs, s_put_s, s_n_to_s, s_new_s;
int mdg; word[8] mdn; // the type parameters of the generic struct whose methods are being compiled
word[8] gbind; int ngbind; // explicit type arguments for the next call: f<int>(...)
-//: generic functions. the template (type parameters stay variables) is compiled once, as before,
-//: and its tokens are kept; a call whose type arguments are all known gets an instance, the same
-//: tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
-//: inside it mean the real type. instances are compiled after the item that asked for them.
+// generic functions. the template (type parameters stay variables) is compiled once, as before,
+// and its tokens are kept; a call whose type arguments are all known gets an instance, the same
+// tokens compiled again with the parameters bound to those types, so new(T) and sizeof(T)
+// inside it mean the real type. instances are compiled after the item that asked for them.
int grec; word gbuf; int gn, gcap; // the tokens of the top-level item being read
int gnew, gnew_np; word[8] gnew_nm; // a generic function was just defined
int fn_method_compile, fnpatchseq;
@@ -318,10 +318,11 @@ int s_this;
const int NCLO = 8; /* initial closure nesting capacity; grows when more closures are compiled */
const int NCAP = 32; /* initial capture capacity per closure; grows when more variables are captured */
-//: ---- closure compilation state ----
-//: closures being compiled, innermost last: the enclosing function's locals
-//: (lo .. hi), how many variables the body has captured so far (names and the
-//: enclosing local each came from), and the body's own hidden env parameter
+// ---- closure compilation state ----
+
+// closures being compiled, innermost last: the enclosing function's locals
+// (lo .. hi), how many variables the body has captured so far (names and the
+// enclosing local each came from), and the body's own hidden env parameter
int nclo, closure_cap = NCLO, capture_width = NCAP;
word[NCLO] clo_lo_initial; word clo_lo = clo_lo_initial;
@@ -334,43 +335,43 @@ word[256] clo_loc_initial; word clo_loc = clo_loc_initial;
word[256] clo_via_initial; word clo_via = clo_via_initial;
-//: ---- errors ----
+// ---- errors ----
-//: the compiler reports up to 20 errors before it stops. a syntax error
-//: (die) skips the token it happened on and sets panic, which skips ahead to
-//: the next ; or } (or the next top-level item) and keeps quiet until then.
-//: other errors (serr) just report: the parse is still in step.
-//:
-//: check mode (cmode) is for the language server, which runs the real compiler
-//: over a document it holds in memory. nothing is printed and nothing halts:
-//: each error is written into dgbuf as a record [severity, line, offset,
-//: length, chars...] and giveup raises chkabort, which makes the lexer return
-//: end of file so every parsing loop winds down by itself.
+// the compiler reports up to 20 errors before it stops. a syntax error
+// (die) skips the token it happened on and sets panic, which skips ahead to
+// the next ; or } (or the next top-level item) and keeps quiet until then.
+// other errors (serr) just report: the parse is still in step.
+//
+// check mode (cmode) is for the language server, which runs the real compiler
+// over a document it holds in memory. nothing is printed and nothing halts:
+// each error is written into dgbuf as a record [severity, line, offset,
+// length, chars...] and giveup raises chkabort, which makes the lexer return
+// end of file so every parsing loop winds down by itself.
int nerr, panic;
-//: bits kept in a local's ltag for the hints: assigned after its declaration,
-//: read, declared with let, initialized with a literal; the size of the object
-//: a new gave it (bits 16 to 39); and how many times it was used (from bit 56)
+// bits kept in a local's ltag for the hints: assigned after its declaration,
+// read, declared with let, initialized with a literal; the size of the object
+// a new gave it (bits 16 to 39); and how many times it was used (from bit 56)
const int LASG = 0x1000, LREAD = 0x2000, LDECL = 0x4000, LCONST = 0x8000, LCNT1 = 0x100000000000000;
const int NEWTYPED = 0x800000;
word[NL] lpos_initial; word lpos = lpos_initial; word[NL] lreadpos_initial; word lreadpos = lreadpos_initial; word[NL] lnsz_initial; word lnsz = lnsz_initial; word[NL] lleaf_initial; word lleaf = lleaf_initial; word[NL] lrsoff_initial; word lrsoff = lrsoff_initial; word[NL] lrskind_initial; word lrskind = lrskind_initial; int newsz, newleaf, nomagic, callpos;
word[NL] lptr_initial; word lptr = lptr_initial; // annotate mode: the local was indexed or dereferenced, so it holds an address
-//: storage facts are shared by aliases. they describe one allocation, not a
-//: variable slot: [length, lifetime, kind, allocation place, end place, owner].
-//: lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle.
+// storage facts are shared by aliases. they describe one allocation, not a
+// variable slot: [length, lifetime, kind, allocation place, end place, owner].
+// lifetime is 0 unknown, 1 live, 2 ended; kind is 1 stack, 2 heap, 3 array handle.
word[NL] lafact_initial; word lafact = lafact_initial;
word[NL] laaddr_initial; word laaddr = laaddr_initial; word xaf, argfact;
-//: value previews are separate from optimization facts. each reference keeps
-//: the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes.
+// value previews are separate from optimization facts. each reference keeps
+// the value proved at that position: 1 integer, 2 float bits, 3 string, 4 bytes.
word lvkind, lvvalue, lvescaped, svkind, svvalue; int xvk; word xvv;
-//: local name lookup keeps a head for each spelling and a shadow link per slot.
+// local name lookup keeps a head for each spelling and a shadow link per slot.
word[NL] lprev_initial; word lprev = lprev_initial;
word[256] lkeys_initial; word lkeys = lkeys_initial;
word[256] lheads_initial; word lheads = lheads_initial; int lkeycap = 256, lkeyn;
int s_new_k, s_recycle_leaf;
-//: dgprev: the next record sits at the end of the previous token
-//: chk_hook, when set, is called after each top-level item of the document in check mode: the language
-//: server streams diagnostics and drops a check whose source has been replaced
+// dgprev: the next record sits at the end of the previous token
+// chk_hook, when set, is called after each top-level item of the document in check mode: the language
+// server streams diagnostics and drops a check whose source has been replaced
word chk_hook;
int cmode, chkabort, chkfuel; word dgbuf, dggrow; int dgcap, dgpos, dgn, dgopen, dgprev, dgmode;
// expand a diagnostic in progress, keeping its output offset within the buffer.
@@ -379,15 +380,15 @@ int dg_expand() {
dgcap = dgcap * 2; rt.outbuf = dgbuf + dgpos + 5; rt.outcap = dgcap - dgpos - 5;
return 0;
}
-//: diagnostic records for check mode. dg_open starts a record [severity, line,
-//: offset, length, text length, text...] at the current token (or just past the
-//: previous one when dgprev is set) and redirects the error text into it with
-//: outmode 2; dg_close seals it with the length of the text and restores output.
-//: severity 1 is an error, 2 a warning, and 4 a hint.
-//: completion: the language server asks what is in scope at a place in the document. probe_off is that place
-//: (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or
-//: before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after
-//: a dot, what is before the dot. the check stops as soon as it has what was asked for
+// diagnostic records for check mode. dg_open starts a record [severity, line,
+// offset, length, text length, text...] at the current token (or just past the
+// previous one when dgprev is set) and redirects the error text into it with
+// outmode 2; dg_close seals it with the length of the text and restores output.
+// severity 1 is an error, 2 a warning, and 4 a hint.
+// completion: the language server asks what is in scope at a place in the document. probe_off is that place
+// (a byte offset; -1 when nobody asks). the check keeps a copy of the locals at the last statement that starts at or
+// before it, with whether the lifetime analysis has used each one up, and, when the place is a member name after
+// a dot, what is before the dot. the check stops as soon as it has what was asked for
int probe_off = -1, probe_has, probe_n, probe_cap, probe_static;
char** probe_name; word probe_kind, probe_state, probe_type, probe_decl, probe_mem;
char* curinc; int incdepth, dgdrop; int incplace; // how many includes deep the compile is; a record to throw away; the place of the outermost include
@@ -460,10 +461,10 @@ int dg_dup() {
while (dgseen[i]) { if (dgseen[i] == key) return 1; i = (i + 1) & (dgseen_cap - 1); }
dgseen[i] = key; dgseen_n++; return 0;
}
-//: hints on; hints small; hints off; set how many hints the code after it gets:
-//: on uses informational ranges, small uses compact hint markers, and off
-//: suppresses hints. each directive is kept with its byte
-//: offset, so a hint is judged by where its code is, not where it was reported.
+// hints on; hints small; hints off; set how many hints the code after it gets:
+// on uses informational ranges, small uses compact hint markers, and off
+// suppresses hints. each directive is kept with its byte
+// offset, so a hint is judged by where its code is, not where it was reported.
const int HINT_SMALL = 1, HINT_ALL = 2;
word hl_off, hl_lev; int hl_n, hl_cap = 16;
int hint_set(int level, int at) {
@@ -588,8 +589,8 @@ int dien(char* name, char* msg) {
next();
return 0;
}
-//: four classification bits live next to each interned spelling. this works
-//: for names in later pool chunks too, without indexing by a fixed pool offset.
+// four classification bits live next to each interned spelling. this works
+// for names in later pool chunks too, without indexing by a fixed pool offset.
const int LSW = 1;
word lsets;
int lsmark(int set, int n) {
@@ -600,13 +601,13 @@ word lsget(int set, int n) {
if (!lsets) return 0;
return (n[-2] >> set) & 1;
}
-//: name resolution for the language server. while a check runs over the document
-//: at rsbase, every name a use or a declaration resolves to is written into rsrec
-//: as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD),
-//: declaration or symbol, and storage facts at that position. a local names its declaration by offset
-//: (-1 when it has none), a symbol names its kind and flags (see rs_sym). the
-//: compiler's own scopes decide, so the highlighter and hover see locals and
-//: parameters first, then globals and built-ins, the way the compiler does.
+// name resolution for the language server. while a check runs over the document
+// at rsbase, every name a use or a declaration resolves to is written into rsrec
+// as a record: the byte offset, kind (RS_LOCAL, RS_PARAM, RS_SYM or RS_FIELD),
+// declaration or symbol, and storage facts at that position. a local names its declaration by offset
+// (-1 when it has none), a symbol names its kind and flags (see rs_sym). the
+// compiler's own scopes decide, so the highlighter and hover see locals and
+// parameters first, then globals and built-ins, the way the compiler does.
const int RS_LOCAL = 1, RS_PARAM = 2, RS_SYM = 3, RS_FIELD = 4, RS_CAP = 32768, RS_W = 13;
int lt_on, lt_n, ltcap = 256, ltxtcap = 2048;
word lt_off, lt_at, lt_len, lt_type, ltxt; int ltxn;
@@ -628,18 +629,18 @@ int field_decl(word t, char* n) {
while (k < fieldn) { if (fieldtypes[k] == t && fieldnames[k] == n) return fieldoffs[k]; k++; }
return 0 - 1;
}
-//: where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1
-//: (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document)
+// where each symbol was defined, for go to definition: sdoff is the byte offset of its name + 1
+// (0 when unknown, as for built-ins) and sdsrc the include path whose text it is in (0 for the document)
word sdoff, sdsrc;
-//: includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the
-//: workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor
-//: sent (a host with no files of its own), incres the path an include was found at
+// includes: incdir is the directory of the file being compiled (0 for the program on stdin), increoot the
+// workspace folder an editor passes in, inc_hook a function that gives the text of a file the editor
+// sent (a host with no files of its own), incres the path an include was found at
char* incdir, increoot, incres, std_buf, icon_buf; char*(char*.m) inc_hook; int std_done, icon_done;
word rsrec, rstmp; word[32] pnoffs; word[32] pnplaces; int rsn, rscap, rsbase, nameoff;
-//: ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when
-//: it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from.
-//: transfers are logged as events, EV_W words each: the local's declaration offset, what
-//: happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one.
+// ownership at a reference: record word 11 holds the local's state (S_* + 1, plus 16 when
+// it owns its object) and word 12 the declaration offset (+ 1) of the owner it borrows from.
+// transfers are logged as events, EV_W words each: the local's declaration offset, what
+// happened (EV_*), its line, and the target's declaration offset (+ 1) when there is one.
const int EV_RETURN = 1, EV_VALUE = 2, EV_UP = 3, EV_DOWN = 4, EV_STORE = 5, EV_GLOBAL = 6, EV_FREE = 7, EV_CAPTURE = 8, EV_MOVE = 9, EV_W = 4;
word evrec; int evn, evcap = 64;
// resize compiler tables. these buffers are collector-managed: m* relinquishes
@@ -933,7 +934,7 @@ int cerrn(int i, char* msg) {
return 0;
}
-//: ---- interning ----
+// ---- interning ----
// the pool: an open addressing hash table (R_IH) of pointers into a string
// pool (R_POOL). each spelling has two spare words: its symbol-chain head at
@@ -985,7 +986,7 @@ char* intern(char* s) {
return p;
}
-//: ---- symbols ----
+// ---- symbols ----
// each interned name keeps the index (+1) of its newest symbol in p[-1], and
// each symbol links to the one it shadows, so lookup is one step.
@@ -1127,7 +1128,7 @@ int fixall(int head, int v) {
return 0;
}
-//: ---- code emission ----
+// ---- code emission ----
// code goes straight into memory at "here". hdelta shifts where it is written
// (an image build emits at one address and stores at another, see nested
@@ -1266,9 +1267,9 @@ word vaddr(int i) {
}
laddr(lslot[i]); if (local_cell && local_cell[i]) op(O_LD); return 0;
}
-//: registers for an o3 region's variables: the cpu has 7. a variable takes the
-//: one its region freed last (when a scope closed), or the next unused one
-//: ---- o3: the optimizing region compiler's bookkeeping ----
+// registers for an o3 region's variables: the cpu has 7. a variable takes the
+// one its region freed last (when a scope closed), or the next unused one
+// ---- o3: the optimizing region compiler's bookkeeping ----
// code inside an o3 region (or a function marked o3) keeps hot locals in the
// cpu's registers. the compiler does not change what it emits for them; it
@@ -1376,7 +1377,7 @@ int strlit(char* s) {
return 0;
}
-//: ---- input sources ----
+// ---- input sources ----
// frame: 0 kind (0 stdin, 1 token list, 2 string) 1 object 2 index 3 end
// 4 transparent 5..7 saved token 8..10 saved char state 11 owned 12 saved line
@@ -1599,7 +1600,7 @@ int bstrlit(char* s) {
int j = hole(), a;
op(O_JMP); a = bstrdata(s); setlit(j, here); lit(a); return 0;
}
-//: ---- float literals and float code ----
+// ---- float literals and float code ----
// a float is one word holding the ieee bits. the lexer collects the literal's
// text in tbuf and fbits asks the core (opcode 611) to convert it, so the
@@ -2028,7 +2029,7 @@ int name() {
return n;
}
-//: ---- operator tables ----
+// ---- operator tables ----
// a sequence is written like asm: one-char mnemonics, numbers, and names
// (resolved when the operator is used, so image builds stay self-contained)
@@ -2245,7 +2246,8 @@ int emit_statement_hooks() {
return 0;
}
-//: ---- function effects and temporary borrows ----
+// ---- function effects and temporary borrows ----
+
word effect_record(int symbol) {
if (symbol < 0) return 0;
if (!effect_symbols) effect_symbols = chk_resize(0, 0, symcap);
@@ -2389,7 +2391,7 @@ int borrow_condition() {
word before = borrow_slots; int c = expr(); rval(); plainuse(c); ftest(); borrow_cleanup_since(before); return c;
}
-//: ---- locals and the analyzer's view of them ----
+// ---- locals and the analyzer's view of them ----
// value tracking is for check mode only; it never changes emitted code.
int vf_symbols() {
@@ -2910,9 +2912,9 @@ int local_hints(int from) {
if (hint_begin(lpos[i], HINT_SMALL)) {
rt.put_s("unused parameter `"); rt.put_s(lname[i]); rt.put_s("`"); dg_close();
}
- } else if ((f & LDECL) && !larr[i]) {
+ } else if ((f & LDECL) && !larr[i] && !rt.eq_w(lname[i], "_")) { // _ discards its value
if (!(f & LREAD)) hint_name(i, " is never read", HINT_SMALL);
- else if ((f & LCONST) && !(f & LASG)) hint_name(i, " is never reassigned; make it a const", HINT_ALL);
+ else if ((f & LCONST) && !(f & LASG) && !(ltype[i] && treadonly(ltype[i]))) hint_name(i, " is never reassigned; make it a const", HINT_ALL);
else if (fchk && lstate[i] == S_M && lown[i] && lnsz[i] > 0 && lnsz[i] <= 64) hint_name(i, " never leaves this function; a stack buffer (let name[n]) avoids the heap", HINT_SMALL);
else if (fchk && lstate[i] == S_M && lown[i] && lused[i] && tline - lused[i] >= 4) hint_name(i, " is last used several lines above the end of its block; close the block sooner so it is freed sooner", HINT_ALL);
}
@@ -3029,20 +3031,20 @@ int merge_err(int i, int sa, int what) {
return 0;
}
-//: ---- snapshots and comparing paths ----
+// ---- snapshots and comparing paths ----
-//: to analyze an if, the compiler snapshots the locals' states before the
-//: branch, compiles the then path, restores the snapshot for the else path, and
-//: then compares or merges the two. a snapshot is [lbase, nloc] followed by
-//: state, owner and home for each local; the stack of them is at R_SNAP.
-//: ---- joins ----
+// to analyze an if, the compiler snapshots the locals' states before the
+// branch, compiles the then path, restores the snapshot for the else path, and
+// then compares or merges the two. a snapshot is [lbase, nloc] followed by
+// state, owner and home for each local; the stack of them is at R_SNAP.
+// ---- joins ----
-//: where control flow comes back together (after an if, a loop, a switch or
-//: ?:) every variable has to agree across the paths that reach the join. one
-//: exception: a variable that became d, or was given away, on some path is dead
-//: after the join, and the paths where it's still alive free it on the way in
-//: (fixups). u objects and borrows don't get this treatment: a u object still
-//: has to leave, and a borrow never changes.
+// where control flow comes back together (after an if, a loop, a switch or
+// ?:) every variable has to agree across the paths that reach the join. one
+// exception: a variable that became d, or was given away, on some path is dead
+// after the join, and the paths where it's still alive free it on the way in
+// (fixups). u objects and borrows don't get this treatment: a u object still
+// has to leave, and a borrow never changes.
int extop; word[JOIN_PATHS] jps_initial; word jps = jps_initial; int jpscap = JOIN_PATHS, njps; // the paths being joined: [lbase, nloc, triples]
// accessors on a path record p (a snapshot or an exit record): the state,
@@ -3171,13 +3173,13 @@ int join2(int sa, int livea, int ja, int livebv, int n0, int what) {
return 0;
}
-//: the rules below are the heart of the analyzer. each is a function the parser
-//: calls at one kind of place where a value moves: recast (u*x), argument
-//: passing, assignment, return, block values, and discarding. the argument
-//: c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a
-//: new object nobody owns yet) or C_VAR + n (a bare local, number n), which is
-//: how the analyzer knows the thing being moved is a variable it tracks.
-//: ---- analyzer rules for the places values flow ----
+// the rules below are the heart of the analyzer. each is a function the parser
+// calls at one kind of place where a value moves: recast (u*x), argument
+// passing, assignment, return, block values, and discarding. the argument
+// c they take is an expression class: C_PLAIN (an ordinary word), C_FRESH (a
+// new object nobody owns yet) or C_VAR + n (a bare local, number n), which is
+// how the analyzer knows the thing being moved is a variable it tracks.
+// ---- analyzer rules for the places values flow ----
// recast: x as u / x as d / x as m
int recast(int c, int t) {
@@ -3431,9 +3433,10 @@ int discard(int c) {
return 0;
}
-//: ---- completion ----
-//: copy local i (kind: 1 local, 2 parameter, 3 captured from the function around a closure) into the probe,
-//: unless a nearer local has its name
+// ---- completion ----
+
+// copy local i (kind: 1 local, 2 parameter, 3 captured from the function around a closure) into the probe,
+// unless a nearer local has its name
int probe_add(int i, int kind) {
int k = 0;
while (k < probe_n) { if (probe_name[k] == lname[i]) return 0; k++; }
@@ -3478,7 +3481,7 @@ int probe_member(word t) {
return 0;
}
-//: ---- types ----
+// ---- types ----
// types are a compiler primitive: records in the type area, hash-consed so
// the same type is always the same pointer and comparing types is comparing
@@ -3839,9 +3842,9 @@ int closeangle() {
expect(s_gt);
return 0;
}
-//: a type, then any number of (params) after it: int(int, int) is a function
-//: returning int, fn(int) one returning a plain word
-//: ---- parsing types ----
+// a type, then any number of (params) after it: int(int, int) is a function
+// returning int, fn(int) one returning a plain word
+// ---- parsing types ----
// grammar, outermost first:
// ptype a type, then any number of (params): int(int, int) is a function type
@@ -4097,7 +4100,7 @@ int kw_struct() {
mdg = 0;
return s;
}
-//: ---- methods ----
+// ---- methods ----
// a method is an ordinary function named Struct.name whose first parameter is
// this, a pointer to the struct. e.method(a) calls Struct.name(e, a). the
@@ -4358,9 +4361,9 @@ int caststart() {
}
return 0;
}
-//: memdebug on; checks and interior pinning. memdebug scrub; also poisons frees.
-//: memdebug off; stops both. at the top level, start() applies it before main.
-//: ---- statements that only configure the compiler ----
+// memdebug on; checks and interior pinning. memdebug scrub; also poisons frees.
+// memdebug off; stops both. at the top level, start() applies it before main.
+// ---- statements that only configure the compiler ----
int kw_memdebug() {
int f = 0;
@@ -4382,10 +4385,10 @@ int kw_memdebug() {
word tenum(char* n) { word t = tnew(TY_INT, 1, n, 0); t[6] = 16; return t; }
// true for a type made by an enum declaration.
word isen(word t) { return t[0] == TY_INT && (t[6] & 16); }
-//: enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A)
-//: member records for tagged enums and for plain enums (pm_en is -1 for a plain
-//: member). a member's payload fields are at most 8.
-//: the text a tagged enum or an extern stub is generated into
+// enum name { A, B = 5 }: each member is a constant, plain (A) and scoped (name.A)
+// member records for tagged enums and for plain enums (pm_en is -1 for a plain
+// member). a member's payload fields are at most 8.
+// the text a tagged enum or an extern stub is generated into
char[GENERATED_WORDS] ebuf_initial; char* ebuf = ebuf_initial; int ebcap = GENERATED_WORDS, ebn;
char*[ENUM_MEMBERS] pm_name_initial, pm_en_initial; word[ENUM_MEMBERS] pm_tag_initial, pm_nf_initial, pm_val_initial, pm_exp_initial;
char** pm_name = pm_name_initial, pm_en = pm_en_initial; word pm_tag = pm_tag_initial, pm_nf = pm_nf_initial, pm_val = pm_val_initial, pm_exp = pm_exp_initial; int pmcap = ENUM_MEMBERS, npm;
@@ -4549,9 +4552,10 @@ int cover_warn(int e, int cov, int sl) {
if (!first) rt.put_c(10);
return 0;
}
-//: ---- include ----
-//: the text of an embedded library, one character to a word and ending in 0, as readfile gives a file.
-//: it is unpacked from the byte string the first time it is asked for.
+// ---- include ----
+
+// the text of an embedded library, one character to a word and ending in 0, as readfile gives a file.
+// it is unpacked from the byte string the first time it is asked for.
char* bstr_text(bstr p) {
int n = rt.len_s(p), i = 0; char* b = m*rt.new(n + 2);
while (i < n) { b[i] = rt.at_s(p, i); i++; }
@@ -4946,11 +4950,11 @@ int doc_attach(char* n) {
dsn = 0;
return 0;
}
-//: the signature of a function as source text, "int put_c(int ch)", kept in the
-//: image as the symbol "sig name" when docs are on, so the language server can
-//: show it for any genome function, with or without a doc comment. the text is
-//: printed through put_c with the output sent to sgb (the same redirect the
-//: check uses for a parse word that prints)
+// the signature of a function as source text, "int put_c(int ch)", kept in the
+// image as the symbol "sig name" when docs are on, so the language server can
+// show it for any genome function, with or without a doc comment. the text is
+// printed through put_c with the output sent to sgb (the same redirect the
+// check uses for a parse word that prints)
char[400] sgb_initial; char* sgb = sgb_initial; int sigcap = 400;
int sig_print(char* n, int rty, int np, char** pn, word pty, int typed) {
int i = 0;
@@ -4982,8 +4986,8 @@ int sig_attach(char* n, int rty, int np, char** pn, word pty, int typed) {
addsym(intern(dnmb), K_DOC, b);
return 0;
}
-//: the field of a struct type, or -1
-//: ---- built-ins that look like functions ----
+// the field of a struct type, or -1
+// ---- built-ins that look like functions ----
// sizeof and typeof take a type or an expression; the expression form is
// compiled with dry set so it contributes only its type. len, cap, push, pop
@@ -5200,7 +5204,7 @@ int kw_typeof() {
xty = tword;
return 0;
}
-//: ---- c abi: extern and cfunc ----
+// ---- c abi: extern and cfunc ----
// extern double pow(double, double); or with parameter names, and an
// optional "= "cname"" to give the zc function another name than the c symbol.
@@ -5623,7 +5627,7 @@ word bintype(int b, word lt, word rhs_type) {
return tword; // operators a program added
}
-//: ---- reflection ----
+// ---- reflection ----
// for programs (and parse words) looking at types at runtime
word tkind(word t) { return t[0]; }
@@ -5635,7 +5639,7 @@ char* tname(word t) { return t[2]; }
word tlen(word t) { return t[3]; }
// the number of fields of a struct type.
word tnfields(word t) { tresolve(t); return t[3]; }
-//: ---- fattening ----
+// ---- fattening ----
// typed new stamps the header, in o3 the same as outside it. fatten is for a
// value that still has new_raw's blank type word when it reaches code that
@@ -5760,8 +5764,8 @@ word tnargs(word t) {
}
// type argument i of a generic struct instance.
word targ(word t, int i) { return t[6][1 + i]; }
-//: the type of a function, by address. untyped functions get one made from their signature
-//: ---- function types by address ----
+// the type of a function, by address. untyped functions get one made from their signature
+// ---- function types by address ----
word sigof(int f) {
int i = nsym, s = -1; word[24] ws; int n, k;
@@ -5779,7 +5783,7 @@ word sigofs(char* nm) {
return sigof(sval[s]);
}
-//: ---- expressions ----
+// ---- expressions ----
// every expression leaves its value on the stack, returns its analyzer class,
// and sets xty to its type. lv means the stack holds an address that hasn't
@@ -5974,7 +5978,7 @@ int byteop(int s) {
if (sname[s] == s_st32) return 617;
return 0;
}
-//: ---- type facts ----
+// ---- type facts ----
// annotate mode (the language server asks for it, ifon) collects the types the
// untyped declarations of a document could be given, as edits: a place, how
@@ -6141,7 +6145,7 @@ int if_emit() {
}
return 0;
}
-//: ---- hints ----
+// ---- hints ----
// findings that are not errors. each is written only in check mode, through
// hint_begin, and none of them changes the code the compiler emits.
@@ -6306,7 +6310,7 @@ int enhcall(int s) {
if (nout > 1) serrn(sname[s], "leaves several values; use it from asm");
return C_PLAIN;
}
-//: ---- closures ----
+// ---- closures ----
// fn(a, b) { ... } is a function whose last, hidden parameter is a pointer to
// its environment. the closure value is a heap block: word 0 is the body's
@@ -6517,9 +6521,10 @@ int closure() {
return C_FRESH;
}
-//: ---- array initializer lists and compound literals ----
-//: keep original token places while measuring and replaying an initializer. measurement never evaluates
-//: value expressions: values run once, in source order, after zero filling the storage.
+// ---- array initializer lists and compound literals ----
+
+// keep original token places while measuring and replaying an initializer. measurement never evaluates
+// value expressions: values run once, in source order, after zero filling the storage.
word array_tokens() {
word q = chk_resize(0, 0, 97); int cap = 32, n = 0, depth = 0;
while (tk != T_EOF) {
@@ -6716,7 +6721,7 @@ int brace_primary() {
src_push(1, q, 0, 0); next(); c = blockexpr(); src_pop(); rt.free(q); return c;
}
-//: ---- the expression grammar ----
+// ---- the expression grammar ----
// precedence, loosest to tightest:
// expr (assignment, ?:) -> bin (binary operators by precedence) ->
@@ -7462,7 +7467,7 @@ int bin(int minp) {
}
return c;
}
-//: ---- assignment, conditionals and blocks as expressions ----
+// ---- assignment, conditionals and blocks as expressions ----
int storekeep(int t) {
if (mdbg && !dry && !ispk(t)) { lit(1); op(O_PICK); callname(s_check); op(O_DROP); }
@@ -7617,7 +7622,7 @@ int blockexpr() {
return c;
}
-//: ---- constant expressions ----
+// ---- constant expressions ----
// constant expressions, evaluated while compiling: numbers, named constants,
// addresses of globals and functions, unary minus and ~, parentheses, and the
@@ -7666,7 +7671,7 @@ int cexpr() {
return v;
}
-//: ---- statements ----
+// ---- statements ----
// after a syntax error: skip to the end of the statement
// statements. stmt is the dispatcher: a keyword statement (if, while, return,
@@ -8280,13 +8285,13 @@ int kw_unchecked() {
return 0;
}
-//: ---- asm blocks: raw vm words, one per token ----
+// ---- asm blocks: raw vm words, one per token ----
-//: numbers push, -n pushes a negative number, #n emits a raw word, one-char
-//: tokens from the mnemonic table are opcodes, names push the address of a
-//: variable or function, :name defines a label for this block.
-//: (labels are local to the block: :name defines one, a later name refers to it,
-//: and references to labels not yet defined are resolved when the block ends)
+// numbers push, -n pushes a negative number, #n emits a raw word, one-char
+// tokens from the mnemonic table are opcodes, names push the address of a
+// variable or function, :name defines a label for this block.
+// (labels are local to the block: :name defines one, a later name refers to it,
+// and references to labels not yet defined are resolved when the block ends)
word asm_names = R_ASM, asm_sites = R_ASM + ASM_LABELS;
word asm_refs = R_ASM + ASM_LABELS + ASM_LABELS, asm_fixes = R_ASM + ASM_LABELS + ASM_LABELS + ASM_REFS;
int asm_labelcap = ASM_LABELS, asm_refcap = ASM_REFS;
@@ -8395,7 +8400,7 @@ int kw_asm() {
return 0;
}
-//: ---- quote: code as data ----
+// ---- quote: code as data ----
// a token list is an object: [n] then kind, value, line for each token. names,
// strings and operators are interned pointers.
@@ -8470,7 +8475,7 @@ int feed(.d q) {
return 0;
}
-//: ---- reading a unit and scanning it for signatures ----
+// ---- reading a unit and scanning it for signatures ----
// a unit from stdin is read whole first (4 chars to a word), then scanned
// once for function signatures, so a function's tags are known before its first
@@ -8616,15 +8621,15 @@ int unit_stdin() {
return 0;
}
-//: ---- nested compiles ----
+// ---- nested compiles ----
-//: compile() and load() can run while another function is being compiled (from
-//: a parse word). the function context is saved, the new code is emitted in
-//: place with a jump around it, and then everything is put back.
-//: the context frame is 34 words: everything about the function being compiled
-//: that a nested compile would otherwise clobber (locals, depth, frame size,
-//: loop stack position, the expression-state globals). closures use the same
-//: pair.
+// compile() and load() can run while another function is being compiled (from
+// a parse word). the function context is saved, the new code is emitted in
+// place with a jump around it, and then everything is put back.
+// the context frame is 34 words: everything about the function being compiled
+// that a nested compile would otherwise clobber (locals, depth, frame size,
+// loop stack position, the expression-state globals). closures use the same
+// pair.
const int CTX_WORDS = 48;
int ctx_push() {
af_forget();
@@ -8661,9 +8666,9 @@ int ctx_pop() {
return 0;
}
-//: ---- functions and the top level ----
+// ---- functions and the top level ----
-//: ---- a function, start to finish ----
+// ---- a function, start to finish ----
// fndef reads the header, then: fn_begin resets the per-function state,
// prologue emits the frame setup, the parameters are declared and stored from
@@ -8877,8 +8882,8 @@ int fndef(int chk, int rty, int ret, int n) {
fn_remember(s, n);
return s;
}
-//: let g = 1, buf[10]; int g = 3; int[10] buf; point origin;
-//: ---- globals, constants, enhancements ----
+// let g = 1, buf[10]; int g = 3; int[10] buf; point origin;
+// ---- globals, constants, enhancements ----
// a global's storage is emitted in place among the code (one word, or the
// whole array), and the symbol's value is its address. a string initializer
@@ -9175,7 +9180,7 @@ int topitem1() {
die("expected a declaration");
return 0;
}
-//: ---- generic functions: templates and instances ----
+// ---- generic functions: templates and instances ----
// which type parameter does name n stand for while an instance is compiled
int gfind(int n) {
@@ -9525,8 +9530,25 @@ int similar(int n) {
return best;
}
// end a compile unit from token from onward, and report what it defined.
+// the module that exports a bare name, when one does: put_c is rt's. the compiler's
+// own modules come first, then the libraries this unit has loaded. 0 when none does.
+char* export_owner(char* name) {
+ int k = 0; word p = included_modules;
+ while (name[k]) { if (name[k] == '.') return 0; k++; }
+ if (export_in("rt", name)) return "rt";
+ if (export_in("core", name)) return "core";
+ if (export_in("run", name)) return "run";
+ while (p) { if (export_in(p[1], name)) return p[1]; p = p[0]; }
+ return 0;
+}
+// does module define name: a function, a global, a constant or a type of it.
+int export_in(char* module, char* name) {
+ int s = lookup_raw(methname(module, name));
+ if (s < 0) return 0;
+ return skind[s] != K_DOC && (skind[s] != K_FN || (sflag[s] & F_DEF));
+}
int unit_end(int from) {
- int i = from, best;
+ int i = from, best; char* owner;
effect_finish(from);
panic = 0;
while (i < nsym) {
@@ -9534,11 +9556,17 @@ int unit_end(int from) {
// elsewhere is another file of the same build, which this document can't see
if (skind[i] == K_FN && !(sflag[i] & F_DEF) && sfix[i] && !(cmode && (sflag[i] & F_DECL)) && !missing_import_name(sname[i])) {
tunpack(sval[i]);
- best = similar(sname[i]);
+ owner = export_owner(sname[i]);
+ best = 0; if (!owner) best = similar(sname[i]);
if (!dry) {
errat();
rt.put_s(sname[i]); rt.put_s(" is used but never defined");
- if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
+ if (owner) {
+ rt.put_s("; did you mean to ");
+ if (!module_imported(owner)) { rt.put_s("include "); rt.put_s(owner); rt.put_s(" and "); }
+ rt.put_s("use "); rt.put_s(owner); rt.put_c('.'); rt.put_s(sname[i]); rt.put_s("?");
+ }
+ else if (best) { rt.put_s("; did you mean '"); rt.put_s(best); rt.put_s("'?"); }
errend();
}
}
@@ -9635,7 +9663,7 @@ int evals(char* s) {
return r;
}
-//: ---- symbol blob ----
+// ---- symbol blob ----
// an image ends with its own symbol table so the genome knows its names:
// kind, value, signature, name chars, 0 ... then a 0 kind.
@@ -9730,7 +9758,7 @@ int build_image(int org, int dst) {
return n;
}
-//: ---- setup ----
+// ---- setup ----
// the collector's roots for the compiler's half of the memory map: the program
// area (globals live between functions), the genome's globals, the input source
@@ -9858,11 +9886,11 @@ int zc_tables() {
add_kw("resize", kw_resize, F_EXPR);
return 0;
}
-//: the default map's type records sit in the last 16384 words of the heap.
-//: the type list is the first words of that range, so it cannot be kept aside
-//: as a buddy block: types_init would overwrite the header and the allocator
-//: would hand the range out again. cut the heap off at R_TY until the unit
-//: has been compiled, then put the tail back for the program.
+// the default map's type records sit in the last 16384 words of the heap.
+// the type list is the first words of that range, so it cannot be kept aside
+// as a buddy block: types_init would overwrite the header and the allocator
+// would hand the range out again. cut the heap off at R_TY until the unit
+// has been compiled, then put the tail back for the program.
int heldk;
// the size class of the type area, for the memory map.
int type_class() {
@@ -9921,7 +9949,7 @@ int start() {
return 0;
}
-//: ---- check mode: the language server ----
+// ---- check mode: the language server ----
// the server runs this compiler, unchanged, over a document it holds in
// memory. chk_unit compiles it as a whole unit, in a machine set up the way
diff --git a/src/lib/std.zc b/src/lib/std.zc
index f232422..dfa9841 100644
--- a/src/lib/std.zc
+++ b/src/lib/std.zc
@@ -13,12 +13,12 @@ include rt, run;
// the constructors arena(n) and bnew(n) and the argv helpers are plain
// functions, because an owned result has to carry its .u tag.
-//: ---- the command line and the environment ----
+// ---- the command line and the environment ----
-//: argc and argv, envc and envp, as passed to main by c. a zc string is
-//: one character per word, so an array of them is char*[?]. libinit() fills
-//: these and the runtime calls it before main, if the program has one (an
-//: image built by build_image, or a check-mode document, does not).
+// argc and argv, envc and envp, as passed to main by c. a zc string is
+// one character per word, so an array of them is char*[?]. libinit() fills
+// these and the runtime calls it before main, if the program has one (an
+// image built by build_image, or a check-mode document, does not).
int argc;
char*[?] argv;
@@ -59,7 +59,7 @@ int libinit() {
return 0;
}
-//: ---- capturing output ----
+// ---- capturing output ----
// capture.start() redirects everything the program writes into a string and
// returns it. writes go to the buffer instead of the terminal until end()
@@ -90,7 +90,7 @@ struct capture {
}
}
-//: ---- character strings ----
+// ---- character strings ----
struct Text {
// the number written in decimal at the start of s. a leading '-' makes it
@@ -246,7 +246,7 @@ struct Text {
}
}
-//: ---- numbers ----
+// ---- numbers ----
// plain integer helpers. none of them can fail: an out of range result wraps
// the same way the arithmetic does.
@@ -331,9 +331,9 @@ struct Num {
}
}
-//: ---- the host ----
+// ---- the host ----
-//: the argument block for Host.raw: the number, then its six arguments.
+// the argument block for Host.raw: the number, then its six arguments.
int rawblk[7];
struct Host {
@@ -378,7 +378,7 @@ struct Host {
}
}
-//: ---- byte strings ----
+// ---- byte strings ----
// the same helpers for packed byte strings. character-string helpers above are unchanged.
// a packed byte string has type bstr; len_s reads its stored byte length.
@@ -468,7 +468,7 @@ struct Bytes {
}
}
-//: ---- arenas ----
+// ---- arenas ----
// a region that is freed all at once.
// n: its capacity in words; values below 1 allocate one word
@@ -489,7 +489,7 @@ struct Arena {
static int reset(word a) { return rt.arena_reset(a); }
}
-//: ---- growable byte buffers ----
+// ---- growable byte buffers ----
// a byte buffer stores its length and capacity in two words, followed by the
// bytes, packed.
@@ -537,9 +537,9 @@ struct Buf {
}
}
-//: array algorithms borrow their input and visit elements in index order.
-//: map and filter return independent handles. reference elements are shared
-//: with the collector, so freeing the input does not invalidate the result.
+// array algorithms borrow their input and visit elements in index order.
+// map and filter return independent handles. reference elements are shared
+// with the collector, so freeing the input does not invalidate the result.
// the first index equal to value, or -1 when no element matches.
int array_find<T>(fixed T[?].m source, T value) {
diff --git a/src/lsp/server.zc b/src/lsp/server.zc
index b578bd1..70f9efd 100644
--- a/src/lsp/server.zc
+++ b/src/lsp/server.zc
@@ -1,20 +1,20 @@
namespace lsp;
include rt, run;
-//: ============================================================================
-//: server.zc: the language server. it is appended to the genome after the
-//: compiler (cat src/runtime/rt.zc src/compiler/compiler.zc src/lsp/server.zc | zc0), so it does not
-//: analyze zc with a second implementation of the grammar: it runs the
-//: compiler itself over the document, in check mode (see chk_unit in
-//: compiler.zc), and reports what the compiler said. a parse word, an
-//: operator or a type the document defines changes how the rest of the
-//: document is read, for the checker and for the highlighter, because both are
-//: the compiler. it speaks json-rpc on stdin and stdout, and when stdin is not
-//: a protocol stream but a source file it prints the diagnostics as text.
-//: ============================================================================
+// ============================================================================
+// 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.
+// 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
@@ -24,11 +24,12 @@ 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;
@@ -36,11 +37,12 @@ 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.
+// 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;
@@ -110,10 +112,11 @@ 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;
char** duri; word dulen, doff, dsize, dver, dgen, dlive;
@@ -167,7 +170,8 @@ int docnew(char* 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
@@ -201,9 +205,10 @@ int ensure_checked(int 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) {
@@ -237,7 +242,8 @@ 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) {
@@ -325,9 +331,10 @@ int check_publish(int i) {
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
@@ -407,8 +414,8 @@ int rs_find(int o) {
}
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.
@@ -505,10 +512,11 @@ int gen_tokens() {
}
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.
+// ---- 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;
@@ -583,10 +591,11 @@ int inc_setup(int i) {
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.
+// ---- 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;
@@ -819,7 +828,8 @@ word text_start() {
return tbase;
}
-//: ---- messages in ----
+// ---- 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() {
@@ -873,7 +883,8 @@ int read_message() {
return 1;
}
-//: ---- messages out ----
+// ---- messages out ----
+
int wid() {
if (idtype == 2) {
wjs(idbuf, idn);
@@ -896,7 +907,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() {
@@ -926,10 +937,11 @@ int reply_error(int code) {
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
+// ---- 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 == '_'; }
@@ -1013,7 +1025,7 @@ 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) {
@@ -1366,11 +1378,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
@@ -1943,11 +1955,12 @@ int do_hover(int i) {
}
-//: ---- 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.
+// ---- 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;
@@ -2094,10 +2107,11 @@ int do_nav(int i, int refs) {
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, embedded
-//: when the image is built, 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
+// ---- the sources of this image ----
+
+// the image carries the text of rt.zc, compiler.zc, server.zc, std.zc, icon.zc and core.zc, embedded
+// when the image is built, 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;
embed "src/runtime/rt.zc" as bstr rt_embedded;
embed "src/compiler/compiler.zc" as bstr compiler_embedded;
@@ -2266,21 +2280,22 @@ int bs_named(char* p, int n) {
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.
+// ---- 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;
@@ -2318,7 +2333,7 @@ int fm_is(int i, int t, char* w) {
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
+// 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;
@@ -2392,9 +2407,10 @@ int fm_lex(int i) {
}
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.
+// ---- 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); }
@@ -2761,7 +2777,7 @@ int fm_comment(int i, int t, int levels) {
}
return 0;
}
-//: the state after token x: braces, parentheses, switch bodies and asm blocks
+// 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];
@@ -2904,8 +2920,9 @@ int format_cli(int first) {
}
-//: ---- completion ----
-//: candidates own their text: another compiler check can replace all symbol and type tables.
+// ---- 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) {
@@ -3157,15 +3174,171 @@ int ca_import_point(int i) {
return at;
}
int ca_newline() { if (ca_crlf) hov_put(13); hov_put(10); return 0; }
+// quick fixes for hints. each record is one edit of the document: it replaces start..end
+// with text built from its kind. the hint's own range and words say what to change.
+const int HF_RENAME = 1, HF_CONST = 2, HF_SHIFT = 3, HF_STACK = 4;
+word hf_list; // [next, kind, start, end, a, b]
+int hf_add(int kind, int start, int end, int a, int b) {
+ word p = lsp_grow(0, 0, 6);
+ p[0] = hf_list; p[1] = kind; p[2] = start; p[3] = end; p[4] = a; p[5] = b; hf_list = p;
+ return 0;
+}
+// does the message of the record at at start with s, or end with s
+int dg_starts(int at, char* s) {
+ int k = 0, n = dgb[at + 4];
+ while (s[k]) { if (k >= n || dgb[at + 5 + k] != s[k]) return 0; k++; }
+ return 1;
+}
+int dg_ends(int at, char* s) {
+ int k = rt.len_w(s), n = dgb[at + 4];
+ if (k > n) return 0;
+ while (k > 0) { k--; n--; if (dgb[at + 5 + n] != s[k]) return 0; }
+ return 1;
+}
+// the uses of the local declared at off, from the compiler's reference records
+int local_uses(int off) {
+ int q = 0, n = 0;
+ while (q < run.rsn) {
+ if (run.rsrec[q * run.RS_W + 1] == run.RS_LOCAL && run.rsrec[q * run.RS_W + 2] == off && run.rsrec[q * run.RS_W] != off) n++;
+ q++;
+ }
+ return n;
+}
+int hf_space(int i, int o) { while (o < dsize[i] && (dc(doff[i], o) == 32 || dc(doff[i], o) == 9)) o++; return o; }
+// the start of the statement whose declared name starts at name: past the previous ; { } or line start
+int hf_head(int i, int name) {
+ int o = name, c;
+ while (o > 0) { c = dc(doff[i], o - 1); if (c == ';' || c == '{' || c == '}' || c == 10) break; o--; }
+ return hf_space(i, o);
+}
+// a single declarator: from the end of the name to ; with no comma outside brackets
+int hf_single(int i, int o) {
+ int depth = 0, c;
+ while (o < dsize[i]) {
+ c = dc(doff[i], o);
+ if (c == '(' || c == '[' || c == '{') depth++;
+ else if (c == ')' || c == ']' || c == '}') depth--;
+ else if (c == ',' && !depth) return 0;
+ else if (c == ';' && !depth) return 1;
+ else if (c == 34 || c == 39) return 0;
+ o++;
+ }
+ return 0;
+}
+// the start of the left operand of the * at op: a name, number, call or index chain. -1 when
+// the operand is not that simple, or when a * / or % binds it to something further left, since
+// the rewrite has to keep the meaning: (operand << k) replaces operand * 2^k
+int hf_operand(int i, int op) {
+ int o = op, depth = 0, c, b;
+ while (o > 0 && (dc(doff[i], o - 1) == 32 || dc(doff[i], o - 1) == 9)) o--;
+ while (o > 0) {
+ c = dc(doff[i], o - 1);
+ if (c == ')' || c == ']') depth++;
+ else if (c == '(' || c == '[') { if (!depth) break; depth--; }
+ else if (!depth && !hid(c) && c != '.') break;
+ else if (depth && (c == 10 || c == ';' || c == '{' || c == '}')) return 0 - 1;
+ o--;
+ }
+ if (o == op || depth) return 0 - 1;
+ b = o; while (b > 0 && (dc(doff[i], b - 1) == 32 || dc(doff[i], b - 1) == 9)) b--;
+ if (b > 0) { c = dc(doff[i], b - 1); if (c == '*' || c == '/' || c == '%' || c == '.' || c == '-' && b > 1 && dc(doff[i], b - 2) == '-') return 0 - 1; }
+ return o;
+}
+// is the text at o the word w, ending there
+int hf_word(int i, int o, char* w) {
+ int k = 0;
+ while (w[k]) { if (o + k >= dsize[i] || dc(doff[i], o + k) != w[k]) return 0; k++; }
+ return o + k >= dsize[i] || !hid(dc(doff[i], o + k));
+}
+// the hint records over the cursor, turned into edits where the change is certain
+int hint_fixes(int i, int cursor, int limit) {
+ int at = 0, start, end, o, digits, n, v, k;
+ while (at < run.dgpos) {
+ start = dgb[at + 2]; end = start + dgb[at + 3];
+ if ((dgb[at] == 3 || dgb[at] == 4) && start >= 0 && cursor >= 0 && start <= limit && end > cursor) {
+ if (dg_starts(at, "unused parameter `")) hf_add(HF_RENAME, start, end, 0, 0);
+ else if (dg_ends(at, " is never read") && !local_uses(start)) hf_add(HF_RENAME, start, end, 0, 0);
+ else if (dg_ends(at, " is never reassigned; make it a const") && hf_single(i, end)) hf_add(HF_CONST, hf_head(i, start), start, end, 0);
+ else if (dg_starts(at, "multiplying by ")) {
+ // the hint sits on the *; the literal follows it
+ o = start; if (dc(doff[i], o) == '*') o = hf_space(i, o + 1); else o = 0 - 1;
+ v = 0; digits = o;
+ while (o >= 0 && digits < dsize[i] && dc(doff[i], digits) >= '0' && dc(doff[i], digits) <= '9') { v = v * 10 + dc(doff[i], digits) - '0'; digits++; }
+ k = 0; while ((1 << k) < v) k++;
+ n = hf_operand(i, start);
+ if (n >= 0 && o >= 0 && digits > o && v > 1 && (1 << k) == v && !hid(dc(doff[i], digits))) hf_add(HF_SHIFT, n, digits, k, start);
+ }
+ else if (dg_ends(at, " never leaves this function; a stack buffer (let name[n]) avoids the heap") && hf_word(i, hf_head(i, start), "let")) {
+ // let name = rt.new(n); becomes let name[n];
+ o = hf_space(i, end);
+ if (dc(doff[i], o) == '=') {
+ o = hf_space(i, o + 1);
+ n = o; k = 0; while (k < 7 && dc(doff[i], n + k) == "rt.new("[k]) k++;
+ if (k == 7) {
+ n = hf_space(i, n + 7); digits = n;
+ while (digits < dsize[i] && dc(doff[i], digits) >= '0' && dc(doff[i], digits) <= '9') digits++;
+ v = hf_space(i, digits);
+ if (digits > n && dc(doff[i], v) == ')' && dc(doff[i], hf_space(i, v + 1)) == ';') hf_add(HF_STACK, end, hf_space(i, v + 1), n, digits);
+ }
+ }
+ }
+ }
+ at += 5 + dgb[at + 4];
+ }
+ return 0;
+}
+// the title and the replacement text of hint fix p, into hov
+int hf_title(word p) {
+ int k = p[2];
+ hn = 0;
+ if (p[1] == HF_RENAME) { hov_text("rename `"); while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` to _"); }
+ else if (p[1] == HF_CONST) { hov_text("make `"); k = p[3]; while (k < p[4]) { hov_put(dc(doff[ca_i], k)); k++; } hov_text("` a const"); }
+ else if (p[1] == HF_SHIFT) { hov_text("write << "); hov_number(p[4]); }
+ else { hov_text("use a stack buffer"); }
+ return 0;
+}
+int hf_text(word p) {
+ int k, c;
+ hn = 0;
+ if (p[1] == HF_RENAME) hov_put('_');
+ else if (p[1] == HF_CONST) {
+ hov_text("const ");
+ if (hf_word(ca_i, p[2], "let")) hov_text("int ");
+ else { k = p[2]; while (k < p[3]) { hov_put(dc(doff[ca_i], k)); k++; } }
+ }
+ else if (p[1] == HF_SHIFT) {
+ // (operand << k): the operand's text runs from the record's start to the *, without trailing blanks
+ hov_put('('); k = p[2]; c = p[5];
+ while (c > k && (dc(doff[ca_i], c - 1) == 32 || dc(doff[ca_i], c - 1) == 9)) c--;
+ while (k < c) { hov_put(dc(doff[ca_i], k)); k++; }
+ hov_text(" << "); hov_number(p[4]); hov_put(')');
+ }
+ else { hov_put('['); k = p[4]; while (k < p[5]) { hov_put(dc(doff[ca_i], k)); k++; } hov_put(']'); }
+ return 0;
+}
int gen_code_actions() {
- word p = ca_list; int first = 1, imported;
+ word p = ca_list; int first = 1, imported, single = ca_list && !ca_list[0];
ws("{\"jsonrpc\":\"2.0\",\"id\":"); wid(); ws(",\"result\":[");
+ p = hf_list;
+ while (p) {
+ if (!first) wc(','); first = 0;
+ hf_title(p); ws("{\"title\":"); wjs(hov, hn);
+ ws(",\"kind\":\"quickfix\",\"isPreferred\":true,\"edit\":{\"documentChanges\":[{\"textDocument\":{\"uri\":");
+ wjs(duri[ca_i], dulen[ca_i]); ws(",\"version\":"); wn(dver[ca_i]); ws("},\"edits\":[");
+ pc_reset(ca_i);
+ ws("{\"range\":{\"start\":"); wpos(p[2]); ws(",\"end\":"); wpos(p[3]); ws("},\"newText\":");
+ hf_text(p); wjs(hov, hn); ws("}]}]}}");
+ p = p[0];
+ }
+ p = ca_list;
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\":");
+ if (!imported) { hov_text("include "); hov_text(p[1]); }
+ if (p[2]) { if (!imported) hov_text(" and "); hov_text("use "); hov_text(p[1]); hov_put('.'); hov_text(hname); }
+ ws("{\"title\":"); wjs(hov, hn); ws(",\"kind\":\"quickfix\",");
+ if (single) ws("\"isPreferred\":true,");
+ ws("\"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) {
@@ -3184,7 +3357,7 @@ int gen_code_actions() {
}
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;
+ ca_list = 0; hf_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; }
@@ -3193,7 +3366,7 @@ int do_code_actions(int i) {
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; }
+ if (error < 0) { hint_fixes(i, cursor, limit); 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);
@@ -3271,8 +3444,9 @@ int gen_cache_stats() {
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
+// ---- dispatch ----
+
+// the text just read belongs to the document named by the uri
int commit_text() {
int i;
if (!thave || !tuseen) return 0 - 1;
@@ -3363,10 +3537,11 @@ 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
+// ---- command line ----
+
+// a source file on stdin instead of a protocol stream: print what the
+// compiler found, one line each. with --annotate, print the file with the
+// types the compiler can see written in
int load_stdin(int first) {
int n = 0, c = first, cap = 4096; word nb; int k;
tbase = rt.new(cap);
@@ -3407,15 +3582,16 @@ int cli(int first) {
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) {
diff --git a/src/runtime/rt.zc b/src/runtime/rt.zc
index a54d88c..ef5a5c6 100644
--- a/src/runtime/rt.zc
+++ b/src/runtime/rt.zc
@@ -1,33 +1,33 @@
namespace rt;
include run;
-//: ============================================================================
-//: rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers,
-//: the heap (size-class free lists) and the mark-sweep collector.
-//: ============================================================================
+// ============================================================================
+// rt.zc: the runtime half of the genome. memory map, i/o, arithmetic helpers,
+// the heap (size-class free lists) and the mark-sweep collector.
+// ============================================================================
-//: how the runtime is laid out, for a reader coming in cold:
-//:
-//: memory map generated constants name the regions used by a build. the
-//: compiler and runtime use the same names and values.
-//: i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two
-//: string forms (char* strings and packed byte strings).
-//: heap a buddy allocator with a 5 word header on every block.
-//: new/free/recycle are built on it. every heap object has at
-//: most one owner (the container that holds it), and freeing
-//: an owner frees what it owns.
-//: growable arrays T[?] handles, built on the heap.
-//: mark-sweep a conservative collector. it is the safety net behind
-//: free: anything the compiler's ownership analysis could not
-//: free gets reclaimed here.
-//: memory debug optional checks (use after free, double free, leaks).
-//: syscalls file and environment access through one gate word.
-//:
-//: everything is a 64-bit word, and an address is a word index, not a byte
-//: index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the
-//: word at that address with @, then outputs it with . (see the vm reference).
-//:
-//: ---- memory map (words) ----
+// how the runtime is laid out, for a reader coming in cold:
+//
+// memory map generated constants name the regions used by a build. the
+// compiler and runtime use the same names and values.
+// i/o, strings thin wrappers over the cpu's i/o opcodes, plus the two
+// string forms (char* strings and packed byte strings).
+// heap a buddy allocator with a 5 word header on every block.
+// new/free/recycle are built on it. every heap object has at
+// most one owner (the container that holds it), and freeing
+// an owner frees what it owns.
+// growable arrays T[?] handles, built on the heap.
+// mark-sweep a conservative collector. it is the safety net behind
+// free: anything the compiler's ownership analysis could not
+// free gets reclaimed here.
+// memory debug optional checks (use after free, double free, leaks).
+// syscalls file and environment access through one gate word.
+//
+// everything is a 64-bit word, and an address is a word index, not a byte
+// index. the cpu is a stack machine, so "asm { a @ . }" pushes a, fetches the
+// word at that address with @, then outputs it with . (see the vm reference).
+//
+// ---- memory map (words) ----
// src/tools/genmap writes the map values for each build between these markers.
// keep the names and order stable so programs can use the same map interface
@@ -51,7 +51,7 @@ const DSHI = 0x1D4FF0;
const MAGIC = 0x2B7E1516;
// @end-map
-//: frames grow in stable segments so pointers to a caller's locals stay valid.
+// frames grow in stable segments so pointers to a caller's locals stay valid.
word frame_floor = FBOT, frame_top = FTOP, frame_node;
int frame_chunk = FTOP - FBOT, frame_guard = (FTOP - FBOT) >> 2, frame_busy, gc_active;
@@ -61,7 +61,7 @@ word return_low = RS0, return_top = MARK0 - 8, return_node;
const int F_STACK = 1024;
const int F_INTERNAL = 512;
-//: ---- virtual enhancements ----
+// ---- virtual enhancements ----
// install(desc) passes a descriptor to the core through the % opcode. the core
// reads it and installs the region it describes in the cpu, so the compiler can replace
@@ -84,7 +84,7 @@ enhance ishl(a, b) micro "a = shl a b";
enhance lfa(k) micro "r1 = ld 589824; k = add r1 k";
// 1 when i is outside 0..n-1 (the unsigned compare also catches negative i), else 0.
enhance outside(i, n) micro "i = ltu i n; i = xor i 1";
-//: ---- bit operations and float conversions ----
+// ---- bit operations and float conversions ----
// these use single machine instructions where the cpu has them.
@@ -114,7 +114,7 @@ enhance zf2s(a) micro "a = f2s a";
// s2f, the software form used where the cpu has none.
enhance zs2f(a) micro "a = s2f a";
-//: runtime code segments retain the addresses of earlier functions and globals.
+// runtime code segments retain the addresses of earlier functions and globals.
word code_blocks;
// distinguish a raw function address from a heap closure's environment block.
int callable_code(word f) {
@@ -124,13 +124,13 @@ int callable_code(word f) {
return 0;
}
-//: ---- i/o ----
+// ---- i/o ----
-//: output can be redirected. mode 1 drops what is written and mode 2 appends
-//: it to a buffer; mode 3 counts characters without storing them (the server
-//: uses these modes while it checks a document,
-//: so a parse word that prints can't write into the protocol stream).
-//: raw_c always reaches the terminal.
+// output can be redirected. mode 1 drops what is written and mode 2 appends
+// it to a buffer; mode 3 counts characters without storing them (the server
+// uses these modes while it checks a document,
+// so a parse word that prints can't write into the protocol stream).
+// raw_c always reaches the terminal.
int outmode; char* outbuf; int outn, outcap; word outgrow;
// write one character to the terminal with no translation.
@@ -185,12 +185,12 @@ int eq_w(char* a, char* b) {
while (a[i] && a[i] == b[i]) i++;
return a[i] == b[i];
}
-//: ---- word helpers ----
+// ---- word helpers ----
-//: copy and fill by word. copy_w copies forward, so overlapping ranges are only
-//: valid when dst is below src. a long run above the program area goes to the
-//: host in one call (ops 18 and 19 of the syscall gate); a host that answers -1
-//: (a board, the js vm, a range that overlaps itself) gets the plain loop.
+// copy and fill by word. copy_w copies forward, so overlapping ranges are only
+// valid when dst is below src. a long run above the program area goes to the
+// host in one call (ops 18 and 19 of the syscall gate); a host that answers -1
+// (a board, the js vm, a range that overlaps itself) gets the plain loop.
const int SYS_FILL = 18, SYS_COPY = 19;
// copy n words from src to dst.
@@ -342,7 +342,7 @@ bstr n_to_s(int n) {
return p;
}
-//: ---- arithmetic the hardware doesn't have ----
+// ---- arithmetic the hardware doesn't have ----
word[64] pow2;
int zrem;
@@ -380,42 +380,42 @@ int zmod(int a, int b) {
// a shifted left by b bits, for b under 64.
int zshl(int a, int b) { return a * pow2[b & 63]; }
-//: ---- heap ----
+// ---- heap ----
-//: a buddy allocator. the heap is cut into aligned power-of-two chunks, every
-//: block is a power of two words, and a freed block merges with its buddy
-//: (the other half of the block they were split from) whenever that one is
-//: free too, so freed space can be reused at any size. every block has a 5
-//: word header before the pointer the program sees:
-//: p[-5] block size
-//: p[-4] flags in the low 16 bits, size class in the next 5
-//: 0 free (check word cleared) or quarantined (check word still p ^ MAGIC)
-//: 1 live 2 releasing 3 marked 4 quarantined but a word still points at it
-//: bit 8 (value 8) is orthogonal: the object is shared. free is a no-op,
-//: own and release do not follow it, and the sweep still collects it when
-//: nothing points at it. bits 0..2 keep the meanings above.
-//: p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0)
-//: p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw)
-//: free blocks sit on a doubly linked list per class, linked through p[0], p[1].
-//: free quarantines: the block stays intact until a sweep finds nothing pointing
-//: at it. recycle is the fast path for a pointer the program has already
-//: dropped: it pushes the block onto its size-class list and does not walk the
-//: heap. a two-word node is still 8 words (the smallest block); a three-word
-//: node fits in 8 instead of 16.
+// a buddy allocator. the heap is cut into aligned power-of-two chunks, every
+// block is a power of two words, and a freed block merges with its buddy
+// (the other half of the block they were split from) whenever that one is
+// free too, so freed space can be reused at any size. every block has a 5
+// word header before the pointer the program sees:
+// p[-5] block size
+// p[-4] flags in the low 16 bits, size class in the next 5
+// 0 free (check word cleared) or quarantined (check word still p ^ MAGIC)
+// 1 live 2 releasing 3 marked 4 quarantined but a word still points at it
+// bit 8 (value 8) is orthogonal: the object is shared. free is a no-op,
+// own and release do not follow it, and the sweep still collects it when
+// nothing points at it. bits 0..2 keep the meanings above.
+// p[-3] p ^ MAGIC p[-2] owner (the container that owns it, or 0)
+// p[-1] its type (a type record from new(T), 0 from new(n), -(n + 1) from o3's new_raw)
+// free blocks sit on a doubly linked list per class, linked through p[0], p[1].
+// free quarantines: the block stays intact until a sweep finds nothing pointing
+// at it. recycle is the fast path for a pointer the program has already
+// dropped: it pushes the block onto its size-class list and does not walk the
+// heap. a two-word node is still 8 words (the smallest block); a three-word
+// node fits in 8 instead of 16.
const int HDR = 5;
-//: p[-4] bit, orthogonal to live / mark / releasing.
+// p[-4] bit, orthogonal to live / mark / releasing.
const int F_SHARE = 8;
-//: flags live in these bits; the size class is above them.
+// flags live in these bits; the size class is above them.
const int F_LOW = 65535;
int hbump; word[32] freel; int gc_runs, gc_freed, gc_auto = 1;
-//: block walks and new chunks stop here.
+// block walks and new chunks stop here.
int hstop;
-//: while nonzero, balloc will not return a block overlapping this range.
+// while nonzero, balloc will not return a block overlapping this range.
int hold_lo, hold_hi;
-//: words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow.
+// words of memory the host has (0: the heap cannot grow); hcut: the heap was cut, so it must not grow.
int hmax, hcut;
-//: memdebug bits, and the line the compiler stamps before new.
+// memdebug bits, and the line the compiler stamps before new.
int dbg, alloc_line;
// the size of the top-level chunk holding heap offset off, within [0, hstop)
@@ -563,7 +563,7 @@ word balloc(int k) {
}
return 0;
}
-//: ---- a heap that grows ----
+// ---- a heap that grows ----
// the heap is [HEAP_LO, hstop). on a map with HEAP_GROW the memory above
// HEAP_HI belongs to the host (a big zeroed array nobody has touched yet), and
@@ -613,7 +613,7 @@ int hfree() {
}
return n;
}
-//: end of the block bfree last made, for the sweep.
+// end of the block bfree last made, for the sweep.
int fend;
// put block b back, merging it with its buddy as long as it can
int bfree(word b) {
@@ -650,10 +650,10 @@ int bdrop(word b) {
// quarantined)? the check word p[-3] == p ^ MAGIC validates the pointer before
// checking it against block metadata; a random value would have to match by chance.
word isobj(word x) { return x >= HEAP_LO + HDR && x < hbump && x[-3] == (x ^ MAGIC) && (x[-4] & 1); }
-//: the number of words to skip in p[0..n) before one that could be a pointer
-//: to a block (see op 20 in core.c); n when none can be. a long payload of
-//: plain numbers costs one host call instead of an isobj per word. a host that
-//: does not have the op (-1) is asked once and then never again.
+// the number of words to skip in p[0..n) before one that could be a pointer
+// to a block (see op 20 in core.c); n when none can be. a long payload of
+// plain numbers costs one host call instead of an isobj per word. a host that
+// does not have the op (-1) is asked once and then never again.
const int SYS_SCAN = 20;
int scan_off;
// walk the n words at p and mark every heap object they point to.
@@ -956,7 +956,7 @@ word tget(word p, int i) {
return p[i];
}
-//: ---- growable arrays ----
+// ---- growable arrays ----
// a T[?] is a handle: [len, cap, items, element size]. items is an object the
// handle owns; growing swaps it for a bigger one and moves its children over.
@@ -1085,12 +1085,12 @@ int dlen(word h) { return h[0]; }
// the capacity of a growable array: how many elements it can hold before it grows.
int dcap(word h) { return h[1]; }
-//: ---- mark-sweep ----
+// ---- mark-sweep ----
-//: conservative and non-moving. roots: the program area (globals live
-//: between functions), the genome's globals, the compiler's input stack,
-//: the live part of the frame stack and the live part of the data stack.
-//: opcode 619 pushes that index. a word below it is not a root.
+// conservative and non-moving. roots: the program area (globals live
+// between functions), the genome's globals, the compiler's input stack,
+// the live part of the frame stack and the live part of the data stack.
+// opcode 619 pushes that index. a word below it is not a root.
int ms_top, ms_over, gc_lo, gc_hi;
word ms_buf = MARK0; int markcap = MARKN;
@@ -1213,12 +1213,12 @@ int scrub() {
return 0;
}
-//: ---- memory debug ----
+// ---- memory debug ----
-//: dbg bits: 1 header checks and allocation sites, 2 poison the payload on free
-//: (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined
-//: block (the mark walks back at most 64 words to a real header). off unless
-//: a program requests. sites are p ^ MAGIC so the collector does not treat them as pointers.
+// dbg bits: 1 header checks and allocation sites, 2 poison the payload on free
+// (the word 57005, which is 0xDEAD), 4 interior pointers keep a quarantined
+// block (the mark walks back at most 64 words to a real header). off unless
+// a program requests. sites are p ^ MAGIC so the collector does not treat them as pointers.
const int NSITE = 64;
const int POISON = 57005;
@@ -1301,28 +1301,28 @@ int memreport() {
return n;
}
-//: ---- host syscalls ----
+// ---- host syscalls ----
-//: a store of the op number to SYSGATE runs it. the three words after the gate
-//: are the arguments, and the store leaves the result in the gate.
-//: 1 open(path, 0 read | 1 write-create) 2 close(fd)
-//: 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word
-//: 5 getenv(name, buf, cap) 6 time() unix seconds
-//: 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap)
-//: 40 raw(block) any linux syscall: the block holds the number and six arguments.
-//: the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc
+// a store of the op number to SYSGATE runs it. the three words after the gate
+// are the arguments, and the store leaves the result in the gate.
+// 1 open(path, 0 read | 1 write-create) 2 close(fd)
+// 3 read(fd, buf, n) 4 write(fd, buf, n) bytes, one per word
+// 5 getenv(name, buf, cap) 6 time() unix seconds
+// 21 argc() 22 argv(i, buf, cap) 23 envc() 24 envp(i, buf, cap)
+// 40 raw(block) any linux syscall: the block holds the number and six arguments.
+// the result is the kernel's, or -errno. see Host.raw in src/lib/std.zc
const int SYSGATE = ZFP + 2;
const int SYS_OPEN = 1, SYS_CLOSE = 2, SYS_READ = 3, SYS_WRITE = 4, SYS_GETENV = 5, SYS_TIME = 6;
-/* terminates the host process with the exit status supplied in argument a. */
+// terminates the host process with the exit status supplied in argument a.
const int SYS_EXIT = 7;
const int SYS_BOPEN = 10, SYS_BREAD = 11, SYS_BWRITE = 12, SYS_BGETENV = 13;
const int SYS_RAW = 40;
-//: the command line and the environment. argv holds the program path, which is
-//: <eval> when the source came from stdin, so argc is 1 even with no arguments.
-//: the string ops take an index and copy one entry across, one character per
-//: word like SYS_GETENV: a zero buffer requests the length alone, so the caller
-//: can size the string before it fetches it. src/lib/std.zc builds the arrays.
+// the command line and the environment. argv holds the program path, which is
+// <eval> when the source came from stdin, so argc is 1 even with no arguments.
+// the string ops take an index and copy one entry across, one character per
+// word like SYS_GETENV: a zero buffer requests the length alone, so the caller
+// can size the string before it fetches it. src/lib/std.zc builds the arrays.
const int SYS_ARGC = 21, SYS_ARG = 22, SYS_ENVC = 23, SYS_ENV = 24;
// byte-string paths. the address is the first byte (addr_b of the word after
// the length), not the string object. char* string syscalls are unchanged.
@@ -1333,15 +1333,15 @@ int bread(int fd, bstr s, int n) { return syscall(SYS_BREAD, fd, addr_b(bstr_sto
int bwrite(int fd, bstr s, int n) { return syscall(SYS_BWRITE, fd, addr_b(bstr_storage(s) + 1), n); }
// copy the value of the environment variable name into dst, returning its length.
int bgetenv(bstr name, bstr dst, int n) { return syscall(SYS_BGETENV, addr_b(bstr_storage(name) + 1), addr_b(bstr_storage(dst) + 1), n); }
-//: ---- c abi ----
+// ---- c abi ----
-//: ops 14..17 reach native code (see the c abi section of core.c and extern in
-//: compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte
-//: string), cload(path) opens a library so its symbols resolve, and
-//: cbmake(entry, sig) is a native function pointer that runs the zc function at
-//: entry (cfunc(f) in the compiler builds the signature). a native pointer
-//: from cbmake only works while a native call is in progress, and not on the
-//: arm64 jit, where it is 0.
+// ops 14..17 reach native code (see the c abi section of core.c and extern in
+// compiler.zc). cfind(name) is 1 if a native symbol resolves (name is a byte
+// string), cload(path) opens a library so its symbols resolve, and
+// cbmake(entry, sig) is a native function pointer that runs the zc function at
+// entry (cfunc(f) in the compiler builds the signature). a native pointer
+// from cbmake only works while a native call is in progress, and not on the
+// arm64 jit, where it is 0.
const int SYS_CCALL = 14, SYS_CFIND = 15, SYS_CBMAKE = 16, SYS_CLOAD = 17;
// the address of the native symbol name in the loaded libraries, or 0 when it is not
diff --git a/tests/drivers/codeactions_client_test.js b/tests/drivers/codeactions_client_test.js
index f1936e7..6f956b9 100644
--- a/tests/drivers/codeactions_client_test.js
+++ b/tests/drivers/codeactions_client_test.js
@@ -9,7 +9,7 @@ class CodeAction { constructor(title, kind) { this.title = title; this.kind = ki
const vscode = { Range, WorkspaceEdit, CodeAction, CodeActionKind: { QuickFix: 'quickfix' }, Uri: { parse: s => s } };
const uri = 'file:///test.zc', doc = { uri: { toString: () => uri }, version: 3 };
const edit = { range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, newText: 'include rt;\n' };
-const source = { title: 'include rt;', kind: 'quickfix', isPreferred: true, edit: { documentChanges: [{ textDocument: { uri, version: 3 }, edits: [edit] }] } };
+const source = { title: 'include rt', kind: 'quickfix', isPreferred: true, edit: { documentChanges: [{ textDocument: { uri, version: 3 }, edits: [edit] }] } };
const actions = convertCodeActions(vscode, [source], doc);
assert.strictEqual(actions.length, 1);
assert.strictEqual(actions[0].kind, 'quickfix');
diff --git a/tests/drivers/hint_fixes_test.js b/tests/drivers/hint_fixes_test.js
new file mode 100644
index 0000000..9513a85
--- /dev/null
+++ b/tests/drivers/hint_fixes_test.js
@@ -0,0 +1,66 @@
+// quick fixes for hints and for library names used without their module: each fix is the
+// edit the hint describes, offered only where the change keeps the program's meaning.
+// usage: node tests/drivers/hint_fixes_test.js path/to/zclsp
+// interpreted hosts (no jit) are far slower; ZC_TEST_TIMEOUT_SCALE stretches every deadline
+const TIMEOUT_SCALE = Number(process.env.ZC_TEST_TIMEOUT_SCALE) || 1;
+const cp = require('child_process');
+const frame = m => { const b = Buffer.from(JSON.stringify(m)); return Buffer.concat([Buffer.from(`Content-Length: ${b.length}\r\n\r\n`), b]); };
+let ok = 0, bad = 0;
+function check(cond, label, extra) {
+ if (cond) { ok++; console.log('ok ' + label); }
+ else { bad++; console.log('FAIL ' + label + (extra === undefined ? '' : ': ' + JSON.stringify(extra).slice(0, 400))); }
+}
+// the actions offered at the first occurrence of needle (plus offset) in text
+function actions(text, needle, offset = 0) {
+ const at = text.indexOf(needle) + offset, before = text.slice(0, at);
+ const position = { line: before.split('\n').length - 1, character: before.length - before.lastIndexOf('\n') - 1 };
+ const uri = 'file:///hint-fixes.zc';
+ const messages = [{ id: 1, method: 'initialize', params: {} },
+ { method: 'textDocument/didOpen', params: { textDocument: { uri, version: 1, text } } },
+ { id: 2, method: 'textDocument/codeAction', params: { textDocument: { uri }, range: { start: position, end: position }, context: { diagnostics: [] } } },
+ { method: 'exit' }];
+ const r = cp.spawnSync(process.argv[2], [], { input: Buffer.concat(messages.map(m => frame({ jsonrpc: '2.0', ...m }))), timeout: 30000 * TIMEOUT_SCALE });
+ const out = r.stdout.toString(), i = out.indexOf('{"jsonrpc":"2.0","id":2');
+ return i < 0 ? null : JSON.parse(out.slice(i, out.indexOf('Content-Length', i) < 0 ? undefined : out.indexOf('Content-Length', i))).result;
+}
+// apply the first action's edits to text
+function apply(text, action) {
+ const lines = text.split('\n');
+ const offset = p => lines.slice(0, p.line).reduce((n, l) => n + l.length + 1, 0) + p.character;
+ const edits = action.edit.documentChanges[0].edits.map(e => [offset(e.range.start), offset(e.range.end), e.newText]).sort((a, b) => b[0] - a[0]);
+ for (const [s, e, t] of edits) text = text.slice(0, s) + t + text.slice(e);
+ return text;
+}
+function fixes(label, text, needle, offset, expected, title) {
+ const result = actions(text, needle, offset);
+ const first = result && result[0];
+ check(first && first.title === title && first.isPreferred === true && apply(text, first) === expected, label, result);
+}
+function none(label, text, needle, offset) {
+ const result = actions(text, needle, offset);
+ check(Array.isArray(result) && result.length === 0, label, result);
+}
+const head = 'include rt;\nfn main() {\n';
+fixes('an unused parameter is renamed to _', 'include rt;\nint g(int a, int b) { return a; }\nfn main() { return g(1, 2); }\n', 'int b', 4,
+ 'include rt;\nint g(int a, int _) { return a; }\nfn main() { return g(1, 2); }\n', 'rename `b` to _');
+fixes('a local that is never read is renamed to _', head + '\tlet unused = rt.get_c();\n\treturn 0;\n}\n', 'unused', 0,
+ head + '\tlet _ = rt.get_c();\n\treturn 0;\n}\n', 'rename `unused` to _');
+none('a local written later is not renamed', head + '\tlet later = 1;\n\tlater = 2;\n\treturn 0;\n}\n', 'later', 0);
+fixes('a typed constant local becomes const', head + '\tint x = 5;\n\trt.put_n(x);\n\treturn 0;\n}\n', 'x = 5', 0,
+ head + '\tconst int x = 5;\n\trt.put_n(x);\n\treturn 0;\n}\n', 'make `x` a const');
+fixes('an untyped constant local becomes const int', head + '\tlet y = 7;\n\trt.put_n(y);\n\treturn 0;\n}\n', 'y = 7', 0,
+ head + '\tconst int y = 7;\n\trt.put_n(y);\n\treturn 0;\n}\n', 'make `y` a const');
+none('one of several declarators is left alone', head + '\tint x = 5, z = rt.get_c();\n\trt.put_n(x + z);\n\treturn 0;\n}\n', 'x = 5', 0);
+fixes('a power-of-two multiply becomes a parenthesized shift', head + '\tlet y = rt.get_c();\n\tlet q[4];\n\tq[0] = y;\n\trt.put_n(q[0] * 16 + y);\n\treturn 0;\n}\n', '* 16', 0,
+ head + '\tlet y = rt.get_c();\n\tlet q[4];\n\tq[0] = y;\n\trt.put_n((q[0] << 4) + y);\n\treturn 0;\n}\n', 'write << 4');
+none('a multiply bound to a division on its left is left alone', head + '\tlet y = rt.get_c();\n\trt.put_n(y / 2 * 16);\n\treturn 0;\n}\n', '* 16', 0);
+fixes('a small heap buffer becomes a stack buffer', head + '\tlet p = rt.new(8);\n\tp[0] = rt.get_c();\n\trt.put_n(p[0]);\n\treturn 0;\n}\n', 'p = rt', 0,
+ head + '\tlet p[8];\n\tp[0] = rt.get_c();\n\trt.put_n(p[0]);\n\treturn 0;\n}\n', 'use a stack buffer');
+fixes('a runtime name without its module is imported and qualified', 'fn main() {\n\tput_c(65);\n\treturn 0;\n}\n', 'put_c', 0,
+ 'include rt;\nfn main() {\n\trt.put_c(65);\n\treturn 0;\n}\n', 'include rt and use rt.put_c');
+fixes('a compiler name is imported from run', 'include rt;\nfn main() {\n\trt.put_n(evals("return 1;"));\n\treturn 0;\n}\n', 'evals', 0,
+ 'include rt;\ninclude run;\nfn main() {\n\trt.put_n(run.evals("return 1;"));\n\treturn 0;\n}\n', 'include run and use run.evals');
+fixes('an imported module only needs the qualifier', 'include rt;\nfn main() {\n\tput_c(65);\n\treturn 0;\n}\n', 'put_c', 0,
+ 'include rt;\nfn main() {\n\trt.put_c(65);\n\treturn 0;\n}\n', 'use rt.put_c');
+console.log(ok + ' hint fix checks passed, ' + bad + ' failed');
+process.exit(bad ? 1 : 0);
diff --git a/tests/drivers/toolchain_lsp_contract_test.js b/tests/drivers/toolchain_lsp_contract_test.js
index 2872282..59d5a4c 100644
--- a/tests/drivers/toolchain_lsp_contract_test.js
+++ b/tests/drivers/toolchain_lsp_contract_test.js
@@ -284,12 +284,12 @@ test('inference', 'typed fields do not constrain the containing parameter to a s
test('quickfix', 'an entire-line request finds the compiler error inside the range', () => {
const source = 'fn main() { rt.put_n(7); }';
const r = run(source, [{ needle: 'fn', method: 'textDocument/codeAction', params: { range: { start: { line: 0, character: 0 }, end: { line: 0, character: source.length } }, context: { diagnostics: [{ range: { start: { line: 99, character: 99 }, end: { line: 99, character: 99 } }, message: 'stale' }] } } }]);
- assert.ok(r.result(2)?.some(a => a.title === 'include rt;'), JSON.stringify(r.result(2)));
+ assert.ok(r.result(2)?.some(a => a.title === 'include rt'), JSON.stringify(r.result(2)));
});
test('quickfix', 'an owner-name cursor can import a qualified module', () => {
const source = 'fn main() { rt.put_n(7); }', start = { line: 0, character: source.indexOf('rt.') };
const r = run(source, [{ needle: 'rt.', method: 'textDocument/codeAction', params: { range: { start, end: start }, context: { diagnostics: [] } } }]);
- assert.ok(r.result(2)?.some(a => a.title === 'include rt;'));
+ assert.ok(r.result(2)?.some(a => a.title === 'include rt'));
});
test('modulehover', 'source library overview is available without a built-in module entry', () => {
const folder = fs.mkdtempSync(path.join(os.tmpdir(), 'zc-module-hover-'));
diff --git a/tests/test.sh b/tests/test.sh
index 811a09a..449da87 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -550,6 +550,9 @@ s_lsp() {
grep -v '^ok' $T/contracts-$group.out
chk $r 0 "lsp: $group contracts"
done
+ node tests/drivers/hint_fixes_test.js $lsp > $T/hintfixes.out 2>&1; r=$?
+ grep -v '^ok' $T/hintfixes.out
+ chk $r 0 "lsp: hint and import quick fixes keep the program's meaning"
node tests/drivers/codeactions_client_test.js > $T/codeactions-client.out 2>&1; r=$?
grep -v '^ok' $T/codeactions-client.out
chk $r 0 "lsp: versioned import quick-fix edits"