Some more stdlib fun and example code and cosmetic changes. (0.38.15)
This commit is contained in:
parent
cb5e61c091
commit
9016b207b8
12 changed files with 330 additions and 4 deletions
|
|
@ -1,5 +1,5 @@
|
||||||
cmake_minimum_required(VERSION 3.10)
|
cmake_minimum_required(VERSION 3.10)
|
||||||
project(fun VERSION 0.38.14 LANGUAGES C)
|
project(fun VERSION 0.38.15 LANGUAGES C)
|
||||||
|
|
||||||
set(CMAKE_C_STANDARD 99)
|
set(CMAKE_C_STANDARD 99)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,60 @@
|
||||||
Conventions for writing C and Fun code in this repository.
|
Conventions for writing C and Fun code in this repository.
|
||||||
|
|
||||||
## General principles
|
## General principles
|
||||||
|
|
||||||
- Two-space indentation. No tabs.
|
- Two-space indentation. No tabs.
|
||||||
- Keep lines reasonably short (~100 cols). Wrap thoughtfully.
|
- Keep lines reasonably short (~100 cols). Wrap thoughtfully.
|
||||||
- Prefer explicit over implicit; favor clarity.
|
- Prefer explicit over implicit; favor clarity.
|
||||||
|
|
||||||
## C (C99)
|
## C (C99)
|
||||||
- Indentation: two spaces, K&R-ish braces.
|
|
||||||
|
- Indentation: four spaces, K&R-ish braces.
|
||||||
- Naming: `snake_case` for functions and variables; `CAPS_SNAKE` for macros.
|
- Naming: `snake_case` for functions and variables; `CAPS_SNAKE` for macros.
|
||||||
- Error handling: return error codes or booleans; avoid hidden globals.
|
- Error handling: return error codes or booleans; avoid hidden globals.
|
||||||
- Headers: minimize includes in headers; forward-declare where practical.
|
- Headers: minimize includes in headers; forward-declare where practical.
|
||||||
- Memory: clearly document ownership; free what you allocate.
|
- Memory: clearly document ownership; free what you allocate.
|
||||||
|
|
||||||
## Fun language
|
## Fun language
|
||||||
|
|
||||||
- Indentation: two spaces.
|
- Indentation: two spaces.
|
||||||
- Naming: `snake_case` for functions/variables; `PascalCase` for classes/constructors.
|
- Naming: `snake_case` for functions/variables; `PascalCase` for classes/constructors.
|
||||||
- Modules: one primary concept per file; export a minimal, cohesive API.
|
- Modules: one primary concept per file; export a minimal, cohesive API.
|
||||||
- Idioms: prefer arrays and maps over ad-hoc structures; keep functions small.
|
- Idioms: prefer arrays and maps over ad-hoc structures; keep functions small.
|
||||||
|
|
||||||
## Formatting and tools
|
## Formatting and tools
|
||||||
|
|
||||||
- No auto-formatters required; follow these simple rules.
|
- No auto-formatters required; follow these simple rules.
|
||||||
- Keep diffs small and focused; avoid reformat-only commits.
|
- Keep diffs small and focused; avoid reformat-only commits.
|
||||||
|
|
||||||
|
## Additional details (merged from legacy MVP draft)
|
||||||
|
|
||||||
|
This section consolidates practical guidelines that were previously kept in docs/style_guide.md.
|
||||||
|
|
||||||
|
1. Files and headers
|
||||||
|
- Keep the standard header block with license and date when editing stdlib files.
|
||||||
|
|
||||||
|
2. Naming
|
||||||
|
- Functions and variables: snake_case (e.g., parse_int, is_some).
|
||||||
|
- Classes: PascalCase (e.g., DateTime).
|
||||||
|
- Constants: ALL_CAPS when truly constant.
|
||||||
|
|
||||||
|
3. Layout
|
||||||
|
- Indent with two spaces; no tabs.
|
||||||
|
- One statement per line; no trailing spaces.
|
||||||
|
- Use blank lines sparingly to separate logical blocks.
|
||||||
|
|
||||||
|
4. Comments
|
||||||
|
- Line comments with //; block comments with /* ... */ for file headers and longer notes.
|
||||||
|
|
||||||
|
5. Collections
|
||||||
|
- Prefer [] for arrays and {"key": value} for dictionaries; use has_key() before subscripting unknown keys.
|
||||||
|
|
||||||
|
6. Error handling
|
||||||
|
- Prefer Result and Option helpers from lib/utils over ad-hoc nil checks.
|
||||||
|
- Avoid unwrap() in library code; propagate errors using and_then()/or_else() patterns.
|
||||||
|
|
||||||
|
7. Examples
|
||||||
|
- Keep examples executable via shebang: #!/usr/bin/env fun
|
||||||
|
- Print informative labels for outputs.
|
||||||
|
|
||||||
|
This guide will evolve. Contributions and suggestions are welcome.
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ Release profile example:
|
||||||
|
|
||||||
```
|
```
|
||||||
cmake --build build_release --target test_opcodes && ./build/test_opcodes
|
cmake --build build_release --target test_opcodes && ./build/test_opcodes
|
||||||
``>
|
```
|
||||||
|
|
||||||
If `fun_test` exists in your configuration:
|
If `fun_test` exists in your configuration:
|
||||||
|
|
||||||
|
|
@ -45,7 +45,8 @@ ctest --test-dir build -j
|
||||||
- C/C++/VM-side tests: look for existing tests under `src` or `spec` and mirror the structure. Add a new source and register it in CMake with an executable or via `add_test()`.
|
- C/C++/VM-side tests: look for existing tests under `src` or `spec` and mirror the structure. Add a new source and register it in CMake with an executable or via `add_test()`.
|
||||||
- Fun-level examples as tests: minimal scripts under `examples/` can act as smoke tests and are runnable via `./play.fun`. Consider adding a new example for new features and have CI invoke a subset.
|
- Fun-level examples as tests: minimal scripts under `examples/` can act as smoke tests and are runnable via `./play.fun`. Consider adding a new example for new features and have CI invoke a subset.
|
||||||
|
|
||||||
Guidelines:
|
### Guidelines:
|
||||||
|
|
||||||
- Keep each test focused; prefer several small tests over one monolith.
|
- Keep each test focused; prefer several small tests over one monolith.
|
||||||
- Avoid nondeterminism; set seeds where randomness is involved.
|
- Avoid nondeterminism; set seeds where randomness is involved.
|
||||||
- Make tests independent of the working directory unless the behavior under test is precisely path resolution.
|
- Make tests independent of the working directory unless the behavior under test is precisely path resolution.
|
||||||
|
|
|
||||||
0
examples/crypto/openssl_md5.fun
Normal file → Executable file
0
examples/crypto/openssl_md5.fun
Normal file → Executable file
0
examples/crypto/openssl_ripemd160.fun
Normal file → Executable file
0
examples/crypto/openssl_ripemd160.fun
Normal file → Executable file
0
examples/crypto/openssl_sha256.fun
Normal file → Executable file
0
examples/crypto/openssl_sha256.fun
Normal file → Executable file
0
examples/crypto/openssl_sha512.fun
Normal file → Executable file
0
examples/crypto/openssl_sha512.fun
Normal file → Executable file
64
examples/error_handling.fun
Executable file
64
examples/error_handling.fun
Executable file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#!/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-02-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Error handling examples using Result and Option helpers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <utils/option.fun>
|
||||||
|
#include <utils/result.fun>
|
||||||
|
|
||||||
|
// Simulate a fallible parse
|
||||||
|
fun parse_int(s)
|
||||||
|
if regex_match("^[+-]?[0-9]+$", s)
|
||||||
|
return ok(to_number(s))
|
||||||
|
return err("invalid integer: " + s)
|
||||||
|
|
||||||
|
// Divide two integers, handling division by zero
|
||||||
|
fun safe_div(a, b)
|
||||||
|
if b == 0
|
||||||
|
return err("division by zero")
|
||||||
|
return ok(a / b)
|
||||||
|
|
||||||
|
// Option example: get environment variable
|
||||||
|
fun get_env_opt(name)
|
||||||
|
v = env(name)
|
||||||
|
if v == nil
|
||||||
|
return none()
|
||||||
|
if v == ""
|
||||||
|
return none()
|
||||||
|
return some(v)
|
||||||
|
|
||||||
|
print("-- Result examples --")
|
||||||
|
r = parse_int("123")
|
||||||
|
print("parse_int 123 ok? " + to_string(is_ok(r)) + ", value: " + to_string(unwrap_or(r, -1)))
|
||||||
|
|
||||||
|
q = parse_int("x12")
|
||||||
|
print("parse_int x12 ok? " + to_string(is_ok(q)) + ", or default 0: " + to_string(unwrap_or(q, 0)))
|
||||||
|
|
||||||
|
print("chained and_then: '10' / '2'")
|
||||||
|
// Simpler explicit flow without nested anonymous functions
|
||||||
|
a = parse_int("10")
|
||||||
|
b = parse_int("2")
|
||||||
|
if is_ok(a) && is_ok(b)
|
||||||
|
res = safe_div(unwrap(a), unwrap(b))
|
||||||
|
else
|
||||||
|
if is_err(a)
|
||||||
|
res = a
|
||||||
|
else
|
||||||
|
res = b
|
||||||
|
print("=> ok? " + to_string(is_ok(res)) + ", value: " + to_string(unwrap_or(res, -1)))
|
||||||
|
|
||||||
|
print("-- Option examples --")
|
||||||
|
home = get_env_opt("HOME")
|
||||||
|
print("HOME is set? " + to_string(is_some(home)) + ", value: " + to_string(unwrap_or(home, "<none>")))
|
||||||
43
examples/match.fun
Executable file
43
examples/match.fun
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/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-02-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Demonstrates dynamic match() helper.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <utils/option.fun>
|
||||||
|
|
||||||
|
print("-- match-like with numbers --")
|
||||||
|
x = 0
|
||||||
|
if (x == -1)
|
||||||
|
print("minus one")
|
||||||
|
else if (x == 0)
|
||||||
|
print("zero")
|
||||||
|
else if (x > 0)
|
||||||
|
print("positive: " + to_string(x))
|
||||||
|
else
|
||||||
|
print("negative: " + to_string(x))
|
||||||
|
|
||||||
|
print("-- match-like with Option --")
|
||||||
|
o1 = some(42)
|
||||||
|
o2 = none()
|
||||||
|
|
||||||
|
if is_some(o1)
|
||||||
|
print("Some(" + to_string(unwrap(o1)) + ")")
|
||||||
|
else
|
||||||
|
print("None")
|
||||||
|
|
||||||
|
if is_some(o2)
|
||||||
|
print("Some(" + to_string(unwrap(o2)) + ")")
|
||||||
|
else
|
||||||
|
print("None")
|
||||||
36
lib/utils/match.fun
Normal file
36
lib/utils/match.fun
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* 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-02-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
// A simple dynamic match helper.
|
||||||
|
// Usage:
|
||||||
|
// match(x, [
|
||||||
|
// {"when": fun(v) v > 0, "then": fun(v) print("pos")},
|
||||||
|
// {"is": 0, "then": fun(v) print("zero")},
|
||||||
|
// {"else": fun(v) print("neg or other")}
|
||||||
|
// ])
|
||||||
|
|
||||||
|
fun match(value, cases)
|
||||||
|
// Iterate cases in order; support keys: is, when, else
|
||||||
|
for c in cases
|
||||||
|
if (has_key(c, "is"))
|
||||||
|
if (value == c["is"])
|
||||||
|
th = c["then"]
|
||||||
|
return th(value)
|
||||||
|
else if (has_key(c, "when"))
|
||||||
|
pred = c["when"]
|
||||||
|
if (pred(value))
|
||||||
|
th = c["then"]
|
||||||
|
return th(value)
|
||||||
|
else if (has_key(c, "else"))
|
||||||
|
el = c["else"]
|
||||||
|
return el(value)
|
||||||
|
// No match; return nil
|
||||||
|
return nil
|
||||||
71
lib/utils/option.fun
Normal file
71
lib/utils/option.fun
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* 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-02-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Option helpers for ergonomic error-absent values.
|
||||||
|
// Represented as dictionaries with a _tag and optional value field.
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
fun some(x)
|
||||||
|
return {"_tag": "Some", "value": x}
|
||||||
|
|
||||||
|
fun none()
|
||||||
|
return {"_tag": "None"}
|
||||||
|
|
||||||
|
// Predicates
|
||||||
|
fun is_some(opt)
|
||||||
|
return opt["_tag"] == "Some"
|
||||||
|
|
||||||
|
fun is_none(opt)
|
||||||
|
return opt["_tag"] == "None"
|
||||||
|
|
||||||
|
// Extractors
|
||||||
|
fun unwrap(opt)
|
||||||
|
if is_some(opt)
|
||||||
|
return opt["value"]
|
||||||
|
// Fall back to raising a runtime error
|
||||||
|
error("called unwrap() on None")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
fun unwrap_or(opt, default)
|
||||||
|
if is_some(opt)
|
||||||
|
return opt["value"]
|
||||||
|
return default
|
||||||
|
|
||||||
|
// Functional helpers
|
||||||
|
// map(opt, f) -> Some(f(value)) or None
|
||||||
|
fun option_map(opt, f)
|
||||||
|
if is_some(opt)
|
||||||
|
return some(f(opt["value"]))
|
||||||
|
return none()
|
||||||
|
|
||||||
|
// and_then(opt, f) where f: a -> Option[b]
|
||||||
|
fun and_then(opt, f)
|
||||||
|
if is_some(opt)
|
||||||
|
return f(opt["value"])
|
||||||
|
return none()
|
||||||
|
|
||||||
|
// or_else(opt, f) where f: () -> Option[a]
|
||||||
|
fun or_else(opt, f)
|
||||||
|
if is_some(opt)
|
||||||
|
return opt
|
||||||
|
return f()
|
||||||
|
|
||||||
|
// to_result(opt, err)
|
||||||
|
fun to_result(opt, err)
|
||||||
|
if is_some(opt)
|
||||||
|
return ok(opt["value"])
|
||||||
|
return err(err)
|
||||||
|
|
||||||
|
// Convenience: try_get(dict, key) -> Option[value]
|
||||||
|
fun try_get(dict, key)
|
||||||
|
if has_key(dict, key)
|
||||||
|
return some(dict[key])
|
||||||
|
return none()
|
||||||
74
lib/utils/result.fun
Normal file
74
lib/utils/result.fun
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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-02-19
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Result helpers: Ok(value) or Err(error)
|
||||||
|
|
||||||
|
// Constructors
|
||||||
|
fun ok(x)
|
||||||
|
return {"_tag": "Ok", "value": x}
|
||||||
|
|
||||||
|
fun err(e)
|
||||||
|
return {"_tag": "Err", "error": e}
|
||||||
|
|
||||||
|
// Predicates
|
||||||
|
fun is_ok(res)
|
||||||
|
return res["_tag"] == "Ok"
|
||||||
|
|
||||||
|
fun is_err(res)
|
||||||
|
return res["_tag"] == "Err"
|
||||||
|
|
||||||
|
// Extractors
|
||||||
|
fun unwrap(res)
|
||||||
|
if is_ok(res)
|
||||||
|
return res["value"]
|
||||||
|
error("called unwrap() on Err: " + to_string(res["error"]))
|
||||||
|
return nil
|
||||||
|
|
||||||
|
fun unwrap_or(res, default)
|
||||||
|
if is_ok(res)
|
||||||
|
return res["value"]
|
||||||
|
return default
|
||||||
|
|
||||||
|
fun unwrap_err(res)
|
||||||
|
if is_err(res)
|
||||||
|
return res["error"]
|
||||||
|
error("called unwrap_err() on Ok")
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// map(res, f) applies f to Ok value; Err passes through
|
||||||
|
fun result_map(res, f)
|
||||||
|
if is_ok(res)
|
||||||
|
return ok(f(res["value"]))
|
||||||
|
return res
|
||||||
|
|
||||||
|
// map_err(res, f) applies f to error; Ok passes through
|
||||||
|
fun map_err(res, f)
|
||||||
|
if is_err(res)
|
||||||
|
return err(f(res["error"]))
|
||||||
|
return res
|
||||||
|
|
||||||
|
// and_then(res, f) where f: a -> Result[b]
|
||||||
|
fun and_then(res, f)
|
||||||
|
if is_ok(res)
|
||||||
|
return f(res["value"]) // caller should return Result
|
||||||
|
return res
|
||||||
|
|
||||||
|
// or_else(res, f) where f: () -> Result[a]
|
||||||
|
fun or_else(res, f)
|
||||||
|
if is_ok(res)
|
||||||
|
return res
|
||||||
|
return f()
|
||||||
|
|
||||||
|
// to_option(res) -> Some(value) or None
|
||||||
|
fun to_option(res)
|
||||||
|
if is_ok(res)
|
||||||
|
return some(res["value"]) // requires option.fun loaded
|
||||||
|
return none()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue