Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
b2d43b3
feat: route variable forcing through IPC command pipe
thiagoralves Apr 2, 2026
183545c
fix: pass cmdPipePath through debug config provider
thiagoralves Apr 2, 2026
0c0683a
debug: add logging throughout variable forcing IPC chain
thiagoralves Apr 2, 2026
490aaf2
fix: socket timeout destroys idle connection, server blocks on dead c…
thiagoralves Apr 2, 2026
2d58e01
debug: add per-attempt logging to connect retries, check socket exist…
thiagoralves Apr 2, 2026
0247fe8
fix: lazy connect on first force command, not at session start
thiagoralves Apr 3, 2026
e136337
feat: force variables via GDB field writes instead of IPC pipe
thiagoralves Apr 3, 2026
c456415
fix: provide stack frame context for GDB evaluate requests
thiagoralves Apr 3, 2026
8339ede
debug: log GDB evaluate results for force field writes
thiagoralves Apr 3, 2026
439ede2
fix: use 'variables' context for GDB evaluate to bypass ST uppercasing
thiagoralves Apr 3, 2026
74a85de
chore: remove debug logging from force-variable
thiagoralves Apr 3, 2026
e2ce484
fix: prevent timer burst after resuming from debugger breakpoint
thiagoralves Apr 3, 2026
c188f97
fix: refresh variables after force, intercept Set Value for IECVar
thiagoralves Apr 3, 2026
876b03c
debug: log all DAP requests through tracker, force refresh result
thiagoralves Apr 3, 2026
5449f55
fix: refresh uses debugEvaluate with frameId, uppercase setExpression
thiagoralves Apr 3, 2026
e4194cc
feat: intercept Set Value for Variables pane via evaluateName cache
thiagoralves Apr 14, 2026
f2094f5
chore: address PR review — remove dead IPC code, fix leaks, clean log…
thiagoralves Apr 14, 2026
64bd2a6
Merge pull request #94 from Autonomy-Logic/feature/vscode-variable-fo…
thiagoralves Apr 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/backend/repl-main-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ function emitProgramDescriptorsAndMain(
' if (std::string(argv[i]) == "--print-vars") print_vars = true;',
);
lines.push(" }");
lines.push("");
lines.push(" if (cyclic) {");
lines.push(
` strucpp::cyclic_run(programs, ${programs.length}, print_vars);`,
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/repl/iec_cyclic.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,14 @@ inline void cyclic_run(ProgramDescriptor* programs, size_t program_count,
fflush(stdout);
}

// Sleep until next tick (drift-free)
// Sleep until next tick.
// If we've fallen behind (e.g., resumed from a debugger breakpoint),
// reset to now to prevent a burst of catch-up cycles.
next_tick += std::chrono::nanoseconds(common_ticktime);
auto now = std::chrono::steady_clock::now();
if (next_tick < now) {
next_tick = now;
}
std::this_thread::sleep_until(next_tick);
}

Expand Down
265 changes: 177 additions & 88 deletions src/runtime/repl/iec_repl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,161 @@ inline bool parse_qualified_name(const std::string& input, std::string& prog_nam
return true;
}

// =============================================================================
// Shared Command Processor
// =============================================================================
// Processes force/unforce/get/set/list commands and returns a plain-text response.
// Used by both the interactive REPL loop and the IPC command server.
// Returns "OK: <message>" on success or "ERR: <message>" on failure.

inline std::string process_command(
const std::string& cmd_line,
ProgramDescriptor* programs,
size_t program_count)
{
// Parse command and arguments
std::string line = cmd_line;
size_t start = line.find_first_not_of(" \t\r\n");
if (start == std::string::npos) return "ERR: Empty command";
size_t end = line.find_last_not_of(" \t\r\n");
line = line.substr(start, end - start + 1);
if (line.empty()) return "ERR: Empty command";

size_t sp = line.find(' ');
std::string cmd = (sp == std::string::npos) ? line : line.substr(0, sp);
std::string args_str = (sp == std::string::npos) ? "" : line.substr(sp + 1);
size_t astart = args_str.find_first_not_of(" \t");
if (astart != std::string::npos) args_str = args_str.substr(astart);
else args_str.clear();

// --- get <program>.<var> ---
if (cmd == "get") {
if (args_str.empty()) return "ERR: Usage: get <program>.<var>";
std::string pn, vn;
if (!parse_qualified_name(args_str, pn, vn)) return "ERR: Invalid format. Use: program.variable";
auto* prog = find_program(programs, program_count, pn);
if (!prog) return "ERR: Unknown program: " + pn;
auto* var = find_var(prog, vn);
if (!var) return "ERR: Unknown variable: " + vn + " in " + pn;
bool forced = var_is_forced(var->type, var->var_ptr);
std::string val = var_value_to_string(var->type, var->var_ptr);
return std::string("OK: ") + pn + "." + vn + " : " +
var_type_name(var->type) + " = " + val + (forced ? " [FORCED]" : "");
}

// --- set <program>.<var> <value> ---
if (cmd == "set") {
size_t vsp = args_str.find(' ');
if (vsp == std::string::npos) return "ERR: Usage: set <program>.<var> <value>";
std::string qname = args_str.substr(0, vsp);
std::string val = args_str.substr(vsp + 1);
size_t vs = val.find_first_not_of(" \t");
if (vs != std::string::npos) val = val.substr(vs);
std::string pn, vn;
if (!parse_qualified_name(qname, pn, vn)) return "ERR: Invalid format. Use: program.variable";
auto* prog = find_program(programs, program_count, pn);
if (!prog) return "ERR: Unknown program: " + pn;
auto* var = find_var(prog, vn);
if (!var) return "ERR: Unknown variable: " + vn + " in " + pn;
if (var_set_value(var->type, var->var_ptr, val)) {
return std::string("OK: ") + pn + "." + vn + " = " + var_value_to_string(var->type, var->var_ptr);
}
return std::string("ERR: Invalid value for ") + var_type_name(var->type) + ": " + val;
}

// --- force <program>.<var> <value> ---
if (cmd == "force") {
size_t vsp = args_str.find(' ');
if (vsp == std::string::npos) return "ERR: Usage: force <program>.<var> <value>";
std::string qname = args_str.substr(0, vsp);
std::string val = args_str.substr(vsp + 1);
size_t vs = val.find_first_not_of(" \t");
if (vs != std::string::npos) val = val.substr(vs);
std::string pn, vn;
if (!parse_qualified_name(qname, pn, vn)) return "ERR: Invalid format. Use: program.variable";
auto* prog = find_program(programs, program_count, pn);
if (!prog) return "ERR: Unknown program: " + pn;
auto* var = find_var(prog, vn);
if (!var) return "ERR: Unknown variable: " + vn + " in " + pn;
if (var_force_value(var->type, var->var_ptr, val)) {
return std::string("OK: ") + pn + "." + vn + " FORCED = " + var_value_to_string(var->type, var->var_ptr);
}
return std::string("ERR: Invalid value for ") + var_type_name(var->type) + ": " + val;
}

// --- unforce <program>.<var> ---
if (cmd == "unforce") {
if (args_str.empty()) return "ERR: Usage: unforce <program>.<var>";
std::string pn, vn;
if (!parse_qualified_name(args_str, pn, vn)) return "ERR: Invalid format. Use: program.variable";
auto* prog = find_program(programs, program_count, pn);
if (!prog) return "ERR: Unknown program: " + pn;
auto* var = find_var(prog, vn);
if (!var) return "ERR: Unknown variable: " + vn + " in " + pn;
var_unforce(var->type, var->var_ptr);
return std::string("OK: ") + pn + "." + vn + " unforced. Value: " + var_value_to_string(var->type, var->var_ptr);
}

// --- unforce_all ---
if (cmd == "unforce_all") {
int count = 0;
for (size_t p = 0; p < program_count; ++p) {
for (size_t i = 0; i < programs[p].var_count; ++i) {
if (var_is_forced(programs[p].vars[i].type, programs[p].vars[i].var_ptr)) {
var_unforce(programs[p].vars[i].type, programs[p].vars[i].var_ptr);
++count;
}
}
}
return "OK: Unforced " + std::to_string(count) + " variable(s)";
}

// --- list_vars [program] ---
if (cmd == "list_vars") {
std::string result = "OK:";
for (size_t p = 0; p < program_count; ++p) {
if (!args_str.empty() && args_str != programs[p].name) continue;
for (size_t i = 0; i < programs[p].var_count; ++i) {
auto& v = programs[p].vars[i];
bool forced = var_is_forced(v.type, v.var_ptr);
result += std::string("\n") + programs[p].name + "." + v.name +
" : " + var_type_name(v.type) + " = " +
var_value_to_string(v.type, v.var_ptr) +
(forced ? " [FORCED]" : "");
}
}
return result;
}

// --- list_forced ---
if (cmd == "list_forced") {
std::string result = "OK:";
for (size_t p = 0; p < program_count; ++p) {
for (size_t i = 0; i < programs[p].var_count; ++i) {
auto& v = programs[p].vars[i];
if (var_is_forced(v.type, v.var_ptr)) {
result += std::string("\n") + programs[p].name + "." + v.name +
" : " + var_type_name(v.type) + " = " +
var_value_to_string(v.type, v.var_ptr);
}
}
}
return result;
}

// --- programs ---
if (cmd == "programs") {
std::string result = "OK:";
for (size_t p = 0; p < program_count; ++p) {
result += std::string("\n") + programs[p].name +
" (" + std::to_string(programs[p].var_count) + " vars)";
}
return result;
}

return "ERR: Unknown command: " + cmd;
}

// =============================================================================
// Colored Output Helpers
// =============================================================================
Expand Down Expand Up @@ -702,100 +857,34 @@ inline void repl_run(ProgramDescriptor* programs, size_t program_count,
continue;
}

// --- vars [program] ---
if (cmd == "vars") {
if (!args_str.empty()) {
auto* prog = find_program(programs, program_count, args_str);
if (!prog) { ic_printf("[red]Unknown program: %s[/]\n", args_str.c_str()); continue; }
for (size_t i = 0; i < prog->var_count; ++i) {
print_var_line(*prog, prog->vars[i]);
}
} else {
for (size_t p = 0; p < program_count; ++p) {
for (size_t i = 0; i < programs[p].var_count; ++i) {
print_var_line(programs[p], programs[p].vars[i]);
}
}
}
continue;
}

// --- get <program>.<var> ---
if (cmd == "get") {
if (args_str.empty()) { ic_println("[red]Usage: get <program>.<var>[/]"); continue; }
std::string pn, vn;
if (!parse_qualified_name(args_str, pn, vn)) { ic_println("[red]Invalid format. Use: program.variable[/]"); continue; }
auto* prog = find_program(programs, program_count, pn);
if (!prog) { ic_printf("[red]Unknown program: %s[/]\n", pn.c_str()); continue; }
auto* var = find_var(prog, vn);
if (!var) { ic_printf("[red]Unknown variable: %s in %s[/]\n", vn.c_str(), pn.c_str()); continue; }
print_var_line(*prog, *var);
continue;
}

// --- set <program>.<var> <value> ---
if (cmd == "set") {
size_t vsp = args_str.find(' ');
if (vsp == std::string::npos) { ic_println("[red]Usage: set <program>.<var> <value>[/]"); continue; }
std::string qname = args_str.substr(0, vsp);
std::string val = args_str.substr(vsp + 1);
// Trim val
size_t vs = val.find_first_not_of(" \t");
if (vs != std::string::npos) val = val.substr(vs);

std::string pn, vn;
if (!parse_qualified_name(qname, pn, vn)) { ic_println("[red]Invalid format. Use: program.variable[/]"); continue; }
auto* prog = find_program(programs, program_count, pn);
if (!prog) { ic_printf("[red]Unknown program: %s[/]\n", pn.c_str()); continue; }
auto* var = find_var(prog, vn);
if (!var) { ic_printf("[red]Unknown variable: %s in %s[/]\n", vn.c_str(), pn.c_str()); continue; }
if (var_set_value(var->type, var->var_ptr, val)) {
ic_printf(" [b]%s[/].%s = [green]%s[/]\n", pn.c_str(), vn.c_str(), var_value_to_string(var->type, var->var_ptr).c_str());
} else {
ic_printf("[red]Invalid value for %s: %s[/]\n", var_type_name(var->type), val.c_str());
// --- vars, get, set, force, unforce, programs ---
// Delegate to shared process_command() for data commands
if (cmd == "vars" || cmd == "get" || cmd == "set" ||
cmd == "force" || cmd == "unforce" || cmd == "programs" ||
cmd == "list_vars" || cmd == "list_forced" || cmd == "unforce_all") {
// For interactive "vars", map to "list_vars" to use the shared processor
std::string effective_line = line;
if (cmd == "vars") {
effective_line = "list_vars" + (args_str.empty() ? "" : " " + args_str);
}
continue;
}

// --- force <program>.<var> <value> ---
if (cmd == "force") {
size_t vsp = args_str.find(' ');
if (vsp == std::string::npos) { ic_println("[red]Usage: force <program>.<var> <value>[/]"); continue; }
std::string qname = args_str.substr(0, vsp);
std::string val = args_str.substr(vsp + 1);
size_t vs = val.find_first_not_of(" \t");
if (vs != std::string::npos) val = val.substr(vs);

std::string pn, vn;
if (!parse_qualified_name(qname, pn, vn)) { ic_println("[red]Invalid format. Use: program.variable[/]"); continue; }
auto* prog = find_program(programs, program_count, pn);
if (!prog) { ic_printf("[red]Unknown program: %s[/]\n", pn.c_str()); continue; }
auto* var = find_var(prog, vn);
if (!var) { ic_printf("[red]Unknown variable: %s in %s[/]\n", vn.c_str(), pn.c_str()); continue; }
if (var_force_value(var->type, var->var_ptr, val)) {
ic_printf(" [b]%s[/].%s [yellow b]FORCED[/] = [green]%s[/]\n",
pn.c_str(), vn.c_str(), var_value_to_string(var->type, var->var_ptr).c_str());
std::string result = process_command(effective_line, programs, program_count);
// Display with isocline coloring
if (result.substr(0, 3) == "OK:") {
std::string msg = result.substr(3);
if (!msg.empty() && msg[0] == ' ') msg = msg.substr(1);
if (!msg.empty()) {
ic_printf("[green]%s[/]\n", msg.c_str());
}
} else if (result.substr(0, 4) == "ERR:") {
std::string msg = result.substr(4);
if (!msg.empty() && msg[0] == ' ') msg = msg.substr(1);
ic_printf("[red]%s[/]\n", msg.c_str());
} else {
ic_printf("[red]Invalid value for %s: %s[/]\n", var_type_name(var->type), val.c_str());
ic_println(result.c_str());
}
continue;
}

// --- unforce <program>.<var> ---
if (cmd == "unforce") {
if (args_str.empty()) { ic_println("[red]Usage: unforce <program>.<var>[/]"); continue; }
std::string pn, vn;
if (!parse_qualified_name(args_str, pn, vn)) { ic_println("[red]Invalid format. Use: program.variable[/]"); continue; }
auto* prog = find_program(programs, program_count, pn);
if (!prog) { ic_printf("[red]Unknown program: %s[/]\n", pn.c_str()); continue; }
auto* var = find_var(prog, vn);
if (!var) { ic_printf("[red]Unknown variable: %s in %s[/]\n", vn.c_str(), pn.c_str()); continue; }
var_unforce(var->type, var->var_ptr);
ic_printf(" [b]%s[/].%s [green]unforced[/]. Value: [green]%s[/]\n",
pn.c_str(), vn.c_str(), var_value_to_string(var->type, var->var_ptr).c_str());
continue;
}

// --- code [line] [end] ---
if (cmd == "code") {
if (source_lines.empty()) {
Expand Down
Loading
Loading