diff --git a/CMakeLists.txt b/CMakeLists.txt index 2455fe8..82920d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,10 @@ set(CMAKE_C_STANDARD_REQUIRED ON) # Core VM/library sources add_library(fun_core src/bytecode.c + src/parser.c src/value.c src/vm.c + src/parser.c ) target_include_directories(fun_core PUBLIC diff --git a/src/fun.c b/src/fun.c index 6fba101..86c14f9 100644 --- a/src/fun.c +++ b/src/fun.c @@ -1,52 +1,31 @@ #include "bytecode.h" #include "value.h" #include "vm.h" +#include "parser.h" #include -int main(void) { +int main(int argc, char **argv) { VM vm; vm_init(&vm); - /* Example: 0..4 loop */ - Bytecode *bc = bytecode_new(); - int c0 = bytecode_add_constant(bc, make_int(0)); - int c1 = bytecode_add_constant(bc, make_int(1)); - int c5 = bytecode_add_constant(bc, make_int(5)); + // If a script path is provided, parse and run it + if (argc > 1) { + const char *path = argv[1]; + Bytecode *bc = parse_file_to_bytecode(path); + if (!bc) { + fprintf(stderr, "Failed to compile script: %s\n", path); + return 1; + } + vm_run(&vm, bc); - // initialize i = 0 - bytecode_add_instruction(bc, OP_LOAD_CONST, c0); - bytecode_add_instruction(bc, OP_STORE_LOCAL, 0); + // Print captured output for user scripts + vm_print_output(&vm); + vm_clear_output(&vm); - int lbl_start = bc->instr_count; - - // load i - bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); - bytecode_add_instruction(bc, OP_PRINT, 0); - - // i = i + 1 - bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c1); - bytecode_add_instruction(bc, OP_ADD, 0); - bytecode_add_instruction(bc, OP_STORE_LOCAL, 0); - - // check i < 5 - bytecode_add_instruction(bc, OP_LOAD_LOCAL, 0); - bytecode_add_instruction(bc, OP_LOAD_CONST, c5); - bytecode_add_instruction(bc, OP_LT, 0); - int jmp_back = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0); - bytecode_add_instruction(bc, OP_JUMP, lbl_start); - - int end_lbl = bc->instr_count; - bytecode_set_operand(bc, jmp_back, end_lbl); - - bytecode_add_instruction(bc, OP_HALT, 0); - - vm_run(&vm, bc); - - // flush captured outputs - vm_clear_output(&vm); - - bytecode_free(bc); + bytecode_free(bc); + return 0; + } + return 0; } diff --git a/src/parser.c b/src/parser.c index e69de29..8b9f063 100644 --- a/src/parser.c +++ b/src/parser.c @@ -0,0 +1,193 @@ +#include "parser.h" +#include "value.h" +#include +#include +#include +#include + +static char *read_file_all(const char *path, size_t *out_len) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } + long sz = ftell(f); + if (sz < 0) { fclose(f); return NULL; } + rewind(f); + char *buf = (char*)malloc((size_t)sz + 1); + if (!buf) { fclose(f); return NULL; } + size_t n = fread(buf, 1, (size_t)sz, f); + fclose(f); + buf[n] = '\0'; + if (out_len) *out_len = n; + return buf; +} + +static void skip_ws(const char *src, size_t len, size_t *pos) { + while (*pos < len) { + char c = src[*pos]; + if (c == ' ' || c == '\t' || c == '\r' || c == '\n') { (*pos)++; continue; } + break; + } +} + +static void skip_line(const char *src, size_t len, size_t *pos) { + while (*pos < len && src[*pos] != '\n') (*pos)++; + if (*pos < len && src[*pos] == '\n') (*pos)++; +} + +static void skip_comments(const char *src, size_t len, size_t *pos) { + for (;;) { + skip_ws(src, len, pos); + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '/') { + *pos += 2; + skip_line(src, len, pos); + continue; + } + if (*pos + 1 < len && src[*pos] == '/' && src[*pos + 1] == '*') { + *pos += 2; + while (*pos + 1 < len && !(src[*pos] == '*' && src[*pos + 1] == '/')) (*pos)++; + if (*pos + 1 < len) *pos += 2; + continue; + } + break; + } +} + +static int starts_with(const char *src, size_t len, size_t pos, const char *kw) { + size_t klen = strlen(kw); + if (pos + klen > len) return 0; + return strncmp(src + pos, kw, klen) == 0; +} + +static void skip_shebang_if_present(const char *src, size_t len, size_t *pos) { + if (*pos == 0 && starts_with(src, len, *pos, "#!")) { + skip_line(src, len, pos); + } +} + +static void skip_identifier(const char *src, size_t len, size_t *pos) { + size_t p = *pos; + if (p < len && (isalpha((unsigned char)src[p]) || src[p] == '_')) { + p++; + while (p < len && (isalnum((unsigned char)src[p]) || src[p] == '_')) p++; + } + *pos = p; +} + +static int consume_char(const char *src, size_t len, size_t *pos, char expected) { + skip_ws(src, len, pos); + if (*pos < len && src[*pos] == expected) { (*pos)++; return 1; } + return 0; +} + +static char *parse_string_literal_any_quote(const char *src, size_t len, size_t *pos) { + skip_ws(src, len, pos); + if (*pos >= len) return NULL; + char quote = src[*pos]; + if (quote != '"' && quote != '\'') return NULL; + (*pos)++; // skip opening quote + size_t cap = 64, out_len = 0; + char *out = (char*)malloc(cap); + if (!out) return NULL; + while (*pos < len) { + char c = src[*pos]; + if (c == quote) { (*pos)++; break; } + if (c == '\\') { + (*pos)++; + if (*pos >= len) break; + char e = src[*pos]; + switch (e) { + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case '\\': c = '\\'; break; + case '"': c = '"'; break; + case '\'': c = '\''; break; + default: c = e; break; + } + } + if (out_len + 1 >= cap) { + cap *= 2; + char *tmp = (char*)realloc(out, cap); + if (!tmp) { free(out); return NULL; } + out = tmp; + } + out[out_len++] = c; + (*pos)++; + } + if (out_len + 1 >= cap) { + char *tmp = (char*)realloc(out, cap + 1); + if (!tmp) { free(out); return NULL; } + out = tmp; + } + out[out_len] = '\0'; + return out; +} + +/* + * Very small compiler: + * - Optionally skips: fun () { ... } wrapper. + * - Emits LOAD_CONST (string) + PRINT for each print("...") encountered. + * - Ends with HALT. + */ +static Bytecode *compile_minimal(const char *src, size_t len) { + Bytecode *bc = bytecode_new(); + size_t pos = 0; + + skip_shebang_if_present(src, len, &pos); + + // Optionally consume a single function wrapper: fun () { ... } + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); + if (starts_with(src, len, pos, "fun")) { + pos += 3; + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); + skip_identifier(src, len, &pos); // function name + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); + (void)consume_char(src, len, &pos, '('); + (void)consume_char(src, len, &pos, ')'); + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); + (void)consume_char(src, len, &pos, '{'); // braces optional; if not present, we continue anyway + } + + // Parse statements until EOF or closing brace + while (pos < len) { + skip_comments(src, len, &pos); + skip_ws(src, len, &pos); + if (pos < len && src[pos] == '}') { pos++; break; } + + if (starts_with(src, len, pos, "print")) { + pos += 5; + (void)consume_char(src, len, &pos, '('); + char *s = parse_string_literal_any_quote(src, len, &pos); + if (s) { + int ci = bytecode_add_constant(bc, make_string(s)); + free(s); + bytecode_add_instruction(bc, OP_LOAD_CONST, ci); + bytecode_add_instruction(bc, OP_PRINT, 0); + } + (void)consume_char(src, len, &pos, ')'); + continue; + } + + // Unknown/unsupported token: advance to avoid infinite loop + pos++; + } + + bytecode_add_instruction(bc, OP_HALT, 0); + return bc; +} + +Bytecode *parse_file_to_bytecode(const char *path) { + size_t len = 0; + char *src = read_file_all(path, &len); + if (!src) { + fprintf(stderr, "Error: cannot read file: %s\n", path); + return NULL; + } + Bytecode *bc = compile_minimal(src, len); + free(src); + return bc; +} \ No newline at end of file diff --git a/src/parser.h b/src/parser.h index e69de29..312b9a1 100644 --- a/src/parser.h +++ b/src/parser.h @@ -0,0 +1,19 @@ +/* + * Parse a .fun source file and compile it into entry bytecode. + * Minimal support: + * - Optional shebang on the first line. + * - Optional single function wrapper: fun () { ... } + * - print("...") statements inside function or at top-level. + * Produces bytecode that executes all discovered print statements and halts. + * + * Returns: newly allocated Bytecode*, or NULL on error. + */ + +#ifndef FUN_PARSER_H +#define FUN_PARSER_H + +#include "bytecode.h" + +Bytecode *parse_file_to_bytecode(const char *path); + +#endif diff --git a/src/vm.h b/src/vm.h index 851aa0a..cd002d7 100644 --- a/src/vm.h +++ b/src/vm.h @@ -39,13 +39,12 @@ typedef struct { // initialize VM (zero state) void vm_init(VM *vm); -// run entry Bytecode (pushes first frame) -void vm_run(VM *vm, Bytecode *entry); - // helper function to clear the output void vm_clear_output(VM *vm); - +void vm_print_output(VM *vm); void vm_free(VM *vm); +// run entry Bytecode (pushes first frame) +void vm_run(VM *vm, Bytecode *entry); static inline int opcode_is_valid(int op) { return op >= OP_NOP && op <= OP_SWAP; // all current opcodes