Initial parser implementation that runs a simple hello_world.fun script.
This commit is contained in:
parent
284b4d3fa9
commit
7d587fe11b
5 changed files with 237 additions and 45 deletions
|
|
@ -7,8 +7,10 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
# Core VM/library sources
|
# Core VM/library sources
|
||||||
add_library(fun_core
|
add_library(fun_core
|
||||||
src/bytecode.c
|
src/bytecode.c
|
||||||
|
src/parser.c
|
||||||
src/value.c
|
src/value.c
|
||||||
src/vm.c
|
src/vm.c
|
||||||
|
src/parser.c
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(fun_core PUBLIC
|
target_include_directories(fun_core PUBLIC
|
||||||
|
|
|
||||||
55
src/fun.c
55
src/fun.c
|
|
@ -1,52 +1,31 @@
|
||||||
#include "bytecode.h"
|
#include "bytecode.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
|
#include "parser.h"
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char **argv) {
|
||||||
VM vm;
|
VM vm;
|
||||||
vm_init(&vm);
|
vm_init(&vm);
|
||||||
|
|
||||||
/* Example: 0..4 loop */
|
// If a script path is provided, parse and run it
|
||||||
Bytecode *bc = bytecode_new();
|
if (argc > 1) {
|
||||||
int c0 = bytecode_add_constant(bc, make_int(0));
|
const char *path = argv[1];
|
||||||
int c1 = bytecode_add_constant(bc, make_int(1));
|
Bytecode *bc = parse_file_to_bytecode(path);
|
||||||
int c5 = bytecode_add_constant(bc, make_int(5));
|
if (!bc) {
|
||||||
|
fprintf(stderr, "Failed to compile script: %s\n", path);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
vm_run(&vm, bc);
|
||||||
|
|
||||||
// initialize i = 0
|
// Print captured output for user scripts
|
||||||
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
|
vm_print_output(&vm);
|
||||||
bytecode_add_instruction(bc, OP_STORE_LOCAL, 0);
|
vm_clear_output(&vm);
|
||||||
|
|
||||||
int lbl_start = bc->instr_count;
|
bytecode_free(bc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// 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);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
193
src/parser.c
193
src/parser.c
|
|
@ -0,0 +1,193 @@
|
||||||
|
#include "parser.h"
|
||||||
|
#include "value.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
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 <ident>() { ... } 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 <ident>() { ... }
|
||||||
|
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;
|
||||||
|
}
|
||||||
19
src/parser.h
19
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 <ident>() { ... }
|
||||||
|
* - 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
|
||||||
7
src/vm.h
7
src/vm.h
|
|
@ -39,13 +39,12 @@ typedef struct {
|
||||||
// initialize VM (zero state)
|
// initialize VM (zero state)
|
||||||
void vm_init(VM *vm);
|
void vm_init(VM *vm);
|
||||||
|
|
||||||
// run entry Bytecode (pushes first frame)
|
|
||||||
void vm_run(VM *vm, Bytecode *entry);
|
|
||||||
|
|
||||||
// helper function to clear the output
|
// helper function to clear the output
|
||||||
void vm_clear_output(VM *vm);
|
void vm_clear_output(VM *vm);
|
||||||
|
void vm_print_output(VM *vm);
|
||||||
void vm_free(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) {
|
static inline int opcode_is_valid(int op) {
|
||||||
return op >= OP_NOP && op <= OP_SWAP; // all current opcodes
|
return op >= OP_NOP && op <= OP_SWAP; // all current opcodes
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue