Compare commits
3 commits
fd582f4d3a
...
44cf629016
| Author | SHA1 | Date | |
|---|---|---|---|
| 44cf629016 | |||
| 231c738016 | |||
| 95a0582608 |
10 changed files with 375 additions and 10 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.10)
|
||||
project(fun VERSION 0.37.45 LANGUAGES C)
|
||||
project(fun VERSION 0.37.48 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
22
examples/class_without_object.fun
Executable file
22
examples/class_without_object.fun
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-12
|
||||
*/
|
||||
|
||||
// Demonstrates the usage of functions in classes without initiating an object.
|
||||
|
||||
include <crypt/sha256.fun>
|
||||
|
||||
print(SHA256().sha256_hex(""))
|
||||
|
||||
/* Expected output:
|
||||
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
|
||||
*/
|
||||
24
examples/console_prompt.fun
Executable file
24
examples/console_prompt.fun
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-12
|
||||
*/
|
||||
|
||||
// Demonstrates the the console prompt.
|
||||
|
||||
include <io/console.fun>
|
||||
|
||||
input = Console().prompt("fun> ")
|
||||
print("You entered: " + input)
|
||||
|
||||
/* Possible output:
|
||||
fun> Fun!
|
||||
You entered: Fun!
|
||||
*/
|
||||
42
examples/progress.fun
Executable file
42
examples/progress.fun
Executable file
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-12
|
||||
*/
|
||||
|
||||
// Demonstrates system library includes after installation to /usr/lib/fun
|
||||
|
||||
// includes can also be done with a leading # like in C, but some programmers maybe like more clean code without a
|
||||
// leading #.
|
||||
include <io/console.fun>
|
||||
include <utils/datetime.fun> // for sleep()
|
||||
|
||||
c = Console()
|
||||
|
||||
print("Progress demo: 0..100")
|
||||
|
||||
total = 100
|
||||
for i in range(0, total + 1)
|
||||
c.progress(i, total, "Downloading")
|
||||
sleep(30) // milliseconds
|
||||
|
||||
print("\nMultiple phases demo")
|
||||
|
||||
// Phase 1
|
||||
phase_total = 40
|
||||
for i in range(0, phase_total + 1)
|
||||
c.progress(i, phase_total, "Phase 1")
|
||||
sleep(20)
|
||||
|
||||
// Phase 2
|
||||
phase_total = 60
|
||||
for i in range(0, phase_total + 1)
|
||||
c.progress(i, phase_total, "Phase 2")
|
||||
sleep(15)
|
||||
29
examples/progress_inline.fun
Normal file
29
examples/progress_inline.fun
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2026 Johannes Findeisen
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2026-01-13
|
||||
*/
|
||||
|
||||
// Realtime progress bar demo without printing extra newlines.
|
||||
// It only updates a single console line in-place. The progress()
|
||||
// helper emits one trailing newline automatically at 100%.
|
||||
|
||||
include <io/console.fun>
|
||||
include <utils/datetime.fun> // for sleep()
|
||||
|
||||
c = Console()
|
||||
|
||||
total = 100
|
||||
for i in range(0, total + 1)
|
||||
c.progress(i, total, "Downloading")
|
||||
sleep(20) // milliseconds
|
||||
|
||||
// Done. No additional prints/newlines here; progress() already
|
||||
// finalized the line when it reached 100%.
|
||||
|
|
@ -1,8 +1,18 @@
|
|||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
/*
|
||||
* Console utilities: prompt, ask, and yes/no helpers built on input().
|
||||
*/
|
||||
|
||||
#include <strings.fun>
|
||||
#include <io/process.fun>
|
||||
|
||||
class Console()
|
||||
// Print a prompt and read a line (no trailing newline)
|
||||
|
|
@ -38,3 +48,66 @@ class Console()
|
|||
else if (a == "n" || a == "no")
|
||||
return 0
|
||||
// otherwise loop again
|
||||
|
||||
// Internal: best-effort terminal columns via `tput cols` (fallback to 80)
|
||||
fun term_cols(this)
|
||||
// Use builtin proc_run directly to avoid any method dispatch quirks
|
||||
r = proc_run("tput cols")
|
||||
if (r["code"] == 0)
|
||||
s = str_trim(r["out"]) // trim trailing newline
|
||||
if (len(s) > 0)
|
||||
n = to_number(s)
|
||||
if (n > 0)
|
||||
return n
|
||||
// Fallback if tput not available or not a TTY
|
||||
return 80
|
||||
|
||||
// Draw/update a full-width progress bar on a single console line.
|
||||
// - current: Number (0..total)
|
||||
// - total: Number (>0)
|
||||
// - label: Optional text shown left of the bar
|
||||
// Returns the integer percent [0..100]. Adds a newline automatically at 100%.
|
||||
fun progress(this, current, total, label)
|
||||
cur = to_number(current)
|
||||
tot = to_number(total)
|
||||
if (tot <= 0)
|
||||
tot = 1
|
||||
pct = to_number((cur * 100) / tot)
|
||||
if (pct < 0)
|
||||
pct = 0
|
||||
if (pct > 100)
|
||||
pct = 100
|
||||
|
||||
cols = this.term_cols()
|
||||
|
||||
lbl = to_string(label)
|
||||
if (len(lbl) > 0)
|
||||
prefix = lbl + " "
|
||||
else
|
||||
prefix = ""
|
||||
|
||||
perc_str = to_string(pct) + "%"
|
||||
// We render: [====....] plus percent; keep the whole line within terminal width
|
||||
// Compute bar width: terminal cols minus prefix, brackets, space, and percent.
|
||||
fixed = len(prefix) + 2 + 1 + len(perc_str) // [] + space + percent
|
||||
bar_w = cols - fixed
|
||||
if (bar_w < 10)
|
||||
bar_w = 10 // minimal bar width for visibility
|
||||
|
||||
filled = to_number((bar_w * pct) / 100)
|
||||
if (filled < 0)
|
||||
filled = 0
|
||||
if (filled > bar_w)
|
||||
filled = bar_w
|
||||
|
||||
bar = "[" + str_repeat("=", filled) + str_repeat(" ", bar_w - filled) + "]"
|
||||
line = prefix + bar + " " + perc_str
|
||||
|
||||
// In-place update: CR + full-width content (we already fit to cols), no newline
|
||||
echo("\r" + line)
|
||||
|
||||
// When done, end the line cleanly
|
||||
if (cur >= tot)
|
||||
print("")
|
||||
|
||||
return pct
|
||||
|
|
|
|||
166
src/repl.c
166
src/repl.c
|
|
@ -860,6 +860,8 @@ static void show_repl_help(void) {
|
|||
printf(" :frame N Select frame N for :locals/:list/:disas (default: top)\n");
|
||||
printf(" :list [±K] Show K lines of source around current frame line (default 5)\n");
|
||||
printf(" :disas [±N] Disassemble around current frame ip (default 5)\n");
|
||||
printf(" :disasm WHAT [off [len]] [to <file>] Hexdump VM memory region to screen or file\n");
|
||||
printf(" WHAT = code | stack | globals | consts\n");
|
||||
printf(" :stack [N] Show top N (default all) stack values\n");
|
||||
printf(" :top Show the top of the VM stack\n");
|
||||
printf(" :locals [FRAME] Show locals of frame (default: selected frame)\n");
|
||||
|
|
@ -898,6 +900,33 @@ static int write_entire_file(const char *path, const char *data, size_t len) {
|
|||
return n == len;
|
||||
}
|
||||
|
||||
/* ---------- Hexdump helper ---------- */
|
||||
static void hexdump_to(FILE *out, const unsigned char *data, size_t len, size_t base_off) {
|
||||
if (!out || !data || len == 0) return;
|
||||
const size_t width = 16;
|
||||
char ascii[17];
|
||||
ascii[16] = '\0';
|
||||
for (size_t i = 0; i < len; i += width) {
|
||||
size_t chunk = (len - i < width) ? (len - i) : width;
|
||||
/* address */
|
||||
fprintf(out, "%08zx ", base_off + i);
|
||||
/* hex bytes */
|
||||
for (size_t j = 0; j < width; ++j) {
|
||||
if (j < chunk) {
|
||||
fprintf(out, "%02x ", data[i + j]);
|
||||
unsigned char c = data[i + j];
|
||||
ascii[j] = (c >= 32 && c <= 126) ? (char)c : '.';
|
||||
} else {
|
||||
fputs(" ", out);
|
||||
ascii[j] = ' ';
|
||||
}
|
||||
if (j == 7) fputc(' ', out); /* extra space between 8-byte halves */
|
||||
}
|
||||
ascii[chunk < width ? chunk : width] = '\0';
|
||||
fprintf(out, " |%s|\n", ascii);
|
||||
}
|
||||
}
|
||||
|
||||
static void print_last_n_lines(const char *path, int n) {
|
||||
if (n <= 0) n = 50;
|
||||
size_t flen = 0;
|
||||
|
|
@ -1388,16 +1417,145 @@ int fun_run_repl(VM *vm) {
|
|||
if (idx < 0) { printf("(no current frame)\n"); continue; }
|
||||
Frame *f = &vm->frames[idx];
|
||||
if (!f->fn) { printf("(no function)\n"); continue; }
|
||||
int curip = f->ip - 1; if (curip < 0) curip = 0;
|
||||
|
||||
/* Ensure we have a valid instruction buffer */
|
||||
if (f->fn->instr_count <= 0 || f->fn->instructions == NULL) {
|
||||
printf("(no instructions)\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
int count = f->fn->instr_count;
|
||||
int curip = f->ip - 1;
|
||||
if (curip < 0) curip = 0;
|
||||
if (curip >= count) curip = count - 1;
|
||||
|
||||
int from = curip - n; if (from < 0) from = 0;
|
||||
int to = curip + n; if (to >= f->fn->instr_count) to = f->fn->instr_count - 1;
|
||||
int to = curip + n; if (to >= count) to = count - 1;
|
||||
if (to < from) { /* nothing to show */ continue; }
|
||||
|
||||
for (int i = from; i <= to; ++i) {
|
||||
Instruction ins = f->fn->instructions[i];
|
||||
const char *opname = (ins.op >= 0 && ins.op < (int)(sizeof(opcode_names)/sizeof(opcode_names[0])))
|
||||
? opcode_names[ins.op] : "???";
|
||||
const char *opname = opcode_is_valid(ins.op) ? opcode_names[ins.op] : "???";
|
||||
printf("%c %6d: %-14s %d\n", (i == curip ? '>' : ' '), i, opname, ins.operand);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "disasm") == 0) {
|
||||
/* Syntax: :disasm WHAT [off [len]] [to <file>]
|
||||
WHAT: code | stack | globals | consts */
|
||||
const char *p = lstrip(arg);
|
||||
if (!p || !*p) {
|
||||
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||
printf(" WHAT = code | stack | globals | consts\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
char what[32];
|
||||
int consumed = 0;
|
||||
if (sscanf(p, "%31s %n", what, &consumed) != 1) {
|
||||
printf("Usage: :disasm WHAT [off [len]] [to <file>]\n");
|
||||
continue;
|
||||
}
|
||||
p += consumed;
|
||||
|
||||
size_t off = 0;
|
||||
size_t len = (size_t)-1; /* default later to clamp */
|
||||
|
||||
/* parse optional off */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (*p && (isdigit((unsigned char)*p))) {
|
||||
char *endp = NULL;
|
||||
long long v = strtoll(p, &endp, 10);
|
||||
if (endp && endp != p && v >= 0) {
|
||||
off = (size_t)v;
|
||||
p = endp;
|
||||
}
|
||||
}
|
||||
/* parse optional len */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (*p && (isdigit((unsigned char)*p))) {
|
||||
char *endp = NULL;
|
||||
long long v = strtoll(p, &endp, 10);
|
||||
if (endp && endp != p && v >= 0) {
|
||||
len = (size_t)v;
|
||||
p = endp;
|
||||
}
|
||||
}
|
||||
|
||||
/* optional: to <file> */
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
int to_file = 0;
|
||||
char path[PATH_MAX];
|
||||
path[0] = '\0';
|
||||
if (strncmp(p, "to", 2) == 0 && (p[2] == '\0' || isspace((unsigned char)p[2]))) {
|
||||
p += 2;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (!*p) { printf("Missing <file> after 'to'\n"); continue; }
|
||||
/* read until whitespace end or end of string; simple paths without spaces */
|
||||
size_t i = 0;
|
||||
while (*p && !isspace((unsigned char)*p) && i + 1 < sizeof(path)) path[i++] = *p++;
|
||||
path[i] = '\0';
|
||||
if (path[0] == '\0') { printf("Invalid file path\n"); continue; }
|
||||
to_file = 1;
|
||||
}
|
||||
|
||||
const unsigned char *base = NULL;
|
||||
size_t total = 0;
|
||||
int ok_region = 1;
|
||||
|
||||
if (strcmp(what, "code") == 0) {
|
||||
int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
|
||||
if (idx < 0) { printf("(no current frame)\n"); continue; }
|
||||
Frame *fr = &vm->frames[idx];
|
||||
if (!fr->fn || fr->fn->instr_count <= 0 || fr->fn->instructions == NULL) {
|
||||
printf("(no code to dump)\n"); continue;
|
||||
}
|
||||
base = (const unsigned char*)fr->fn->instructions;
|
||||
total = (size_t)fr->fn->instr_count * sizeof(Instruction);
|
||||
} else if (strcmp(what, "consts") == 0) {
|
||||
int idx = (selected_frame >= 0 && selected_frame <= vm->fp) ? selected_frame : vm->fp;
|
||||
if (idx < 0) { printf("(no current frame)\n"); continue; }
|
||||
Frame *fr = &vm->frames[idx];
|
||||
if (!fr->fn || fr->fn->const_count <= 0 || fr->fn->constants == NULL) {
|
||||
printf("(no constants to dump)\n"); continue;
|
||||
}
|
||||
base = (const unsigned char*)fr->fn->constants;
|
||||
total = (size_t)fr->fn->const_count * sizeof(Value);
|
||||
} else if (strcmp(what, "stack") == 0) {
|
||||
int count = vm->sp + 1;
|
||||
if (count <= 0) { printf("(stack empty)\n"); continue; }
|
||||
base = (const unsigned char*)vm->stack;
|
||||
total = (size_t)count * sizeof(Value);
|
||||
} else if (strcmp(what, "globals") == 0) {
|
||||
base = (const unsigned char*)vm->globals;
|
||||
total = (size_t)MAX_GLOBALS * sizeof(Value);
|
||||
} else {
|
||||
ok_region = 0;
|
||||
}
|
||||
|
||||
if (!ok_region) {
|
||||
printf("Unknown region '%s'. Use one of: code, stack, globals, consts\n", what);
|
||||
continue;
|
||||
}
|
||||
if (!base || total == 0) { printf("(nothing to dump)\n"); continue; }
|
||||
|
||||
if (off >= total) { printf("(empty range: offset beyond end)\n"); continue; }
|
||||
size_t avail = total - off;
|
||||
if (len == (size_t)-1 || len == 0 || len > avail) {
|
||||
/* default to min(256, avail) */
|
||||
len = avail < 256 ? avail : 256;
|
||||
}
|
||||
|
||||
if (!to_file) {
|
||||
printf("Hexdump %s: total=%zu, offset=%zu, len=%zu\n", what, total, off, len);
|
||||
hexdump_to(stdout, base + off, len, off);
|
||||
} else {
|
||||
FILE *fout = fopen(path, "wb");
|
||||
if (!fout) { printf("Failed to open '%s' for writing\n", path); continue; }
|
||||
hexdump_to(fout, base + off, len, off);
|
||||
fclose(fout);
|
||||
printf("Wrote hexdump (%zu bytes from %s) to %s\n", len, what, path);
|
||||
}
|
||||
continue;
|
||||
} else if (strcmp(cmd, "printv") == 0) {
|
||||
const char *spec = lstrip(arg);
|
||||
if (!spec || !*spec) { printf("Usage: :printv local[i] | stack[i] | global[i]\n"); continue; }
|
||||
|
|
|
|||
16
src/vm.c
16
src/vm.c
|
|
@ -503,10 +503,6 @@ void vm_print_output(VM *vm) {
|
|||
printf("\n");
|
||||
}
|
||||
}
|
||||
/* If the last item was partial (from echo), terminate the line for cleanliness */
|
||||
if (vm->output_count > 0 && vm->output_is_partial[vm->output_count - 1]) {
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
void vm_run(VM *vm, Bytecode *entry) {
|
||||
|
|
@ -853,6 +849,18 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Stream console output in realtime for scripts:
|
||||
* When PRINT/ECHO pushed items into the VM's output buffer, flush them
|
||||
* immediately to stdout and clear the buffer to avoid end-of-run bursts.
|
||||
* This keeps REPL compatibility (REPL still prints after each submit),
|
||||
* while regular script execution shows progress bars live.
|
||||
*/
|
||||
if (inst.op == OP_PRINT || inst.op == OP_ECHO) {
|
||||
vm_print_output(vm);
|
||||
vm_clear_output(vm);
|
||||
fflush(stdout);
|
||||
}
|
||||
}
|
||||
g_active_vm = NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
/**
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://fun-lang.xyz/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements OP_ECHO: print top-of-stack value without trailing newline.
|
||||
* Now stores the value into the VM's output buffer and marks it as partial,
|
||||
* so the CLI can render echo output together with following print output.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue