diff --git a/CMakeLists.txt b/CMakeLists.txt index 9595b43..5b08938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.34 LANGUAGES C) +project(fun VERSION 0.37.35 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/cli_argv_dump.fun b/examples/cli_argv_dump.fun new file mode 100755 index 0000000..e9241d5 --- /dev/null +++ b/examples/cli_argv_dump.fun @@ -0,0 +1,32 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-30 + */ + +// Demonstrates accessing raw script arguments via lib/cli.fun +// Usage: +// FUN_LIB_DIR="$(pwd)/lib" ./build_debug/fun ./examples/cli_argv_dump.fun -f --force=42 foo bar + +#include + +args = argv() + +print(join(["FUN_ARGC=", env("FUN_ARGC")], "")) +print(join(["FUN_ARGS='", env("FUN_ARGS"), "'"], "")) +print("Raw argv array:") +print(args) + +/* Expected output: +FUN_ARGC=3 +FUN_ARGS='-f --force --force=42' +Raw argv array: +[-f, --force, --force=42 +*/ diff --git a/examples/cli_parse_example.fun b/examples/cli_parse_example.fun new file mode 100755 index 0000000..f178ca3 --- /dev/null +++ b/examples/cli_parse_example.fun @@ -0,0 +1,37 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2025-12-30 + */ + +// Demonstrates parsing flags like -f, --force, --force=42 and positionals +// Usage examples: +// FUN_LIB_DIR="$(pwd)/lib" ./build_debug/fun ./examples/cli_parse_example.fun -f foo +// FUN_LIB_DIR="$(pwd)/lib" ./build_debug/fun ./examples/cli_parse_example.fun --force bar +// FUN_LIB_DIR="$(pwd)/lib" ./build_debug/fun ./examples/cli_parse_example.fun --force=42 alpha beta +// FUN_LIB_DIR="$(pwd)/lib" ./build_debug/fun ./examples/cli_parse_example.fun -abc x y + +#include + +args = argv() +parsed = parse_args(args) +flags = parsed["flags"] +pos = parsed["positionals"] + +if (flags["f"] == 1 || flags["force"] == 1) + print("Force enabled") + +if (has_key(flags, "force") && typeof(flags["force"]) == "string") + print(join(["Force value=", flags["force"]], "")) + +print("Flags map:") +print(flags) +print("Positionals:") +print(pos) diff --git a/examples/env_all.fun b/examples/env_all.fun old mode 100644 new mode 100755 diff --git a/lib/cli.fun b/lib/cli.fun new file mode 100644 index 0000000..70d2f0d --- /dev/null +++ b/lib/cli.fun @@ -0,0 +1,53 @@ +// Simple CLI helpers for Fun scripts. +// +// argv(): returns an array of the raw arguments passed to the script +// (not including the script path). Requires the interpreter to +// export FUN_ARGC and FUN_ARGV_i environment variables. +// parse_args(args): parses flags and positionals from argv() like: +// -f -> flags["f"] = 1 +// --force -> flags["force"] = 1 +// --force=42 -> flags["force"] = "42" +// -abc -> flags["a"]=1, flags["b"]=1, flags["c"]=1 + +fun argv() + n = to_number(env("FUN_ARGC")) + a = [] + i = 0 + while (i < n) + key = join(["FUN_ARGV_", to_string(i)], "") + push(a, env(key)) + i = i + 1 + return a + +// Returns: { "flags": map, "positionals": array } +fun parse_args(args) + flags = {} + pos = [] + i = 0 + n = len(args) + while (i < n) + s = args[i] + if (len(s) >= 2 && substr(s, 0, 2) == "--") + eq = find(s, "=") + if (eq >= 0) + key = substr(s, 2, eq - 2) // between -- and = + val = substr(s, eq + 1, len(s) - (eq + 1)) + flags[key] = val + else + key = substr(s, 2, len(s) - 2) + flags[key] = 1 + else if (len(s) == 2 && substr(s, 0, 1) == "-") + // short -f boolean + key = substr(s, 1, 1) + flags[key] = 1 + else if (len(s) > 2 && substr(s, 0, 1) == "-") + // compact short flags like -abc -> a=1, b=1, c=1 + j = 1 + while (j < len(s)) + k = substr(s, j, 1) + flags[k] = 1 + j = j + 1 + else + push(pos, s) + i = i + 1 + return { "flags": flags, "positionals": pos } diff --git a/src/fun.c b/src/fun.c index 041e306..08ae886 100644 --- a/src/fun.c +++ b/src/fun.c @@ -1,4 +1,13 @@ -/** +/* +* This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2025 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + */ + + /* * Main entry point for the Fun language interpreter. * Builds a CLI that runs a script file if provided; otherwise starts the REPL * when compiled with FUN_WITH_REPL enabled. @@ -85,6 +94,46 @@ int main(int argc, char **argv) { if (argi < argc) { const char *path = argv[argi]; + + /* Collect script arguments (everything after script path) and expose via env vars */ + int sargi = argi + 1; /* first script arg following the script path */ + int sargc = (sargi < argc) ? (argc - sargi) : 0; + + /* Export FUN_ARGC */ + { + char buf[32]; + snprintf(buf, sizeof(buf), "%d", sargc); + setenv("FUN_ARGC", buf, 1); + } + + /* Export FUN_ARGV_i */ + for (int i = 0; i < sargc; ++i) { + char key[32]; + snprintf(key, sizeof(key), "FUN_ARGV_%d", i); + setenv(key, argv[sargi + i], 1); + } + + /* Optional: space-joined convenience string FUN_ARGS */ + if (sargc > 0) { + size_t total = 0; + for (int i = 0; i < sargc; ++i) { + total += strlen(argv[sargi + i]) + 1; /* +1 for space or NUL */ + } + char *joined = (char*)malloc(total); + if (joined) { + joined[0] = '\0'; + for (int i = 0; i < sargc; ++i) { + strcat(joined, argv[sargi + i]); + if (i + 1 < sargc) strcat(joined, " "); + } + setenv("FUN_ARGS", joined, 1); + free(joined); + } + } else { + /* Ensure FUN_ARGS is at least cleared for consistency */ + setenv("FUN_ARGS", "", 1); + } + Bytecode *bc = parse_file_to_bytecode(path); if (!bc) { fprintf(stderr, "Failed to compile script: %s\n", path);