Added namespaces to includes like in Python. (0.14.0)
This commit is contained in:
parent
cb8d5685bb
commit
6bd13f6e96
7 changed files with 406 additions and 17 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.16)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(fun VERSION 0.13.2 LANGUAGES C)
|
project(fun VERSION 0.14.0 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
5
demo.fun
Normal file → Executable file
5
demo.fun
Normal file → Executable file
|
|
@ -1,16 +1,19 @@
|
||||||
#!/usr/bin/env fun
|
#!/usr/bin/env fun
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This file is part of the Fun programming language.
|
* This file is part of the Fun programming language.
|
||||||
* https://hanez.org/project/fun/
|
* https://hanez.org/project/fun/
|
||||||
*
|
*
|
||||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||||
* Licensed under the terms of the ISC license.
|
* Licensed under the terms of the ISC license.
|
||||||
* https://opensource.org/license/isc-license-txt
|
* https://opensource.org/license/isc-license-txt
|
||||||
|
*
|
||||||
|
* Added: 2025-09-30
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Fun Interactive Demo
|
// Fun Interactive Demo
|
||||||
// Run: FUN_LIB_DIR="$(pwd)/lib" ./build/fun demo.fun (Linux/macOS/FreeBSD)
|
// Run: FUN_LIB_DIR="$(pwd)/lib" ./build/fun demo.fun (Linux/macOS/FreeBSD)
|
||||||
|
// set FUN_LIB_DIR=%CD%\lib && build-debug\fun.exe demo.fun (Windows CMD)
|
||||||
// $env:FUN_LIB_DIR="$PWD\lib"; .\build\fun.exe demo.fun (Windows PowerShell)
|
// $env:FUN_LIB_DIR="$PWD\lib"; .\build\fun.exe demo.fun (Windows PowerShell)
|
||||||
|
|
||||||
// Use a stdlib helper from the repository (fallback to ./lib via preprocessor)
|
// Use a stdlib helper from the repository (fallback to ./lib via preprocessor)
|
||||||
|
|
|
||||||
57
examples/include_namespace.fun
Executable file
57
examples/include_namespace.fun
Executable file
|
|
@ -0,0 +1,57 @@
|
||||||
|
#!/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
|
||||||
|
*
|
||||||
|
* Added: 2025-09-30
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Demonstration of the include-as namespace feature.
|
||||||
|
//
|
||||||
|
// Run examples (without installing) by pointing FUN_LIB_DIR to the repo lib:
|
||||||
|
// Linux/macOS/FreeBSD:
|
||||||
|
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/include_namespace.fun
|
||||||
|
// Windows (CMD):
|
||||||
|
// set FUN_LIB_DIR=%CD%\lib && build-debug\fun.exe examples\include_namespace.fun
|
||||||
|
// Windows (PowerShell):
|
||||||
|
// $env:FUN_LIB_DIR="$PWD\lib"; .\build\fun.exe examples\include_namespace.fun
|
||||||
|
|
||||||
|
// Import stdlib math helpers into alias 'm'
|
||||||
|
#include <utils/math.fun> as m
|
||||||
|
|
||||||
|
// Import a local module (this repository file) into alias 'mod'
|
||||||
|
#include "examples/namespaced_mod.fun" as mod
|
||||||
|
|
||||||
|
print("=== include-as namespace demo ===")
|
||||||
|
|
||||||
|
print("Using m.add and m.times from <utils/math.fun>:")
|
||||||
|
print("m.add(2, 3) = " + to_string(m.add(2, 3)))
|
||||||
|
print("m.times(4, 5) = " + to_string(m.times(4, 5)))
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("Using mod.hello and mod.Greeter from namespaced_mod.fun:")
|
||||||
|
print(mod.hello("Fun"))
|
||||||
|
|
||||||
|
g = mod.Greeter("Hi")
|
||||||
|
g.say("World")
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("=== done ===")
|
||||||
|
|
||||||
|
/* Expected output:
|
||||||
|
=== include-as namespace demo ===
|
||||||
|
Using m.add and m.times from <utils/math.fun>:
|
||||||
|
m.add(2, 3) = 5
|
||||||
|
m.times(4, 5) = 20
|
||||||
|
|
||||||
|
Using mod.hello and mod.Greeter from namespaced_mod.fun:
|
||||||
|
Hello, Fun!
|
||||||
|
Hi World
|
||||||
|
|
||||||
|
=== done ===
|
||||||
|
*/
|
||||||
25
examples/namespaced_mod.fun
Normal file
25
examples/namespaced_mod.fun
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env fun
|
||||||
|
// The shebang line makes no sense here because this is a library which will
|
||||||
|
// never be executed, but it shows that it is not wrong.
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* Added: 2025-09-30
|
||||||
|
*/
|
||||||
|
|
||||||
|
// namespaced_mod.fun
|
||||||
|
// Simple module to demonstrate include-as namespaces with functions and classes.
|
||||||
|
|
||||||
|
fun hello(name)
|
||||||
|
return "Hello, " + to_string(name) + "!"
|
||||||
|
|
||||||
|
class Greeter(string prefix)
|
||||||
|
// Methods must declare 'this' as the first parameter
|
||||||
|
fun say(this, name)
|
||||||
|
print(this.prefix + " " + to_string(name))
|
||||||
0
examples/sha256_str_demo.fun
Normal file → Executable file
0
examples/sha256_str_demo.fun
Normal file → Executable file
105
src/parser.c
105
src/parser.c
|
|
@ -94,6 +94,63 @@ static void calc_line_col(const char *src, size_t len, size_t pos, int *out_line
|
||||||
|
|
||||||
/* --------------------------- */
|
/* --------------------------- */
|
||||||
|
|
||||||
|
/* Namespace alias tracking (for include-as):
|
||||||
|
We scan preprocessed source for lines starting with "// __ns_alias__: <name>"
|
||||||
|
and treat dot-calls on those identifiers as plain function calls (no implicit 'this'). */
|
||||||
|
static char *g_ns_aliases[64];
|
||||||
|
static int g_ns_alias_count = 0;
|
||||||
|
|
||||||
|
static void ns_aliases_reset(void) {
|
||||||
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
||||||
|
free(g_ns_aliases[i]);
|
||||||
|
g_ns_aliases[i] = NULL;
|
||||||
|
}
|
||||||
|
g_ns_alias_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ns_aliases_scan(const char *src, size_t len) {
|
||||||
|
const char *marker = "// __ns_alias__: ";
|
||||||
|
size_t mlen = strlen(marker);
|
||||||
|
size_t i = 0;
|
||||||
|
while (i < len) {
|
||||||
|
/* find start of line */
|
||||||
|
size_t ls = i;
|
||||||
|
/* move to end of line first */
|
||||||
|
while (i < len && src[i] != '\n') i++;
|
||||||
|
size_t le = i;
|
||||||
|
/* include trailing '\n' in next iteration */
|
||||||
|
if (i < len && src[i] == '\n') i++;
|
||||||
|
|
||||||
|
if (le - ls >= mlen && strncmp(src + ls, marker, mlen) == 0) {
|
||||||
|
size_t p = ls + mlen;
|
||||||
|
/* read identifier */
|
||||||
|
size_t start = p;
|
||||||
|
while (p < le && (src[p] == ' ' || src[p] == '\t')) p++;
|
||||||
|
if (p < le && (isalpha((unsigned char)src[p]) || src[p] == '_')) {
|
||||||
|
size_t q = p + 1;
|
||||||
|
while (q < le && (isalnum((unsigned char)src[q]) || src[q] == '_')) q++;
|
||||||
|
size_t n = q - p;
|
||||||
|
if (n > 0 && g_ns_alias_count < (int)(sizeof(g_ns_aliases)/sizeof(g_ns_aliases[0]))) {
|
||||||
|
char *name = (char*)malloc(n + 1);
|
||||||
|
if (name) {
|
||||||
|
memcpy(name, src + p, n);
|
||||||
|
name[n] = '\0';
|
||||||
|
g_ns_aliases[g_ns_alias_count++] = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_ns_alias(const char *name) {
|
||||||
|
if (!name) return 0;
|
||||||
|
for (int i = 0; i < g_ns_alias_count; ++i) {
|
||||||
|
if (strcmp(g_ns_aliases[i], name) == 0) return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#include "parser_utils.c"
|
#include "parser_utils.c"
|
||||||
|
|
||||||
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
|
/* very small global symbol table for LOAD_GLOBAL/STORE_GLOBAL */
|
||||||
|
|
@ -1122,11 +1179,21 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
free(mname);
|
free(mname);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* Prepare: stack has <obj>. Duplicate it to preserve 'this' across INDEX_GET */
|
|
||||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
/* If base identifier is a namespace alias -> treat as plain function call: no implicit 'this' */
|
||||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
int is_ns = is_ns_alias(name);
|
||||||
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */
|
|
||||||
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */
|
if (!is_ns) {
|
||||||
|
/* Method sugar with implicit 'this' */
|
||||||
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||||
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||||
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: obj, func */
|
||||||
|
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> stack: func, obj (this) */
|
||||||
|
} else {
|
||||||
|
/* Plain property function call: obj["mname"] -> func */
|
||||||
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||||
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> stack: func */
|
||||||
|
}
|
||||||
|
|
||||||
/* Consume '(' and parse args */
|
/* Consume '(' and parse args */
|
||||||
*pos = callp + 1;
|
*pos = callp + 1;
|
||||||
|
|
@ -1141,8 +1208,8 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
}
|
}
|
||||||
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; }
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; }
|
||||||
|
|
||||||
/* Call with implicit 'this' (+1 arg) */
|
/* Call */
|
||||||
bytecode_add_instruction(bc, OP_CALL, argc + 1);
|
bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
|
||||||
free(mname);
|
free(mname);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1218,11 +1285,19 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stack has obj: duplicate to preserve 'this' */
|
int is_ns = is_ns_alias(name);
|
||||||
bytecode_add_instruction(bc, OP_DUP, 0);
|
|
||||||
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
if (!is_ns) {
|
||||||
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */
|
/* Method sugar with implicit 'this' */
|
||||||
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */
|
bytecode_add_instruction(bc, OP_DUP, 0);
|
||||||
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||||
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> obj, func */
|
||||||
|
bytecode_add_instruction(bc, OP_SWAP, 0); /* -> func, obj */
|
||||||
|
} else {
|
||||||
|
/* Plain property function call */
|
||||||
|
bytecode_add_instruction(bc, OP_LOAD_CONST, kci);
|
||||||
|
bytecode_add_instruction(bc, OP_INDEX_GET, 0); /* -> func */
|
||||||
|
}
|
||||||
|
|
||||||
*pos = callp + 1;
|
*pos = callp + 1;
|
||||||
int argc = 0;
|
int argc = 0;
|
||||||
|
|
@ -1236,7 +1311,7 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
||||||
}
|
}
|
||||||
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; }
|
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after arguments"); free(mname); free(name); return 0; }
|
||||||
|
|
||||||
bytecode_add_instruction(bc, OP_CALL, argc + 1);
|
bytecode_add_instruction(bc, OP_CALL, is_ns ? argc : (argc + 1));
|
||||||
free(mname);
|
free(mname);
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -3233,6 +3308,10 @@ static Bytecode *compile_minimal(const char *src, size_t len) {
|
||||||
Bytecode *bc = bytecode_new();
|
Bytecode *bc = bytecode_new();
|
||||||
size_t pos = 0;
|
size_t pos = 0;
|
||||||
|
|
||||||
|
/* Refresh namespace alias table for this compilation unit */
|
||||||
|
ns_aliases_reset();
|
||||||
|
ns_aliases_scan(src, len);
|
||||||
|
|
||||||
skip_shebang_if_present(src, len, &pos);
|
skip_shebang_if_present(src, len, &pos);
|
||||||
|
|
||||||
/* Allow top-of-file function definitions; do not skip any leading 'fun' line */
|
/* Allow top-of-file function definitions; do not skip any leading 'fun' line */
|
||||||
|
|
|
||||||
|
|
@ -256,6 +256,158 @@ static void sb_append_ch(StrBuf *sb, char c) {
|
||||||
sb->buf[sb->len] = '\0';
|
sb->buf[sb->len] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---- Export collection for include-as namespaces ---- */
|
||||||
|
typedef struct {
|
||||||
|
char **names;
|
||||||
|
int count;
|
||||||
|
int cap;
|
||||||
|
} NameList;
|
||||||
|
|
||||||
|
static void nl_init(NameList *nl) {
|
||||||
|
nl->names = NULL;
|
||||||
|
nl->count = 0;
|
||||||
|
nl->cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nl_add(NameList *nl, const char *name) {
|
||||||
|
if (!name || !name[0]) return;
|
||||||
|
if (nl->count >= nl->cap) {
|
||||||
|
int ncap = nl->cap ? nl->cap * 2 : 8;
|
||||||
|
char **nn = (char**)realloc(nl->names, (size_t)ncap * sizeof(char*));
|
||||||
|
if (!nn) return;
|
||||||
|
nl->names = nn;
|
||||||
|
nl->cap = ncap;
|
||||||
|
}
|
||||||
|
nl->names[nl->count++] = strdup(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nl_free(NameList *nl) {
|
||||||
|
if (!nl) return;
|
||||||
|
for (int i = 0; i < nl->count; ++i) free(nl->names[i]);
|
||||||
|
free(nl->names);
|
||||||
|
nl->names = NULL;
|
||||||
|
nl->count = nl->cap = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Collect top-level (indent=0) exported symbols: function and class names.
|
||||||
|
Ignores lines inside comments/strings and ignores nested indent. */
|
||||||
|
static void collect_exports_top_level(const char *text, NameList *out) {
|
||||||
|
if (!text || !out) return;
|
||||||
|
size_t len = strlen(text);
|
||||||
|
int in_line = 0, in_block = 0, in_sq = 0, in_dq = 0, esc = 0;
|
||||||
|
int bol = 1;
|
||||||
|
for (size_t i = 0; i < len; ) {
|
||||||
|
char c = text[i];
|
||||||
|
|
||||||
|
if (in_line) {
|
||||||
|
if (c == '\n') { in_line = 0; bol = 1; }
|
||||||
|
else { bol = 0; }
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_block) {
|
||||||
|
if (c == '*' && (i + 1) < len && text[i + 1] == '/') {
|
||||||
|
i += 2;
|
||||||
|
bol = 0;
|
||||||
|
in_block = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
bol = (c == '\n');
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_sq) {
|
||||||
|
if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; }
|
||||||
|
if (!esc && c == '\'') { in_sq = 0; }
|
||||||
|
esc = 0;
|
||||||
|
bol = (c == '\n');
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (in_dq) {
|
||||||
|
if (!esc && c == '\\') { esc = 1; i++; bol = 0; continue; }
|
||||||
|
if (!esc && c == '"') { in_dq = 0; }
|
||||||
|
esc = 0;
|
||||||
|
bol = (c == '\n');
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == '/' && (i + 1) < len && text[i + 1] == '/') {
|
||||||
|
in_line = 1;
|
||||||
|
bol = 0;
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '/' && (i + 1) < len && text[i + 1] == '*') {
|
||||||
|
in_block = 1;
|
||||||
|
bol = 0;
|
||||||
|
i += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c == '\'') { in_sq = 1; bol = 0; i++; continue; }
|
||||||
|
if (c == '"') { in_dq = 1; bol = 0; i++; continue; }
|
||||||
|
|
||||||
|
if (bol) {
|
||||||
|
/* Compute leading spaces to filter out indented constructs */
|
||||||
|
size_t j = i;
|
||||||
|
int spaces = 0;
|
||||||
|
while (j < len && text[j] == ' ') { spaces++; j++; }
|
||||||
|
if (j < len && text[j] == '\t') {
|
||||||
|
/* tabs not allowed for indentation; treat as not top-level */
|
||||||
|
bol = 0;
|
||||||
|
i = j + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
/* Only consider top-level (indent == 0) */
|
||||||
|
if (spaces == 0) {
|
||||||
|
/* Check for 'fun ' or 'class ' */
|
||||||
|
const char *kw1 = "fun";
|
||||||
|
const char *kw2 = "class";
|
||||||
|
if (j + 3 <= len && strncmp(text + j, kw1, 3) == 0 && (j + 3 == len || isspace((unsigned char)text[j + 3]))) {
|
||||||
|
size_t p = j + 3;
|
||||||
|
while (p < len && (text[p] == ' ' || text[p] == '\t')) p++;
|
||||||
|
/* read identifier */
|
||||||
|
size_t start = p;
|
||||||
|
if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) {
|
||||||
|
p++;
|
||||||
|
while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) p++;
|
||||||
|
size_t n = p - start;
|
||||||
|
if (n > 0) {
|
||||||
|
char tmp[256];
|
||||||
|
size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1);
|
||||||
|
memcpy(tmp, text + start, copy);
|
||||||
|
tmp[copy] = '\0';
|
||||||
|
nl_add(out, tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (j + 5 <= len && strncmp(text + j, kw2, 5) == 0 && (j + 5 == len || isspace((unsigned char)text[j + 5]))) {
|
||||||
|
size_t p = j + 5;
|
||||||
|
while (p < len && (text[p] == ' ' || text[p] == '\t')) p++;
|
||||||
|
/* read identifier */
|
||||||
|
size_t start = p;
|
||||||
|
if (p < len && (isalpha((unsigned char)text[p]) || text[p] == '_')) {
|
||||||
|
p++;
|
||||||
|
while (p < len && (isalnum((unsigned char)text[p]) || text[p] == '_')) p++;
|
||||||
|
size_t n = p - start;
|
||||||
|
if (n > 0) {
|
||||||
|
char tmp[256];
|
||||||
|
size_t copy = (n < sizeof(tmp) - 1) ? n : (sizeof(tmp) - 1);
|
||||||
|
memcpy(tmp, text + start, copy);
|
||||||
|
tmp[copy] = '\0';
|
||||||
|
nl_add(out, tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* move forward one char */
|
||||||
|
bol = (c == '\n');
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static char *preprocess_includes_internal(const char *src, int depth) {
|
static char *preprocess_includes_internal(const char *src, int depth) {
|
||||||
if (!src) return NULL;
|
if (!src) return NULL;
|
||||||
if (depth > 64) {
|
if (depth > 64) {
|
||||||
|
|
@ -304,8 +456,31 @@ static char *preprocess_includes_internal(const char *src, int depth) {
|
||||||
memcpy(path, src + path_start, path_len);
|
memcpy(path, src + path_start, path_len);
|
||||||
path[path_len] = '\0';
|
path[path_len] = '\0';
|
||||||
|
|
||||||
/* advance to end of line */
|
/* parse optional 'as <alias>' then advance to end of line */
|
||||||
k++;
|
k++;
|
||||||
|
char ns[64]; ns[0] = '\0';
|
||||||
|
/* skip spaces/tabs */
|
||||||
|
size_t ap = k;
|
||||||
|
while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) ap++;
|
||||||
|
/* optional 'as' */
|
||||||
|
const char *askw = "as";
|
||||||
|
if (ap + 2 <= len && strncmp(src + ap, askw, 2) == 0 && (ap + 2 == len || isspace((unsigned char)src[ap + 2]))) {
|
||||||
|
ap += 2;
|
||||||
|
while (ap < len && (src[ap] == ' ' || src[ap] == '\t')) ap++;
|
||||||
|
/* read identifier [A-Za-z_][A-Za-z0-9_]* */
|
||||||
|
size_t start = ap;
|
||||||
|
if (ap < len && (isalpha((unsigned char)src[ap]) || src[ap] == '_')) {
|
||||||
|
ap++;
|
||||||
|
while (ap < len && (isalnum((unsigned char)src[ap]) || src[ap] == '_')) ap++;
|
||||||
|
size_t n = ap - start;
|
||||||
|
size_t copy = (n < sizeof(ns) - 1) ? n : (sizeof(ns) - 1);
|
||||||
|
memcpy(ns, src + start, copy);
|
||||||
|
ns[copy] = '\0';
|
||||||
|
}
|
||||||
|
/* ignore anything else on line */
|
||||||
|
}
|
||||||
|
/* advance to end of line */
|
||||||
|
k = ap;
|
||||||
while (k < len && src[k] != '\n') k++;
|
while (k < len && src[k] != '\n') k++;
|
||||||
if (k < len && src[k] == '\n') k++;
|
if (k < len && src[k] == '\n') k++;
|
||||||
|
|
||||||
|
|
@ -362,16 +537,66 @@ static char *preprocess_includes_internal(const char *src, int depth) {
|
||||||
sb_append(&out, resolved[0] ? resolved : "(unresolved)");
|
sb_append(&out, resolved[0] ? resolved : "(unresolved)");
|
||||||
sb_append(&out, "\n");
|
sb_append(&out, "\n");
|
||||||
} else {
|
} else {
|
||||||
char *exp = preprocess_includes_internal(inc, depth + 1);
|
/* Strip optional UTF-8 BOM and top-of-file shebang from included text before preprocessing */
|
||||||
|
const char *startp = inc;
|
||||||
|
size_t off = 0;
|
||||||
|
if ((unsigned char)inc[0] == 0xEF && (unsigned char)inc[1] == 0xBB && (unsigned char)inc[2] == 0xBF) {
|
||||||
|
off = 3;
|
||||||
|
}
|
||||||
|
startp = inc + off;
|
||||||
|
if (startp[0] == '#' && startp[1] == '!') {
|
||||||
|
/* skip until end of line, handling CR, LF, CRLF */
|
||||||
|
const char *q = startp;
|
||||||
|
while (*q && *q != '\n' && *q != '\r') q++;
|
||||||
|
if (*q == '\r') { q++; if (*q == '\n') q++; }
|
||||||
|
else if (*q == '\n') { q++; }
|
||||||
|
startp = q;
|
||||||
|
}
|
||||||
|
char *inc_clean = strdup(startp);
|
||||||
|
char *exp = preprocess_includes_internal(inc_clean, depth + 1);
|
||||||
free(inc);
|
free(inc);
|
||||||
|
free(inc_clean);
|
||||||
if (exp) {
|
if (exp) {
|
||||||
|
/* If alias requested, initialize namespace map before including content */
|
||||||
|
if (ns[0] != '\0') {
|
||||||
|
/* Announce alias so the parser can treat dot-call without implicit 'this' */
|
||||||
|
sb_append(&out, "// __ns_alias__: ");
|
||||||
|
sb_append(&out, ns);
|
||||||
|
sb_append(&out, "\n");
|
||||||
|
|
||||||
|
sb_append(&out, ns);
|
||||||
|
sb_append(&out, " = {}\n");
|
||||||
|
}
|
||||||
|
|
||||||
/* mark file origin for better error messages */
|
/* mark file origin for better error messages */
|
||||||
sb_append(&out, "// __include_begin__: ");
|
sb_append(&out, "// __include_begin__: ");
|
||||||
sb_append(&out, resolved);
|
sb_append(&out, resolved);
|
||||||
|
if (ns[0] != '\0') {
|
||||||
|
sb_append(&out, " as ");
|
||||||
|
sb_append(&out, ns);
|
||||||
|
}
|
||||||
sb_append(&out, "\n");
|
sb_append(&out, "\n");
|
||||||
|
|
||||||
|
/* append expanded included content */
|
||||||
sb_append(&out, exp);
|
sb_append(&out, exp);
|
||||||
/* ensure included chunk ends with newline to preserve line structure */
|
/* 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');
|
if (out.len == 0 || out.buf[out.len - 1] != '\n') sb_append_ch(&out, '\n');
|
||||||
|
|
||||||
|
/* If alias is present, export top-level fun/class into alias map */
|
||||||
|
if (ns[0] != '\0') {
|
||||||
|
NameList nl; nl_init(&nl);
|
||||||
|
collect_exports_top_level(exp, &nl);
|
||||||
|
for (int ei = 0; ei < nl.count; ++ei) {
|
||||||
|
sb_append(&out, ns);
|
||||||
|
sb_append(&out, ".");
|
||||||
|
sb_append(&out, nl.names[ei]);
|
||||||
|
sb_append(&out, " = ");
|
||||||
|
sb_append(&out, nl.names[ei]);
|
||||||
|
sb_append(&out, "\n");
|
||||||
|
}
|
||||||
|
nl_free(&nl);
|
||||||
|
}
|
||||||
|
|
||||||
free(exp);
|
free(exp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue