diff --git a/Makefile b/Makefile index 4fc1b6c..531578a 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,8 @@ make: trie $(wildcard src/*.c) mkdir -p bin gcc src/*.c -Wall -Wextra -pedantic -ggdb -o bin/sysh -trie: triegen.py commands - python triegen.py commands src/trie.c +trie: gen/triegen.py gen/commands + python gen/triegen.py gen/commands gen/syscalls_x86_64 src/trie.c clean: rm -f src/trie.c diff --git a/examples/cat.sysh b/examples/cat.sysh new file mode 100644 index 0000000..fb8db4c --- /dev/null +++ b/examples/cat.sysh @@ -0,0 +1,5 @@ +.set $buf { .alloc 1024 } +.set $read { read 0 $buf 1024 } +write 1 $buf $read +.free $buf + diff --git a/examples/helloworld.sysh b/examples/helloworld.sysh new file mode 100644 index 0000000..0188033 --- /dev/null +++ b/examples/helloworld.sysh @@ -0,0 +1 @@ +write 1 "Hello, world!\n" 14 diff --git a/gen/commands b/gen/commands new file mode 100644 index 0000000..927ae4a --- /dev/null +++ b/gen/commands @@ -0,0 +1,10 @@ +.alloc C_ALLOC +.realloc C_REALLOC +.free C_FREE +.set C_SET +.cpy C_CPY +.deref C_DEREF +.strerror C_STRERROR +.if C_IF +.while C_WHILE +.for C_FOR diff --git a/commands b/gen/syscalls_x86_64 similarity index 100% rename from commands rename to gen/syscalls_x86_64 diff --git a/triegen.py b/gen/triegen.py similarity index 86% rename from triegen.py rename to gen/triegen.py index 40eb3a3..aa40a32 100755 --- a/triegen.py +++ b/gen/triegen.py @@ -9,17 +9,18 @@ if len(sys.argv) < 3: print("Not enough arguments. Usage: triegen.py ") sys.exit(1) -input_file = sys.argv[1] -output_file = sys.argv[2] +output_file = sys.argv[len(sys.argv) - 1] -with open(input_file, 'r') as f: - data = [re.split('\s+', l.strip()) for l in f.read().split('\n') if len(l.strip()) > 0] +data = [] +for input_file in sys.argv[1:-1]: + with open(input_file, 'r') as f: + data += [re.split('\s+', l.strip()) for l in f.read().split('\n') if len(l.strip()) > 0] trie = {} for line in data: key = line[0] + '\0' - val = int(line[1]) + val = line[1] trie_local = trie for c in key: if c == '\0': @@ -67,7 +68,7 @@ with open(output_file, 'w') as f: f.write("#include \n") f.write("#include \"trie.h\"\n\n") f.write("/* auto-generated by triegen.py */\n\n") - f.write("int trie_get(const char* key) {\n ") + f.write("long trie_get(const char* key) {\n ") write_trie(f, trie, 0) f.write(" return -1;\n}\n") diff --git a/src/eval.c b/src/eval.c index 4fc3e73..4499f5d 100644 --- a/src/eval.c +++ b/src/eval.c @@ -3,34 +3,67 @@ #include #include #include +#include #include "eval.h" +#include "hashmap.h" #include "parser.h" #include "scanner.h" +#include "trie.h" -static long eval_syscall(Line* line) { +#define LOGERROR(msg) fprintf(stderr, "sysh: " msg ## "\n") + +static void log_error(const char* format, ...) { + va_list args; + va_start(args, format); + fprintf(stderr, "sysh: "); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +} + +bool eval_arg(Argument arg, long* result, bool* cloned, Hashmap* vars) { + if(arg.type == ARG_NUM) { + *result = arg.as.num; + if(cloned) *cloned = false; + return true; + } else if(arg.type == ARG_STR && cloned != NULL) { + int len = strlen(arg.as.str); + char* buf = malloc(len + 1); + strcpy(buf, arg.as.str); + *result = (long)buf; + *cloned = true; + return true; + } else if(arg.type == ARG_BLOCK) { + *result = eval_block(&arg.as.block, vars); + if(cloned) *cloned = false; + return true; + } else if(arg.type == ARG_VAR) { + if(hashmap_get(vars, arg.as.str, result)) { + if(cloned) *cloned = false; + return true; + } else { + return false; + } + } { + return false; + } +} + +static long eval_syscall(Line* line, Hashmap* vars) { if(line->len > 6) { - fprintf(stderr, "sysh: too many args for syscall\n"); - return 0; + log_error("too many arguments for syscall: got %d", line->len); + return -1; } long args[6] = {0,0,0,0,0,0}; bool cloned[6] = {0,0,0,0,0,0}; for(int i = 0; i < line->len; i++) { Argument arg = line->args[i]; - if(arg.type == ARG_NUM) { - args[i] = arg.as.num; - } else if(arg.type == ARG_STR) { - int len = strlen(arg.as.str); - char* buf = malloc(len + 1); - strcpy(buf, arg.as.str); - args[i] = (long)buf; - cloned[i] = true; - } else { + if(!eval_arg(arg, &args[i], &cloned[i], vars)) { for(int j = 0; j < line->len; j++) { if(cloned[j]) free((void*)args[j]); } - fprintf(stderr, "sysh: invalid arg type\n"); - return 0; + log_error("bad argument to syscall"); + return -1; } } long result = syscall(line->id, args[0], args[1], args[2], args[3], args[4], args[5]); @@ -40,24 +73,93 @@ static long eval_syscall(Line* line) { return result; } -static long eval_line(Line* line) { - if(line->id >= 0) { - return eval_syscall(line); +static long eval_alloc(Line* line, Hashmap* vars) { + if(line->len != 1) { + log_error(".alloc expected 1 arg, got %d", line->len); + return -1; + } + long val; + if(!eval_arg(line->args[0], &val, NULL, vars)) { + log_error("bad argument to .alloc"); + return -1; + } + return (long)malloc(val); +} + +static long eval_realloc(Line* line, Hashmap* vars) { + if(line->len != 2) { + log_error(".realloc expected 2 args, got %d", line->len); + return -1; + } + long val1; + long val2; + if(!eval_arg(line->args[0], &val1, NULL, vars)) { + log_error("bad argument to .realloc"); + return -1; + } + if(!eval_arg(line->args[1], &val2, NULL, vars)) { + log_error("bad argument to .realloc"); + return -1; + } + return (long)realloc((void*)val1, val2); +} + +static long eval_free(Line* line, Hashmap* vars) { + if(line->len != 1) { + log_error(".free expected 1 args, got %d", line->len); + return -1; + } + long val; + if(!eval_arg(line->args[0], &val, NULL, vars)) { + log_error("bad argument to .free"); + return -1; + } + free((void*)val); + return 0; +} + +static long eval_set(Line* line, Hashmap* vars) { + if(line->len < 0 || line->len > 2) { + log_error(".set expected 1 or 2 args, got %d", line->len); + return -1; + } + if(line->args[0].type != ARG_VAR) { + log_error("bad argument to .set"); + return -1; + } + if(line->len == 2) { + long val; + bool cloned; + if(!eval_arg(line->args[1], &val, &cloned, vars)) { + log_error("bad argument to .set"); + return -1; + } + hashmap_add(vars, strdup(line->args[0].as.str), val); } else { - // TODO - return 0; + hashmap_remove(vars, line->args[0].as.str); + } + return 0; +} + +static long eval_line(Line* line, Hashmap* vars) { + if(line->id >= 0) { + return eval_syscall(line, vars); + } else switch(line->id) { + case C_ALLOC: return eval_alloc(line, vars); + case C_REALLOC: return eval_realloc(line, vars); + case C_FREE: return eval_free(line, vars); + case C_SET: return eval_set(line, vars); + default: return 0; // unreachable } } - -long eval_block(Block* block) { +long eval_block(Block* block, Hashmap* vars) { long result = 0; for(int i = 0; i < block->len; i++) { - result = eval_line(&block->lines[i]); + result = eval_line(&block->lines[i], vars); if(errno > 0) { fprintf(stderr, "E%d: %s\n", errno, strerror(errno)); } - break; } return result; } diff --git a/src/eval.h b/src/eval.h index ea2bfa1..b07950e 100644 --- a/src/eval.h +++ b/src/eval.h @@ -1,5 +1,6 @@ #pragma once +#include "hashmap.h" #include "parser.h" -long eval_block(Block* block); +long eval_block(Block* block, Hashmap* vars); diff --git a/src/hashmap.c b/src/hashmap.c new file mode 100644 index 0000000..4f6799a --- /dev/null +++ b/src/hashmap.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include +#include + +#include "hashmap.h" + +static uint32_t hash_string(const char* key) { + uint32_t hash = 2166136261u; + while(*key != '\0') { + hash ^= (uint8_t)(*key); + hash *= 16777619; + key++; + } + return hash; +} + +void hashmap_init(Hashmap* hashmap) { + hashmap->len = 0; + hashmap->capacity = 0; + hashmap->entries = NULL; +} + +void hashmap_free(Hashmap* hashmap) { + for(int i = 0; i < hashmap->capacity; i++) { + Entry e = hashmap->entries[i]; + if(e.key != NULL) free((char*)e.key); + } + free(hashmap->entries); + hashmap_init(hashmap); +} + +static Entry* hashmap_find(Entry* entries, int capacity, const char* key) { + uint32_t index = hash_string(key) % capacity; + Entry* tombstone = NULL; + while(true) { + Entry* entry = &entries[index]; + if(entry->key == NULL) { + if(entry->value == 1) { + if(tombstone == NULL) tombstone = entry; + } else { + return (tombstone == NULL) ? entry : tombstone; + } + } else if(strcmp(entry->key, key) == 0) { + return entry; + } + } +} + +static void hashmap_grow(Hashmap* map, int capacity) { + Entry* entries = malloc(capacity * sizeof(Entry)); + for(int i = 0; i < capacity; i++) { + entries[i].key = NULL; + entries[i].value = 0; + } + map->len = 0; + for(int i = 0; i < map->capacity; i++) { + Entry* entry = &map->entries[i]; + if(entry->key == NULL) continue; + + Entry* dest = hashmap_find(entries, capacity, entry->key); + dest->key = entry->key; + dest->value = entry->value; + map->len++; + } + free(map->entries); + + map->entries = entries; + map->capacity = capacity; +} + +bool hashmap_get(Hashmap* hashmap, const char* key, long* value) { + if(hashmap->len == 0) return false; + + Entry* e = hashmap_find(hashmap->entries, hashmap->capacity, key); + if(e->key == NULL) return false; + + *value = e->value; + return true; +} + +bool hashmap_add(Hashmap* hashmap, char* key, long value) { + if(hashmap->len + 1 > hashmap->capacity * TABLE_MAX_LOAD) { + int capacity = (hashmap->capacity == 0 ? 8 : (2 * hashmap->capacity)); + hashmap_grow(hashmap, capacity); + } + Entry* e = hashmap_find(hashmap->entries, hashmap->capacity, key); + bool new_key = e->key == NULL; + if(new_key) { + hashmap->len++; + } + e->key = key; + e->value = value; + return new_key; +} + +bool hashmap_remove(Hashmap* hashmap, const char* key) { + if(hashmap->len == 0) return false; + + Entry* e = hashmap_find(hashmap->entries, hashmap->capacity, key); + if(e->key == NULL) return false; + + e->key = NULL; + e->value = 1; + return true; +} diff --git a/src/hashmap.h b/src/hashmap.h new file mode 100644 index 0000000..d0f94a4 --- /dev/null +++ b/src/hashmap.h @@ -0,0 +1,23 @@ +#pragma once +#include + +#define TABLE_MAX_LOAD 0.75 + +typedef struct { + const char* key; + long value; +} Entry; + +typedef struct { + int len; + int capacity; + Entry* entries; +} Hashmap; + +void hashmap_init(Hashmap* hashmap); +void hashmap_free(Hashmap* hashmap); + +bool hashmap_add(Hashmap* hashmap, char* key, long value); +bool hashmap_get(Hashmap* hashmap, const char* key, long* value); +bool hashmap_remove(Hashmap* hashmap, const char* key); + diff --git a/src/main.c b/src/main.c index d93a12b..f2ec596 100644 --- a/src/main.c +++ b/src/main.c @@ -1,9 +1,12 @@ +#include #include #include #include +#include #include #include "eval.h" +#include "hashmap.h" #include "parser.h" #include "scanner.h" @@ -11,9 +14,11 @@ #define PROMPT "[%ld]sysh$ " #define EPROMPT "[E]sysh$ " -void repl() { +static long repl() { char buf[LINE_LEN]; printf(PROMPT, 0L); + Hashmap vars; + hashmap_init(&vars); while(fgets(buf, LINE_LEN, stdin)) { Scanner sc = init_scanner(buf); BlockResult br = parse(&sc); @@ -21,14 +26,51 @@ void repl() { printf("sysh: %s\n", br.as.err); printf(EPROMPT); } else if(br.as.ok.len > 0) { - long result = eval_block(&br.as.ok); + long result = eval_block(&br.as.ok, &vars); printf(PROMPT, result); block_free(&br.as.ok); } } -} - -int main(void) { - repl(); + hashmap_free(&vars); return 0; } + +static long run_file(const char* name) { + FILE* file = fopen(name, "r"); + if(file == NULL) { + fprintf(stderr, "%s\n", strerror(errno)); + return 1; + } + fseek(file, 0, SEEK_END); + long fsize = ftell(file); + fseek(file, 0, SEEK_SET); + + char* buf = malloc(fsize + 1); + fread(buf, fsize, 1, file); + fclose(file); + + buf[fsize] = '\0'; + + Hashmap vars; + hashmap_init(&vars); + Scanner sc = init_scanner(buf); + BlockResult br = parse(&sc); + if(!br.is_ok) { + printf("sysh: %s\n", br.as.err); + } else if(br.as.ok.len > 0) { + eval_block(&br.as.ok, &vars); + block_free(&br.as.ok); + } + hashmap_free(&vars); + free(buf); + + return 0; +} + +int main(int argc, const char** argv) { + if(argc < 1) return 1; + if(argc == 1) return repl(); + if(argc == 2) return run_file(argv[1]); + fprintf(stderr, "usage: %s [file]\n", argv[0]); + return 1; +} diff --git a/src/parser.c b/src/parser.c index 88f7527..012b0f7 100644 --- a/src/parser.c +++ b/src/parser.c @@ -65,8 +65,9 @@ void line_free(Line* line) { free(line->args); } +static BlockResult parse_block(Scanner* sc, bool braced); -static LineResult parse_line(Scanner* sc, int id) { +static LineResult parse_line(Scanner* sc, int id, bool* brace_end) { Line line; line_init(&line, id); while(true) { @@ -74,6 +75,10 @@ static LineResult parse_line(Scanner* sc, int id) { switch(tok.type) { case TOK_EOF: case TOK_EOL: + *brace_end = false; + return OK(line, LineResult); + case TOK_RBRACE: + *brace_end = true; return OK(line, LineResult); case TOK_ERR: line_free(&line); @@ -90,9 +95,17 @@ static LineResult parse_line(Scanner* sc, int id) { case TOK_CMD: line_add(&line, (Argument){.type = ARG_CMD, .as.str = tok.as.str}); break; + case TOK_LBRACE: { + BlockResult br = parse_block(sc, true); + if(!br.is_ok) { + line_free(&line); + return ERR(br.as.err, LineResult); + } + line_add(&line, (Argument){.type = ARG_BLOCK, .as.block = br.as.ok}); + } break; default: line_free(&line); - return ERR("unexpected token", LineResult); + return ERR("unexpected token in line: %d", LineResult); } } } @@ -118,16 +131,25 @@ static BlockResult parse_block(Scanner* sc, bool braced) { block_free(&block); return ERR("invalid syscall or command name", BlockResult); } - LineResult sr = parse_line(sc, id); + bool brace_end; + LineResult sr = parse_line(sc, id, &brace_end); if(!sr.is_ok) { block_free(&block); return ERR(sr.as.err, BlockResult); } + if(brace_end && !braced) { + line_free(&sr.as.ok); + block_free(&block); + return ERR("unexpected token in block", BlockResult); + } block_add(&block, sr.as.ok); + if(brace_end) { + return OK(block, BlockResult); + } } break; default: block_free(&block); - return ERR("unexpected token", BlockResult); + return ERR("unexpected token in block", BlockResult); } } return OK(block, BlockResult); diff --git a/src/trie.h b/src/trie.h index 46fcd3b..a1f7c9f 100644 --- a/src/trie.h +++ b/src/trie.h @@ -1,3 +1,14 @@ #pragma once -int trie_get(const char* key); +#define C_ALLOC -2 +#define C_REALLOC -3 +#define C_FREE -4 +#define C_SET -5 +#define C_CPY -6 +#define C_DEREF -7 +#define C_STRERROR -8 +#define C_IF -9 +#define C_WHILE -10 +#define C_FOR -11 + +long trie_get(const char* key);