The runtime's twin emitters share their bodies, and six dead symbols go
This commit is contained in:
commit
26439dc22e
@ -653,8 +653,6 @@ let stamp_of prog =
|
||||
| st -> Printf.sprintf "%s:%d:%f" path st.Unix.st_size st.Unix.st_mtime
|
||||
| exception Unix.Unix_error _ -> path
|
||||
|
||||
let clang_stamp = lazy (stamp_of clang)
|
||||
|
||||
(* Compile one C translation unit to an object file, reusing a cached one when
|
||||
the source text, the compiler and the flags are all unchanged. The key has
|
||||
to carry [opt] and [target]: the acceptance table builds the same programs
|
||||
|
||||
12
lib/dev.ml
12
lib/dev.ml
@ -3395,17 +3395,17 @@ let ignore_sigpipe () =
|
||||
and this is a *daemon* whose client died.
|
||||
|
||||
What makes this answerable without a new protocol is that the connection is
|
||||
per session and not per request. [emacs/flan-dev.el] opens one
|
||||
[make-network-process] in [flan-dev--open], keeps it in
|
||||
[flan-dev--connection], and reopens only when the process is dead; every
|
||||
site that tears it down deliberately sends [close] first. [serve] mirrors
|
||||
per session and not per request. [emacs/flan.el] opens one
|
||||
[make-network-process] in [flan--open], keeps it in [flan--connection], and
|
||||
reopens only when the process is dead; every site that tears it down
|
||||
deliberately sends [close] first. [serve] mirrors
|
||||
that shape — it loops on the one fd until EOF — so while an editor is
|
||||
attached this loop is not even cycling. An editor left open and idle
|
||||
overnight is therefore *attached* overnight, and the grace below can never
|
||||
accumulate under it. That is structural, not a number chosen to outlast
|
||||
human patience, and it is why no PID handshake and no heartbeat op is
|
||||
needed: holding the socket open is the heartbeat, and a client that only
|
||||
ever polls — or one that has [flan-dev-poll-interval] set to nil and polls
|
||||
ever polls — or one that has [flan-poll-interval] set to nil and polls
|
||||
never — is indistinguishable from any other attached client.
|
||||
|
||||
So the signal is client *absence*, and the only absence this is allowed to
|
||||
@ -3456,7 +3456,7 @@ let client_grace () =
|
||||
window would kill the session, which is the bug this whole change exists to
|
||||
remove, reintroduced one line further out. The grace does not reopen that
|
||||
hole: it reads the client, not the program, and all of an Emacs's windows
|
||||
share the one [flan-dev--connection], so closing one of them changes
|
||||
share the one [flan--connection], so closing one of them changes
|
||||
nothing this loop can see. *)
|
||||
let accept_loop ?grace t ls =
|
||||
let grace = match grace with Some g -> g | None -> client_grace () in
|
||||
|
||||
@ -57,15 +57,11 @@ let tag_of_int = function
|
||||
dynload_stubs.c, one field at a time. Everything allocated here is owned by
|
||||
[Dynload] and released together after the call. *)
|
||||
|
||||
let rec marshal (f : Form.t) : Dynload.addr =
|
||||
let p = Dynload.take form_size in
|
||||
write p f;
|
||||
p
|
||||
|
||||
(* Into an existing 24 bytes, which is what an argument array needs: the macro
|
||||
takes a [Form] slice, and a slice is contiguous elements and not an array of
|
||||
pointers. *)
|
||||
and write p (f : Form.t) =
|
||||
pointers — so this writes *into* memory the caller took, and every caller
|
||||
here takes it as part of an array. *)
|
||||
let rec write p (f : Form.t) =
|
||||
let tag t = Dynload.poke_i32 p 0 (tag_int t) in
|
||||
let str t s =
|
||||
tag t;
|
||||
|
||||
@ -656,13 +656,6 @@ let elem_ty loc (t : Types.t) =
|
||||
| Types.String -> Types.Int Types.U8
|
||||
| t -> at loc "indexing %s is not in the JS dialect" (Types.to_string t)
|
||||
|
||||
let is_bytes (t : Types.t) =
|
||||
match t with
|
||||
| Types.String -> true
|
||||
| Types.Slice (Types.Int Types.U8) | Types.Array (_, Types.Int Types.U8) ->
|
||||
true
|
||||
| _ -> false
|
||||
|
||||
let rec value f (e : Tast.expr) : string =
|
||||
refuse_ty e.Tast.loc e.Tast.ty;
|
||||
match e.Tast.e with
|
||||
|
||||
@ -203,80 +203,109 @@ void flan_dev_emit(const uint8_t *bytes, int64_t len) {
|
||||
result_len += n;
|
||||
}
|
||||
|
||||
static void emit_cstr(const char *s) {
|
||||
flan_dev_emit((const uint8_t *)s, (int64_t)strlen(s));
|
||||
/* ── Rendering a scalar, into whichever buffer is being written ──────── */
|
||||
|
||||
/* This file has two buffers a rendered value can go into: the result buffer
|
||||
* above, which an evaluated expression's rendering streams into for the REPL
|
||||
* to read back, and the current watch slot further down. What they hold is
|
||||
* the same four renderings — an i64, a u64, an f64, a quoted string — and the
|
||||
* only thing that differed was which of [flan_dev_emit] and
|
||||
* [flan_dev_watch_emit] the bytes went to. So that is the parameter, and the
|
||||
* eight exported entry points are eight one-line calls into these four.
|
||||
*
|
||||
* The exports stay eight. The compiler emits the result four by name — see
|
||||
* [Session.externs] — and the watch four are exported C, which a program can
|
||||
* reach through declare-c and which the [(watch ...)] arm described further
|
||||
* down would be pointed at. They are ABI, and ABI does not collapse just
|
||||
* because the bodies did. */
|
||||
typedef void (*sink)(const uint8_t *, int64_t);
|
||||
|
||||
static void put(sink out, const char *s) {
|
||||
out((const uint8_t *)s, (int64_t)strlen(s));
|
||||
}
|
||||
|
||||
/* Rendered in C so that u64 is not a lie: the language's own i64->bytes is
|
||||
* signed, and anything past 2^63 would come back negative. */
|
||||
void flan_dev_emit_u64(uint64_t x) {
|
||||
static void put_u64(sink out, uint64_t x) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%llu", (unsigned long long)x);
|
||||
emit_cstr(buf);
|
||||
put(out, buf);
|
||||
}
|
||||
|
||||
void flan_dev_emit_i64(int64_t x) {
|
||||
static void put_i64(sink out, int64_t x) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%lld", (long long)x);
|
||||
emit_cstr(buf);
|
||||
put(out, buf);
|
||||
}
|
||||
|
||||
/* Unsigned NaN, for flan_f64_to_bytes's reason and one of its own: the REPL
|
||||
* and println must not disagree about what a value looks like, which is the
|
||||
* rule the escape table below is already held to. A NaN's sign bit is decided
|
||||
* by whether the value was folded or computed, so showing it makes the printed
|
||||
* form depend on the backend and the optimisation level rather than on the
|
||||
* number. */
|
||||
void flan_dev_emit_f64(double x) {
|
||||
/* Unsigned NaN, and the rule is flan_rt.c's rather than a second statement of
|
||||
* it: [flan_f64_format] is what println renders through, and the REPL, a watch
|
||||
* row and println must not disagree about what one value looks like. A NaN's
|
||||
* sign bit is decided by whether the value was folded or computed, so showing
|
||||
* it would make the printed form depend on the backend and the optimisation
|
||||
* level rather than on the number. */
|
||||
extern int flan_f64_format(double x, char *buf, size_t cap);
|
||||
|
||||
static void put_f64(sink out, double x) {
|
||||
char buf[64];
|
||||
if (x != x) snprintf(buf, sizeof buf, "nan");
|
||||
else snprintf(buf, sizeof buf, "%g", x);
|
||||
emit_cstr(buf);
|
||||
flan_f64_format(x, buf, sizeof buf);
|
||||
put(out, buf);
|
||||
}
|
||||
|
||||
/* Quoted and escaped, in C, because doing it in the generated IR would be a
|
||||
* loop per string and the language has no allocator to build the result in.
|
||||
* A string whose content is not escaped does not round-trip and reads as a
|
||||
* framing bug rather than as the value it is. */
|
||||
void flan_dev_emit_str(const uint8_t *bytes, int64_t len) {
|
||||
* framing bug rather than as the value it is — and a newline in a watched
|
||||
* string would become a second row of the table on the wire.
|
||||
*
|
||||
* The table is flan_rt.c's [flan_escape_char], which is also what println's
|
||||
* [flan_escape_bytes] escapes through. One table, three framings of it. */
|
||||
extern int flan_escape_char(unsigned char c, char *out);
|
||||
|
||||
static void put_str(sink out, const uint8_t *bytes, int64_t len) {
|
||||
size_t n = len < 0 ? 0 : (size_t)len;
|
||||
emit_cstr("\"");
|
||||
put(out, "\"");
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
unsigned char c = bytes[i];
|
||||
switch (c) {
|
||||
case '"': emit_cstr("\\\""); break;
|
||||
case '\\': emit_cstr("\\\\"); break;
|
||||
case '\n': emit_cstr("\\n"); break;
|
||||
case '\t': emit_cstr("\\t"); break;
|
||||
case '\r': emit_cstr("\\r"); break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof buf, "\\x%02x", c);
|
||||
emit_cstr(buf);
|
||||
} else {
|
||||
flan_dev_emit(&c, 1);
|
||||
}
|
||||
}
|
||||
char e[4];
|
||||
int k = flan_escape_char(bytes[i], e);
|
||||
out((const uint8_t *)e, (int64_t)k);
|
||||
}
|
||||
emit_cstr("\"");
|
||||
put(out, "\"");
|
||||
}
|
||||
|
||||
/* Closing a value: the ellipsis a full buffer earns, and the counter back to
|
||||
* even. Shared by [flan_dev_result_end] and [flan_dev_watch_end], which is
|
||||
* the same seqlock twice over two different buffers.
|
||||
*
|
||||
* Room for the ellipsis is made rather than assumed: the buffer is full by
|
||||
* definition when [full] is set.
|
||||
*
|
||||
* The counter goes last, and back to even, so a reader that sees the new
|
||||
* generation sees the whole value. [| 1] first for the same reason [begin]
|
||||
* sets rather than increments: this must land on an even count whatever state
|
||||
* an abandoned write left behind. */
|
||||
static void end_value(char *buf, size_t *len, size_t cap, int full,
|
||||
uint64_t *gen) {
|
||||
if (full) {
|
||||
const char *ell = "...";
|
||||
size_t k = strlen(ell);
|
||||
if (*len > cap - k) *len = cap - k;
|
||||
memcpy(buf + *len, ell, k);
|
||||
*len += k;
|
||||
}
|
||||
__atomic_store_n(gen, (*gen | 1) + 1, __ATOMIC_RELEASE);
|
||||
}
|
||||
|
||||
void flan_dev_emit_u64(uint64_t x) { put_u64(flan_dev_emit, x); }
|
||||
void flan_dev_emit_i64(int64_t x) { put_i64(flan_dev_emit, x); }
|
||||
void flan_dev_emit_f64(double x) { put_f64(flan_dev_emit, x); }
|
||||
|
||||
void flan_dev_emit_str(const uint8_t *bytes, int64_t len) {
|
||||
put_str(flan_dev_emit, bytes, len);
|
||||
}
|
||||
|
||||
void flan_dev_result_end(void) {
|
||||
if (result_full) {
|
||||
/* Room is made for it rather than assumed: the buffer is full by
|
||||
* definition when this fires. */
|
||||
const char *ell = "...";
|
||||
size_t k = strlen(ell);
|
||||
if (result_len > RESULT_MAX - k) result_len = RESULT_MAX - k;
|
||||
memcpy(result + result_len, ell, k);
|
||||
result_len += k;
|
||||
}
|
||||
/* Last, and back to even, so a reader that sees the new generation sees the
|
||||
* whole value. [| 1] first for the same reason [begin] sets rather than
|
||||
* increments: this must land on an even count whatever state an abandoned
|
||||
* write left behind. */
|
||||
__atomic_store_n(&generation, (generation | 1) + 1, __ATOMIC_RELEASE);
|
||||
end_value(result, &result_len, RESULT_MAX, result_full, &generation);
|
||||
}
|
||||
|
||||
/* Copy the current value out, with the counter that says which one it is.
|
||||
@ -444,10 +473,6 @@ void flan_dev_watch_enable(int on) {
|
||||
__atomic_store_n(&watch_on, on ? 1 : 0, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
int flan_dev_watch_enabled(void) {
|
||||
return __atomic_load_n(&watch_on, __ATOMIC_RELAXED);
|
||||
}
|
||||
|
||||
/* The slot a name owns, or NULL if the table is full.
|
||||
*
|
||||
* Linear, because the table is 64 long and a hash would need a policy for
|
||||
@ -523,76 +548,27 @@ void flan_dev_watch_emit(const uint8_t *bytes, int64_t len) {
|
||||
s->len += (uint32_t)n;
|
||||
}
|
||||
|
||||
static void watch_cstr(const char *str) {
|
||||
flan_dev_watch_emit((const uint8_t *)str, (int64_t)strlen(str));
|
||||
}
|
||||
/* The four renderings, aimed at the watch slot instead of the result buffer.
|
||||
* The bodies are [put_i64] and friends above — see the note there for why the
|
||||
* entry points stay four while the rendering is one. */
|
||||
void flan_dev_watch_emit_i64(int64_t x) { put_i64(flan_dev_watch_emit, x); }
|
||||
void flan_dev_watch_emit_u64(uint64_t x) { put_u64(flan_dev_watch_emit, x); }
|
||||
void flan_dev_watch_emit_f64(double x) { put_f64(flan_dev_watch_emit, x); }
|
||||
|
||||
void flan_dev_watch_emit_i64(int64_t x) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%lld", (long long)x);
|
||||
watch_cstr(buf);
|
||||
}
|
||||
|
||||
void flan_dev_watch_emit_u64(uint64_t x) {
|
||||
char buf[32];
|
||||
snprintf(buf, sizeof buf, "%llu", (unsigned long long)x);
|
||||
watch_cstr(buf);
|
||||
}
|
||||
|
||||
/* Unsigned NaN, the same rule [flan_dev_emit_f64] states: a watch row and a
|
||||
* REPL answer for one value must read the same. */
|
||||
void flan_dev_watch_emit_f64(double x) {
|
||||
char buf[64];
|
||||
if (x != x) snprintf(buf, sizeof buf, "nan");
|
||||
else snprintf(buf, sizeof buf, "%g", x);
|
||||
watch_cstr(buf);
|
||||
}
|
||||
|
||||
/* Quoted and escaped, for [flan_dev_emit_str]'s reason and one more: a string
|
||||
* whose content is not escaped does not round-trip, and a newline in one would
|
||||
* become a second row of the table on the wire rather than part of a value. */
|
||||
void flan_dev_watch_emit_str(const uint8_t *bytes, int64_t len) {
|
||||
size_t n = len < 0 ? 0 : (size_t)len;
|
||||
watch_cstr("\"");
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
unsigned char c = bytes[i];
|
||||
switch (c) {
|
||||
case '"': watch_cstr("\\\""); break;
|
||||
case '\\': watch_cstr("\\\\"); break;
|
||||
case '\n': watch_cstr("\\n"); break;
|
||||
case '\t': watch_cstr("\\t"); break;
|
||||
case '\r': watch_cstr("\\r"); break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
char buf[8];
|
||||
snprintf(buf, sizeof buf, "\\x%02x", c);
|
||||
watch_cstr(buf);
|
||||
} else {
|
||||
flan_dev_watch_emit(&c, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
watch_cstr("\"");
|
||||
put_str(flan_dev_watch_emit, bytes, len);
|
||||
}
|
||||
|
||||
void flan_dev_watch_end(void) {
|
||||
watch_slot *s = watch_cur;
|
||||
watch_cur = NULL;
|
||||
if (s == NULL) return;
|
||||
if (s->full) {
|
||||
/* Room is made for it rather than assumed: the value is full by
|
||||
* definition when this fires. */
|
||||
const char *ell = "...";
|
||||
size_t k = strlen(ell);
|
||||
if (s->len > WATCH_VAL - k) s->len = (uint32_t)(WATCH_VAL - k);
|
||||
memcpy(s->val + s->len, ell, k);
|
||||
s->len += (uint32_t)k;
|
||||
}
|
||||
/* Last, and back to even, so a reader that sees the new generation sees the
|
||||
* whole value. [| 1] first for the same reason begin sets rather than
|
||||
* increments: this must land on an even count whatever an abandoned write
|
||||
* left behind. */
|
||||
__atomic_store_n(&s->gen, (s->gen | 1) + 1, __ATOMIC_RELEASE);
|
||||
/* [len] widened and narrowed around the shared close, which counts in
|
||||
* size_t because the result buffer does; a slot's own length is 32 bits
|
||||
* and [WATCH_VAL] is 192, so neither conversion can lose anything. */
|
||||
size_t len = s->len;
|
||||
end_value(s->val, &len, WATCH_VAL, s->full, &s->gen);
|
||||
s->len = (uint32_t)len;
|
||||
}
|
||||
|
||||
/* ── Watching one scalar, with no compiler change ───────────────────── */
|
||||
@ -630,13 +606,6 @@ int32_t flan_dev_watch_i64(const char *name, int64_t x) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t flan_dev_watch_u64(const char *name, uint64_t x) {
|
||||
if (!flan_dev_watch_begin(name)) return 0;
|
||||
flan_dev_watch_emit_u64(x);
|
||||
flan_dev_watch_end();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t flan_dev_watch_f64(const char *name, double x) {
|
||||
if (!flan_dev_watch_begin(name)) return 0;
|
||||
flan_dev_watch_emit_f64(x);
|
||||
|
||||
@ -457,11 +457,18 @@ static void emit(const char *s) {
|
||||
static void emit_n(const uint8_t *p, int64_t n) { flan_write_stdout(p, n); }
|
||||
|
||||
/* A text inside a structure, quoted and escaped. The same table as
|
||||
* flan_rt.c's [flan_escape_bytes] and flan_dev.c's [flan_dev_emit_str], and
|
||||
* for the same reason those two are the same as each other: three printers
|
||||
* that disagree about what a string looks like is three wire formats. If that
|
||||
* table changes, change this one. Streamed rather than built, so there is no
|
||||
* buffer to overrun and no length to cap. */
|
||||
* flan_rt.c's [flan_escape_char], which is where the typed side's printers —
|
||||
* [flan_escape_bytes] and flan_dev.c's emitters — now share their one copy of
|
||||
* it: printers that disagree about what a string looks like are that many
|
||||
* wire formats.
|
||||
*
|
||||
* This copy is deliberate, and the argument for it is docs/SPIKE-DUPLICITY.md
|
||||
* §9's: the dyn printer lives inside the runtime that owns the storage it
|
||||
* walks, which is why it prints a dyn vec structurally where the typed
|
||||
* printer answers <vec>. The whole printer is this side's; the table it
|
||||
* shares with the other side is the part that must not drift. If that table
|
||||
* changes, change this one. Streamed rather than built, so there is no buffer
|
||||
* to overrun and no length to cap. */
|
||||
static void emit_escaped(const uint8_t *p, int64_t n) {
|
||||
int64_t i;
|
||||
emit("\"");
|
||||
|
||||
@ -3,12 +3,12 @@
|
||||
* This header is not compiled into a program. The build embeds the runtime's
|
||||
* .c files as strings and hands each one to clang on its own, with no include
|
||||
* path (see [Build.compile_c]), so runtime/flan_dyn.c declares everything it
|
||||
* defines and this file declares it a second time. That is the same standing
|
||||
* arrangement flan_escape_bytes and flan_dev_emit_str already live under —
|
||||
* "if either table changes, change both" — and it is made mechanical rather
|
||||
* than hopeful: test/dyn_ops.c includes this header and names every function
|
||||
* below, so a signature that drifts from the implementation is a link error in
|
||||
* `dune test` rather than a surprise at someone else's call site.
|
||||
* defines and this file declares it a second time. That second copy is not
|
||||
* held honest by hand the way a repeated escape table would be: it is made
|
||||
* mechanical, because test/dyn_ops.c includes this header and names every
|
||||
* function below, so a signature that drifts from the implementation is a
|
||||
* link error in `dune test` rather than a surprise at someone else's call
|
||||
* site.
|
||||
*
|
||||
* Who reads it: the compiler lane, which emits calls to these names, and the
|
||||
* C tests. The whole of the boundary is here. What is behind it — the value
|
||||
|
||||
@ -134,10 +134,9 @@ void *flan_restart_frame(int32_t i) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Aim the transfer channel at a frame obtained earlier. The same store
|
||||
* [flan_break_resume] makes and the same one an invoke-restart makes — this
|
||||
* only spells it without a lookup, for a caller that did its looking up when
|
||||
* the stack was worth reading. */
|
||||
/* Aim the transfer channel at a frame obtained earlier. The same store an
|
||||
* invoke-restart makes — this only spells it without a lookup, for a caller
|
||||
* that did its looking up when the stack was worth reading. */
|
||||
void flan_restart_take(void *frame, void *xfer) { *(void **)xfer = frame; }
|
||||
|
||||
/* [T] and string are both ptr+len — see Emit.ll. */
|
||||
@ -340,10 +339,19 @@ int64_t flan_bytes_to_i64(const uint8_t *p, int64_t n) {
|
||||
*
|
||||
* The test is x != x rather than isnan, which keeps math.h out of this file
|
||||
* and is the same comparison the prelude uses. An infinity still prints signed:
|
||||
* there the sign is the value. */
|
||||
* there the sign is the value.
|
||||
*
|
||||
* Spelled as a format into a caller's buffer rather than inline here, because
|
||||
* the rule has a second reader: flan_dev.c's REPL emitter and its watch table
|
||||
* render a f64 for an editor to show, and a build where the inspector said
|
||||
* "-nan" and println said "nan" would be the same disagreement one layer out.
|
||||
* That file calls this; there is one copy of the rule. */
|
||||
int flan_f64_format(double x, char *buf, size_t cap) {
|
||||
return (x != x) ? snprintf(buf, cap, "nan") : snprintf(buf, cap, "%g", x);
|
||||
}
|
||||
|
||||
void flan_f64_to_bytes(double x, uint8_t *buf, flan_slice *out) {
|
||||
int n = (x != x) ? snprintf((char *)buf, FLAN_NUM_BYTES, "nan")
|
||||
: snprintf((char *)buf, FLAN_NUM_BYTES, "%g", x);
|
||||
int n = flan_f64_format(x, (char *)buf, FLAN_NUM_BYTES);
|
||||
out->ptr = buf;
|
||||
out->len = fit(n);
|
||||
}
|
||||
@ -364,15 +372,45 @@ void flan_u64_to_bytes(uint64_t x, uint8_t *buf, flan_slice *out) {
|
||||
out->len = fit(n);
|
||||
}
|
||||
|
||||
/* The escape table itself: what one byte reads as inside a quoted string,
|
||||
* written into [out] and returning how many bytes that took. Never more than
|
||||
* four, which is what every caller's headroom is sized from.
|
||||
*
|
||||
* A table rather than a printer, because the callers frame the same mapping
|
||||
* differently and that framing is the part that is genuinely theirs: the one
|
||||
* below builds a capped slice to hand back, and flan_dev.c's streams into a
|
||||
* fixed buffer it does not own the end of. What they must not differ about is
|
||||
* this switch — the REPL and println disagreeing about what a struct looks
|
||||
* like is two wire formats — so this switch exists once and they share it.
|
||||
*
|
||||
* flan_dyn.c keeps its own copy of the table on purpose; see the comment
|
||||
* there and docs/SPIKE-DUPLICITY.md §9. */
|
||||
int flan_escape_char(unsigned char c, char *out) {
|
||||
switch (c) {
|
||||
case '"': out[0] = '\\'; out[1] = '"'; return 2;
|
||||
case '\\': out[0] = '\\'; out[1] = '\\'; return 2;
|
||||
case '\n': out[0] = '\\'; out[1] = 'n'; return 2;
|
||||
case '\t': out[0] = '\\'; out[1] = 't'; return 2;
|
||||
case '\r': out[0] = '\\'; out[1] = 'r'; return 2;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
out[0] = '\\';
|
||||
out[1] = 'x';
|
||||
out[2] = "0123456789abcdef"[c >> 4];
|
||||
out[3] = "0123456789abcdef"[c & 0xf];
|
||||
return 4;
|
||||
}
|
||||
out[0] = (char)c;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* A string *inside* a printed structure, quoted and escaped, so that the run
|
||||
* of bytes can be told from the punctuation around it — (S {:name "a b"}) has
|
||||
* two fields if the quotes are missing and one if they are there.
|
||||
*
|
||||
* This is the same escape table as flan_dev_emit_str in flan_dev.c, and
|
||||
* deliberately so: the REPL and println must not disagree about what a struct
|
||||
* looks like. It cannot be the *same function* because the dev one streams
|
||||
* into the result buffer and this one has to hand back a slice; if either
|
||||
* table changes, change both.
|
||||
* The table is [flan_escape_char] above, which is also what flan_dev.c's
|
||||
* emitters use; this function is the framing and nothing else.
|
||||
*
|
||||
* Its own buffer, not `scratch`: escaping is the one conversion whose output
|
||||
* is not a bounded handful of characters. Over-long input is truncated with an
|
||||
@ -394,20 +432,10 @@ void flan_escape_bytes(const uint8_t *p, int64_t n, flan_slice *out) {
|
||||
escaped[w++] = '"';
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
if (w + 5 + 4 >= ESCAPE_MAX) { cut = 1; break; }
|
||||
unsigned char c = p[i];
|
||||
switch (c) {
|
||||
case '"': escaped[w++] = '\\'; escaped[w++] = '"'; break;
|
||||
case '\\': escaped[w++] = '\\'; escaped[w++] = '\\'; break;
|
||||
case '\n': escaped[w++] = '\\'; escaped[w++] = 'n'; break;
|
||||
case '\t': escaped[w++] = '\\'; escaped[w++] = 't'; break;
|
||||
case '\r': escaped[w++] = '\\'; escaped[w++] = 'r'; break;
|
||||
default:
|
||||
if (c < 0x20) {
|
||||
w += (size_t)snprintf(escaped + w, 5, "\\x%02x", c);
|
||||
} else {
|
||||
escaped[w++] = (char)c;
|
||||
}
|
||||
}
|
||||
char e[4];
|
||||
int k = flan_escape_char(p[i], e);
|
||||
memcpy(escaped + w, e, (size_t)k);
|
||||
w += (size_t)k;
|
||||
}
|
||||
if (cut) { escaped[w++] = '.'; escaped[w++] = '.'; escaped[w++] = '.'; }
|
||||
escaped[w++] = '"';
|
||||
@ -603,16 +631,6 @@ static uint32_t flan_name_id(const uint8_t *s, int64_t n) {
|
||||
return h;
|
||||
}
|
||||
|
||||
/* What the break loop calls to resume: look a restart up by the name someone
|
||||
* typed and aim the channel at it. 0 if no frame offers it, and then the loop
|
||||
* says so rather than resuming into nothing. */
|
||||
int32_t flan_break_resume(const uint8_t *name, int64_t namelen, void *xfer) {
|
||||
void *r = flan_find_restart(flan_name_id(name, namelen));
|
||||
if (r == NULL) return 0;
|
||||
*(void **)xfer = r;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void flan_error(uint32_t type_id, void *condition, void *xfer,
|
||||
const uint8_t *name, int64_t namelen) {
|
||||
flan_signal(type_id, condition, xfer);
|
||||
|
||||
3
vendor/agent/flan_agent.c
vendored
3
vendor/agent/flan_agent.c
vendored
@ -85,7 +85,6 @@ uint64_t flan_dev_result_cap(void);
|
||||
* [enable] — the table is written only while somebody is reading it, so
|
||||
* opening and closing a watch buffer is a message that arrives here. */
|
||||
void flan_dev_watch_enable(int on);
|
||||
int flan_dev_watch_enabled(void);
|
||||
/* And [reset], which opens a new accumulation window for the numeric slots.
|
||||
* It moves one counter and touches no slot, so the game thread stays the only
|
||||
* writer of the table. */
|
||||
@ -310,8 +309,6 @@ extern void (*flan_break_hook)(const uint8_t *name, int64_t namelen,
|
||||
* flan_rt.c, which carries the argument for why they are two hooks and not
|
||||
* one. This end of it is [trap_stop] below. */
|
||||
extern void (*flan_trap_hook)(const uint8_t *name, int64_t namelen);
|
||||
extern int32_t flan_break_resume(const uint8_t *name, int64_t namelen,
|
||||
void *xfer);
|
||||
extern int32_t flan_restart_count(void);
|
||||
/* The shadow stack (runtime/flan_dev.c). The compiler pushes a frame per Flan
|
||||
* call in a dev build; these read one, and only ever on the thread that owns
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user