454 lines
12 KiB
C++
454 lines
12 KiB
C++
/*
|
|
* main.cpp
|
|
* Entry point, input loop, command dispatch, and chaining
|
|
* Copyright (c) 2025-2026 Daniel Hammer
|
|
*/
|
|
|
|
#include "shell.h"
|
|
|
|
// ---- Shared state definitions ----
|
|
|
|
char cwd[128] = "";
|
|
int current_drive = 0;
|
|
int last_exit = 0;
|
|
char session_user[32] = "";
|
|
char session_home[64] = "";
|
|
|
|
void sync_cwd() {
|
|
char abs[128];
|
|
if (montauk::getcwd(abs, sizeof(abs)) < 0) {
|
|
current_drive = 0;
|
|
cwd[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
int drive = parse_drive_prefix(abs);
|
|
if (drive < 0) {
|
|
current_drive = 0;
|
|
cwd[0] = '\0';
|
|
return;
|
|
}
|
|
|
|
current_drive = drive;
|
|
int plen = drive_prefix_len(abs);
|
|
const char* rel = abs + plen;
|
|
if (*rel == '/') rel++;
|
|
scopy(cwd, rel, sizeof(cwd));
|
|
}
|
|
|
|
// ---- Session info (read once at startup) ----
|
|
|
|
void read_session() {
|
|
// TODO Why is getuser treated as not defined in VSCode intellisense?
|
|
|
|
montauk::getuser(
|
|
(char *)&session_user, // Buffer
|
|
32 // Buffer max size
|
|
);
|
|
|
|
scopy(session_home, "0:/users/", sizeof(session_home));
|
|
scat(session_home, session_user, sizeof(session_home));
|
|
}
|
|
|
|
// ---- Command history ----
|
|
|
|
static constexpr int HISTORY_MAX = 32;
|
|
static char history[HISTORY_MAX][256];
|
|
static int history_count = 0;
|
|
static int history_next = 0;
|
|
|
|
static void history_add(const char* line) {
|
|
if (line[0] == '\0') return;
|
|
if (history_count > 0) {
|
|
int prev = (history_next + HISTORY_MAX - 1) % HISTORY_MAX;
|
|
if (streq(history[prev], line)) return;
|
|
}
|
|
scopy(history[history_next], line, 256);
|
|
history_next = (history_next + 1) % HISTORY_MAX;
|
|
if (history_count < HISTORY_MAX) history_count++;
|
|
}
|
|
|
|
static const char* history_get(int idx) {
|
|
if (idx < 0 || idx >= history_count) return nullptr;
|
|
int pos = (history_next + HISTORY_MAX - 1 - idx) % HISTORY_MAX;
|
|
return history[pos];
|
|
}
|
|
|
|
// ---- Prompt ----
|
|
|
|
static void prompt() {
|
|
char drv[4];
|
|
if (current_drive >= 10) {
|
|
drv[0] = '0' + current_drive / 10;
|
|
drv[1] = '0' + current_drive % 10;
|
|
drv[2] = '\0';
|
|
} else {
|
|
drv[0] = '0' + current_drive;
|
|
drv[1] = '\0';
|
|
}
|
|
montauk::print(drv);
|
|
montauk::print(":/");
|
|
if (cwd[0]) montauk::print(cwd);
|
|
montauk::print("> ");
|
|
}
|
|
|
|
// ---- Line editing ----
|
|
|
|
static void erase_input(int len) {
|
|
for (int i = 0; i < len; i++) montauk::putchar('\b');
|
|
for (int i = 0; i < len; i++) montauk::putchar(' ');
|
|
for (int i = 0; i < len; i++) montauk::putchar('\b');
|
|
}
|
|
|
|
static void replace_line(char* line, int* pos, const char* newContent) {
|
|
int oldLen = *pos;
|
|
erase_input(oldLen);
|
|
int newLen = slen(newContent);
|
|
if (newLen > 255) newLen = 255;
|
|
for (int i = 0; i < newLen; i++) {
|
|
line[i] = newContent[i];
|
|
montauk::putchar(newContent[i]);
|
|
}
|
|
line[newLen] = '\0';
|
|
*pos = newLen;
|
|
}
|
|
|
|
// ---- Command dispatch (single command, already expanded) ----
|
|
|
|
static int process_command(const char* line) {
|
|
line = skip_spaces(line);
|
|
if (*line == '\0') return 0;
|
|
|
|
// Variable assignment: NAME=VALUE
|
|
{
|
|
int eq = -1;
|
|
bool valid = (line[0] >= 'A' && line[0] <= 'Z') ||
|
|
(line[0] >= 'a' && line[0] <= 'z') || line[0] == '_';
|
|
if (valid) {
|
|
for (int i = 1; line[i]; i++) {
|
|
if (line[i] == '=') { eq = i; break; }
|
|
if (!is_var_char(line[i])) break;
|
|
}
|
|
}
|
|
if (eq > 0) {
|
|
char name[32];
|
|
int nlen = eq < 31 ? eq : 31;
|
|
for (int i = 0; i < nlen; i++) name[i] = line[i];
|
|
name[nlen] = '\0';
|
|
const char* val = line + eq + 1;
|
|
int vlen = slen(val);
|
|
char vbuf[128];
|
|
if (vlen >= 2 && ((val[0] == '"' && val[vlen-1] == '"') ||
|
|
(val[0] == '\'' && val[vlen-1] == '\''))) {
|
|
int j = 0;
|
|
for (int i = 1; i < vlen - 1 && j < 127; i++) vbuf[j++] = val[i];
|
|
vbuf[j] = '\0';
|
|
var_set(name, vbuf);
|
|
} else {
|
|
var_set(name, val);
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Parse command name and arguments
|
|
char cmd[128];
|
|
int i = 0;
|
|
while (line[i] && line[i] != ' ' && i < 127) {
|
|
cmd[i] = line[i];
|
|
i++;
|
|
}
|
|
cmd[i] = '\0';
|
|
|
|
const char* args = nullptr;
|
|
if (line[i] == ' ') {
|
|
args = skip_spaces(line + i);
|
|
if (*args == '\0') args = nullptr;
|
|
}
|
|
|
|
// Bare drive switch: "1:", "2:", etc.
|
|
if (has_drive_prefix(cmd) && cmd[drive_prefix_len(cmd)] == '\0' && args == nullptr) {
|
|
int drive = parse_drive_prefix(cmd);
|
|
if (!switch_drive(drive)) {
|
|
montauk::print("No such drive: ");
|
|
montauk::print(cmd);
|
|
montauk::print("/\n");
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Builtins
|
|
if (streq(cmd, "help")) { cmd_help(); return 0; }
|
|
if (streq(cmd, "ls")) { cmd_ls(args ? args : ""); return 0; }
|
|
if (streq(cmd, "cd")) { return cmd_cd(args ? args : ""); }
|
|
if (streq(cmd, "man")) { return cmd_man(args ? args : ""); }
|
|
if (streq(cmd, "true")) { return 0; }
|
|
if (streq(cmd, "false")) { return 1; }
|
|
|
|
if (streq(cmd, "pwd")) {
|
|
sync_cwd();
|
|
char path[128];
|
|
build_dir_path(cwd, path, sizeof(path));
|
|
montauk::print(path);
|
|
montauk::putchar('\n');
|
|
return 0;
|
|
}
|
|
|
|
if (streq(cmd, "echo")) {
|
|
if (!args) {
|
|
montauk::putchar('\n');
|
|
return 0;
|
|
}
|
|
bool no_newline = false;
|
|
if (starts_with(args, "-n ")) {
|
|
no_newline = true;
|
|
args = skip_spaces(args + 3);
|
|
} else if (streq(args, "-n")) {
|
|
return 0;
|
|
}
|
|
montauk::print(args);
|
|
if (!no_newline) montauk::putchar('\n');
|
|
return 0;
|
|
}
|
|
|
|
if (streq(cmd, "set")) {
|
|
if (!args) {
|
|
// List all variables
|
|
if (session_user[0]) {
|
|
montauk::print("USER=");
|
|
montauk::print(session_user);
|
|
montauk::putchar('\n');
|
|
}
|
|
if (session_home[0]) {
|
|
montauk::print("HOME=");
|
|
montauk::print(session_home);
|
|
montauk::putchar('\n');
|
|
}
|
|
sync_cwd();
|
|
char path[128];
|
|
build_dir_path(cwd, path, sizeof(path));
|
|
montauk::print("PWD=");
|
|
montauk::print(path);
|
|
montauk::putchar('\n');
|
|
int vc = var_user_count();
|
|
for (int j = 0; j < vc; j++) {
|
|
montauk::print(var_user_name(j));
|
|
montauk::putchar('=');
|
|
montauk::print(var_user_value(j));
|
|
montauk::putchar('\n');
|
|
}
|
|
return 0;
|
|
}
|
|
// set VAR=value
|
|
int eq = -1;
|
|
for (int j = 0; args[j]; j++) {
|
|
if (args[j] == '=') { eq = j; break; }
|
|
}
|
|
if (eq > 0) {
|
|
char name[32];
|
|
int nlen = eq < 31 ? eq : 31;
|
|
for (int j = 0; j < nlen; j++) name[j] = args[j];
|
|
name[nlen] = '\0';
|
|
var_set(name, args + eq + 1);
|
|
return 0;
|
|
}
|
|
// set VAR (show value)
|
|
const char* val = var_get(args);
|
|
if (val) {
|
|
montauk::print(args);
|
|
montauk::putchar('=');
|
|
montauk::print(val);
|
|
montauk::putchar('\n');
|
|
} else {
|
|
montauk::print(args);
|
|
montauk::print(": not set\n");
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (streq(cmd, "unset")) {
|
|
if (!args) {
|
|
montauk::print("Usage: unset <variable>\n");
|
|
return 1;
|
|
}
|
|
var_unset(args);
|
|
return 0;
|
|
}
|
|
|
|
if (streq(cmd, "exit")) {
|
|
montauk::print("Have a pleasant day.\n");
|
|
montauk::exit(last_exit);
|
|
}
|
|
|
|
// External command
|
|
return exec_external(cmd, args);
|
|
}
|
|
|
|
// ---- Command line execution with chaining ----
|
|
|
|
static void execute_line(const char* raw) {
|
|
// Step 1: expand tilde
|
|
char texp[512];
|
|
expand_tilde(raw, texp, sizeof(texp));
|
|
|
|
// Step 2: expand variables
|
|
char expanded[512];
|
|
expand_vars(texp, expanded, sizeof(expanded));
|
|
|
|
// Step 3: strip comments
|
|
strip_comment(expanded);
|
|
|
|
// Step 4: split on ;, &&, || and execute with chaining logic
|
|
const char* p = expanded;
|
|
int prev = 0;
|
|
enum { OP_NONE, OP_SEMI, OP_AND, OP_OR } pending = OP_NONE;
|
|
|
|
while (true) {
|
|
p = skip_spaces(p);
|
|
if (!*p) break;
|
|
|
|
// Extract next command segment
|
|
char seg[256];
|
|
int si = 0;
|
|
int op = OP_NONE;
|
|
bool in_sq = false, in_dq = false;
|
|
|
|
while (*p && si < 255) {
|
|
if (*p == '\'' && !in_dq) { in_sq = !in_sq; seg[si++] = *p++; continue; }
|
|
if (*p == '"' && !in_sq) { in_dq = !in_dq; seg[si++] = *p++; continue; }
|
|
if (!in_sq && !in_dq) {
|
|
if (*p == ';') { op = OP_SEMI; p++; break; }
|
|
if (*p == '&' && p[1] == '&') { op = OP_AND; p += 2; break; }
|
|
if (*p == '|' && p[1] == '|') { op = OP_OR; p += 2; break; }
|
|
}
|
|
seg[si++] = *p++;
|
|
}
|
|
seg[si] = '\0';
|
|
|
|
// Trim trailing spaces
|
|
while (si > 0 && seg[si - 1] == ' ') seg[--si] = '\0';
|
|
|
|
// Decide whether to run based on pending operator
|
|
bool run = true;
|
|
if (pending == OP_AND && prev != 0) run = false;
|
|
if (pending == OP_OR && prev == 0) run = false;
|
|
|
|
if (run && seg[0]) {
|
|
prev = process_command(seg);
|
|
}
|
|
|
|
pending = (decltype(pending))op;
|
|
if (op == OP_NONE) break;
|
|
}
|
|
|
|
last_exit = prev;
|
|
}
|
|
|
|
// ---- Arrow key scancodes ----
|
|
|
|
static constexpr uint8_t SC_UP = 0x48;
|
|
static constexpr uint8_t SC_DOWN = 0x50;
|
|
static constexpr uint8_t SC_LEFT = 0x4B;
|
|
static constexpr uint8_t SC_RIGHT = 0x4D;
|
|
|
|
static bool empty_key_event(const Montauk::KeyEvent& ev) {
|
|
return ev.scancode == 0 && ev.ascii == 0 && !ev.pressed &&
|
|
!ev.shift && !ev.ctrl && !ev.alt;
|
|
}
|
|
|
|
static void wait_key_event(Montauk::KeyEvent* out) {
|
|
if (!out) return;
|
|
|
|
for (;;) {
|
|
montauk::getkey(out);
|
|
if (!empty_key_event(*out))
|
|
return;
|
|
|
|
uint64_t serial = montauk::input_wait(0, 0);
|
|
if (!montauk::is_key_available())
|
|
montauk::input_wait(serial, ~0ULL);
|
|
}
|
|
}
|
|
|
|
// ---- Entry point ----
|
|
|
|
extern "C" void _start() {
|
|
sync_cwd();
|
|
read_session();
|
|
|
|
montauk::print("\n");
|
|
montauk::print(" MontaukOS\n");
|
|
montauk::print(" Copyright (c) 2025-2026 Montauk Operating System\n");
|
|
montauk::print("\n");
|
|
|
|
if (session_user[0]) {
|
|
montauk::print(" Logged in as ");
|
|
montauk::print(session_user);
|
|
montauk::putchar('\n');
|
|
montauk::print("\n");
|
|
}
|
|
|
|
montauk::print(" Type 'help' for available commands.\n");
|
|
montauk::print("\n");
|
|
|
|
char line[256];
|
|
int pos = 0;
|
|
int hist_nav = -1;
|
|
|
|
prompt();
|
|
|
|
while (true) {
|
|
Montauk::KeyEvent ev;
|
|
wait_key_event(&ev);
|
|
|
|
if (!ev.pressed) continue;
|
|
|
|
// Arrow keys: ascii == 0, check scancode
|
|
if (ev.ascii == 0) {
|
|
if (ev.scancode == SC_UP) {
|
|
int next = hist_nav + 1;
|
|
const char* entry = history_get(next);
|
|
if (entry) {
|
|
hist_nav = next;
|
|
replace_line(line, &pos, entry);
|
|
}
|
|
} else if (ev.scancode == SC_DOWN) {
|
|
if (hist_nav > 0) {
|
|
hist_nav--;
|
|
const char* entry = history_get(hist_nav);
|
|
if (entry) {
|
|
replace_line(line, &pos, entry);
|
|
}
|
|
} else if (hist_nav == 0) {
|
|
hist_nav = -1;
|
|
erase_input(pos);
|
|
pos = 0;
|
|
line[0] = '\0';
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (ev.ascii == '\n') {
|
|
montauk::putchar('\n');
|
|
line[pos] = '\0';
|
|
history_add(line);
|
|
execute_line(line);
|
|
pos = 0;
|
|
hist_nav = -1;
|
|
prompt();
|
|
} else if (ev.ascii == '\b') {
|
|
if (pos > 0) {
|
|
pos--;
|
|
montauk::putchar('\b');
|
|
montauk::putchar(' ');
|
|
montauk::putchar('\b');
|
|
}
|
|
} else if (ev.ascii >= ' ' && pos < 255) {
|
|
line[pos++] = ev.ascii;
|
|
montauk::putchar(ev.ascii);
|
|
}
|
|
}
|
|
}
|