#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; } vsnprintf (buf, CPRINTF_BUF_MAX, fmt, args); va_end (args); strbuf_append_str (&context->strbuf, buf); 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 ...\n"); cprintf (context, "help\n"); cprintf (context, "cat \n"); cprintf (context, "ls \n"); cprintf (context, "mkfile \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); 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); }