Added basic local includes relative to $PWD and includes for files located at /usr/lib/fun.
This commit is contained in:
parent
d25c4b616d
commit
5b7b063086
8 changed files with 356 additions and 6 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fun VERSION 0.5.2 LANGUAGES C)
|
||||
project(fun VERSION 0.6.0 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
@ -68,6 +68,10 @@ add_custom_target(distclean
|
|||
install(TARGETS fun
|
||||
RUNTIME DESTINATION /usr/bin)
|
||||
|
||||
install(DIRECTORY lib/
|
||||
DESTINATION /usr/lib/fun
|
||||
FILES_MATCHING PATTERN "*.fun")
|
||||
|
||||
# - Optionally install example scripts
|
||||
option(FUN_INSTALL_EXAMPLES "Install example .fun scripts" ON)
|
||||
if(FUN_INSTALL_EXAMPLES)
|
||||
|
|
|
|||
24
examples/include_lib.fun
Normal file
24
examples/include_lib.fun
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
// Demonstrates system library includes after installation to /usr/lib/fun
|
||||
|
||||
include <hello.fun>
|
||||
include <utils/math.fun>
|
||||
|
||||
print("== include lib demo ==")
|
||||
hello_lib()
|
||||
|
||||
number x = 10
|
||||
number y = 32
|
||||
|
||||
print("add(" + to_string(x) + ", " + to_string(y) + ") = " + to_string(add(x, y)))
|
||||
print("times(" + to_string(x) + ", " + to_string(y) + ") = " + to_string(times(x, y)))
|
||||
29
examples/include_local.fun
Normal file
29
examples/include_local.fun
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
// To test this you need to go to ./examples/ and test includes running ../build/fun include_local.fun
|
||||
// Local includes are always relative from the $PWD.
|
||||
|
||||
// Demonstrates local include:
|
||||
// This resolves relative to the current working directory where 'fun' is executed.
|
||||
#include "include_local_util.fun"
|
||||
|
||||
print("== include local demo ==")
|
||||
greet("Fun")
|
||||
|
||||
number a = 2
|
||||
number b = 3
|
||||
print("sum(" + to_string(a) + ", " + to_string(b) + ") = " + to_string(sum(a, b)))
|
||||
|
||||
// Tip:
|
||||
// To include from the system library directory (/usr/lib/fun), use angle brackets:
|
||||
// #include <some_lib.fun>
|
||||
|
||||
16
examples/include_local_util.fun
Normal file
16
examples/include_local_util.fun
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
// Simple utility module to be included locally with: #include "include_local_util.fun"
|
||||
|
||||
fun greet(name)
|
||||
print("Hello, " + name)
|
||||
|
||||
fun sum(a, b)
|
||||
return a + b
|
||||
17
lib/hello.fun
Normal file
17
lib/hello.fun
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
/*
|
||||
* A tiny system library module installed to /usr/lib/fun
|
||||
* Usage (after installation):
|
||||
* #include <hello.fun>
|
||||
*/
|
||||
|
||||
fun hello_lib()
|
||||
print("Hello from system lib!")
|
||||
17
lib/utils/math.fun
Normal file
17
lib/utils/math.fun
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the ISC license.
|
||||
* https://opensource.org/license/isc-license-txt
|
||||
*/
|
||||
|
||||
// Basic math helpers to be included from system lib:
|
||||
// #include <utils/math.fun>
|
||||
|
||||
fun add(a, b)
|
||||
return a + b
|
||||
|
||||
fun times(a, b)
|
||||
return a * b
|
||||
24
src/parser.c
24
src/parser.c
|
|
@ -3009,6 +3009,12 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
|||
fprintf(stderr, "Error: cannot read file: %s\n", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Preprocess includes before compiling */
|
||||
char *prep = preprocess_includes(src);
|
||||
const char *compile_src = prep ? prep : src;
|
||||
size_t compile_len = strlen(compile_src);
|
||||
|
||||
/* reset error state */
|
||||
g_has_error = 0;
|
||||
g_err_pos = 0;
|
||||
|
|
@ -3016,19 +3022,21 @@ Bytecode *parse_file_to_bytecode(const char *path) {
|
|||
g_err_line = 0;
|
||||
g_err_col = 0;
|
||||
|
||||
Bytecode *bc = compile_minimal(src, len);
|
||||
Bytecode *bc = compile_minimal(compile_src, compile_len);
|
||||
|
||||
if (g_has_error) {
|
||||
int line = 1, col = 1;
|
||||
calc_line_col(src, len, g_err_pos, &line, &col);
|
||||
calc_line_col(compile_src, compile_len, g_err_pos, &line, &col);
|
||||
g_err_line = line;
|
||||
g_err_col = col;
|
||||
fprintf(stderr, "Parse error %s:%d:%d: %s\n", path ? path : "<input>", line, col, g_err_msg);
|
||||
if (bc) bytecode_free(bc);
|
||||
if (prep) free(prep);
|
||||
free(src);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (prep) free(prep);
|
||||
free(src);
|
||||
return bc;
|
||||
}
|
||||
|
|
@ -3038,7 +3046,11 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
|||
fprintf(stderr, "Error: null source provided\n");
|
||||
return NULL;
|
||||
}
|
||||
size_t len = strlen(source);
|
||||
|
||||
/* Preprocess includes before compiling */
|
||||
char *prep = preprocess_includes(source);
|
||||
const char *compile_src = prep ? prep : source;
|
||||
size_t len = strlen(compile_src);
|
||||
|
||||
/* reset error state */
|
||||
g_has_error = 0;
|
||||
|
|
@ -3047,16 +3059,18 @@ Bytecode *parse_string_to_bytecode(const char *source) {
|
|||
g_err_line = 0;
|
||||
g_err_col = 0;
|
||||
|
||||
Bytecode *bc = compile_minimal(source, len);
|
||||
Bytecode *bc = compile_minimal(compile_src, len);
|
||||
|
||||
if (g_has_error) {
|
||||
int line = 1, col = 1;
|
||||
calc_line_col(source, len, g_err_pos, &line, &col);
|
||||
calc_line_col(compile_src, len, g_err_pos, &line, &col);
|
||||
g_err_line = line;
|
||||
g_err_col = col;
|
||||
if (bc) bytecode_free(bc);
|
||||
if (prep) free(prep);
|
||||
return NULL;
|
||||
}
|
||||
if (prep) free(prep);
|
||||
return bc;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -177,3 +177,232 @@ static int64_t parse_int_literal_value(const char *src, size_t len, size_t *pos,
|
|||
*ok = 1;
|
||||
return sign * val;
|
||||
}
|
||||
|
||||
/* === Include preprocessor ===
|
||||
* Supports:
|
||||
* - #include "path" (resolved relative to current working directory)
|
||||
* - #include <path> (resolved under /usr/lib/fun/)
|
||||
* Also accepts 'include' without '#', at the start of a line (after spaces/tabs).
|
||||
* Directives are recognized only when not inside strings or block comments.
|
||||
*/
|
||||
|
||||
static void *xrealloc(void *ptr, size_t newcap) {
|
||||
void *np = realloc(ptr, newcap);
|
||||
return np;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char *buf;
|
||||
size_t len;
|
||||
size_t cap;
|
||||
} StrBuf;
|
||||
|
||||
static void sb_init(StrBuf *sb) {
|
||||
sb->buf = (char*)malloc(256);
|
||||
sb->cap = sb->buf ? 256 : 0;
|
||||
sb->len = 0;
|
||||
if (sb->buf) sb->buf[0] = '\0';
|
||||
}
|
||||
|
||||
static void sb_reserve(StrBuf *sb, size_t need) {
|
||||
if (need <= sb->cap) return;
|
||||
size_t nc = sb->cap ? sb->cap : 256;
|
||||
while (nc < need) nc *= 2;
|
||||
char *nb = (char*)xrealloc(sb->buf, nc);
|
||||
if (!nb) return;
|
||||
sb->buf = nb;
|
||||
sb->cap = nc;
|
||||
}
|
||||
|
||||
static void sb_append_n(StrBuf *sb, const char *s, size_t n) {
|
||||
if (n == 0) return;
|
||||
sb_reserve(sb, sb->len + n + 1);
|
||||
if (!sb->buf) return;
|
||||
memcpy(sb->buf + sb->len, s, n);
|
||||
sb->len += n;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
static void sb_append(StrBuf *sb, const char *s) {
|
||||
sb_append_n(sb, s, strlen(s));
|
||||
}
|
||||
|
||||
static void sb_append_ch(StrBuf *sb, char c) {
|
||||
sb_reserve(sb, sb->len + 2);
|
||||
if (!sb->buf) return;
|
||||
sb->buf[sb->len++] = c;
|
||||
sb->buf[sb->len] = '\0';
|
||||
}
|
||||
|
||||
static char *preprocess_includes_internal(const char *src, int depth) {
|
||||
if (!src) return NULL;
|
||||
if (depth > 64) {
|
||||
fprintf(stderr, "Include error: include nesting too deep\n");
|
||||
return strdup("");
|
||||
}
|
||||
|
||||
const char *LIB_DIR = "/usr/lib/fun/";
|
||||
size_t len = strlen(src);
|
||||
StrBuf out;
|
||||
sb_init(&out);
|
||||
int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0;
|
||||
int bol = 1; /* beginning of line */
|
||||
|
||||
for (size_t i = 0; i < len; ) {
|
||||
char c = src[i];
|
||||
|
||||
/* Detect include directive at BOL, outside comments/strings */
|
||||
if (bol && !in_block && !in_sq && !in_dq) {
|
||||
size_t j = i;
|
||||
/* skip leading spaces/tabs */
|
||||
while (j < len && (src[j] == ' ' || src[j] == '\t')) j++;
|
||||
size_t k = j;
|
||||
if (k < len && src[k] == '#') k++;
|
||||
const char *kw = "include";
|
||||
size_t kwlen = 7;
|
||||
if (k + kwlen <= len && strncmp(src + k, kw, kwlen) == 0) {
|
||||
k += kwlen;
|
||||
/* next must be space/tab or delimiter */
|
||||
while (k < len && (src[k] == ' ' || src[k] == '\t')) k++;
|
||||
if (k < len && (src[k] == '"' || src[k] == '<')) {
|
||||
char opener = src[k];
|
||||
char closer = (opener == '"') ? '"' : '>';
|
||||
k++;
|
||||
size_t path_start = k;
|
||||
while (k < len && src[k] != closer) k++;
|
||||
if (k < len && src[k] == closer) {
|
||||
size_t path_len = k - path_start;
|
||||
char *path = (char*)malloc(path_len + 1);
|
||||
if (path) {
|
||||
memcpy(path, src + path_start, path_len);
|
||||
path[path_len] = '\0';
|
||||
|
||||
/* advance to end of line */
|
||||
k++;
|
||||
while (k < len && src[k] != '\n') k++;
|
||||
if (k < len && src[k] == '\n') k++;
|
||||
|
||||
/* resolve file path */
|
||||
char resolved[1024];
|
||||
if (opener == '<') {
|
||||
snprintf(resolved, sizeof(resolved), "%s%s", LIB_DIR, path);
|
||||
} else {
|
||||
snprintf(resolved, sizeof(resolved), "%s", path);
|
||||
}
|
||||
free(path);
|
||||
|
||||
/* read and recursively expand */
|
||||
size_t inc_len = 0;
|
||||
char *inc = read_file_all(resolved, &inc_len);
|
||||
if (!inc) {
|
||||
fprintf(stderr, "Include error: cannot read '%s'\n", resolved);
|
||||
sb_append(&out, "// include error: cannot read ");
|
||||
sb_append(&out, resolved);
|
||||
sb_append(&out, "\n");
|
||||
} else {
|
||||
char *exp = preprocess_includes_internal(inc, depth + 1);
|
||||
free(inc);
|
||||
if (exp) {
|
||||
sb_append(&out, exp);
|
||||
/* ensure included chunk ends with newline to preserve line structure */
|
||||
if (out.len == 0 || out.buf[out.len - 1] != '\n') sb_append_ch(&out, '\n');
|
||||
free(exp);
|
||||
}
|
||||
}
|
||||
|
||||
/* move input pointer to start of next line */
|
||||
i = k;
|
||||
bol = 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* normal stateful copy with comment/string tracking */
|
||||
if (in_line) {
|
||||
sb_append_ch(&out, c);
|
||||
if (c == '\n') { in_line = 0; bol = 1; } else { bol = 0; }
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (in_block) {
|
||||
sb_append_ch(&out, c);
|
||||
if (c == '*' && (i + 1) < len && src[i + 1] == '/') {
|
||||
sb_append_ch(&out, '/');
|
||||
i += 2;
|
||||
bol = 0;
|
||||
in_block = 0;
|
||||
continue;
|
||||
}
|
||||
bol = (c == '\n') ? 1 : 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (in_sq) {
|
||||
sb_append_ch(&out, c);
|
||||
if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; }
|
||||
if (!esc && c == '\'') { in_sq = 0; }
|
||||
esc = 0;
|
||||
bol = (c == '\n') ? 1 : 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (in_dq) {
|
||||
sb_append_ch(&out, c);
|
||||
if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; }
|
||||
if (!esc && c == '"') { in_dq = 0; }
|
||||
esc = 0;
|
||||
bol = (c == '\n') ? 1 : 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* outside any special state */
|
||||
if (c == '/' && (i + 1) < len && src[i + 1] == '/') {
|
||||
sb_append_ch(&out, '/');
|
||||
sb_append_ch(&out, '/');
|
||||
i += 2;
|
||||
in_line = 1;
|
||||
bol = 0;
|
||||
continue;
|
||||
}
|
||||
if (c == '/' && (i + 1) < len && src[i + 1] == '*') {
|
||||
sb_append_ch(&out, '/');
|
||||
sb_append_ch(&out, '*');
|
||||
i += 2;
|
||||
in_block = 1;
|
||||
bol = 0;
|
||||
continue;
|
||||
}
|
||||
if (c == '\'') {
|
||||
sb_append_ch(&out, c);
|
||||
in_sq = 1;
|
||||
bol = 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
sb_append_ch(&out, c);
|
||||
in_dq = 1;
|
||||
bol = 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
sb_append_ch(&out, c);
|
||||
bol = (c == '\n') ? 1 : 0;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!out.buf) return strdup("");
|
||||
/* ensure NUL-terminated */
|
||||
if (out.cap == out.len) sb_reserve(&out, out.len + 1);
|
||||
if (out.buf) out.buf[out.len] = '\0';
|
||||
return out.buf;
|
||||
}
|
||||
|
||||
static char *preprocess_includes(const char *src) {
|
||||
return preprocess_includes_internal(src, 0);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue