Files
mop3/ce/ce.c

569 lines
14 KiB
C

#include <arena.h>
#include <desc.h>
#include <liballoc.h>
#include <list.h>
#include <minmax.h>
#include <path.h>
#include <printf.h>
#include <process.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <str_status.h>
#include <string.h>
#include <system.h>
#include <terminal_cursor.h>
static struct arena arena;
struct strbuf {
char* items;
size_t count, capacity;
};
void strbuf_append (struct strbuf* strbuf, char c) {
if (strbuf->items == NULL) {
strbuf->capacity = 256;
strbuf->count = 0;
strbuf->items = arena_malloc (&arena, strbuf->capacity);
} else {
if (strbuf->count == strbuf->capacity) {
size_t prev_capacity = strbuf->capacity;
strbuf->capacity *= 2;
strbuf->items = arena_realloc (&arena, strbuf->items, prev_capacity, strbuf->capacity);
}
}
strbuf->items[strbuf->count++] = c;
}
void strbuf_append_str (struct strbuf* strbuf, const char* s) {
while (*s)
strbuf_append (strbuf, *s++);
}
struct context {
struct strbuf strbuf;
};
#define CPRINTF_BUF_MAX 4096
void cprintf (struct context* context, const char* fmt, ...) {
va_list args;
va_start (args, fmt);
char* buf = malloc (CPRINTF_BUF_MAX);
if (buf == NULL) {
va_end (args);
return;
}
int len = vsnprintf (buf, CPRINTF_BUF_MAX, fmt, args);
va_end (args);
if (len > 0) {
for (int i = 0; i < len; i++)
strbuf_append (&context->strbuf, buf[i]);
}
free (buf);
}
#define LINE_BUFFER_MAX 1024
#define TOKEN_MAX 64
#define CMD_ARGS_MAX 64
#define PROMPT "$ "
#define TOKEN_CLASS_OPAREN 0
#define TOKEN_CLASS_CPAREN 1
#define TOKEN_CLASS_WORD 2
#define TOKEN_CLASS_SEMICOLON 3
#define TOKEN_CLASS_REDIR 4
struct token {
struct list_node_link tokens_link;
char buffer[TOKEN_MAX];
int class;
};
#define PREC_NONE 0
#define PREC_LOWEST 1
#define PREC_SEQ 2
#define PREC_REDIR 3
#define AST_NODE_CLASS_CMD 0
#define AST_NODE_CLASS_SUBSHELL 1
#define AST_NODE_CLASS_SEQ 2
#define AST_NODE_CLASS_REDIR 3
struct ast_node;
struct ast_cmd {
char* name;
char* args[CMD_ARGS_MAX];
int arg_count;
};
struct ast_subshell {
struct ast_node* inner;
};
struct ast_seq {
struct ast_node* left;
struct ast_node* right;
};
struct ast_redir {
struct ast_node* source;
char* file_path;
};
struct ast_node {
int class;
union {
struct ast_cmd cmd;
struct ast_subshell subshell;
struct ast_seq seq;
struct ast_redir redir;
} u;
};
struct parser;
typedef struct ast_node* (*nud_func_t) (struct parser* parser, struct token* token);
typedef struct ast_node* (*led_func_t) (struct parser* parser, struct token* token,
struct ast_node* left);
struct parse_rule {
nud_func_t nud;
led_func_t led;
int precedence;
};
struct parser {
struct token* current;
struct token* next;
};
static struct ast_node* word_nud (struct parser* parser, struct token* token);
static struct ast_node* oparen_nud (struct parser* parser, struct token* token);
static struct ast_node* semicolon_led (struct parser* parser, struct token* token,
struct ast_node* left);
static struct ast_node* redir_led (struct parser* parser, struct token* token,
struct ast_node* left);
static struct parse_rule parse_rules[] = {
[TOKEN_CLASS_WORD] = {&word_nud, NULL, PREC_NONE},
[TOKEN_CLASS_OPAREN] = {&oparen_nud, NULL, PREC_NONE},
[TOKEN_CLASS_CPAREN] = {NULL, NULL, PREC_NONE},
[TOKEN_CLASS_SEMICOLON] = {NULL, &semicolon_led, PREC_SEQ},
[TOKEN_CLASS_REDIR] = {NULL, &redir_led, PREC_REDIR},
};
static struct token* get_token (struct list_node_link* link) {
if (link == NULL)
return NULL;
return list_entry (link, struct token, tokens_link);
}
static struct token* advance (struct parser* parser) {
struct token* token = parser->next;
if (token != NULL) {
parser->current = token;
parser->next = get_token (token->tokens_link.next);
} else {
parser->current = NULL;
}
return token;
}
static struct ast_node* parse_precedence (struct parser* parser, int precedence) {
struct token* token = advance (parser);
if (token == NULL)
return NULL;
nud_func_t nud = parse_rules[token->class].nud;
if (nud == NULL)
return NULL;
struct ast_node* left = nud (parser, token);
while (parser->next && precedence < parse_rules[parser->next->class].precedence) {
token = advance (parser);
led_func_t led = parse_rules[token->class].led;
if (led != NULL)
left = led (parser, token, left);
}
return left;
}
static struct ast_node* word_nud (struct parser* parser, struct token* token) {
struct ast_node* node = arena_malloc (&arena, sizeof (*node));
node->class = AST_NODE_CLASS_CMD;
node->u.cmd.name = token->buffer;
node->u.cmd.arg_count = 0;
while (parser->next != NULL && parser->next->class == TOKEN_CLASS_WORD) {
struct token* arg = advance (parser);
if (node->u.cmd.arg_count < CMD_ARGS_MAX)
node->u.cmd.args[node->u.cmd.arg_count++] = arg->buffer;
}
return node;
}
static struct ast_node* oparen_nud (struct parser* parser, struct token* token) {
struct ast_node* node = arena_malloc (&arena, sizeof (*node));
node->class = AST_NODE_CLASS_SUBSHELL;
node->u.subshell.inner = parse_precedence (parser, PREC_LOWEST);
if (parser->next != NULL && parser->next->class == TOKEN_CLASS_CPAREN)
advance (parser);
return node;
}
static struct ast_node* semicolon_led (struct parser* parser, struct token* token,
struct ast_node* left) {
struct ast_node* node = arena_malloc (&arena, sizeof (*node));
node->class = AST_NODE_CLASS_SEQ;
node->u.seq.left = left;
node->u.seq.right = parse_precedence (parser, PREC_SEQ);
return node;
}
static struct ast_node* redir_led (struct parser* parser, struct token* token,
struct ast_node* left) {
struct ast_node* node = arena_malloc (&arena, sizeof (*node));
node->class = AST_NODE_CLASS_REDIR;
node->u.redir.source = left;
struct token* next_token = advance (parser);
if (next_token != NULL && next_token->class == TOKEN_CLASS_WORD)
node->u.redir.file_path = next_token->buffer;
return node;
}
static int e_pid;
static int e_pgid;
static bool run = true;
static void putch (char ch) { mail_send (e_pgid, &ch, 1); }
void putchar_ (char ch) { putch (ch); }
static void tokenize (struct list_node_link** tokens, const char* text) {
const char* p = text;
while (*p) {
if (isspace (*p)) {
p++;
continue;
}
if (*p == '(' || *p == ')' || *p == ';' || *p == '>') {
struct token* token = arena_malloc (&arena, sizeof (*token));
memset (token, 0, sizeof (*token));
token->buffer[0] = *p;
if (*p == '(')
token->class = TOKEN_CLASS_OPAREN;
else if (*p == ')')
token->class = TOKEN_CLASS_CPAREN;
else if (*p == ';')
token->class = TOKEN_CLASS_SEMICOLON;
else if (*p == '>')
token->class = TOKEN_CLASS_REDIR;
list_append (*tokens, &token->tokens_link);
p++;
continue;
}
if (isprint (*p)) {
struct token* token = arena_malloc (&arena, sizeof (*token));
memset (token, 0, sizeof (*token));
size_t i = 0;
while (*p && !isspace (*p) && *p != '(' && *p != ')' && *p != ';' && *p != '>') {
if (i < TOKEN_MAX - 1)
token->buffer[i++] = *p;
p++;
}
token->class = TOKEN_CLASS_WORD;
list_append (*tokens, &token->tokens_link);
continue;
}
printf ("ERROR unknown character '%c'\n", *p);
p++;
}
}
static void execute (struct ast_node* root, struct context* context);
static void parse_and_execute (struct list_node_link* tokens) {
struct parser parser;
parser.current = NULL;
parser.next = get_token (tokens);
while (parser.next != NULL) {
struct ast_node* root = parse_precedence (&parser, PREC_NONE);
if (root != NULL) {
struct context context = {0};
execute (root, &context);
if (context.strbuf.items != NULL)
printf ("%.*s", (int)context.strbuf.count, context.strbuf.items);
}
}
}
static void echo (struct context* context, char** strings, size_t strings_count) {
for (size_t i = 0; i < strings_count; i++)
cprintf (context, "%s ", strings[i]);
}
static void mkfile (struct context* context, char** file_paths, size_t files_count) {
char volume[VOLUME_MAX];
const char* path;
int ret;
for (size_t i = 0; i < files_count; i++) {
const char* file_path = file_paths[i];
if (!path_parse (file_path, volume, &path)) {
cprintf (context, "ERROR bad path '%s'\n", file_path);
continue;
}
if ((ret = volume_open (volume)) < 0) {
cprintf (context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
continue;
}
create_file (path);
volume_close ();
}
}
static void cat (struct context* context, char** file_paths, size_t files_count) {
struct desc desc;
char volume[VOLUME_MAX];
const char* path;
int ret;
for (size_t i = 0; i < files_count; i++) {
const char* file_path = file_paths[i];
if (!path_parse (file_path, volume, &path)) {
cprintf (context, "ERROR bad path '%s'\n", file_path);
continue;
}
if ((ret = volume_open (volume)) < 0) {
cprintf (context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
continue;
}
describe (path, &desc);
if (desc.type != FS_FILE)
goto close;
char* buffer = malloc (desc.size + 1);
if (buffer == NULL)
goto close;
memset (buffer, 0, desc.size + 1);
read_file (path, 0, (uint8_t*)buffer, desc.size);
cprintf (context, "%s\n", buffer);
close:
if (buffer != NULL)
free (buffer);
volume_close ();
}
}
static void ls (struct context* context, const char* path_string) {
struct desc desc;
char volume[VOLUME_MAX];
const char* path;
int ret;
struct dir_entry entry;
if (!path_parse (path_string, volume, &path)) {
cprintf (context, "ERROR bad path '%s'\n", path_string);
return;
}
if ((ret = volume_open (volume)) < 0) {
cprintf (context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
return;
}
describe (path, &desc);
if (desc.type != FS_DIR) {
cprintf (context, "ERROR '%s' is not a directory\n", path_string);
volume_close ();
return;
}
size_t entries = desc.size;
cprintf (context, "total entries: %zu\n", entries);
cprintf (context, "\n");
for (size_t entry_num = 0; entry_num < entries; entry_num++) {
memset (&desc, 0, sizeof (desc));
memset (&entry, 0, sizeof (entry));
read_dir_entry (path, &entry, entry_num);
describe (entry.path, &desc);
cprintf (context, "%c%-40s %-40zu\n", (desc.type == FS_DIR ? '*' : ' '), entry.path, desc.size);
}
volume_close ();
}
static void quit1 (struct context* context) {
run = false;
cprintf (context, "Goodbye!\n");
}
static void help (struct context* context) {
cprintf (context, "Available commands:\n");
cprintf (context, "echo <word1> <word2> <word3> ...\n");
cprintf (context, "help\n");
cprintf (context, "cat <file path>\n");
cprintf (context, "ls <directory path>\n");
cprintf (context, "mkfile <file path>\n");
cprintf (context, "quit\n");
}
static void execute_cmd (struct ast_cmd* cmd, struct context* context) {
if (strcmp (cmd->name, "echo") == 0) {
echo (context, cmd->args, cmd->arg_count);
} else if (strcmp (cmd->name, "help") == 0) {
help (context);
} else if (strcmp (cmd->name, "cat") == 0) {
cat (context, cmd->args, cmd->arg_count);
} else if (strcmp (cmd->name, "ls") == 0) {
ls (context, cmd->args[0]);
} else if (strcmp (cmd->name, "quit") == 0) {
quit1 (context);
} else if (strcmp (cmd->name, "mkfile") == 0) {
mkfile (context, cmd->args, cmd->arg_count);
} else {
cprintf (context, "ERROR unknown command '%s'\n", cmd->name);
}
}
static void execute_redir (struct ast_redir* redir, struct context* context) {
char volume[VOLUME_MAX];
const char* path;
int ret;
if (!(path_parse (redir->file_path, volume, &path))) {
cprintf (context, "ERROR bad path '%s'\n", redir->file_path);
return;
}
if ((ret = volume_open (volume)) < 0) {
cprintf (context, "ERROR could not open volume '%s': %s\n", volume, str_status[-ret]);
return;
}
create_file (path);
write_file (path, 0, (uint8_t*)context->strbuf.items, context->strbuf.count - 1);
volume_close ();
}
static void execute (struct ast_node* root, struct context* context) {
struct context subcontext = {0};
switch (root->class) {
case AST_NODE_CLASS_CMD:
execute_cmd (&root->u.cmd, context);
break;
case AST_NODE_CLASS_SUBSHELL:
execute (root->u.subshell.inner, &subcontext);
break;
case AST_NODE_CLASS_SEQ:
execute (root->u.seq.left, context);
execute (root->u.seq.right, context);
break;
case AST_NODE_CLASS_REDIR:
execute (root->u.redir.source, &subcontext);
execute_redir (&root->u.redir, &subcontext);
break;
}
}
static void exec_line (const char* line) {
struct list_node_link* tokens = NULL;
tokenize (&tokens, line);
if (tokens != NULL)
parse_and_execute (tokens);
arena_reset (&arena);
}
void app_main (void) {
e_pid = get_exec_pid ();
e_pgid = get_procgroup (e_pid);
size_t line_cursor = 0;
char line_buffer[LINE_BUFFER_MAX + 1];
memset (line_buffer, 0, sizeof (line_buffer));
while (run) {
printf (PROMPT);
char ch = 0;
for (;;) {
mail_receive (&ch, 1);
if (ch == '\n')
break;
if (line_cursor < LINE_BUFFER_MAX) {
line_buffer[line_cursor++] = ch;
printf ("%c", ch);
}
}
printf ("\n");
exec_line (line_buffer);
line_cursor = 0;
memset (line_buffer, 0, sizeof (line_buffer));
}
arena_destroy (&arena);
}