1
0
Fork 0
forked from fun/fun

Some cleanups. Added download page to ./web/. No code changes. (0.40.5)

This commit is contained in:
Johannes Findeisen 2026-04-19 00:38:06 +02:00
commit d2f673bf24
33 changed files with 34 additions and 1194 deletions

View file

@ -10,7 +10,7 @@ function(_fun_print_feature name flag)
endif()
endfunction()
# Include each extension module (Tcl/Tk removed)
# Include each extension module
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/SQLITE.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/PCSC.cmake)
include(${CMAKE_SOURCE_DIR}/cmake/Extensions/JSON.cmake)

View file

@ -1,33 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**
* LibreSSL MD5 builtin
*/
case OP_LIBRESSL_MD5: {
Value vdata = pop_value(vm);
char *s = value_to_string_alloc(&vdata);
free_value(vdata);
if (!s) {
push_value(vm, make_string(""));
break;
}
char *hex = fun_libressl_md5_hex((const unsigned char *)s, strlen(s));
free(s);
if (!hex) {
push_value(vm, make_string(""));
break;
}
Value out = make_string(hex);
free(hex);
push_value(vm, out);
break;
}

View file

@ -1,33 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**
* LibreSSL RIPEMD-160 builtin
*/
case OP_LIBRESSL_RIPEMD160: {
Value vdata = pop_value(vm);
char *s = value_to_string_alloc(&vdata);
free_value(vdata);
if (!s) {
push_value(vm, make_string(""));
break;
}
char *hex = fun_libressl_ripemd160_hex((const unsigned char *)s, strlen(s));
free(s);
if (!hex) {
push_value(vm, make_string(""));
break;
}
Value out = make_string(hex);
free(hex);
push_value(vm, out);
break;
}

View file

@ -1,33 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**
* LibreSSL SHA-256 builtin
*/
case OP_LIBRESSL_SHA256: {
Value vdata = pop_value(vm);
char *s = value_to_string_alloc(&vdata);
free_value(vdata);
if (!s) {
push_value(vm, make_string(""));
break;
}
char *hex = fun_libressl_sha256_hex((const unsigned char *)s, strlen(s));
free(s);
if (!hex) {
push_value(vm, make_string(""));
break;
}
Value out = make_string(hex);
free(hex);
push_value(vm, out);
break;
}

View file

@ -1,33 +0,0 @@
/*
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*
* Added: 2026-02-19
*/
/**
* LibreSSL SHA-512 builtin
*/
case OP_LIBRESSL_SHA512: {
Value vdata = pop_value(vm);
char *s = value_to_string_alloc(&vdata);
free_value(vdata);
if (!s) {
push_value(vm, make_string(""));
break;
}
char *hex = fun_libressl_sha512_hex((const unsigned char *)s, strlen(s));
free(s);
if (!hex) {
push_value(vm, make_string(""));
break;
}
Value out = make_string(hex);
free(hex);
push_value(vm, out);
break;
}

View file

@ -1,33 +0,0 @@
/*
* 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: 2025-11-26
*/
/**
* OP_LIBSQL_CLOSE: (handle:int) -> Nil
*/
case OP_LIBSQL_CLOSE: {
#ifdef FUN_WITH_LIBSQL
Value vh = pop_value(vm);
int hid = (int)vh.i;
free_value(vh);
LibSqlHandle *h = libsql_reg_get(hid);
if (h && h->db) {
sqlite3_close(h->db);
h->db = NULL;
libsql_reg_del(hid);
}
push_value(vm, make_nil());
#else
Value v = pop_value(vm);
free_value(v);
push_value(vm, make_nil());
#endif
break;
}

View file

@ -1,42 +0,0 @@
/*
* 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: 2025-11-26
*/
/**
* OP_LIBSQL_EXEC: (handle:int, sql:string) -> int rc (0=OK)
*/
case OP_LIBSQL_EXEC: {
#ifdef FUN_WITH_LIBSQL
Value vsql = pop_value(vm);
Value vh = pop_value(vm);
int hid = (int)vh.i;
char *sql = value_to_string_alloc(&vsql);
free_value(vh);
free_value(vsql);
LibSqlHandle *h = libsql_reg_get(hid);
if (!h || !h->db || !sql) {
if (sql) free(sql);
push_value(vm, make_int(SQLITE_MISUSE));
break;
}
char *errmsg = NULL;
int rc = sqlite3_exec(h->db, sql, NULL, NULL, &errmsg);
if (errmsg) sqlite3_free(errmsg);
free(sql);
push_value(vm, make_int(rc));
#else
Value v1 = pop_value(vm);
free_value(v1);
Value v2 = pop_value(vm);
free_value(v2);
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,45 +0,0 @@
/*
* 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: 2025-11-26
*/
/**
* OP_LIBSQL_OPEN: (url_or_path:string) -> handle:int (>0) or 0 on error
*/
case OP_LIBSQL_OPEN: {
#ifdef FUN_WITH_LIBSQL
Value vpath = pop_value(vm);
char *path = value_to_string_alloc(&vpath);
free_value(vpath);
if (!path) {
push_value(vm, make_int(0));
break;
}
sqlite3 *db = NULL;
int rc = sqlite3_open(path, &db);
free(path);
if (rc != SQLITE_OK || !db) {
if (db) sqlite3_close(db);
push_value(vm, make_int(0));
break;
}
LibSqlHandle *h = libsql_reg_add(db);
if (!h) {
sqlite3_close(db);
push_value(vm, make_int(0));
break;
}
push_value(vm, make_int(h->id));
#else
Value v = pop_value(vm);
free_value(v);
push_value(vm, make_int(0));
#endif
break;
}

View file

@ -1,76 +0,0 @@
/*
* 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: 2025-11-26
*/
/**
* OP_LIBSQL_QUERY: (handle:int, sql:string) -> array<map<string,any>>
*/
case OP_LIBSQL_QUERY: {
#ifdef FUN_WITH_LIBSQL
Value vsql = pop_value(vm);
Value vh = pop_value(vm);
int hid = (int)vh.i;
char *sql = value_to_string_alloc(&vsql);
free_value(vh);
free_value(vsql);
LibSqlHandle *h = libsql_reg_get(hid);
if (!h || !h->db || !sql) {
if (sql) free(sql);
push_value(vm, make_array_from_values(NULL, 0));
break;
}
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(h->db, sql, -1, &stmt, NULL) != SQLITE_OK) {
free(sql);
push_value(vm, make_array_from_values(NULL, 0));
break;
}
free(sql);
Value rows = make_array_from_values(NULL, 0);
int ncols = sqlite3_column_count(stmt);
while (sqlite3_step(stmt) == SQLITE_ROW) {
Value row = make_map_empty();
for (int i = 0; i < ncols; i++) {
const char *name = sqlite3_column_name(stmt, i);
int type = sqlite3_column_type(stmt, i);
Value kv;
switch (type) {
case SQLITE_INTEGER:
kv = make_int((int64_t)sqlite3_column_int64(stmt, i));
break;
case SQLITE_FLOAT:
kv = make_float(sqlite3_column_double(stmt, i));
break;
case SQLITE_TEXT:
kv = make_string((const char *)sqlite3_column_text(stmt, i));
break;
case SQLITE_NULL:
kv = make_nil();
break;
default:
kv = make_nil();
break; /* ignore blobs for now */
}
(void)map_set(&row, name ? name : "", kv);
}
(void)array_push(&rows, row);
/* Do NOT free 'row' here; owned by rows array. */
}
sqlite3_finalize(stmt);
push_value(vm, rows);
#else
Value v1 = pop_value(vm);
free_value(v1);
Value v2 = pop_value(vm);
free_value(v2);
push_value(vm, make_array_from_values(NULL, 0));
#endif
break;
}

View file

@ -1,66 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_BOX */
case OP_NC_BOX: {
Value stylev = pop_value(vm);
Value hv = pop_value(vm);
Value wv = pop_value(vm);
Value yv = pop_value(vm);
Value xv = pop_value(vm);
int style = (stylev.type == VAL_INT ? (int)stylev.i : (stylev.type == VAL_FLOAT ? (int)stylev.d : 0));
int h = (hv.type == VAL_INT ? (int)hv.i : (hv.type == VAL_FLOAT ? (int)hv.d : 0));
int w = (wv.type == VAL_INT ? (int)wv.i : (wv.type == VAL_FLOAT ? (int)wv.d : 0));
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
free_value(stylev); free_value(hv); free_value(wv); free_value(yv); free_value(xv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std && h >= 2 && w >= 2) {
/* Pick characters: style 0 light (ASCII), 1 heavy (Unicode), fallback to ASCII */
uint32_t hch = (style == 1 ? 0x2501 : '-'); // heavy box drawings horizontal
uint32_t vch = (style == 1 ? 0x2503 : '|'); // heavy vertical
uint32_t tl = (style == 1 ? 0x250F : '+');
uint32_t tr = (style == 1 ? 0x2513 : '+');
uint32_t bl = (style == 1 ? 0x2517 : '+');
uint32_t br = (style == 1 ? 0x251B : '+');
/* Draw edges */
// top and bottom
{
char topc[5]; int tlen=0; uint32_t cp=hch;
if (cp<=0x7F){topc[0]=(char)cp;tlen=1;} else if (cp<=0x7FF){topc[0]=(char)(0xC0|(cp>>6)); topc[1]=(char)(0x80|(cp&0x3F)); tlen=2;} else if (cp<=0xFFFF){topc[0]=(char)(0xE0|(cp>>12)); topc[1]=(char)(0x80|((cp>>6)&0x3F)); topc[2]=(char)(0x80|(cp&0x3F)); tlen=3;} else {topc[0]=(char)(0xF0|(cp>>18)); topc[1]=(char)(0x80|((cp>>12)&0x3F)); topc[2]=(char)(0x80|((cp>>6)&0x3F)); topc[3]=(char)(0x80|(cp&0x3F)); tlen=4;}
topc[tlen]='\0';
for (int i=1;i<w-1;i++){ ncplane_putstr_yx(_fun_nc_std, y, x+i, topc); ncplane_putstr_yx(_fun_nc_std, y+h-1, x+i, topc);}
}
// left and right
{
char vertc[5]; int vlen=0; uint32_t cp=vch;
if (cp<=0x7F){vertc[0]=(char)cp;vlen=1;} else if (cp<=0x7FF){vertc[0]=(char)(0xC0|(cp>>6)); vertc[1]=(char)(0x80|(cp&0x3F)); vlen=2;} else if (cp<=0xFFFF){vertc[0]=(char)(0xE0|(cp>>12)); vertc[1]=(char)(0x80|((cp>>6)&0x3F)); vertc[2]=(char)(0x80|(cp&0x3F)); vlen=3;} else {vertc[0]=(char)(0xF0|(cp>>18)); vertc[1]=(char)(0x80|((cp>>12)&0x3F)); vertc[2]=(char)(0x80|((cp>>6)&0x3F)); vertc[3]=(char)(0x80|(cp&0x3F)); vlen=4;}
vertc[vlen]='\0';
for (int i=1;i<h-1;i++){ ncplane_putstr_yx(_fun_nc_std, y+i, x, vertc); ncplane_putstr_yx(_fun_nc_std, y+i, x+w-1, vertc);}
}
// corners
{
char cbuf[5]; int clen=0; uint32_t cp;
cp=tl; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y, x, cbuf);
cp=tr; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y, x+w-1, cbuf);
cp=bl; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y+h-1, x, cbuf);
cp=br; if (cp<=0x7F){cbuf[0]=(char)cp;clen=1;} else if (cp<=0x7FF){cbuf[0]=(char)(0xC0|(cp>>6)); cbuf[1]=(char)(0x80|(cp&0x3F)); clen=2;} else if (cp<=0xFFFF){cbuf[0]=(char)(0xE0|(cp>>12)); cbuf[1]=(char)(0x80|((cp>>6)&0x3F)); cbuf[2]=(char)(0x80|(cp&0x3F)); clen=3;} else {cbuf[0]=(char)(0xF0|(cp>>18)); cbuf[1]=(char)(0x80|((cp>>12)&0x3F)); cbuf[2]=(char)(0x80|((cp>>6)&0x3F)); cbuf[3]=(char)(0x80|(cp&0x3F)); clen=4;} cbuf[clen]='\0'; ncplane_putstr_yx(_fun_nc_std, y+h-1, x+w-1, cbuf);
}
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)style; (void)h; (void)w; (void)y; (void)x;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,28 +0,0 @@
/**
* 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-03
*/
/* NC_CLEAR */
case OP_NC_CLEAR: {
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std) {
ncplane_erase(_fun_nc_std);
notcurses_render(_fun_nc);
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)_fun_nc;
(void)_fun_nc_std;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,23 +0,0 @@
/**
* 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-03
*/
/* Common helpers/state for Notcurses TUI ops */
#pragma once
#include "value.h"
/* Forward declarations to avoid depending on the notcurses headers unless enabled */
struct notcurses;
struct ncplane;
/* Shared static state within vm.c translation unit (header is included into vm.c at file scope) */
static struct notcurses *_fun_nc = NULL;
static struct ncplane *_fun_nc_std = NULL;

View file

@ -1,40 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_DRAW_CHAR */
case OP_NC_DRAW_CHAR: {
Value yv = pop_value(vm);
Value xv = pop_value(vm);
Value chv = pop_value(vm);
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'?'));
free_value(yv);
free_value(xv);
free_value(chv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std) {
char utf8[5];
int len = 0;
if (cp <= 0x7F) { utf8[0] = (char)cp; len = 1; }
else if (cp <= 0x7FF) { utf8[0] = (char)(0xC0 | (cp >> 6)); utf8[1] = (char)(0x80 | (cp & 0x3F)); len = 2; }
else if (cp <= 0xFFFF) { utf8[0] = (char)(0xE0 | (cp >> 12)); utf8[1] = (char)(0x80 | ((cp >> 6) & 0x3F)); utf8[2] = (char)(0x80 | (cp & 0x3F)); len = 3; }
else { utf8[0] = (char)(0xF0 | (cp >> 18)); utf8[1] = (char)(0x80 | ((cp >> 12) & 0x3F)); utf8[2] = (char)(0x80 | ((cp >> 6) & 0x3F)); utf8[3] = (char)(0x80 | (cp & 0x3F)); len = 4; }
utf8[len] = '\0';
ncplane_putstr_yx(_fun_nc_std, y, x, utf8);
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)cp; (void)x; (void)y;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,40 +0,0 @@
/**
* 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-03
*/
/* NC_DRAW_TEXT */
case OP_NC_DRAW_TEXT: {
Value textv = pop_value(vm);
Value xv = pop_value(vm);
Value yv = pop_value(vm);
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
char *text = value_to_string_alloc(&textv);
free_value(textv);
free_value(xv);
free_value(yv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std && text) {
ncplane_putstr_yx(_fun_nc_std, y, x, text);
notcurses_render(_fun_nc);
free(text);
push_value(vm, make_int(0));
} else {
if (text) free(text);
push_value(vm, make_int(-1));
}
#else
(void)_fun_nc;
(void)_fun_nc_std;
if (text) free(text);
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,45 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_FILL */
case OP_NC_FILL: {
Value chv = pop_value(vm);
Value hv = pop_value(vm);
Value wv = pop_value(vm);
Value yv = pop_value(vm);
Value xv = pop_value(vm);
int h = (hv.type == VAL_INT ? (int)hv.i : (hv.type == VAL_FLOAT ? (int)hv.d : 0));
int w = (wv.type == VAL_INT ? (int)wv.i : (wv.type == VAL_FLOAT ? (int)wv.d : 0));
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)' '));
free_value(chv); free_value(hv); free_value(wv); free_value(yv); free_value(xv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std && h > 0 && w > 0) {
char utf8[5]; int ulen = 0;
if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; }
else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; }
else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; }
else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; }
utf8[ulen] = '\0';
for (int yy = 0; yy < h; yy++) {
for (int xx = 0; xx < w; xx++) {
ncplane_putstr_yx(_fun_nc_std, y + yy, x + xx, utf8);
}
}
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)cp; (void)x; (void)y; (void)w; (void)h;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,29 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_GET_SIZE */
case OP_NC_GET_SIZE: {
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc_std) {
int rows = 0, cols = 0;
ncplane_dim_yx(_fun_nc_std, &rows, &cols);
Value tmp[2];
tmp[0] = make_int(rows);
tmp[1] = make_int(cols);
Value arr = make_array_from_values(tmp, 2);
push_value(vm, arr);
} else {
push_value(vm, make_int(-1));
}
#else
(void)_fun_nc_std;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,45 +0,0 @@
/**
* 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-03
*/
/* NC_GETCH */
case OP_NC_GETCH: {
Value to_ms_v = pop_value(vm);
int timeout_ms = (to_ms_v.type == VAL_INT ? (int)to_ms_v.i : (to_ms_v.type == VAL_FLOAT ? (int)to_ms_v.d : 0));
free_value(to_ms_v);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc) {
/* For simplicity, use blocking get for now when timeout<=0 */
if (timeout_ms <= 0) {
ncinput ni;
uint32_t id = notcurses_get_blocking(_fun_nc, &ni);
push_value(vm, make_int((int)id));
} else {
/* Polling approximation: render then nonblocking get once */
ncinput ni;
uint32_t id = notcurses_get_nblock(_fun_nc, &ni);
if (id == 0) {
/* no input available now: return -1 to indicate timeout */
push_value(vm, make_int(-1));
} else {
push_value(vm, make_int((int)id));
}
}
} else {
push_value(vm, make_int(-1));
}
#else
(void)_fun_nc;
(void)_fun_nc_std;
(void)timeout_ms;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,39 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_DRAW_HLINE */
case OP_NC_DRAW_HLINE: {
Value chv = pop_value(vm);
Value lenv = pop_value(vm);
Value xv = pop_value(vm);
Value yv = pop_value(vm);
int len = (lenv.type == VAL_INT ? (int)lenv.i : (lenv.type == VAL_FLOAT ? (int)lenv.d : 0));
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'-'));
free_value(lenv); free_value(xv); free_value(yv); free_value(chv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std && len > 0) {
char utf8[5]; int ulen = 0;
if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; }
else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; }
else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; }
else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; }
utf8[ulen] = '\0';
for (int i = 0; i < len; i++) ncplane_putstr_yx(_fun_nc_std, y, x + i, utf8);
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)cp; (void)x; (void)y; (void)len;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,34 +0,0 @@
/**
* 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-03
*/
/* NC_INIT */
case OP_NC_INIT: {
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc) {
push_value(vm, make_int(1));
break;
}
struct notcurses_options opts = {0};
_fun_nc = notcurses_core_init(&opts, NULL);
if (!_fun_nc) {
push_value(vm, make_int(0));
break;
}
_fun_nc_std = notcurses_stdplane(_fun_nc);
push_value(vm, make_int(1));
#else
(void)_fun_nc;
(void)_fun_nc_std;
fprintf(stderr, "Notcurses support disabled at build time. Reconfigure with -DFUN_WITH_NOTCURSES=ON.\n");
push_value(vm, make_int(0));
#endif
break;
}

View file

@ -1,23 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_RENDER */
case OP_NC_RENDER: {
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc) {
int rc = notcurses_render(_fun_nc);
push_value(vm, make_int(rc == 0 ? 0 : -1));
} else {
push_value(vm, make_int(-1));
}
#else
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,71 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_SET_STYLE */
case OP_NC_SET_STYLE: {
Value fgv = pop_value(vm);
Value bgv = pop_value(vm);
Value stylev = pop_value(vm);
int style = (stylev.type == VAL_INT ? (int)stylev.i : (stylev.type == VAL_FLOAT ? (int)stylev.d : 0));
int bg = (bgv.type == VAL_INT ? (int)bgv.i : (bgv.type == VAL_FLOAT ? (int)bgv.d : 0));
int fg = (fgv.type == VAL_INT ? (int)fgv.i : (fgv.type == VAL_FLOAT ? (int)fgv.d : 0));
free_value(stylev);
free_value(bgv);
free_value(fgv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std) {
/* Extract RGB components */
unsigned fg_r = (unsigned)((fg >> 16) & 0xFF);
unsigned fg_g = (unsigned)((fg >> 8) & 0xFF);
unsigned fg_b = (unsigned)(fg & 0xFF);
unsigned bg_r = (unsigned)((bg >> 16) & 0xFF);
unsigned bg_g = (unsigned)((bg >> 8) & 0xFF);
unsigned bg_b = (unsigned)(bg & 0xFF);
/* Set colors */
ncplane_set_fg_rgb8(_fun_nc_std, fg_r, fg_g, fg_b);
ncplane_set_bg_rgb8(_fun_nc_std, bg_r, bg_g, bg_b);
/* Map simple bitmask to notcurses styles */
/* some notcurses versions spell these differently; provide fallbacks */
#ifndef NCSTYLE_DIM
# ifdef NCSTYLE_FAINT
# define NCSTYLE_DIM NCSTYLE_FAINT
# else
# define NCSTYLE_DIM 0
# endif
#endif
#ifndef NCSTYLE_REVERSE
# ifdef NCSTYLE_REVERSED
# define NCSTYLE_REVERSE NCSTYLE_REVERSED
# else
# define NCSTYLE_REVERSE 0
# endif
#endif
unsigned styles = 0;
if (style & 1) styles |= NCSTYLE_BOLD;
if (style & 2) styles |= NCSTYLE_DIM;
if (style & 4) styles |= NCSTYLE_ITALIC;
if (style & 8) styles |= NCSTYLE_UNDERLINE;
if (style & 16) styles |= NCSTYLE_STRUCK;
if (style & 32) styles |= NCSTYLE_REVERSE;
ncplane_set_styles(_fun_nc_std, styles);
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)style;
(void)bg;
(void)fg;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,26 +0,0 @@
/**
* 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-03
*/
/* NC_SHUTDOWN */
case OP_NC_SHUTDOWN: {
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc) {
notcurses_stop(_fun_nc);
_fun_nc = NULL;
_fun_nc_std = NULL;
}
#else
(void)_fun_nc;
(void)_fun_nc_std;
#endif
push_value(vm, make_int(0));
break;
}

View file

@ -1,39 +0,0 @@
/**
* This file is part of the Fun programming language.
* https://fun-lang.xyz/
*
* Copyright 2026 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
/* NC_DRAW_VLINE */
case OP_NC_DRAW_VLINE: {
Value chv = pop_value(vm);
Value lenv = pop_value(vm);
Value xv = pop_value(vm);
Value yv = pop_value(vm);
int len = (lenv.type == VAL_INT ? (int)lenv.i : (lenv.type == VAL_FLOAT ? (int)lenv.d : 0));
int x = (xv.type == VAL_INT ? (int)xv.i : (xv.type == VAL_FLOAT ? (int)xv.d : 0));
int y = (yv.type == VAL_INT ? (int)yv.i : (yv.type == VAL_FLOAT ? (int)yv.d : 0));
uint32_t cp = (chv.type == VAL_INT ? (uint32_t)chv.i : (chv.type == VAL_FLOAT ? (uint32_t)chv.d : (uint32_t)'|'));
free_value(lenv); free_value(xv); free_value(yv); free_value(chv);
#ifdef FUN_WITH_NOTCURSES
if (_fun_nc && _fun_nc_std && len > 0) {
char utf8[5]; int ulen = 0;
if (cp <= 0x7F) { utf8[0] = (char)cp; ulen = 1; }
else if (cp <= 0x7FF) { utf8[0]=(char)(0xC0|(cp>>6)); utf8[1]=(char)(0x80|(cp&0x3F)); ulen=2; }
else if (cp <= 0xFFFF) { utf8[0]=(char)(0xE0|(cp>>12)); utf8[1]=(char)(0x80|((cp>>6)&0x3F)); utf8[2]=(char)(0x80|(cp&0x3F)); ulen=3; }
else { utf8[0]=(char)(0xF0|(cp>>18)); utf8[1]=(char)(0x80|((cp>>12)&0x3F)); utf8[2]=(char)(0x80|((cp>>6)&0x3F)); utf8[3]=(char)(0x80|(cp&0x3F)); ulen=4; }
utf8[ulen] = '\0';
for (int i = 0; i < len; i++) ncplane_putstr_yx(_fun_nc_std, y + i, x, utf8);
push_value(vm, make_int(0));
} else {
push_value(vm, make_int(-1));
}
#else
(void)cp; (void)x; (void)y; (void)len;
push_value(vm, make_int(-1));
#endif
break;
}

View file

@ -1,56 +0,0 @@
/**
* 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: 2025-12-23
*/
/* TK_BIND */
case OP_TK_BIND: {
/* stack: ..., id, event, command -> rc */
Value cmdv = pop_value(vm);
Value eventv = pop_value(vm);
Value idv = pop_value(vm);
char *cmd = value_to_string_alloc(&cmdv);
char *event = value_to_string_alloc(&eventv);
char *id = value_to_string_alloc(&idv);
free_value(cmdv);
free_value(eventv);
free_value(idv);
if (!id || !event || !cmd) {
if (id) free(id);
if (event) free(event);
if (cmd) free(cmd);
push_value(vm, make_int(-1));
break;
}
/*
* Construct: bind .id <event> {command}
* Note: for now, command is just raw Tcl as well,
* but could be extended to call Fun functions if we had a callback mechanism.
*/
size_t slen = strlen(id) + strlen(event) + strlen(cmd) + 32;
char *script = (char *)malloc(slen);
if (!script) {
free(id);
free(event);
free(cmd);
push_value(vm, make_int(-1));
break;
}
snprintf(script, slen, "bind .%s %s {%s}", id, event, cmd);
int rc = fun_tk_eval_script(script);
free(script);
free(id);
free(event);
free(cmd);
push_value(vm, make_int(rc));
break;
}

View file

@ -1,58 +0,0 @@
/* TK_BUTTON */
case OP_TK_BUTTON: {
/* stack: ..., id, text -> rc */
Value textv = pop_value(vm);
Value idv = pop_value(vm);
char *text = value_to_string_alloc(&textv);
char *id = value_to_string_alloc(&idv);
free_value(textv);
free_value(idv);
if (!id) {
if (text) free(text);
push_value(vm, make_int(-1));
break;
}
if (!text) {
text = strdup("");
}
size_t n = 0;
for (const char *p = text; *p; ++p) {
n += (*p == '\\' || *p == '"') ? 2 : 1;
}
char *et = (char *)malloc(n + 1);
if (!et) {
free(id);
free(text);
push_value(vm, make_int(-1));
break;
}
char *q = et;
for (const char *p = text; *p; ++p) {
if (*p == '\\' || *p == '"') *q++ = '\\';
*q++ = *p;
}
*q = '\0';
free(text);
size_t slen = strlen(id) + strlen(et) + 196;
char *script = (char *)malloc(slen);
if (!script) {
free(id);
free(et);
push_value(vm, make_int(-1));
break;
}
/*
* Default behavior: clicking the button should terminate the app. We set
* -command {catch {destroy .}; exit 0} to both destroy the window and exit
* the process. Using 'catch' makes it safe if the window is already gone.
*/
snprintf(script, slen,
"if {[winfo exists .%s]} { .%s configure -text \"%s\" -command {catch {destroy .}; exit 0} } else { button .%s -text \"%s\" -command {catch {destroy .}; exit 0} }",
id, id, et, id, et);
int rc = fun_tk_eval_script(script);
free(script);
free(id);
free(et);
push_value(vm, make_int(rc));
break;
}

View file

@ -1,21 +0,0 @@
/*
* 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: 2025-12-09
*/
/* TK_EVAL */
case OP_TK_EVAL: {
Value text = pop_value(vm);
char *s = value_to_string_alloc(&text);
free_value(text);
int rc = fun_tk_eval_script(s ? s : "");
if (s) free(s);
push_value(vm, make_int(rc));
break;
}

View file

@ -1,55 +0,0 @@
/* TK_LABEL */
case OP_TK_LABEL: {
/* stack: ..., id, text -> rc */
Value textv = pop_value(vm);
Value idv = pop_value(vm);
char *text = value_to_string_alloc(&textv);
char *id = value_to_string_alloc(&idv);
free_value(textv);
free_value(idv);
if (!id) {
if (text) free(text);
push_value(vm, make_int(-1));
break;
}
if (!text) {
text = strdup("");
}
/* escape id minimally (dots and word chars are fine) -> just use as-is */
/* escape text for Tcl double quotes */
size_t n = 0;
for (const char *p = text; *p; ++p) {
n += (*p == '\\' || *p == '"') ? 2 : 1;
}
char *et = (char *)malloc(n + 1);
if (!et) {
free(id);
free(text);
push_value(vm, make_int(-1));
break;
}
char *q = et;
for (const char *p = text; *p; ++p) {
if (*p == '\\' || *p == '"') *q++ = '\\';
*q++ = *p;
}
*q = '\0';
free(text);
size_t slen = strlen(id) + strlen(et) + 128;
char *script = (char *)malloc(slen);
if (!script) {
free(id);
free(et);
push_value(vm, make_int(-1));
break;
}
snprintf(script, slen,
"if {[winfo exists .%s]} { .%s configure -text \"%s\" } else { label .%s -text \"%s\" }",
id, id, et, id, et);
int rc = fun_tk_eval_script(script);
free(script);
free(id);
free(et);
push_value(vm, make_int(rc));
break;
}

View file

@ -1,23 +0,0 @@
/*
* 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: 2025-12-09
*/
/* TK_LOOP */
case OP_TK_LOOP: {
fun_tk_loop();
#ifdef FUN_WITH_TCLTK
/* Ensure the process terminates once the GUI window(s) are closed. */
exit(0);
#else
/* When Tk support is not compiled in, behave as a no-op returning Nil. */
push_value(vm, make_nil());
#endif
break; /* not reached when FUN_WITH_TCLTK */
}

View file

@ -1,23 +0,0 @@
/* TK_PACK */
case OP_TK_PACK: {
Value idv = pop_value(vm);
char *id = value_to_string_alloc(&idv);
free_value(idv);
if (!id) {
push_value(vm, make_int(-1));
break;
}
size_t slen = strlen(id) + 16;
char *script = (char *)malloc(slen);
if (!script) {
free(id);
push_value(vm, make_int(-1));
break;
}
snprintf(script, slen, "pack .%s", id);
int rc = fun_tk_eval_script(script);
free(script);
free(id);
push_value(vm, make_int(rc));
break;
}

View file

@ -1,17 +0,0 @@
/*
* 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: 2025-12-09
*/
/* TK_RESULT */
case OP_TK_RESULT: {
const char *r = fun_tk_get_result();
push_value(vm, make_string(r ? r : ""));
break;
}

View file

@ -1,52 +0,0 @@
/*
* 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: 2025-12-09
*/
/* TK_WM_TITLE */
case OP_TK_WM_TITLE: {
Value titlev = pop_value(vm);
char *title = value_to_string_alloc(&titlev);
free_value(titlev);
if (!title) {
push_value(vm, make_int(-1));
break;
}
/* Escape backslashes and double quotes for Tcl double-quoted strings */
size_t n = 0;
for (const char *p = title; *p; ++p) {
n += (*p == '\\' || *p == '"') ? 2 : 1;
}
char *esc = (char *)malloc(n + 1);
if (!esc) {
free(title);
push_value(vm, make_int(-1));
break;
}
char *q = esc;
for (const char *p = title; *p; ++p) {
if (*p == '\\' || *p == '"') *q++ = '\\';
*q++ = *p;
}
*q = '\0';
free(title);
size_t slen = strlen(esc) + 32;
char *script = (char *)malloc(slen);
if (!script) {
free(esc);
push_value(vm, make_int(-1));
break;
}
snprintf(script, slen, "wm title . \"%s\"", esc);
int rc = fun_tk_eval_script(script);
free(script);
free(esc);
push_value(vm, make_int(rc));
break;
}

View file

@ -6,18 +6,12 @@
<div class="trigger">
<h2 style="display:none;">Navigation</h2>
<ul style="list-style: none;">
<li><a class="page-link" href="/" style="text-decoration:none;" title="/">/</a></li>
<li><a class="page-link" href="/about/" style="text-decoration:none;" title="/about/">/about/</a></li>
<li><a class="page-link" href="/community/" style="text-decoration:none;" title="/community/">/community/</a></li>
<li><a class="page-link" href="/contact/" style="text-decoration:none;" title="/contact/">/contact/</a></li>
<!--<li><a class="page-link" href="/development/" style="text-decoration:none;" title="/development/">/development/</a></li>-->
<li><a class="page-link" href="/documentation/" style="text-decoration:none;" title="/documentation/">/documentation/</a></li>
<!--<li><a class="page-link" href="/examples/" style="text-decoration:none;" title="/examples/">/examples/</a></li>-->
<!--<li><a class="page-link" href="/faq/" style="text-decoration:none;" title="/faq/">/faq/</a></li>-->
<!--
<li>----------------</li>
<li><a class="page-link" href="/account/" style="text-decoration:none;" title="/account/">/account/</a></li>
-->
<li><a class="page-link" href="/" style="text-decoration:none;" title="Homepage">/</a></li>
<li><a class="page-link" href="/about/" style="text-decoration:none;" title="About">About</a></li>
<li><a class="page-link" href="/community/" style="text-decoration:none;" title="Community">Community</a></li>
<li><a class="page-link" href="/contact/" style="text-decoration:none;" title="Contact">Contact</a></li>
<li><a class="page-link" href="/documentation/" style="text-decoration:none;" title="Documentation">Documentation</a></li>
<li><a class="page-link" href="/download/" style="text-decoration:none;" title="Download">Download</a></li>
<!--
https://www.w3schools.com/howto/howto_js_toggle_dark_mode.asp
https://dev.to/warish/how-to-create-a-dark-mode-toggle-with-html-css-and-javascript-378m

27
web/download/download.md Normal file
View file

@ -0,0 +1,27 @@
---
layout: page
title: Download
subtitle: Download Fun
description: Download Fun archives or checkout source code.
metasub: news
noToc: true
noDate: false
tags:
- fun
- programming
- language
- download
_description: The programming language that just makes fun!
noComments: true
permalink: /download/
---
Actually, no packages for some OS are existing. You need to download the source code and compile it by yourself.
## Archives
- [https://git.xw3.org/fun/fun/tags](https://git.xw3.org/fun/fun/tags){:class="git"}
## Git
- [https://git.xw3.org/fun/fun/](https://git.xw3.org/fun/fun/){:class="git"}