All notable changes to this project are recorded here, grouped as Added, Changed, Fixed, Removed and Security. The project uses Semantic Versioning. Before 1.0.0, any minor release may change the language, the C API, the ABI and the JSON the command line writes; each entry says what a program or a host has to do about it.
boxes[0].ss[1].xs[j]). The verifier refused it under every build and switch as a fault in the compiler, with K0411 ("load.at at 102 reads layout 0 at 24 bytes into what an address is at, which has no such field there"): with struct S { tag: i32; xs: [i32; 4] }, struct Box { ss: [S; 2] }, boxes holding one Box of S(1, [1, 2, 3, 4]) and S(2, [5, 6, 7, 8]) and j worked out while running as 2, a program printing boxes[0].ss[1].xs[j], writing 9 there and printing boxes[0].ss[1].xs[2] now prints 7 and 9. A run of runs with its first index written down (bs[0].grid[1][j]) is the same case. Nothing the verifier accepted before changes, and no instruction the compiler writes changes. (#254)K0505 ("this assigns to something that is not a place, which the checker allowed"): with struct P { x: i32; y: i32 }, grid: [[i32; 3]; 2] holding [[1, 2, 3], [4, 5, 6]], xs: [i32; 3] holding [1, 2, 3], ps: [P; 2] holding [P(1, 2), P(3, 4)], and i and j worked out while running as 1 and 2, grid[i][j] = 9, xs[i] += 5 and ps[i].y = 9 were each refused, and printing {grid[1][2]} {xs[1]} {ps[1].y} after them now prints 9 7 9. Where more than one index is worked out while running, each is held to its own count, so an index past its run is refused with K0604 naming that index and that run's count ("index 3 is outside 3 of them"), in every engine and in a body compiled to C. kest emit writes one new instruction, in.run, and load.slots and store.slots carry how many slots of one of the run they read or write. A program that compiled before compiles to the same instructions, with load.slots and store.slots each two bytes wider. (#256)ss[i].xs[j] = 9), in a local and inside an element of an array, with = or an operator. Each was refused under every build and switch as a fault in the compiler, with K0505 ("this assigns to something that is not a place, which the checker allowed"): with struct S { tag: i32; xs: [i32; 4] }, ss: [S; 2] holding S(1, [1, 2, 3, 4]) and S(2, [5, 6, 7, 8]), and i and j worked out while running as 1 and 2, ss[i].xs[j] = 9 followed by printing ss[1].xs[2] now prints 9. An index past its run is refused with K0604 and that run's count. Inside an element, a write whose right-hand side writes the same element, or grows the array, keeps what the right-hand side wrote. A program that compiled before compiles to the same instructions. (#257)K0505 ("this leaves the stack 1 deep and a statement leaves it as it found it"): with struct Row { tag: i32; count: [i32; 3] }, a function returning rows[k].count[at] did not compile, and now, with rows holding Row(7, [10, 20, 30]), it answers 30 for k 0 and at 2. The same held for a run of bool and for a run inside a struct inside the element. A program that compiled before compiles to the same instructions. (#246)=, +=, -=, *= or /=. Every such write was refused as a fault in the compiler, with K0505 ("this assigns to something that is not a place, which the checker allowed"), whichever field the run was; only a run held in a local could be written at such an index. With struct Row { count: [i32; 3] }, rows holding Row([10, 20, 30]) and at worked out while running as 1, rows[0].count[at] = 99 followed by rows[0].count[at - 1] += 5 now leaves the run 15 99 30. An index past the run is refused with K0604 ("index 3 is outside 3 of them"), the sentence a run in a local gets, in every engine and in a body compiled to C. A write whose right-hand side grows the array is written into the array as it is after it grew. kest emit writes these as two new instructions, load.elem.run and store.elem.run. A program that compiled before compiles to the same instructions. A run of runs written at two indexes worked out while running (grid[i][j] = 9) is still refused, in a local as well as in an element (#249). (#250)bool, or a set of flags, read through an address worked out before the read (load.at) is looked at only when the address is in memory a host lent, as a read that knows its array already was. Every such read was looked at, of the machine's own memory too. Nothing a program or a host sees changes: every read refused before is refused with the same line. A program reading rows[k].cells[i] 800,000 times from a [Row] of 1000 struct Row { cells: [bool; 4] } it filled itself runs in 131,499,448 instructions where it took 162,700,128, under perf stat -e instructions:u with the program and commands the pull request gives. (#247)kest lsp and kest dap read a \u escape that is half of a UTF-16 pair with no other half as U+FFFD (EF BF BD). They wrote the three bytes UTF-8 would use for that number, \ud800 as ED A0 80, which spell no character: an editor holds its buffer as UTF-16 and can send a lone half this way, and the language server then published K0107 ("the byte 0xed starts no character") once for each of the three bytes, while the file the same editor saves, with U+FFFD in the half's place, checks with nothing to say under kest check. A buffer with a lone half now checks, and under kest dap a lone half in a path or an expression reads as U+FFFD. A whole pair, and every other \u escape, is read as before. (#233)~ on a set flips only those bits, where it flipped every bit of its width, and S(bits) keeps only those bits, where it kept every bit it was given; either made a set that wrote itself as a set it did not equal. With flags S: u8 { A B }, u8(S(u8(255))) and u8(~S()) answer 3 where they answered 255, S(u8(12)) is S() where it held 12, and ~S() == S.A | S.B is true where it was false, in every engine and compiled to C. ~ on a whole number, and a set with a name for every bit of its width, answer as before. A deterministic program observes this, so the profile moves from kest-det 3 to kest-det 4. (#238)bool that is not nought or one, or a set of flags with a bit none of its names cover, out of memory a host lent is refused with K0668 where it reads it, as a tag with no case is refused with K0651. It went on with a value that was neither: with flags Mood: u8 { Hungry Tired } and a lent [Sense] of struct Sense { up: bool; mood: Mood }, a host writing 2 into up had if senses[0].up take its branch while senses[0].up == true was false, and writing 255 into mood had u8(senses[0].mood) answer 255, a Mood that wrote itself as Mood.Hungry | Mood.Tired and was not equal to it. Each read now stops with "bool here holds 2, which is neither true nor false" or "Mood here holds 255, which has bits it has no names for". A body compiled to C refuses with the same code and the same sentence. A lend whose bytes are values of their types, and a program reading its own memory, answer as before. (#239)kest hostile hands a door that takes a set of flags one of four, chosen by the seed: the empty set, every flag the set names, one flag, or some of them. It wrote the empty set every time, so a door that went wrong on a flag was never handed one: for a door Engine.felt(m: Mood) with flags Mood: u8 { Hungry Tired }, seeds 1 to 8 wrote 64 calls of it, and every one was Engine.felt(Mood()). The same file and seed now write a different program wherever a door takes a set; a file with no such door is written as before, byte for byte. (#243)kest fmt keeps the brackets around a block that gives a value (|x| value) wherever it is an operand: before a call, a field or an index, after a unary operator, and inside a binary operator. It dropped them and wrote a different expression while answering 0: (|| 41 + 1)() came out as || 41 + 1(), a block giving a call, and (|| c + 1) * 2 as || c + 1 * 2; where that form was not stable, fmt refused the file as a fault in the formatter. None of these blocks compile (K0367), so no program that runs changes, but fmt now writes different bytes for a file holding one. A block in braces, and a block handed to a function as an argument, are written as before. (#227)kest_held_begin and kest_held_call refuse an args of NULL with a count above nought, answering false and writing K0636 to errors with the door as the host named it ("step was handed 2 values and no address to find them at"). They copied count values from NULL and crashed the host. Values handed with an address are copied exactly as before. (#225)kest_gave_text holds the text it writes out to the text it is in, as kest_call does for text handed in. It copied as many bytes as the frame's length slot said, from wherever its address slot pointed, asking only that the address was not NULL: a host that wrote 64 into the length of two bytes of text had 64 copied, and the checked build stopped on a read of poisoned memory; with -1 it read 4095 bytes. A frame whose text has an address that is not the machine's, or a length past the text there, now answers -1 with K0632 ("slot N of the frame holds text with L in its length slot, which the text there cannot be, so it is not what f gave back"), and no room to list the program's own text answers -1 with K0605. What a call left in the frame is written out exactly as before. (#215)kest_text refuses a length past 2,147,483,647 bytes, the most len counts, with K0630 and the number, before a byte is read. A host that handed 2,147,483,648 bytes over had them taken, and the program's len answered 2147483648, which no i32 holds; at 4,294,967,295 bytes the room asked for the copy wrapped to nought and the refusal said K0605 "out of memory". Text of 2,147,483,647 bytes or fewer is taken exactly as before. (#216)kest_takes_text refuses a NULL among its words with K0635 naming the word, before any word is read and with nothing written into the frame. A list with a hole in it crashed the host: in the reader for a number, in strlen for text. (#219)kest_build_from refuses a file with no path, the first or any after it, with K0701 ("file N of the M a build was handed has no path", counted from nought) and answers NULL. A missing path after the first crashed the host at the first import, and one on the first file was said as a build handed no files. (#221)kest_entry and kest_entry_of answer -1. kest_borrow with no element answers a lend with nothing in it and K0610. kest_build_layout answers nought. kest_build, kest_build_within and kest_held_new with no path answer NULL with K0701 ("a build handed no path has no program to read") written to errors. (#222)kest_call, or a door answers with, is held to the text its address is in: what the heap place was asked for, or the program's own text it was cut from. Its length slot was never asked about, and the program read as many bytes as it said. fn echo(t: text) -> text { return "{t}!" }, called with two bytes of text said to be 200, copied 200 bytes, and the checked build stopped on a read of poisoned heap memory; four bytes said to be 12 read what a value before them left in the place, and the program's own text said to be longer read on into whatever the compiler wrote after it. A length past the text, or below nought, is now refused with K0636 going in and K0652 coming back ("takes text in slot N with L in its length slot, which the text there cannot be"); a negative length was taken before and the program stopped with K0630. Text cut short is read as before, and text whose length fits is taken exactly as before. (#205)K0605 (no room) and kest_runtime_ran_out counts it. It was refused with K0636, or K0652 for a door's answer, as though the host's length ran past the text. (#212)os.read, under the command line and in a release binary, answers a file's text when the file holds a zero byte, where it answered none, which is what a missing file answers: a program could not read back what it had just written. A zero is a character of text and Io.read already handed it over. A path holding a zero still names no file. (#210)kest_array_length, kest_lend_ends or kest_call, or answers with from a door, has to be exactly what kest_call or kest_borrow gave: the front of an array or store header. Anything else in the machine's heap was read as a header. Text made with kest_text and beginning with the four bytes of an array's tag ("RRAKAAAABBBBCC\001CDDDDDDDDEEEEEEEE") was an array 1094795585 long with room for 1111638594 to kest_array_length, a lend kest_lend_ends answered true for, and, handed to kest_call for a [u8] or answered from a door, stopped the host with a segmentation fault; a handle moved by one byte was read from the middle of a header. A call in or a door's answer with one is now refused with K0636 or K0652 ("an address of this machine's where no handle begins"), kest_array_length answers 0, and kest_lend_ends answers false with K0637. A handle the machine gave is answered exactly as before. (#202)C a constant of type [[[bool; 25]; 25]; 40] (25000 slots), a fn copied that copies its argument into a local and returns it, and main holding C and copied(C), the program was refused under kest and kest-debug, by kest emit and by kest build --release with K0408 ("store.n at 15 names slot 50000 of the 34464 there are"), and ran only under KEST_PLAIN=1. A program that never comes near 65535 slots compiles exactly as before. (#200)load.n 0, 65535 and a loop onto itself, with room for 65535 slots, was still being walked by kest_module_prove after a minute, where the same chunk one slot shallower is refused at once with K0411. It is now refused with K0411 too. A body whose stack stays under 65535 slots is walked exactly as before. (#197)struct After { e: Nothing; n: i64 } pushed into an array and read back had n as 0 where it was 42, in both engines and compiled to C, and nothing was said; the same held behind a [Nothing; 3] or a Nothing?, and an enum behind one was read back as a tag with no case (K0651), though the program wrote it. A host asking kest_case_of about the tag behind one was told it had no cases, and a host handing over a value with text behind one, filled correctly, was refused with K0636. A value with no such struct in it is moved exactly as before. (#194)K0502 and the number. Past that, an instruction naming one of them wrapped to the start of the function's values and read another, and nothing was said: a function reading two different tables of type [[[bool; 32]; 32]; 32] and then returning 7 answered 0, and a loop the compiler unrolls, two turns over one such table, counted 3 where the answer is 2. A function is not carried into one whose values it would take past the limit; it is called instead. A function within the limit compiles exactly as before. (#190)[T; N] copied through a generic function's signature, when a call settles what T is, is held to the 65535 bytes and slots a value may be, and one too big is refused with K0327 at the call. Its size wrapped to nought: with D of 32768 bytes, pushing two(D, D) of fn two<T>(a: T, b: T) -> [T; 2] twice into a [[T; 2]] wrote past the end of the array's element, and a release kest aborted with double free or corruption. A literal laid out as a [T; N] too big is refused at the literal. A program whose [T; N]s fit is laid out exactly as before. (#186)K0502 and the number, at the name or value that takes it past. It was refused with K0505, as a fault in the compiler, or with K0408, with a number that is not what it holds: let both = [C, C], with C of 32768 slots, was said to leave -32768 slots of stack. A function within the limit compiles exactly as before. (#187)K0502) is told that and nothing else. What came after was said as the compiler's own fault (K0505), and the body was handed to the backend, which refused it too and stopped the build: a piece of text with 65537 holes was said as K0502, K0505 and K0639, as though the build had run out of memory. A function within every limit compiles exactly as before. (#192)Big? for a struct Big { xs: [i64; 8191] } of 65528 bytes, is refused with K0327. Its size wrapped to nought: a struct holding one was laid out with its fields over each other, and pushing none twice into a [Big?] wrote past the array's element, so a release kest could abort with double free or corruption. A program whose optionals fit is laid out exactly as before. (#183)kest_held_reload's said, where it was told at most 511 bytes of the line, cut inside a name and with no place after it, however much room it gave. A said too short for the line is cut where a whole character ends. (#176)K0662, from kest_native_failed, and a door's name in K0654 and K0631 were cut at 160 bytes, possibly inside a character. (#173)K0657) or does not keep the caller's promise (K0623) is refused with the function's whole name and what it takes and gives, in both engines, where a long name was cut at 160 bytes and the rest of the sentence lost. A frame a body compiled to C finds wrong at a crossing is said whole too, where it was cut at 200 bytes. (#180)kest emit --c writes that reads an enum out of memory a host wrote or lent, and finds a tag with no case, says K0651 in the machine's words, whole and with the machine's suggestion after it. An enum name longer than 56 bytes cut the sentence short, possibly inside a character, while the machine said it whole. (#181)K0333 names each of the first eight combinations a match leaves unanswered whole, however long its cases' names are. A long name was cut to fit 128 bytes, so the message named a case the enum does not have, and the combinations after it were counted as "more". (#175)K0329 says what a call passed and what each candidate takes whole. A long type name was cut at 256 bytes and the names after it left out. (#177)K0701 lists every source directory of the project whole. A list longer than 512 bytes stopped inside a path, with no closing backtick and the directories after it left out. (#178)kest debug's locals and the debug adapter's variables show a local holding text whole. Text of more than 253 bytes was cut short, with no closing quote, nothing to say there was more, and possibly half a character at the end. (#179)K0639 because a build on another thread ran out, and a language server's unsaved buffers and the lexer's last token room were shared by every build in the process. (#130)kest_entry and kest_entry_of find a function by the name its file writes in a program whose module is named in 252 bytes or more, where they answered -1. (#156)module line names a module of 256 bytes or more checks like any other. A method call such as p.get() was refused with K0307 though get is declared in the file, a call qualified with the file's own module was not found, and kest check --json listed every declaration as under no module ("module":null). (#148, #155)source directories was refused as if there were no project; kest doctor said the library was not found, a file using io without importing it got no hint, and kest release said kest.h and the archive were not there. A run that ran out of memory while looking for the library says K0639, where it could take a library from somewhere else. (#131, #137)kest new refuses with K0667, before making anything, a name that is not UTF-8, has a control character in it, or has a directory longer than 255 bytes. A name with a line break wrote more lines into the manifest, a name with " or \ made --json answer JSON that does not parse, and a name of about 200 bytes or more left empty directories behind and came back 1 without a word. A name with a control character or a byte that is not UTF-8 is now refused where a project was made. (#129)--room, --fuel and --work refuse a negative amount, which was read as one just under 2^64: kest run --fuel -100 ran with no step limit. A script that passed a negative amount is now refused. (#141)kest call and a host handing words over with kest_takes_text read a whole number the way the language writes one. 010 is 10, where it was 8; a u64 word up to 18446744073709551615 is read, where anything past 2^63-1 was refused with K0624; and +5 and 5 are refused. (#160)std.os's read, write and exists answer for no file when the path holds a zero byte, in kest and in the host emit --c writes into a release. The path was cut at the zero, so os.write("keep.txt\0.other", ...) wrote keep.txt. (#150)--json, kest lsp and kest dap write holds a byte that is not UTF-8 as \ufffd, where it wrote the byte raw and a reader refused the whole answer: kest check --json on a file named bad\377.kest could not be decoded. Text that was UTF-8 is written as before. (#146)kest lsp and kest dap read a header line of any length, where a line of 512 bytes or more could end the header early, stopping the server, or be read as another Content-Length. They read a client's numbers as JSON writes them: an id of 1e300 is answered with the id it came with, where the answer held -9223372036854775808, and inf, NaN, 0x10, +1 and txyz are not read as numbers or as true. (#136, #143)kest debug reads a command of any length, so break <path>:<line> with a path longer than about 500 bytes sets the breakpoint, where it ran the rest of the path as another command and the program ran past the line. break refuses a line that is not a plain line number: break 3x, break +3 and break 4294967299 each set a breakpoint at line 3. (#139, #152)_findfirst gave when it fails, so a directory that is not there is not reported as one that cannot be read, or the other way round. (#169)K0310) or called with the wrong count (K0309); a generic function, struct or enum with more than sixteen fields or arguments could not tell what its type parameter was (K0343); and an overloaded name called with more than sixteen arguments had nothing that took them (K0329). (#115)K0329, kest call could not call it (K0624), and it was not found when named as a value. A second function taking the same parameters as one past the thirty-second was accepted, so one of the two could never be called; it is now refused with K0304. (#119)match whose subjects make 2^32 combinations or more is refused with K0333, as too many, and the message says "at least 4294967295 combinations". The count wrapped at 2^32: four enums of 256 cases made no combinations at all, so a match a, b, c, d with a single arm was accepted as answering every one and returned that arm's value for all of them. A program like that is now refused. (#121)0.0 without a word: 1.5 followed by seventy zeros was 0.0. Every shorter literal reads the same bits as before. (#124)kest call on a name several functions share makes the machine big enough for the function it chose, not for the first of that name: with count() and count(a0, a1, a2) in one file, kest call file.kest count 1 2 3 was refused with K0602 before it ran. (#118)kest parse --json counts what a string's holes hold in nodes: a file returning len("{s + s}") and one returning len("{s}") both had 8. Only files with holes count differently. (#116)kest lsp, renaming a name another module declares edits every file of the project that names it, the declaring file included, in one edit, instead of the open file alone, which left the program naming something nothing declared. A rename that cannot be made whole is refused with a message rather than made in part: a name from the standard library or from outside the project, a file naming it outside the project (a file reached through a link out of the project counts as outside), a file in no project (add a kest.project), a project that does not build or that there was no room to read. A reference list is refused too when the checker had no room to note every use. A local is still renamed in its own file. (#97)kest reads and writes every path in UTF-8, so a file or directory whose name has a character outside the system's code page (a CJK or emoji directory, nā) is found, built and renamed in like any other, where it named another file or none. A project reached through a link into such a directory was taken for another directory, and a rename could count that directory's files as the project's. This needs Windows 10 version 1903 or later. On an older Windows, kest reads paths in the system's code page as before and refuses a rename that would edit another file. kest lsp reads a URI with a drive letter as VS Code writes it (file:///c%3A/x), where it read the path as /c:/x, and writes file:///C:/x back instead of file://C:/x. A Windows host that hands the library UTF-8 paths sets the same code page with a manifest of its own, as embedding.md says. (#102)de_DE, fr_FR, ps_AF) has numbers read and written as in the C locale. Under a decimal comma the lexer read 1.5 as 1 and 0.1 as 0, every number was printed with a comma and a .0 after it (0,001.0), and emit --c and build --release wrote constants such as 0x1,8p+0, which is not C; the editor tools' JSON reader, the debugger, the disassembler and two of the machine's messages did the same. A host may now set any locale. Nothing changes in the C locale. (#105)kest lsp, rename and references answer when asked from where a local, a parameter or a function is declared, as they do from a use, instead of answering null and []. References put the declaration first when the request asks for it with includeDeclaration. A declaration nothing uses still gets no answer. (#106)kest build --release works with the Windows kest.exe. It links kest.lib, which the MSVC build and the Windows archive hold, where it looked for libkest.a and refused with K0663; it runs cl when CC is not set and hands cl or clang-cl MSVC's flags; it names the release after the last / or \, so examples\math.kest makes math; and it leaves no .obj behind. A release made on Windows writes the same bytes as kest run and as every other platform, with \n line ends instead of \r\n, and keeps a multiply and an add two roundings under MSVC as under Clang and GCC. (#107)kest.project cannot be read as one (a name it does not know, an edition it does not know, more source lines than it reads), check, run, build, test and the rest say K0709 with what is wrong and come back 1, instead of passing over the manifest to the directory above, reading the program in the first edition, saying K0649, or running no tests and coming back 0. kest lsp shows it at the start of the open file, and shows a build that ran out of memory there as the command line says it. A program in a project whose manifest has a mistake in it is now refused where it ran before. (#59)performance.now(), never going below what it last answered, instead of the wall clock in whole milliseconds; the processor-time clocks are the monotonic one, and any other clock is refused with EINVAL. (#61)Host.fileRead, Io.read, Host.arg and Engine.name refuse with K0662 when the heap has no room for the text they hand a program, instead of handing back an empty piece the machine refused with K0652, as text a broken host made up. Host.fileRead with no room to read the file refuses rather than answering none, and a length past 32 bits is refused rather than cut. The host emit --c writes does the same in Host.arg and Host.fileRead. (#65)K0101), or a byte (K0106), instead of reading the line break, or a byte past the end of the file, as an escape (K0103); lex --json no longer writes a byte past the end of the file. A string or byte ends at \r\n as it does at \n, without a K0109 for the carriage return. (#67)K0108 instead of ending the file where it stood: check passed over everything after a NUL, and fmt -w and the language server's formatting deleted it. fmt writes nothing, and the language server's formatting answers no edit, unless what the formatter wrote holds the same words as the file. A file holding such a control character is now refused where it was read. (#70)kest test with no room to list a project's tests, kest build --release with no room for the release's name or the compiler's words, and tick with no room for its events say K0639 and come back 1, instead of running some of the tests and passing, making nothing and coming back 0, or saying K0663 or K0621. (#71)kest_held_reload says there was no room when the host has none for the copies, frames and lists it keeps, instead of saying save did not answer bytes or restore would not make a world. A held world with no room to watch its files answers kest_held_changed with true, where it never noticed an edit again. (#72)kest lsp shows a module the open file reaches that does not build, with its first mistake, on the line that imports it or on the import it is reached through, and sends that module's own file what is wrong with it, and an empty list once a build reads it clean. Files are named to the editor from the root, the standard library's included when KEST_LIB or the path kest was started by is relative. (#74)Host.fileRead and Io.read, in the command line and in the host emit --c writes, refuse a read of more than 2,147,483,647 bytes (KEST_MOST_READ) with K0662 and the number. On a host whose sizes are 32 bits wide, as the playground's are, the buffer's size wrapped there and the read wrote past it. A program reading a file of 2 GiB or more is now refused on every host. Reading a program from the standard input, and kest hostile's read, say K0639 at that size. (#76)K0639 and frees what it made, instead of running and refusing the program's first value with K0605 ("the program has used 0 bytes and this asked for 0 more"). (#79)/* is refused with K0112, a code of its own; K0106 means only a byte with no closing quote. The message and the suggestion are the same. (#80)scratch { } block whose lists the host would not grow says how many bytes they asked for, by the machine and not a ceiling, instead of "asked for 0 more". (#81)kest_held_reload says there was no room to call save whenever the machine ran out during the call: a second reload refused for the same size as the first used to say save did not answer bytes. A call refused for call depth or stack slots (K0602) counts as no room too, so a save stopped by depth is no longer blamed on the program. (#82, #88)kest lsp, go to definition and references reach a name written under the module it came from (thing.n(), thing.c, the standard library's math.min(1, 2)), a call settled among overloads, a function of several named as a value (the one its type settles on), and the name of a type wherever it is written (let h: Here, Here(1), thing.Point, thing.Door.Open). References list each use once, where a use in a generic's body or a constant's type was listed once for every time it was read. (#89, #95)K0201 ("expected the end of the hole"), where it was dropped without a word: "[{s:4}]" printed [7] for an s of 7, and fmt refused the file as a fault in the formatter. A program with something after a hole's value is now refused. (#93)K0639; K0638 is said only for the stack or frames a host picked. A host that saw K0638 for a missing heap arena sees K0639 instead. emit and every build no longer hand on a module whose proof could not start, emit --c and build --release free the C writer's scratch memory when compiling stops partway, and hostile, new, doctor, lsp and dap say K0639 when their own memory cannot be made, instead of K0701, K0605 or nothing. (#30)kest lsp answers every request. A message that is not JSON is answered with -32700 and no id; one there was no memory to hold, or whose answer could not be written, with -32603 and K0639, under its own id when that is a number or a plain name of at most 64 characters. kest dap says a message it could not read in the debug console and refuses a request whose response could not be written. A Content-Length of 2^64 - 1 no longer reads past the end of a buffer. (#31)lsp and dap refuse a message nested more than 64 deep and say the limit, instead of running out of stack and crashing. (#34)K0639 instead of crashing the compiler. (#37)K0606) no longer prints freed memory in the build report when memory runs short; the report says K0639. (#38)check and emit hand on their listing, and call --json its needs, only when it is whole. With no room for a part they used to print ? for a type (and a different signature under --json), or leave lines out, and come back 0; now the run says K0639, and under --json the listing's fields are null. This holds on every target, the WebAssembly build the playground runs included. (#40, #42, #44)fmt, parse, lex, check and run say K0639 (K0658 under --room) and come back 1 when reading a file runs out of memory, instead of saying the file could not be read (K0701), or printing nothing and coming back 0. (#48)resumes is no longer read as one that does not when memory runs short: fmt and fmt -w no longer drop resumes c.at, parse keeps it, and the checker and the compiler no longer blame the program with K0368 or K0505. (#49)K0639 (K0658 under --room) instead of being taken for no manifest, which made an import from one of its sources say K0701. (#51)fmt --json no longer hands an editor an edit that replaces the changed bytes with "" when memory runs short, which deleted that part of the file on save. lex --json no longer writes "" for a token or comment it had no room to copy or leaves out comments, and lex no longer drops every comment from its listing; each says K0639 instead. Under --json, a span that holds a NUL byte is now written whole, with the byte as \u0000, where it used to be cut off at that byte. lex --json's cost, held and askings now count the comment list: one allocation more, which is what lex takes. (#52)--room ceiling counts it once. Under a ceiling, a manifest line with a name kest does not know can now be refused as no room rather than as an unknown name. (#54)kest build --release starts the C compiler directly with an argument list instead of through sh or cmd.exe, so no path has to be quoted for a shell; on Windows a CC naming a .bat or .cmd file is still run by cmd.exe, as Windows runs every batch file. CC is split into words at spaces and tabs, the first naming the compiler and the rest passed as flags before kest's own; a run in double quotes stays one word, so a compiler under C:/Program Files can be named. A compiler that refuses and a compiler that is not there both still give K0663. (#21)--json, doc now writes a schema 4 object with schema, diagnostics and errors; hostile writes one object with file, seed, calls, unmakeable and the written file as text instead of ignoring the option; and debug --json is refused with K0649, pointing at dap. The command-line docs name the commands that report cost and work. (#23)doc writes its page whole or not at all: running out of memory partway through no longer leaves half an object before the refusal, and doc honours --room. Output with enough room is unchanged. (#25)fmt, lex and parse are refused cleanly when a file's arena cannot be made, instead of crashing, and fmt running out of memory now says K0639 in text and in --json rather than reporting a fault in the formatter or nothing at all. (#27)The first release: a statically typed language for the gameplay half of a game, a bytecode machine an engine embeds through one C header, and a C backend for release builds.
text, structs, fixed runs, arrays, stores and references, enums that carry values, sets of flags, optionals, vectors, generics, function values, blocks, defer, match, working memory with scratch, and bodies that wait between frames.no.alloc, no.host and deterministic, the last under the kest-det profile 3, which gives the same bits on Linux on x86-64 and arm64, macOS on arm64 and Windows on x86-64.include/kest.h (ABI 4): building a program, machines with ceilings on stack, call depth, heap and steps, doors a program calls into its host, lending host memory, worlds kept across frames, and reloading a program under a running world.check, run, call, tick, emit, fmt, lex, parse, profile, new, build, test, doc, doctor, hostile, debug, lsp and dap, with --json output (schema 4) from the commands that check, run, build and format a program.kest build --release and kest emit --c, which write a program as C for a release build that answers what the machine answers.., _ and -, not starting with -; any other name is refused with K0663 before anything is written. Every path in the compiler's command is quoted for the shell, and a long CC or a deep install is never cut short.lib/std, written in Kest.