1
0
Fork 0
forked from fun/fun

Initial parser implementation that runs a simple hello_world.fun script.

This commit is contained in:
Johannes Findeisen 2025-09-13 23:50:01 +02:00
commit 7d587fe11b
5 changed files with 237 additions and 45 deletions

View file

@ -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;
}