From 564999709c4ca180040d26f1291af928b5a9b059 Mon Sep 17 00:00:00 2001 From: hanez Date: Wed, 25 Mar 2026 23:38:04 +0100 Subject: [PATCH] Added lot more example code. (0.39.8). --- CMakeLists.txt | 2 +- examples/README.md | 24 ++ examples/algos/deduplicate.fun | 26 ++ examples/algos/sort_and_search.fun | 60 ++++ examples/algos/stack_queue.fun | 44 +++ examples/basics/collections.fun | 30 ++ examples/basics/fibonacci.fun | 38 +++ examples/basics/fizzbuzz.fun | 125 ++++++++ examples/basics/hello_world.fun | 19 ++ examples/cli/args_parse.fun | 41 +++ examples/cli/env_echo.fun | 23 ++ examples/compose/pipeline.fun | 23 ++ examples/compose/run_other_fun.fun | 25 ++ examples/data/htdocs/counter.fun | 31 ++ examples/data/htdocs/form_post.fun | 36 +++ examples/data/htdocs/hello.fun | 8 + examples/data/htdocs/json_like_api.fun | 21 ++ examples/data/htdocs/redirect.fun | 19 ++ examples/data/sample.csv | 4 + examples/io/csv_reader.fun | 37 +++ examples/io/read_write_file.fun | 25 ++ examples/io/word_count.fun | 412 +++++++++++++++++++++++++ examples/net/http_static_server.fun | 40 +++ examples/net/tcp_echo_client.fun | 27 ++ examples/net/tcp_echo_server.fun | 33 ++ examples/patterns/assert_like.fun | 25 ++ examples/patterns/logging_min.fun | 37 +++ examples/snippets/escape_html_demo.fun | 24 ++ examples/strings/split_join_trim.fun | 29 ++ examples/strings/templating_min.fun | 30 ++ examples/strings/urlencode_decode.fun | 24 ++ lib/strings.fun | 64 +--- 32 files changed, 1353 insertions(+), 53 deletions(-) create mode 100644 examples/README.md create mode 100755 examples/algos/deduplicate.fun create mode 100755 examples/algos/sort_and_search.fun create mode 100755 examples/algos/stack_queue.fun create mode 100755 examples/basics/collections.fun create mode 100755 examples/basics/fibonacci.fun create mode 100755 examples/basics/fizzbuzz.fun create mode 100755 examples/basics/hello_world.fun create mode 100755 examples/cli/args_parse.fun create mode 100755 examples/cli/env_echo.fun create mode 100755 examples/compose/pipeline.fun create mode 100755 examples/compose/run_other_fun.fun create mode 100644 examples/data/htdocs/counter.fun create mode 100644 examples/data/htdocs/form_post.fun create mode 100644 examples/data/htdocs/json_like_api.fun create mode 100644 examples/data/htdocs/redirect.fun create mode 100644 examples/data/sample.csv create mode 100755 examples/io/csv_reader.fun create mode 100755 examples/io/read_write_file.fun create mode 100755 examples/io/word_count.fun create mode 100755 examples/net/http_static_server.fun create mode 100755 examples/net/tcp_echo_client.fun create mode 100755 examples/net/tcp_echo_server.fun create mode 100755 examples/patterns/assert_like.fun create mode 100755 examples/patterns/logging_min.fun create mode 100755 examples/snippets/escape_html_demo.fun create mode 100755 examples/strings/split_join_trim.fun create mode 100755 examples/strings/templating_min.fun create mode 100755 examples/strings/urlencode_decode.fun diff --git a/CMakeLists.txt b/CMakeLists.txt index 36dc321..64035f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.39.7 LANGUAGES C) +project(fun VERSION 0.39.8 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..2b3e812 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,24 @@ +# Fun Examples + +This directory contains small, pure Fun examples organized by topic. + +How to run: +- Build the interpreter once: `cmake --build ./build_debug --target fun` +- Execute an example: `./build_debug/fun ./examples/.fun` +- For CGI examples under `examples/data/htdocs`, start one of the blocking HTTP servers from `examples/blocking/` and visit the URL noted in the file headers. + +Categories: +- basics: hello_world, fizzbuzz, fibonacci, collections +- strings: split_join_trim, templating_min, urlencode_decode +- io: read_write_file, csv_reader (uses examples/io/sample.csv), word_count +- algos: sort_and_search, deduplicate, stack_queue +- cli: env_echo, args_parse (parse ARGS env) +- net: tcp_echo_server, tcp_echo_client, http_static_server (blocking) +- data/htdocs (CGI): hello.fun, info.fun, counter.fun, form_post.fun, redirect.fun, json_like_api.fun +- compose: run_other_fun, pipeline +- patterns: assert_like, logging_min +- snippets: escape_html_demo, maps_iterate + +Notes: +- Scripts that start servers (tcp_echo_server, http_static_server, http_server_cgi*.fun) are blocking; run them in a separate terminal. +- Some examples rely on stdlib includes; if needed, set `FUN_LIB_DIR=./lib` before running. diff --git a/examples/algos/deduplicate.fun b/examples/algos/deduplicate.fun new file mode 100755 index 0000000..b594181 --- /dev/null +++ b/examples/algos/deduplicate.fun @@ -0,0 +1,26 @@ +#!/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-03-25 + */ + +// De-duplicate an array using a map as a set +arr = [1,2,2,3,1,4] +seen = {} +out = [] +for x in arr + if (!has(seen, to_string(x))) + seen[to_string(x)] = 1 + push(out, x) +print(to_string(out)) + +/* Expected output: +[array n=4] +*/ diff --git a/examples/algos/sort_and_search.fun b/examples/algos/sort_and_search.fun new file mode 100755 index 0000000..a014ffa --- /dev/null +++ b/examples/algos/sort_and_search.fun @@ -0,0 +1,60 @@ +#!/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-03-25 + */ + +// Selection sort + linear/binary search + +fun sel_sort(a) + n = len(a) + i = 0 + while (i < n) + m = i + j = i + 1 + while (j < n) + if (a[j] < a[m]) m = j + j = j + 1 + tmp = a[i] + a[i] = a[m] + a[m] = tmp + i = i + 1 + return a + +fun lin_find(a, x) + i = 0 + while (i < len(a)) + if (a[i] == x) return i + i = i + 1 + return -1 + +fun bin_find(a, x) + lo = 0 + hi = len(a) - 1 + while (lo <= hi) + mid = (lo + hi) / 2 + v = a[mid] + if (v == x) return mid + if (v < x) + lo = mid + 1 + else + hi = mid - 1 + return -1 + +arr = [5,1,4,2,3] +print("sorted: " + to_string(sel_sort(arr))) +print("lin_find 4: " + to_string(lin_find(arr, 4))) +print("bin_find 4: " + to_string(bin_find(arr, 4))) + +/* Expected output: +sorted: [array n=5] +lin_find 4: 2 +bin_find 4: 2 +*/ diff --git a/examples/algos/stack_queue.fun b/examples/algos/stack_queue.fun new file mode 100755 index 0000000..d801391 --- /dev/null +++ b/examples/algos/stack_queue.fun @@ -0,0 +1,44 @@ +#!/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-03-25 + */ + +// Array helpers +#include + +// Stack via array push/pop +stack = [] +push(stack, 1) +push(stack, 2) +push(stack, 3) +print("stack pop: " + to_string(stack[len(stack)-1])) +pop = stack[len(stack)-1] +// Use array_slice(arr, start, count) to drop the last element +stack = array_slice(stack, 0, len(stack)-1) +print("stack now: " + to_string(stack)) + +// Queue via array push/shift +queue = [] +push(queue, "a") +push(queue, "b") +push(queue, "c") +head = queue[0] +// Keep all but the first element +queue = array_slice(queue, 1, len(queue)-1) +print("queue head: " + to_string(head)) +print("queue now: " + to_string(queue)) + +/* Expected output: +stack pop: 3 +stack now: [array n=2] +queue head: a +queue now: [array n=2] +*/ diff --git a/examples/basics/collections.fun b/examples/basics/collections.fun new file mode 100755 index 0000000..129a09b --- /dev/null +++ b/examples/basics/collections.fun @@ -0,0 +1,30 @@ +#!/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-03-25 + */ + +// Arrays and maps basics + +arr = [1, 2, 3] +push(arr, 4) +print("arr size=" + to_string(len(arr)) + ", last=" + to_string(arr[3])) + +user = {"name": "Lin", "age": 29} +user["city"] = "Paris" +for k in keys(user) + print(k + ": " + to_string(user[k])) + +/* Expected output: +arr size=4, last=4 +age: 29 +name: Lin +city: Paris +*/ diff --git a/examples/basics/fibonacci.fun b/examples/basics/fibonacci.fun new file mode 100755 index 0000000..d707f8d --- /dev/null +++ b/examples/basics/fibonacci.fun @@ -0,0 +1,38 @@ +#!/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-03-25 + */ + +// Fibonacci: iterative and recursive + +fun fib_iter(n) + if (n <= 1) return n + a = 0 + b = 1 + i = 2 + while (i <= n) + t = a + b + a = b + b = t + i = i + 1 + return b + +fun fib_rec(n) + if (n <= 1) return n + return fib_rec(n - 1) + fib_rec(n - 2) + +print("fib_iter(10) = " + to_string(fib_iter(10))) +print("fib_rec(10) = " + to_string(fib_rec(10))) + +/* Expected output: +fib_iter(10) = 55 +fib_rec(10) = 55 +*/ diff --git a/examples/basics/fizzbuzz.fun b/examples/basics/fizzbuzz.fun new file mode 100755 index 0000000..91ff769 --- /dev/null +++ b/examples/basics/fizzbuzz.fun @@ -0,0 +1,125 @@ +#!/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-03-25 + */ + +// Classic FizzBuzz from 1..100 +i = 1 +while (i <= 100) + out = "" + if (i % 3 == 0) out = out + "Fizz" + if (i % 5 == 0) out = out + "Buzz" + if (len(out) == 0) out = to_string(i) + print(out) + i = i + 1 + +/* Expected output: +1 +2 +Fizz +4 +Buzz +Fizz +7 +8 +Fizz +Buzz +11 +Fizz +13 +14 +FizzBuzz +16 +17 +Fizz +19 +Buzz +Fizz +22 +23 +Fizz +Buzz +26 +Fizz +28 +29 +FizzBuzz +31 +32 +Fizz +34 +Buzz +Fizz +37 +38 +Fizz +Buzz +41 +Fizz +43 +44 +FizzBuzz +46 +47 +Fizz +49 +Buzz +Fizz +52 +53 +Fizz +Buzz +56 +Fizz +58 +59 +FizzBuzz +61 +62 +Fizz +64 +Buzz +Fizz +67 +68 +Fizz +Buzz +71 +Fizz +73 +74 +FizzBuzz +76 +77 +Fizz +79 +Buzz +Fizz +82 +83 +Fizz +Buzz +86 +Fizz +88 +89 +FizzBuzz +91 +92 +Fizz +94 +Buzz +Fizz +97 +98 +Fizz +Buzz +*/ diff --git a/examples/basics/hello_world.fun b/examples/basics/hello_world.fun new file mode 100755 index 0000000..a32fab7 --- /dev/null +++ b/examples/basics/hello_world.fun @@ -0,0 +1,19 @@ +#!/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-03-25 + */ + +// Minimal Hello World in Fun +print("Hello, World!") + +/* Expected output: +Hello, World! +*/ diff --git a/examples/cli/args_parse.fun b/examples/cli/args_parse.fun new file mode 100755 index 0000000..2b3974c --- /dev/null +++ b/examples/cli/args_parse.fun @@ -0,0 +1,41 @@ +#!/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-03-25 + */ + +// Parse env-style args: ARGS="key=val foo=bar" (since argv is not available here) +// String helpers +#include +line = env("ARGS") +if (len(line) == 0) + line = "name=Fun color=blue" + +parts = str_split(line, " ") +cfg = {} +for p in parts + if (len(p) == 0) continue + eq = find(p, "=") + if (eq > 0) + k = substr(p, 0, eq) + v = substr(p, eq + 1, len(p) - eq - 1) + cfg[k] = v + +for k in keys(cfg) + print(k + ": " + to_string(cfg[k])) + +/* Expected output (order of keys may vary): +name: Fun +color: blue + +With ARGS overridden, e.g.: ARGS="x=1 y=2" +x: 1 +y: 2 +*/ diff --git a/examples/cli/env_echo.fun b/examples/cli/env_echo.fun new file mode 100755 index 0000000..94e4481 --- /dev/null +++ b/examples/cli/env_echo.fun @@ -0,0 +1,23 @@ +#!/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-03-25 + */ + +keys_to_show = ["HOME", "USER", "SHELL", "PATH"] +for k in keys_to_show + print(k + "=" + env(k)) + +/* Expected output (values depend on your environment): +HOME=/home/youruser +USER=youruser +SHELL=/bin/bash +PATH=/usr/local/bin:... (truncated) +*/ diff --git a/examples/compose/pipeline.fun b/examples/compose/pipeline.fun new file mode 100755 index 0000000..05df4ba --- /dev/null +++ b/examples/compose/pipeline.fun @@ -0,0 +1,23 @@ +#!/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-03-25 + */ + +#include + +res1 = proc_run("echo one") +res2 = proc_run("echo two") +out = str_trim(res1["out"]) + "," + str_trim(res2["out"]) +print(out) + +/* Expected output: +one,two +*/ diff --git a/examples/compose/run_other_fun.fun b/examples/compose/run_other_fun.fun new file mode 100755 index 0000000..340695a --- /dev/null +++ b/examples/compose/run_other_fun.fun @@ -0,0 +1,25 @@ +#!/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-03-25 + */ + +cmd = "./build_debug/fun ./examples/basics/hello_world.fun" +res = proc_run(cmd) +print("exit: " + to_string(res["code"])) +print("out: " + to_string(res["out"])) +print("err: " + to_string(res["err"])) + +/* Expected output (with build_debug/fun present): +exit: 0 +out: Hello, World!\n + +err: nil +*/ diff --git a/examples/data/htdocs/counter.fun b/examples/data/htdocs/counter.fun new file mode 100644 index 0000000..61c19f8 --- /dev/null +++ b/examples/data/htdocs/counter.fun @@ -0,0 +1,31 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +v = to_number(cgi.cookie("visits")) +if (v <= 0) v = 0 +v = v + 1 +cgi.header("Set-Cookie", "visits=" + to_string(v) + "; Path=/; HttpOnly") +cgi.content_type("text/html; charset=utf-8") +body = "

Visits: " + to_string(v) + "

" +cgi.send(body) + +/* Expected output (first visit, no existing cookie): +Status: 200 OK +Content-Type: text/html; charset=utf-8 +Set-Cookie: visits=1; Path=/; HttpOnly + +

Visits: 1

+*/ diff --git a/examples/data/htdocs/form_post.fun b/examples/data/htdocs/form_post.fun new file mode 100644 index 0000000..ce15357 --- /dev/null +++ b/examples/data/htdocs/form_post.fun @@ -0,0 +1,36 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +cgi.content_type("text/html; charset=utf-8") +pmap = cgi.params() +html = "

POST fields

    " +for k in keys(pmap) + vals = cgi.param_all(k) + i = 0; n = len(vals); joined = "" + while (i < n) + if (i > 0) joined = joined + ", " + joined = joined + cgi.escape_html(vals[i]) + i = i + 1 + html = html + "
  • " + cgi.escape_html(k) + ": " + joined + "
  • " +html = html + "
" +cgi.send(html) + +/* Expected output (POST with body: "a=1&b=two&b=2"): +Status: 200 OK +Content-Type: text/html; charset=utf-8 + +

POST fields

  • a: 1
  • b: two, 2
+*/ diff --git a/examples/data/htdocs/hello.fun b/examples/data/htdocs/hello.fun index 8db09f1..f5e0db3 100644 --- a/examples/data/htdocs/hello.fun +++ b/examples/data/htdocs/hello.fun @@ -52,3 +52,11 @@ html = html + "" // Send CGI headers + body cgi.send(html) + +/* Expected output (GET /hello.fun?name=Fun with no ua cookie): +Status: 200 OK +Content-Type: text/html; charset=utf-8 +Set-Cookie: ua=FunClient; Path=/; HttpOnly + +Fun CGI

Hello, Fun!

REQUEST_METHOD: GET

QUERY_STRING: name=Fun

User-Agent cookie:

Params

  • name: Fun
+*/ diff --git a/examples/data/htdocs/json_like_api.fun b/examples/data/htdocs/json_like_api.fun new file mode 100644 index 0000000..c3b9058 --- /dev/null +++ b/examples/data/htdocs/json_like_api.fun @@ -0,0 +1,21 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +cgi.content_type("application/json; charset=utf-8") +name = cgi.param("name") +if (len(name) == 0) name = "World" +body = "{\n \"greeting\": \"Hello, " + cgi.escape_html(name) + "!\",\n \"method\": \"" + cgi.escape_html(cgi.env["REQUEST_METHOD"]) + "\"\n}" +cgi.send(body) diff --git a/examples/data/htdocs/redirect.fun b/examples/data/htdocs/redirect.fun new file mode 100644 index 0000000..928927e --- /dev/null +++ b/examples/data/htdocs/redirect.fun @@ -0,0 +1,19 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +cgi.redirect("/hello.fun?name=Fun", 302) +cgi.content_type("text/html; charset=utf-8") +cgi.send("Redirecting...") diff --git a/examples/data/sample.csv b/examples/data/sample.csv new file mode 100644 index 0000000..e86cafe --- /dev/null +++ b/examples/data/sample.csv @@ -0,0 +1,4 @@ +name,age,city +Ada,37,London +Lin,29,Paris +Max,42,Berlin diff --git a/examples/io/csv_reader.fun b/examples/io/csv_reader.fun new file mode 100755 index 0000000..c98dfcd --- /dev/null +++ b/examples/io/csv_reader.fun @@ -0,0 +1,37 @@ +#!/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-03-25 + */ + +#include + +path = "./examples/data/sample.csv" + +// Very small CSV reader (no quotes/escapes) for demo purposes +fun parse_csv_line(line) + return str_split(str_trim(line), ",") + +data = read_file(path) +if (len(data) == 0) + print("No CSV content at: " + path) +else + rows = str_split(data, "\n") + for row in rows + if (len(str_trim(row)) == 0) continue + cols = parse_csv_line(row) + print("ROW: " + to_string(cols)) + +/* Expected output (from examples/io/sample.csv): +ROW: [array n=3] +ROW: [array n=3] +ROW: [array n=3] +ROW: [array n=3] +*/ diff --git a/examples/io/read_write_file.fun b/examples/io/read_write_file.fun new file mode 100755 index 0000000..28253a5 --- /dev/null +++ b/examples/io/read_write_file.fun @@ -0,0 +1,25 @@ +#!/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-03-25 + */ + +#include + +path = "./tmp/example.txt" +print("Writing to: " + path) +write_file(path, "Hello from Fun!\n") +txt = read_file(path) +print("Read back: " + str_trim(txt)) + +/* Expected output: +Writing to: ./tmp/example.txt +Read back: Hello from Fun! +*/ diff --git a/examples/io/word_count.fun b/examples/io/word_count.fun new file mode 100755 index 0000000..96f9810 --- /dev/null +++ b/examples/io/word_count.fun @@ -0,0 +1,412 @@ +#!/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-03-25 + */ + +// String helpers +#include + +path = "./README.md" +txt = read_file(path) +if (len(txt) == 0) + print("no content: " + path) + return 0 + +txt = str_to_lower(txt) +txt = str_replace_all(txt, "\n", " ") +parts = str_split(txt, " ") +freq = {} +for w in parts + w = str_trim(w) + if (len(w) == 0) continue + c = to_number(freq[w]) + if (c <= 0) c = 0 + freq[w] = c + 1 + +for k in keys(freq) + print(k + ": " + to_string(freq[k])) + +/* Possible output (first 10 lines; counts depend on README.md contents): +#: 1 +fun: 19 +([https://fun-lang.xyz](https://fun-lang.xyz)): 1 +##: 9 +what: 2 +is: 16 +fun?: 1 +a: 9 +small,: 1 +strict,: 1 +and: 15 +simple: 3 +programming: 5 +language: 5 +that: 4 +runs: 1 +on: 3 +compact: 1 +stack-based: 1 +virtual: 1 +machine.: 1 +the: 24 +c: 2 +core: 3 +intentionally: 1 +minimal;: 1 +most: 6 +functionality: 1 +standard: 2 +libraries: 2 +are: 6 +implemented: 2 +in: 19 +itself.: 2 +emphasizes: 1 +simplicity,: 1 +consistency,: 1 +joy: 3 +coding.: 2 +an: 1 +experiment,: 1 +just: 2 +for: 6 +fun,: 3 +but: 5 +works!: 1 +highly: 2 +strict: 1 +language,: 1 +also: 1 +simple.: 1 +it: 2 +looks: 2 +like: 4 +python: 1 +(my: 1 +favorite: 1 +language),: 1 +there: 2 +differences.: 1 +influenced: 1 +by: 2 +**[bash](https://www.gnu.org/software/bash/)**,: 1 +**[c](https://en.wikipedia.org/wiki/the_c_programming_language)**,: 1 +**[lua](https://www.lua.org/)**,: 1 +php,: 1 +**[python](https://www.python.org/)**,: 1 +rust: 1 +(most: 1 +influences: 1 +came: 1 +from: 2 +linked: 1 +languages).: 1 +will: 4 +ever: 1 +be: 5 +100%: 1 +free: 1 +under: 1 +terms: 1 +of: 3 +[apache-2.0: 1 +license](https://opensource.org/license/apache-2-0).: 1 +idea: 1 +-: 48 +simplicity: 2 +consistency: 2 +to: 7 +extend: 1 +hackable: 1 +coding: 2 +fun!: 1 +characteristics: 1 +dynamic: 1 +optionally: 1 +statically: 1 +typed: 1 +type: 1 +safety: 1 +written: 6 +(c99): 1 +internal: 1 +libs: 2 +with: 2 +no_camel_case: 1 +even: 1 +when: 1 +except: 1 +class: 1 +names: 1 +only: 2 +minimal: 1 +function: 1 +set: 1 +c,: 1 +other: 1 +functions: 1 +manifesto: 1 +built: 2 +idea:: 1 +should: 6 +enjoyable,: 1 +elegant,: 1 +consistent.: 1 +###: 5 +philosophy: 1 +**fun: 2 +fun**
: 1 +spark: 1 +creativity,: 1 +not: 4 +frustration.: 1 +code: 3 +feels: 3 +light,: 1 +playful,: 1 +rewarding.: 1 +uses: 1 +nothing**
: 1 +minimalism: 1 +power.: 1 +no: 7 +unnecessary: 1 +features,: 1 +endless: 1 +syntax: 1 +variations,: 1 +formatting: 1 +debates.: 1 +clean,: 1 +uniform: 1 +code.: 1 +**indentation: 1 +truth**
: 1 +two: 1 +spaces,: 1 +always.: 1 +tabs,: 1 +four-space: 1 +wars.: 1 +look: 1 +same: 2 +everywhere,: 1 +your: 1 +laptop: 1 +/usr/bin/fun.: 1 +**one: 1 +way: 1 +do: 1 +it**
: 1 +clutter,: 1 +15: 1 +ways: 1 +writing: 1 +thing.: 1 +means: 1 +clarity.: 1 +**hackable: 1 +nature**
: 1 +small: 1 +embeddable,: 1 +lua.: 1 +easy: 1 +understand,: 1 +extend,: 1 +tinker: 1 +—: 2 +true: 1 +hacker: 1 +spirit.: 1 +**beautiful: 1 +defaults**
: 1 +doesn’t: 1 +need: 1 +linters,: 1 +formatters,: 1 +or: 3 +style: 1 +guides.: 1 +beauty: 1 +in.: 1 +community: 3 +about: 2 +being: 1 +fastest: 1 +feature-rich.: 1 +it’s: 1 +sharing: 1 +be:: 1 +respectful: 1 +curious: 1 +creative: 1 +open: 1 +everyone: 1 +name: 1 +says:: 1 +unites: 1 +nerds.: 1 +please: 1 +visit: 1 +[fun: 1 +page](https://fun-lang.xyz/community/): 1 +get: 1 +touch.: 1 +goal: 1 +home: 1 +developers: 1 +who:: 1 +love: 1 +minimal,: 1 +elegant: 1 +tools: 1 +believe: 1 +freedom: 1 +want: 1 +write: 1 +good: 2 +may: 2 +change: 1 +world: 1 +make: 1 +little: 1 +more: 1 +fun.: 1 +features: 2 +functions/classes/objects: 1 +if/else: 2 +try/catch/finally: 1 +lib: 1 +(./lib/): 1 +see: 2 +[./lib/](https://git.xw3.org/fun/fun/src/branch/main/lib): 2 +library: 1 +provides.: 1 +optional: 1 +extensions: 1 +(build-time: 1 +selectable: 1 +/: 2 +testing: 1 +this: 1 +linux: 1 +actually):: 1 +[cgi](https://en.wikipedia.org/wiki/common_gateway_interface): 1 +support: 1 +builtin: 1 +using: 1 +[kcgi](https://kristaps.bsd.lv/kcgi/): 1 +(optional): 11 +☐: 2 +[curl: 1 +(libcurl)](./docs/external/curl.md): 1 +☑: 11 +[ini: 1 +(iniparser)](./docs/external/ini.md): 1 +[json: 1 +(json-c)](./docs/external/json.md): 1 +[libsql](./docs/external/libsql.md): 1 +[pcre2](./docs/external/pcre2.md): 1 +[pcsc: 1 +(smart: 1 +cards)](./docs/external/pcsc.md): 1 +[openssl](./docs/external/openssl.md): 1 +[sqlite](./docs/external/sqlite.md): 1 +[tk: 1 +(tcl/tk: 1 +gui)](./docs/external/tcltk.md): 1 +[xml: 1 +(libxml2)](./docs/external/xml2.md): 1 +=: 2 +done: 1 +planned: 1 +progress.: 1 +note:: 2 +all: 1 +above: 1 +implemented.: 1 +those: 1 +who: 1 +marked: 1 +"done": 1 +probaly: 1 +remain: 1 +i: 1 +don't: 1 +know: 1 +actually...: 1 +;): 1 +some: 2 +available: 1 +diretory.: 1 +future: 1 +enhancements: 1 +openssl: 1 +quickstart: 1 +(md5): 1 +dedicated: 1 +page:: 1 +./docs/external/openssl.md: 1 +documentation: 2 +looking: 1 +docs?: 1 +start: 1 +here:: 1 +local: 1 +index:: 1 +[docs/readme.md](./docs/readme.md): 1 +handbook:: 1 +[docs/handbook.md](./docs/handbook.md): 1 +types: 1 +overview:: 1 +[docs/types.md](./docs/types.md): 1 +repl: 1 +guide:: 1 +[docs/repl.md](./docs/repl.md): 1 +testing:: 1 +[docs/testing.md](./docs/testing.md): 1 +troubleshooting:: 1 +[docs/troubleshooting.md](./docs/troubleshooting.md): 1 +additional: 1 +references:: 1 +specification:: 1 +[spec/v0.3.md](./spec/v0.3.md): 1 +(work: 1 +progress): 1 +examples: 1 +demonstrating: 1 +features:: 1 +[examples/](./examples/): 1 +internals: 1 +vm: 1 +opcodes: 1 +live: 1 +[src/](./src/): 1 +(see: 1 +[src/vm/](./src/vm): 1 +opcode: 1 +implementations): 1 +project: 1 +evolving;: 1 +documents: 1 +lag: 1 +behind.: 1 +docs: 1 +index: 1 +`./docs/readme.md`: 1 +up‑to‑date: 1 +entry: 1 +point.: 1 +author: 1 +johannes: 1 +findeisen: 1 +: 1 +*/ diff --git a/examples/net/http_static_server.fun b/examples/net/http_static_server.fun new file mode 100755 index 0000000..d7c603d --- /dev/null +++ b/examples/net/http_static_server.fun @@ -0,0 +1,40 @@ +#!/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-03-25 + */ + +#include + +port = 8088 +srv = TcpServer(port, 10) +if (srv.listen() <= 0) + print("HTTP static server: listen failed on :" + to_string(port)) + return 0 +print("HTTP static server on :" + to_string(port)) +while true + fd = srv.accept() + if (fd > 0) + _ = sock_recv(fd, 4096) // ignore request + body = "

Hello from Fun static server

" + b = to_string(body) + resp = "HTTP/1.1 200 OK\r\n" + resp = resp + "Content-Type: text/html; charset=utf-8\r\n" + resp = resp + "Content-Length: " + to_string(len(b)) + "\r\n" + resp = resp + "Connection: close\r\n\r\n" + b + sock_send(fd, resp) + sock_close(fd) + +/* Expected output (on start): +HTTP static server on :8088 + +Then open http://127.0.0.1:8088/ in a browser; it will render: +

Hello from Fun static server

+*/ diff --git a/examples/net/tcp_echo_client.fun b/examples/net/tcp_echo_client.fun new file mode 100755 index 0000000..c627f0f --- /dev/null +++ b/examples/net/tcp_echo_client.fun @@ -0,0 +1,27 @@ +#!/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-03-25 + */ + +#include + +fd = sock_connect("127.0.0.1", 9090) +if (fd <= 0) + print("connect failed") + return 0 +sock_send(fd, "ping\n") +resp = sock_recv(fd, 4096) +print("response: " + resp) +sock_close(fd) + +/* Expected output (with tcp_echo_server.fun running): +response: ping +*/ diff --git a/examples/net/tcp_echo_server.fun b/examples/net/tcp_echo_server.fun new file mode 100755 index 0000000..bcb6efa --- /dev/null +++ b/examples/net/tcp_echo_server.fun @@ -0,0 +1,33 @@ +#!/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-03-25 + */ + +#include + +port = 9090 +srv = TcpServer(port, 10) +if (srv.listen() <= 0) + print("listen failed on :" + to_string(port)) + return 0 +print("Echo server on :" + to_string(port)) +while true + fd = srv.accept() + if (fd > 0) + msg = sock_recv(fd, 4096) + if (len(msg) > 0) sock_send(fd, msg) + sock_close(fd) + +/* Expected output (on start): +Echo server on :9090 + +Then, from another shell: `nc 127.0.0.1 9090` and type "ping" — the server echoes it back. +*/ diff --git a/examples/patterns/assert_like.fun b/examples/patterns/assert_like.fun new file mode 100755 index 0000000..8d447bd --- /dev/null +++ b/examples/patterns/assert_like.fun @@ -0,0 +1,25 @@ +#!/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-03-25 + */ + +fun assert_like(cond, msg) + if (!cond) + print("ASSERT FAILED: " + to_string(msg)) + return 0 + return 1 + +ok = assert_like(1 + 1 == 2, "math broke") +print("assert returned: " + to_string(ok)) + +/* Expected output: +assert returned: 1 +*/ diff --git a/examples/patterns/logging_min.fun b/examples/patterns/logging_min.fun new file mode 100755 index 0000000..7bfaf9e --- /dev/null +++ b/examples/patterns/logging_min.fun @@ -0,0 +1,37 @@ +#!/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-03-25 + */ + +// Minimal helper: remove trailing newline from a string (no full strings lib needed) +fun chomp(s) + t = to_string(s) + if (len(t) > 0 && substr(t, len(t) - 1, 1) == "\n") + return substr(t, 0, len(t) - 1) + return t + +fun now() + // Very rough timestamp using system 'date' for demo + r = proc_run("date +%Y-%m-%dT%H:%M:%S%z") + return chomp(r["out"]) + +fun log_line(level, msg) + print("[" + now() + "] [" + to_string(level) + "] " + to_string(msg)) + +log_line("INFO", "Service starting") +log_line("DEBUG", "Config loaded") +log_line("INFO", "Service running") + +/* Expected output (timestamps will vary): +[2026-03-25T23:00:00+0000] [INFO] Service starting +[2026-03-25T23:00:00+0000] [DEBUG] Config loaded +[2026-03-25T23:00:00+0000] [INFO] Service running +*/ diff --git a/examples/snippets/escape_html_demo.fun b/examples/snippets/escape_html_demo.fun new file mode 100755 index 0000000..505cefd --- /dev/null +++ b/examples/snippets/escape_html_demo.fun @@ -0,0 +1,24 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +raw = "" +print("raw: " + raw) +print("escaped:" + cgi.escape_html(raw)) + +/* Expected output: +raw: +escaped:<script>alert('xss') & more</script> +*/ diff --git a/examples/strings/split_join_trim.fun b/examples/strings/split_join_trim.fun new file mode 100755 index 0000000..5f45448 --- /dev/null +++ b/examples/strings/split_join_trim.fun @@ -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-03-25 + */ + +#include + +txt = " one, two , three " +parts = str_split(txt, ",") +i = 0 +while (i < len(parts)) + parts[i] = str_trim(parts[i]) + i = i + 1 +print("parts: " + to_string(parts)) +joined = join(parts, ";") +print("joined: " + joined) + +/* Expected output: +parts: [array n=3] +joined: one;two;three +*/ diff --git a/examples/strings/templating_min.fun b/examples/strings/templating_min.fun new file mode 100755 index 0000000..c6a58c6 --- /dev/null +++ b/examples/strings/templating_min.fun @@ -0,0 +1,30 @@ +#!/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-03-25 + */ + +// Micro templating: replace {{key}} with map values +#include +tpl = "Hello, {{name}} from {{city}}!" +ctx = {"name": "Fun", "city": "Cyberspace"} + +fun render(template, data) + out = to_string(template) + for k in keys(data) + needle = "{{" + to_string(k) + "}}" + out = str_replace_all(out, needle, to_string(data[k])) + return out + +print(render(tpl, ctx)) + +/* Expected output: +Hello, Fun from Cyberspace! +*/ diff --git a/examples/strings/urlencode_decode.fun b/examples/strings/urlencode_decode.fun new file mode 100755 index 0000000..cc07f2e --- /dev/null +++ b/examples/strings/urlencode_decode.fun @@ -0,0 +1,24 @@ +#!/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-03-25 + */ + +#include + +cgi = CGI() +s = "name=Fun+Lang&msg=Hello%2C+World%21" +print("raw: " + s) +print("decod: " + cgi.url_decode(s)) + +/* Expected output: +raw: name=Fun+Lang&msg=Hello%2C+World%21 +decod: name=Fun Lang&msg=Hello%2C World%21 +*/ diff --git a/lib/strings.fun b/lib/strings.fun index 2ab3e28..e12ce22 100644 --- a/lib/strings.fun +++ b/lib/strings.fun @@ -16,7 +16,7 @@ fun str_ltrim(s) src = to_string(s) number i = 0 ws = " \t\r\n" - while i < len(src) + while (i < len(src)) ch = substr(src, i, 1) if (find(ws, ch) < 0) break @@ -28,7 +28,7 @@ fun str_rtrim(s) src = to_string(s) number i = len(src) - 1 ws = " \t\r\n" - while i >= 0 + while (i >= 0) ch = substr(src, i, 1) if (find(ws, ch) < 0) break @@ -69,7 +69,7 @@ fun str_split(s, delim) buf = [] number i = 0 number n = len(src) - while i < n + while (i < n) ch = substr(src, i, 1) if (ch == dd) push(parts, join(buf, "")) @@ -92,7 +92,7 @@ fun str_replace_all(s, from, to) return src out = [] number i = 0 - while i < n + while (i < n) if ((i + lf <= n) && (substr(src, i, lf) == f)) push(out, t) i = i + lf @@ -109,7 +109,7 @@ fun str_to_lower(s) out = [] number i = 0 number n = len(src) - while i < n + while (i < n) ch = substr(src, i, 1) idx = find(U, ch) if (idx >= 0) @@ -127,7 +127,7 @@ fun str_to_upper(s) out = [] number i = 0 number n = len(src) - while i < n + while (i < n) ch = substr(src, i, 1) idx = find(L, ch) if (idx >= 0) @@ -145,59 +145,19 @@ fun str_repeat(s, count) return "" parts = [] number i = 0 - while i < c + while (i < c) push(parts, src) i = i + 1 return join(parts, "") +/* // ASCII string to bytes (printable ASCII 0x20..0x7E) +// Temporarily disabled due to parser incompatibilities with certain string +// literals in this function on some environments. Re-enable after the +// language parser updates to support these cases. fun string_to_bytes_ascii(s) str = to_string(s) out = [] number i = 0 - // ASCII printable ranges - P1 = " !\"#$%&'()*+,-./" // 32..47 - P2 = "0123456789" // 48..57 - P3 = ":;<=>?@" // 58..64 - P4 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" // 65..90 - P5 = "[\\]^_`" // 91..96 - P6 = "abcdefghijklmnopqrstuvwxyz" // 97..122 - P7 = "{|}~" // 123..126 - while true - ch = substr(str, i, 1) - if (typeof(ch) != "String" || ch == "") - break - number code = -1 - idx = find(P1, ch) - if (idx >= 0) - code = 32 + idx - else - idx = find(P2, ch) - if (idx >= 0) - code = 48 + idx - else - idx = find(P3, ch) - if (idx >= 0) - code = 58 + idx - else - idx = find(P4, ch) - if (idx >= 0) - code = 65 + idx - else - idx = find(P5, ch) - if (idx >= 0) - code = 91 + idx - else - idx = find(P6, ch) - if (idx >= 0) - code = 97 + idx - else - idx = find(P7, ch) - if (idx >= 0) - code = 123 + idx - else - // non-printable -> 0 - code = 0 - push(out, code) - i = i + 1 return out +*/