Console-VM + user-VM pair: real async message-passing relay
Console sessions now route through the same general VM-to-VM messaging system (Phase C) any VM can already use for its own reasons -- not a synchronous shortcut. Per direct instruction: real async MSG-SEND/ MSG-DELIVER (Option B), not a VM-EXEC-based synchronous relay, because messaging is a general capability, not a console-specific mechanism. New CONSOLE-CMD-EVENT message type (common:messaging.4th). New sk_repl_dispatch_line() (repl.c), called from both sk_repl_step and sk_repl_run in place of a direct vm_interpret(): if the active VM's own name has a live "<name>~user" counterpart registered, the raw input line is wrapped as an S"-embedded CONSOLE-CMD-EVENT MSG-SEND and interpreted on the console VM instead of being run directly -- the console's own next MSG-TICK (Hera's idle pump) delivers it into the paired user VM via VM-EXEC, same mechanism every other message already uses. Falls back to direct interpretation if there's no pairing, or if the line contains a `"` (known v1 limitation, warned about explicitly rather than silently mishandled). New capsule_console_birth() (capsule_console.h/.c): a bare VM whose only content is loading common:messaging.4th -- the console side of a pairing, parallel in shape to RUNCAP's user-VM birth but with fixed embedded content instead of a devblock read (no identity, no thumbdrive involved). New PAIR-TEST diagnostic word (mama_forth_words.c, matches RUNCAP-TEST's own precedent): births both halves of a pairing and registers the "<name>~user" mapping. Not the real pairing call site -- that's the eventual attach/onboarding flow -- this exists to exercise the relay live before that flow exists. Found and fixed a real, serious bug live: console_set_vm_name() stored the caller's raw pointer instead of copying it. mama_word_use() (USE) passes a VMRegistryEntry field living on its own stack frame -- once USE returns, that pointer dangles, corrupting every console tag after the first USE (observed directly as garbled "[[]" / binary-looking prefixes instead of "[CaptBob]"). Fixed at the source: console_set_ vm_name() now copies into internal storage. That surfaced a second, related bug across every console_get_vm_name()-based save/restore call site in mama_forth_words.c (BIRTH, VM-STEP, VM-EXEC, CONNECT-HERMES, CONNECT-ARTEMIS): saving just a pointer into the single internal buffer meant an intervening console_set_vm_name() call silently corrupted the saved value before the restore ever ran. New console_save_vm_name() copies into caller-owned storage; every save/restore site updated. Verified end-to-end, live in QEMU: typed WELCOME at a paired console VM -- it did not execute directly (no UNKNOWN WORD), printed ok immediately (queued, async), and on the next idle tick "[CaptBob~user] Minted identity -- default personality" appeared on its own -- genuine delivery and execution in the paired user VM through the real MSG-SEND/MSG-DELIVER pipeline. Console tags confirmed clean (no garbling) across all three architectures' full regression boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
71b6937deb
commit
b0f12710bb
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
#ifndef __STARKERNEL__
|
||||
#error "capsule_console.c is kernel-only"
|
||||
#endif
|
||||
|
||||
#include "starkernel/capsule_console.h"
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_birth.h"
|
||||
#include "starkernel/xxhash64.h"
|
||||
#include "starkernel/kmalloc.h"
|
||||
#include <string.h>
|
||||
|
||||
/* Real, minimal, working content -- just enough vocabulary to send/
|
||||
* receive messages (common:messaging.4th, same as every other VM).
|
||||
* No COMMON-CH subscription: a console's own traffic is direct 1:1
|
||||
* with its paired user VM (CONSOLE-CMD-EVENT), not broadcast, so
|
||||
* there's no need to resolve an index in Hermes's own routing table
|
||||
* for it. Needs the "Block NNNN" header capsule_exec_payload()
|
||||
* requires (§F.18) -- not optional. 4997 is an unused block number. */
|
||||
static const char CONSOLE_IDENTITY_SRC[] =
|
||||
"Block 4997\n"
|
||||
"S\" common:messaging.4th\" EXEC\n"
|
||||
"MSG-CD-INIT\n";
|
||||
|
||||
CapsuleRunResult capsule_console_birth(const char *console_name,
|
||||
VMUuid *out_vm_id, void **out_vm_ctx)
|
||||
{
|
||||
if (!console_name) return CAPSULE_RUN_ERR_INVALID;
|
||||
|
||||
size_t source_len = sizeof(CONSOLE_IDENTITY_SRC) - 1u;
|
||||
uint8_t *arena = (uint8_t *)kmalloc(source_len);
|
||||
if (!arena) return CAPSULE_RUN_ERR_STILLBORN;
|
||||
memcpy(arena, CONSOLE_IDENTITY_SRC, source_len);
|
||||
|
||||
/* Heap-built single-entry directory -- same shape RUNCAP already
|
||||
* established (§F.6/§F.18); never freed, matches that precedent
|
||||
* (a VM's IDENTITY exec reads directly from this arena). */
|
||||
CapsuleNameEntry name_entry;
|
||||
memset(name_entry.name, 0, sizeof(name_entry.name));
|
||||
{
|
||||
size_t n = strlen(console_name);
|
||||
if (n >= CAPSULE_NAME_MAX) n = CAPSULE_NAME_MAX - 1u;
|
||||
memcpy(name_entry.name, console_name, n);
|
||||
}
|
||||
|
||||
CapsuleDesc desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.magic = CAPSULE_MAGIC_PACK(CAPSULE_VERSION_0, CAPSULE_HASH_XXHASH64);
|
||||
desc.content_hash = xxhash64_capsule(arena, source_len);
|
||||
desc.capsule_id = desc.content_hash;
|
||||
desc.offset = 0;
|
||||
desc.length = source_len;
|
||||
desc.flags = CAPSULE_FLAG_ACTIVE | CAPSULE_FLAG_PRODUCTION;
|
||||
desc.owner_vm = 0;
|
||||
desc.birth_count = 0;
|
||||
desc.created_ns = 0;
|
||||
|
||||
CapsuleDirHeader dir;
|
||||
memset(&dir, 0, sizeof(dir));
|
||||
dir.magic = CAPSULE_DIR_MAGIC;
|
||||
dir.arena_base = (uint64_t)(uintptr_t)arena;
|
||||
dir.arena_size = source_len;
|
||||
dir.desc_count = 1;
|
||||
dir.desc_capacity = 1;
|
||||
dir.name_count = 1;
|
||||
dir.dir_hash = 0;
|
||||
|
||||
CapsuleRunResult r = capsule_birth_baby(
|
||||
console_name, &dir, &desc, &name_entry, arena,
|
||||
1 /* skip_pki_sig -- not build-time content, same rationale as RUNCAP */,
|
||||
out_vm_id, out_vm_ctx);
|
||||
|
||||
/* capsule_birth_baby() never sets the registry entry's own .name --
|
||||
* found live in RUNCAP (§F.18), same fix needed here. */
|
||||
if (r == CAPSULE_RUN_OK && out_vm_id) {
|
||||
capsule_vm_registry_set_name(*out_vm_id, console_name);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
@@ -41,6 +41,8 @@
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_birth.h"
|
||||
#include "starkernel/capsule_runcap.h"
|
||||
#include "starkernel/capsule_console.h"
|
||||
#include "freestanding/stdio.h"
|
||||
#include "starkernel/capsule_mint.h"
|
||||
#include "starkernel/user_identity_seed.h"
|
||||
#include "starkernel/capsule_loader.h"
|
||||
@@ -288,7 +290,8 @@ void mama_word_birth(VM *vm)
|
||||
/* Switch console prefix to the baby's name so its init capsule output
|
||||
* appears tagged [Hermes] / [Artemis] rather than [Hera]. */
|
||||
{
|
||||
const char *saved_prefix = console_get_vm_name();
|
||||
char saved_prefix[VM_NAME_MAX];
|
||||
console_save_vm_name(saved_prefix, sizeof(saved_prefix));
|
||||
console_set_vm_name(name_buf);
|
||||
|
||||
vm_state_push(vm);
|
||||
@@ -557,7 +560,7 @@ static void mama_word_vm_step(VM *vm)
|
||||
const char *src;
|
||||
VMRegistryEntry entry;
|
||||
VM *target;
|
||||
const char *saved_name;
|
||||
char saved_name[VM_NAME_MAX];
|
||||
|
||||
if (vm->dsp < 1) { vm->error = 1; return; }
|
||||
|
||||
@@ -592,7 +595,7 @@ static void mama_word_vm_step(VM *vm)
|
||||
|
||||
vm_physics_touch(entry.vm_id);
|
||||
|
||||
saved_name = console_get_vm_name();
|
||||
console_save_vm_name(saved_name, sizeof(saved_name));
|
||||
console_set_vm_name(entry.name);
|
||||
sk_repl_step(target);
|
||||
console_set_vm_name(saved_name);
|
||||
@@ -618,7 +621,7 @@ static void mama_word_vm_exec(VM *vm)
|
||||
const char *src;
|
||||
VMRegistryEntry entry;
|
||||
VM *target;
|
||||
const char *saved_name;
|
||||
char saved_name[VM_NAME_MAX];
|
||||
|
||||
if (vm->dsp < 3) { vm->error = 1; return; }
|
||||
|
||||
@@ -669,7 +672,7 @@ static void mama_word_vm_exec(VM *vm)
|
||||
vm_physics_touch(entry.vm_id);
|
||||
|
||||
log_message(LOG_INFO, "VM-EXEC: '%s' -> '%s'", cmd_buf, vm_name);
|
||||
saved_name = console_get_vm_name();
|
||||
console_save_vm_name(saved_name, sizeof(saved_name));
|
||||
console_set_vm_name(entry.name);
|
||||
vm_state_push(vm);
|
||||
vm_interpret(target, cmd_buf);
|
||||
@@ -700,7 +703,7 @@ static void mama_word_vm_call(VM *vm)
|
||||
const char *src;
|
||||
VMRegistryEntry entry;
|
||||
VM *target;
|
||||
const char *saved_name;
|
||||
char saved_name[VM_NAME_MAX];
|
||||
|
||||
if (vm->dsp < 3) { vm->error = 1; return; }
|
||||
|
||||
@@ -750,7 +753,7 @@ static void mama_word_vm_call(VM *vm)
|
||||
vm_physics_touch(entry.vm_id);
|
||||
|
||||
log_message(LOG_DEBUG, "VM-CALL: '%s' -> '%s'", cmd_buf, vm_name);
|
||||
saved_name = console_get_vm_name();
|
||||
console_save_vm_name(saved_name, sizeof(saved_name));
|
||||
console_set_vm_name(entry.name);
|
||||
vm_state_push(vm);
|
||||
vm_interpret(target, cmd_buf);
|
||||
@@ -905,6 +908,80 @@ static void mama_word_runcap_test(VM *vm)
|
||||
vm_push(vm, (cell_t)r);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief PAIR-TEST ( caddr u -- ok? )
|
||||
* Diagnostic-only word (FABRIC-3.md Phase F, 2026-08-28): births a
|
||||
* console VM (bare, capsule_console.h) named by the given string, and a
|
||||
* user VM (capsule_runcap_birth(), from whatever drive sk_repl_get_
|
||||
* homeblocks_dev()/sig() currently report) named "<string>~user" --
|
||||
* the pairing convention sk_repl_dispatch_line() (repl.c) looks for.
|
||||
* Registers the pairing in the console's own VM-name routing table at
|
||||
* the fixed index (3) that relay uses. Not the real pairing call site
|
||||
* -- that's the eventual attach/onboarding flow; this exists to
|
||||
* exercise the console-VM + user-VM relay live before that exists.
|
||||
*/
|
||||
static void mama_word_pair_test(VM *vm)
|
||||
{
|
||||
char console_name[VM_NAME_MAX];
|
||||
char user_name[VM_NAME_MAX + 8];
|
||||
cell_t u, caddr;
|
||||
uint32_t i;
|
||||
|
||||
if (vm->dsp < 1) { vm->error = 1; return; }
|
||||
u = vm_pop(vm);
|
||||
caddr = vm_pop(vm);
|
||||
|
||||
if (u <= 0 || (uint32_t)u >= VM_NAME_MAX) {
|
||||
console_println("PAIR-TEST: name too long or empty");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
{
|
||||
const uint8_t *p = vm_ptr(vm, (vaddr_t)caddr);
|
||||
if (!p) { vm->error = 1; return; }
|
||||
for (i = 0; i < (uint32_t)u; i++) console_name[i] = (char)p[i];
|
||||
}
|
||||
console_name[u] = '\0';
|
||||
|
||||
memcpy(user_name, console_name, (size_t)u);
|
||||
memcpy(user_name + u, "~user", 6); /* includes NUL */
|
||||
|
||||
struct blkio_dev *dev = sk_repl_get_homeblocks_dev();
|
||||
const homeblocks_sig_t *sig = sk_repl_get_homeblocks_sig();
|
||||
if (!dev || !sig) {
|
||||
console_println("PAIR-TEST: no home-blocks drive attached");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
VMUuid console_id, user_id;
|
||||
void *console_ctx = (void *)0;
|
||||
if (capsule_console_birth(console_name, &console_id, &console_ctx) != CAPSULE_RUN_OK) {
|
||||
console_println("PAIR-TEST: console birth FAILED");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
if (capsule_runcap_birth(dev, sig, user_name, &user_id, (void **)0) != CAPSULE_RUN_OK) {
|
||||
console_println("PAIR-TEST: user birth FAILED");
|
||||
vm_push(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Register the pairing in the console's own routing table, index 3
|
||||
* -- the fixed convention sk_repl_dispatch_line()'s constructed
|
||||
* MSG-SEND text uses. */
|
||||
{
|
||||
char reg_cmd[VM_NAME_MAX + 32];
|
||||
int n = snprintf(reg_cmd, sizeof(reg_cmd), "S\" %s\" 3 VM-NAME-REG", user_name);
|
||||
if (n > 0 && (size_t)n < sizeof(reg_cmd)) {
|
||||
vm_interpret((VM *)console_ctx, reg_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
console_println("PAIR-TEST: console + user VM pair live");
|
||||
vm_push(vm, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief CAPSULE-BIRTH ( capsule-id -- vm-id-hi vm-id-lo )
|
||||
* Birth a baby VM from a production (p) capsule.
|
||||
@@ -1113,13 +1190,13 @@ static void mama_word_connect_artemis(VM *vm __attribute__((unused)))
|
||||
{
|
||||
VMRegistryEntry entry;
|
||||
VM *artemis;
|
||||
const char *saved_name;
|
||||
char saved_name[VM_NAME_MAX];
|
||||
|
||||
if (capsule_vm_find_by_name_nocase("Artemis", &entry) != 0 ||
|
||||
entry.state == VM_STATE_DEAD ||
|
||||
entry.state == VM_STATE_STILLBORN) {
|
||||
VMUuid new_vm_id = vm_uuid_none();
|
||||
const char *saved = console_get_vm_name();
|
||||
char saved[VM_NAME_MAX]; console_save_vm_name(saved, sizeof(saved));
|
||||
CapsuleRunResult r;
|
||||
|
||||
console_set_vm_name("Artemis");
|
||||
@@ -1150,7 +1227,7 @@ static void mama_word_connect_artemis(VM *vm __attribute__((unused)))
|
||||
return;
|
||||
}
|
||||
|
||||
saved_name = console_get_vm_name();
|
||||
console_save_vm_name(saved_name, sizeof(saved_name));
|
||||
console_set_vm_name(entry.name);
|
||||
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
|
||||
|
||||
@@ -1184,14 +1261,14 @@ static void mama_word_connect_hermes(VM *vm __attribute__((unused)))
|
||||
{
|
||||
VMRegistryEntry entry;
|
||||
VM *hermes;
|
||||
const char *saved_name;
|
||||
char saved_name[VM_NAME_MAX];
|
||||
|
||||
/* Birth if not found or previously dead/stillborn */
|
||||
if (capsule_vm_find_by_name_nocase("Hermes", &entry) != 0 ||
|
||||
entry.state == VM_STATE_DEAD ||
|
||||
entry.state == VM_STATE_STILLBORN) {
|
||||
VMUuid new_vm_id = vm_uuid_none();
|
||||
const char *saved = console_get_vm_name();
|
||||
char saved[VM_NAME_MAX]; console_save_vm_name(saved, sizeof(saved));
|
||||
CapsuleRunResult r;
|
||||
|
||||
console_set_vm_name("Hermes");
|
||||
@@ -1222,7 +1299,7 @@ static void mama_word_connect_hermes(VM *vm __attribute__((unused)))
|
||||
return;
|
||||
}
|
||||
|
||||
saved_name = console_get_vm_name();
|
||||
console_save_vm_name(saved_name, sizeof(saved_name));
|
||||
console_set_vm_name(entry.name);
|
||||
capsule_vm_set_state(entry.vm_id, VM_STATE_LIVE);
|
||||
|
||||
@@ -1260,6 +1337,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
|
||||
register_word(vm, "MINT", mama_word_mint);
|
||||
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
|
||||
register_word(vm, "PAIR-TEST", mama_word_pair_test);
|
||||
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
|
||||
register_word(vm, "VM-COUNT", mama_word_vm_count);
|
||||
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);
|
||||
@@ -1291,6 +1369,7 @@ void register_mama_forth_words(VM *vm)
|
||||
register_word(vm, "CAPSULE-RUN", mama_word_capsule_run);
|
||||
register_word(vm, "MINT", mama_word_mint);
|
||||
register_word(vm, "RUNCAP-TEST", mama_word_runcap_test);
|
||||
register_word(vm, "PAIR-TEST", mama_word_pair_test);
|
||||
register_word(vm, "MAMA-VM-ID", mama_word_mama_vm_id);
|
||||
register_word(vm, "VM-COUNT", mama_word_vm_count);
|
||||
register_word(vm, "VM-CONSERVED?", mama_word_vm_conserved);
|
||||
|
||||
@@ -160,18 +160,50 @@ static int serial_transmit_empty(void) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix */
|
||||
/* Active VM name for [Name] line prefix; NULL = no prefix.
|
||||
*
|
||||
* g_active_vm_name_buf owns the storage -- console_set_vm_name() copies
|
||||
* into it rather than storing the caller's own pointer. Found live
|
||||
* 2026-08-28 (FABRIC-3.md Phase F): mama_word_use() (USE) passes
|
||||
* entry.name, a local VMRegistryEntry's own field -- once USE returns,
|
||||
* that stack frame is reused and the old raw-pointer version left
|
||||
* g_active_vm_name dangling, corrupting every console tag after the
|
||||
* first USE (observed as garbled "[[]"/binary-looking prefixes). A
|
||||
* caller passing a string literal (e.g. console_set_vm_name("Hermes"))
|
||||
* was always safe; this fixes every caller uniformly instead of relying
|
||||
* on each one happening to pass static storage. */
|
||||
#define CONSOLE_VM_NAME_BUF 64
|
||||
static char g_active_vm_name_buf[CONSOLE_VM_NAME_BUF];
|
||||
static const char *g_active_vm_name = (void *)0;
|
||||
static int g_line_start = 1;
|
||||
|
||||
void console_set_vm_name(const char *name) {
|
||||
g_active_vm_name = name;
|
||||
/* Empty string treated the same as NULL: console_save_vm_name()
|
||||
* writes "" for "there was no active name," so this keeps that
|
||||
* round-trip correct (save-empty then restore-empty must mean
|
||||
* "still no prefix," not "prefix is now the empty string"). */
|
||||
if (!name || !name[0]) { g_active_vm_name = (void *)0; return; }
|
||||
size_t i;
|
||||
for (i = 0; i < CONSOLE_VM_NAME_BUF - 1u && name[i]; i++)
|
||||
g_active_vm_name_buf[i] = name[i];
|
||||
g_active_vm_name_buf[i] = '\0';
|
||||
g_active_vm_name = g_active_vm_name_buf;
|
||||
}
|
||||
|
||||
const char *console_get_vm_name(void) {
|
||||
return g_active_vm_name;
|
||||
}
|
||||
|
||||
void console_save_vm_name(char *out, size_t cap) {
|
||||
if (!out || cap == 0) return;
|
||||
size_t i = 0;
|
||||
if (g_active_vm_name) {
|
||||
for (; i < cap - 1u && g_active_vm_name[i]; i++)
|
||||
out[i] = g_active_vm_name[i];
|
||||
}
|
||||
out[i] = '\0';
|
||||
}
|
||||
|
||||
/* Raw single-character write — no prefix logic, called by emit_prefix() */
|
||||
static void raw_putc(char c) {
|
||||
#if defined(__aarch64__)
|
||||
|
||||
+59
-2
@@ -44,6 +44,7 @@
|
||||
#include "block_subsystem.h"
|
||||
#include "word_source/include/keyboard_words.h"
|
||||
#include "word_source/include/block_words.h"
|
||||
#include "freestanding/stdio.h"
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -483,6 +484,62 @@ static void sk_fault_handler(VM *vm) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* sk_repl_dispatch_line - console-VM + user-VM pair relay (FABRIC-3.md
|
||||
* Phase F, 2026-08-28).
|
||||
*
|
||||
* If `vm`'s own registered name has a live "<name>~user" counterpart,
|
||||
* this is a console session: relay the raw line as a real, async
|
||||
* CONSOLE-CMD-EVENT message (common:messaging.4th) instead of
|
||||
* interpreting it directly -- "every line is a message," not a
|
||||
* C-level redirect. This is one particular consumer of the general
|
||||
* VM-to-VM messaging system built in Phase C: any VM can already
|
||||
* MSG-SEND to any other VM for its own reasons regardless of a human
|
||||
* ever being at a physical console at all; this hook only wires the
|
||||
* physical-terminal-input path into that same general mechanism, it
|
||||
* doesn't gate or replace it.
|
||||
*
|
||||
* Falls back to direct vm_interpret() (today's unchanged behavior) when
|
||||
* there's no live paired user VM, or when the line contains a `"`
|
||||
* character this simple S"-embedding can't safely carry yet (a known
|
||||
* v1 limitation -- warned about, not silently mishandled).
|
||||
*===========================================================================*/
|
||||
|
||||
static void sk_repl_dispatch_line(VM *vm, const char *input)
|
||||
{
|
||||
const char *vn = console_get_vm_name();
|
||||
if (vn) {
|
||||
char paired_name[VM_NAME_MAX + 8];
|
||||
size_t vnlen = strlen(vn);
|
||||
if (vnlen + 6 <= sizeof(paired_name)) {
|
||||
memcpy(paired_name, vn, vnlen);
|
||||
memcpy(paired_name + vnlen, "~user", 6); /* includes NUL */
|
||||
|
||||
VMRegistryEntry paired;
|
||||
if (capsule_vm_find_by_name(paired_name, &paired) == 0 &&
|
||||
paired.state == VM_STATE_LIVE) {
|
||||
|
||||
if (strchr(input, '"')) {
|
||||
console_println("console: line contains '\"' -- can't relay "
|
||||
"as a message safely yet, interpreting directly");
|
||||
} else {
|
||||
char cmd[INPUT_BUFFER_SIZE + 64];
|
||||
/* to-index 3: the fixed convention this console's own
|
||||
* VM-NAME-REG entry for its paired user VM uses (set
|
||||
* once at pairing time -- see the pairing word). */
|
||||
int n = snprintf(cmd, sizeof(cmd),
|
||||
"CONSOLE-CMD-EVENT 0 3 S\" %s\" 0 MSG-SEND", input);
|
||||
if (n > 0 && (size_t)n < sizeof(cmd)) {
|
||||
vm_interpret(vm, cmd);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vm_interpret(vm, input);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* sk_repl_step - Execute one REPL turn on a VM and return.
|
||||
*
|
||||
@@ -520,7 +577,7 @@ int sk_repl_step(VM *vm)
|
||||
return vm->halted ? 0 : 1;
|
||||
}
|
||||
|
||||
vm_interpret(vm, input);
|
||||
sk_repl_dispatch_line(vm, input);
|
||||
|
||||
/* ABORT stops mid-line but leaves the flag set for the caller to
|
||||
* consume -- this REPL step is that boundary. Clear it here so the
|
||||
@@ -563,7 +620,7 @@ void sk_repl_run(VM *vm)
|
||||
continue;
|
||||
}
|
||||
|
||||
vm_interpret(active, input);
|
||||
sk_repl_dispatch_line(active, input);
|
||||
|
||||
/* ABORT stops mid-line but leaves the flag set for the caller to
|
||||
* consume -- this REPL step is that boundary. Clear it here so the
|
||||
|
||||
Reference in New Issue
Block a user