diff options
| author | 2026-10-14 08:09:40 +0000 | |
|---|---|---|
| committer | 2026-10-14 08:09:40 +0000 | |
| commit | a3e9e53e64a1aa5692bca2b60bcbff6a0fcfdba1 (patch) | |
| tree | 2598c50e36e488935211c2e304b720a58315bacb /src/compiler/compiler.zc | |
| parent | embed: 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.
Diffstat (limited to 'src/compiler/compiler.zc')
| -rw-r--r-- | src/compiler/compiler.zc | 562 |
1 files changed, 295 insertions, 267 deletions
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 |
