aboutsummaryrefslogtreecommitdiff
path: root/src/compiler/compiler.zc
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 /src/compiler/compiler.zc
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.
Diffstat (limited to 'src/compiler/compiler.zc')
-rw-r--r--src/compiler/compiler.zc562
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