Files
MontaukOS/programs/src/shell/main.cpp
T

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 Project\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);
}
}
}