Compare commits
6 commits
391a7689e5
...
6ee27276a8
| Author | SHA1 | Date | |
|---|---|---|---|
| 6ee27276a8 | |||
| a88eec7938 | |||
| 5e641da65d | |||
| b5864fe4bd | |||
| 302caf3ce7 | |||
| 0314f37f2c |
39 changed files with 563 additions and 89 deletions
31
examples/blocking/net/http.fun
Normal file
31
examples/blocking/net/http.fun
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Example: Simple HTTP server using stdlib net/http_server
|
||||
*
|
||||
* Run:
|
||||
* $ fun examples/blocking/net/http.fun
|
||||
*
|
||||
* It will serve files from the ./web directory on http://127.0.0.1:8080
|
||||
*
|
||||
* Test with curl in another terminal:
|
||||
* $ curl -v http://127.0.0.1:8080/
|
||||
* $ curl -v http://127.0.0.1:8080/index.html
|
||||
*
|
||||
* You can also place a .fun script in ./web (e.g. hello.fun) and call it:
|
||||
* $ curl -v 'http://127.0.0.1:8080/hello.fun?name=Fun'
|
||||
*/
|
||||
|
||||
#include <net/http_server.fun>
|
||||
|
||||
// Configure
|
||||
PORT = 8080
|
||||
DOC_ROOT = "./examples/data/htdocs/"
|
||||
|
||||
print("Starting simple HTTP server example…")
|
||||
print("Serving '" + DOC_ROOT + "' on http://127.0.0.1:" + to_string(PORT))
|
||||
print("Press Ctrl+C to stop.")
|
||||
|
||||
srv = HTTPServer(PORT)
|
||||
srv.set_htdocs(DOC_ROOT)
|
||||
|
||||
// Start (blocking loop)
|
||||
srv.start()
|
||||
370
lib/net/http.fun
Normal file
370
lib/net/http.fun
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
/*
|
||||
* 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-07-04
|
||||
*/
|
||||
|
||||
#include <io/socket.fun>
|
||||
#include <strings.fun>
|
||||
|
||||
/*
|
||||
* HTTP — minimal HTTP/1.1 server library (Phase 1)
|
||||
*
|
||||
* Goals for Phase 1:
|
||||
* - HTTP/1.1 request parsing (request-line, headers)
|
||||
* - Message body handling via Content-Length and Transfer-Encoding: chunked
|
||||
* - Persistent connections (keep-alive) with Connection: close handling
|
||||
* - Simple handler callback API: handler(req) -> res
|
||||
* - No TLS, no pipelining, no compression, no auth (to be added later)
|
||||
*
|
||||
* API overview:
|
||||
* class HTTP(number port)
|
||||
* fun start(this, handler)
|
||||
* - handler: function taking `req` map and returning `res` map
|
||||
*
|
||||
* Request map `req` fields:
|
||||
* - method: string
|
||||
* - target: raw request-target (string)
|
||||
* - path: path part without query (string)
|
||||
* - query: query string without '?' (string)
|
||||
* - version: "HTTP/1.1" or "HTTP/1.0"
|
||||
* - headers: map<string,string> (lower-cased header names)
|
||||
* - body: string (for Phase 1; streaming may arrive later)
|
||||
* - remote_fd: number (client socket fd)
|
||||
*
|
||||
* Response map `res` fields:
|
||||
* - status: number (e.g. 200)
|
||||
* - reason: optional string (defaults from common codes)
|
||||
* - headers: optional map<string,string>
|
||||
* - body: optional string (defaults to empty)
|
||||
*/
|
||||
|
||||
// Utility: common status text
|
||||
fun _http_reason(status)
|
||||
code = to_number(status)
|
||||
if (code == 200) return "OK"
|
||||
if (code == 201) return "Created"
|
||||
if (code == 202) return "Accepted"
|
||||
if (code == 204) return "No Content"
|
||||
if (code == 301) return "Moved Permanently"
|
||||
if (code == 302) return "Found"
|
||||
if (code == 303) return "See Other"
|
||||
if (code == 304) return "Not Modified"
|
||||
if (code == 307) return "Temporary Redirect"
|
||||
if (code == 308) return "Permanent Redirect"
|
||||
if (code == 400) return "Bad Request"
|
||||
if (code == 401) return "Unauthorized"
|
||||
if (code == 403) return "Forbidden"
|
||||
if (code == 404) return "Not Found"
|
||||
if (code == 405) return "Method Not Allowed"
|
||||
if (code == 408) return "Request Timeout"
|
||||
if (code == 411) return "Length Required"
|
||||
if (code == 413) return "Payload Too Large"
|
||||
if (code == 414) return "URI Too Long"
|
||||
if (code == 415) return "Unsupported Media Type"
|
||||
if (code == 417) return "Expectation Failed"
|
||||
if (code == 426) return "Upgrade Required"
|
||||
if (code == 431) return "Request Header Fields Too Large"
|
||||
if (code == 500) return "Internal Server Error"
|
||||
if (code == 501) return "Not Implemented"
|
||||
if (code == 503) return "Service Unavailable"
|
||||
if (code == 505) return "HTTP Version Not Supported"
|
||||
return ""
|
||||
|
||||
// Utility: write full response and return whether connection should stay open
|
||||
fun _http_write_response(fd, res, keep_alive)
|
||||
status = to_number(res["status"])
|
||||
reason = res["reason"]
|
||||
if (len(to_string(reason)) == 0)
|
||||
reason = _http_reason(status)
|
||||
if (len(reason) == 0)
|
||||
reason = "OK"
|
||||
|
||||
body = res["body"]
|
||||
if (typeof(body) != "string")
|
||||
body = to_string(body)
|
||||
blen = len(body)
|
||||
|
||||
headers = res["headers"]
|
||||
if (typeof(headers) != "map")
|
||||
headers = {}
|
||||
|
||||
// Ensure Content-Length for simple bodies (no streaming in Phase 1)
|
||||
headers["content-length"] = to_string(blen)
|
||||
if (len(to_string(headers["content-type"])) == 0)
|
||||
headers["content-type"] = "text/plain; charset=utf-8"
|
||||
|
||||
// Connection header per keep-alive flag
|
||||
if (keep_alive)
|
||||
headers["connection"] = "keep-alive"
|
||||
else
|
||||
headers["connection"] = "close"
|
||||
|
||||
// Build response string
|
||||
start = "HTTP/1.1 " + to_string(status) + " " + reason + "\r\n"
|
||||
head = ""
|
||||
// Normalize: send headers with canonical casing best-effort
|
||||
// but here we just send lower-case names as kept
|
||||
for (k in keys(headers))
|
||||
v = to_string(headers[k])
|
||||
// join: "name: value\r\n"
|
||||
head = head + k + ": " + v + "\r\n"
|
||||
|
||||
resp = start + head + "\r\n" + body
|
||||
sock_send(fd, resp)
|
||||
return keep_alive
|
||||
|
||||
// Utility: decode chunked transfer-encoding body from a buffer and fd (if needs more)
|
||||
fun _http_read_chunked(fd, after_headers, max_body)
|
||||
// after_headers: initial buffer containing any bytes after CRLFCRLF
|
||||
data = after_headers
|
||||
body = ""
|
||||
// Read line by line: <hex-size> [;ext] CRLF, then that many bytes, then CRLF, repeat, then trailer part ends with CRLF CRLF
|
||||
// For simplicity, we ignore chunk extensions and trailers in Phase 1.
|
||||
while true
|
||||
// ensure we have a full line
|
||||
while find(data, "\r\n") < 0
|
||||
more = sock_recv(fd, 4096)
|
||||
if (len(more) == 0) return {"ok": 0, "body": body}
|
||||
data = data + more
|
||||
if (max_body > 0 && len(body) + len(data) > max_body) return {"ok": 0, "body": body}
|
||||
|
||||
pos = find(data, "\r\n")
|
||||
line = substr(data, 0, pos)
|
||||
data = substr(data, pos + 2, len(data) - pos - 2)
|
||||
semi = find(line, ";")
|
||||
size_str = line
|
||||
if (semi >= 0)
|
||||
size_str = substr(line, 0, semi)
|
||||
size_str = str_trim(size_str)
|
||||
// hex to number
|
||||
// Fun doesn’t have hex parse; do manual
|
||||
n = 0
|
||||
i = 0
|
||||
while i < len(size_str)
|
||||
ch = substr(size_str, i, 1)
|
||||
cl = str_to_lower(ch)
|
||||
d = -1
|
||||
if (cl >= "0" && cl <= "9") d = to_number(cl) - to_number("0")
|
||||
else if (cl >= "a" && cl <= "f") d = 10 + (to_number(cl) - to_number("a"))
|
||||
if (d < 0)
|
||||
return {"ok": 0, "body": body}
|
||||
n = n * 16 + d
|
||||
i = i + 1
|
||||
|
||||
// read exactly n bytes
|
||||
while len(data) < n + 2
|
||||
more2 = sock_recv(fd, 4096)
|
||||
if (len(more2) == 0) return {"ok": 0, "body": body}
|
||||
data = data + more2
|
||||
if (max_body > 0 && len(body) + len(data) > max_body) return {"ok": 0, "body": body}
|
||||
|
||||
chunk = substr(data, 0, n)
|
||||
crlf = substr(data, n, 2)
|
||||
// Must be CRLF after chunk
|
||||
if (crlf != "\r\n") return {"ok": 0, "body": body}
|
||||
data = substr(data, n + 2, len(data) - n - 2)
|
||||
body = body + chunk
|
||||
if (max_body > 0 && len(body) > max_body) return {"ok": 0, "body": body}
|
||||
if (n == 0)
|
||||
// reach end; ignore trailers (until CRLFCRLF)
|
||||
// optional: consume trailers
|
||||
// try to find CRLFCRLF in remaining data; if not there, read minimal amount
|
||||
// but for Phase 1 we stop here
|
||||
return {"ok": 1, "body": body}
|
||||
|
||||
class HTTP(number port)
|
||||
|
||||
// Configuration limits (conservative defaults)
|
||||
max_header_bytes = 32 * 1024
|
||||
max_start_line_bytes = 8 * 1024
|
||||
max_body_bytes = 8 * 1024 * 1024 // 8 MiB for Phase 1
|
||||
read_chunk_size = 4096
|
||||
|
||||
fun _construct(this, port)
|
||||
this.port = port
|
||||
this.server = TcpServer(port, 128)
|
||||
|
||||
// Start the HTTP server. `handler` is a function: handler(req) -> res
|
||||
fun start(this, handler)
|
||||
if (this.server.listen() <= 0)
|
||||
print("HTTP: failed to listen on port " + to_string(this.port))
|
||||
return 0
|
||||
print("HTTP: listening on port " + to_string(this.port))
|
||||
while true
|
||||
cfd = this.server.accept()
|
||||
if (cfd > 0)
|
||||
this._handle_connection(cfd, handler)
|
||||
return 1
|
||||
|
||||
// Connection loop with keep-alive
|
||||
fun _handle_connection(this, fd, handler)
|
||||
// read-buffer that may carry over data between requests on same socket
|
||||
carry = ""
|
||||
while true
|
||||
parsed = this._read_and_parse_request(fd, carry)
|
||||
if (parsed["ok"] != 1)
|
||||
break
|
||||
req = parsed["req"]
|
||||
carry = parsed["carry"]
|
||||
|
||||
// handler returns res map
|
||||
res = handler(req)
|
||||
if (typeof(res) != "map")
|
||||
res = {"status": 500, "reason": "Internal Server Error", "body": "Handler error"}
|
||||
|
||||
// decide keep-alive
|
||||
h_conn = req["headers"]["connection"]
|
||||
ver = req["version"]
|
||||
// default keep-alive for HTTP/1.1 unless Connection: close
|
||||
keep = (ver == "HTTP/1.1") && (str_to_lower(to_string(h_conn)) != "close")
|
||||
if (str_to_lower(to_string(h_conn)) == "keep-alive")
|
||||
keep = true
|
||||
if (str_to_lower(to_string(h_conn)) == "close")
|
||||
keep = false
|
||||
|
||||
stayed = _http_write_response(fd, res, keep)
|
||||
if (!stayed)
|
||||
break
|
||||
|
||||
sock_close(fd)
|
||||
return 1
|
||||
|
||||
// Reads from fd until a full request is available or error; returns {ok, req, carry}
|
||||
fun _read_and_parse_request(this, fd, carry)
|
||||
data = carry
|
||||
// Accumulate until we see end of headers (CRLFCRLF or LFLF)
|
||||
// also enforce size limits
|
||||
while true
|
||||
// Check if headers end exists
|
||||
p = find(data, "\r\n\r\n")
|
||||
if (p < 0)
|
||||
p = find(data, "\n\n")
|
||||
if (p >= 0)
|
||||
break
|
||||
if (len(data) > this.max_header_bytes + this.max_start_line_bytes)
|
||||
return {"ok": 0}
|
||||
more = sock_recv(fd, this.read_chunk_size)
|
||||
if (len(more) == 0)
|
||||
return {"ok": 0}
|
||||
data = data + more
|
||||
|
||||
// Split start-line and headers
|
||||
// Find first line break
|
||||
lf = find(data, "\n")
|
||||
if (lf < 0 || lf > this.max_start_line_bytes)
|
||||
return {"ok": 0}
|
||||
// Handle CRLF or LF
|
||||
first_line = substr(data, 0, (lf > 0 && substr(data, lf-1, 1) == "\r") ? lf-1 : lf)
|
||||
headers_block = substr(data, (substr(data, lf-1, 1) == "\r") ? lf+1 : lf+1, // start after first line CRLF/LF
|
||||
0) // placeholder, will correct below
|
||||
|
||||
// Find header-body separator
|
||||
sep = find(data, "\r\n\r\n")
|
||||
sep_len = 4
|
||||
if (sep < 0)
|
||||
sep = find(data, "\n\n")
|
||||
sep_len = 2
|
||||
headers_raw = substr(data, (substr(data, lf-1, 1) == "\r") ? lf+1 : lf+1, sep - ((substr(data, lf-1, 1) == "\r") ? (lf+1) : (lf+1)))
|
||||
after = substr(data, sep + sep_len, len(data) - sep - sep_len)
|
||||
|
||||
// Parse start-line: METHOD SP request-target SP HTTP/version
|
||||
parts = str_split(first_line, " ")
|
||||
if (len(parts) < 3)
|
||||
return {"ok": 0}
|
||||
method = str_trim(parts[0])
|
||||
target = str_trim(parts[1])
|
||||
version = str_trim(parts[2])
|
||||
if (version != "HTTP/1.1" && version != "HTTP/1.0")
|
||||
return {"ok": 0}
|
||||
|
||||
// Parse headers into map with lower-cased names; ignore obs-fold
|
||||
hdr_lines = str_split(headers_raw, "\n")
|
||||
headers = {}
|
||||
i = 0
|
||||
while i < len(hdr_lines)
|
||||
line = hdr_lines[i]
|
||||
line = str_trim(line)
|
||||
if (len(line) == 0)
|
||||
i = i + 1
|
||||
continue
|
||||
colon = find(line, ":")
|
||||
if (colon <= 0)
|
||||
// invalid header; reject
|
||||
return {"ok": 0}
|
||||
name = str_to_lower(str_trim(substr(line, 0, colon)))
|
||||
value = str_trim(substr(line, colon + 1, len(line) - colon - 1))
|
||||
// merge duplicates with comma
|
||||
prev = headers[name]
|
||||
if (len(to_string(prev)) > 0)
|
||||
headers[name] = prev + ", " + value
|
||||
else
|
||||
headers[name] = value
|
||||
i = i + 1
|
||||
|
||||
// Handle request-target: split path and query
|
||||
path = target
|
||||
query = ""
|
||||
qpos = find(target, "?")
|
||||
if (qpos >= 0)
|
||||
path = substr(target, 0, qpos)
|
||||
query = substr(target, qpos + 1, len(target) - qpos - 1)
|
||||
|
||||
// Determine body
|
||||
body = ""
|
||||
// transfer-encoding has precedence
|
||||
te = str_to_lower(to_string(headers["transfer-encoding"]))
|
||||
cl = to_string(headers["content-length"])
|
||||
if (len(te) > 0)
|
||||
if (te == "chunked")
|
||||
dec = _http_read_chunked(fd, after, this.max_body_bytes)
|
||||
if (dec["ok"] != 1)
|
||||
return {"ok": 0}
|
||||
body = dec["body"]
|
||||
after = ""
|
||||
else
|
||||
// unsupported TE
|
||||
return {"ok": 0}
|
||||
else if (len(cl) > 0)
|
||||
// parse numeric Content-Length
|
||||
n = 0
|
||||
j = 0
|
||||
while j < len(cl)
|
||||
ch = substr(cl, j, 1)
|
||||
if (!(ch >= "0" && ch <= "9"))
|
||||
return {"ok": 0}
|
||||
n = n * 10 + (to_number(ch) - to_number("0"))
|
||||
j = j + 1
|
||||
if (n > this.max_body_bytes)
|
||||
return {"ok": 0}
|
||||
// ensure we have n bytes in `after`, otherwise read
|
||||
while len(after) < n
|
||||
moreb = sock_recv(fd, this.read_chunk_size)
|
||||
if (len(moreb) == 0)
|
||||
return {"ok": 0}
|
||||
after = after + moreb
|
||||
if (len(after) > this.max_body_bytes)
|
||||
return {"ok": 0}
|
||||
body = substr(after, 0, n)
|
||||
after = substr(after, n, len(after) - n)
|
||||
else
|
||||
// no body; leave `after` as carry for potential next requests
|
||||
body = ""
|
||||
|
||||
req = {
|
||||
"method": method,
|
||||
"target": target,
|
||||
"path": path,
|
||||
"query": query,
|
||||
"version": version,
|
||||
"headers": headers,
|
||||
"body": body,
|
||||
"remote_fd": fd
|
||||
}
|
||||
|
||||
return {"ok": 1, "req": req, "carry": after}
|
||||
|
|
@ -33,8 +33,8 @@ mastodon: https://fosstodon.org/@fun
|
|||
truncatewords_length: 100000
|
||||
|
||||
# Set to your git root and branch to show a link to the file source in the footer. Unset to disable.
|
||||
git: https://git.xw3.org/fun/fun/
|
||||
git_short: https://git.xw3.org
|
||||
git: https://xw3.org/fun/fun/
|
||||
git_short: https://xw3.org
|
||||
branch: src/branch/main/
|
||||
|
||||
lifetime: 3600
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
<li style="display:inline;"> <a href="{{ site.mastodon }}" style="text-decoration:none;" title="Mastodon" class="mastodon" rel="me">Mastodon</a> -</li>
|
||||
<li style="display:inline;"> <a href="{{ site.git }}wiki" style="text-decoration:none;" title="Wiki" class="wiki">Wiki</a></li>
|
||||
</ul>
|
||||
<p style="text-align:center;"><a href="/copyright/" style="text-decoration:none;" title="Copyright">Copyright</a> © {{ site.time | date: "%Y" }} - Johannes Findeisen (<a href="https://git.xw3.org/hanez" class="user">hanez</a>)</p>
|
||||
<p style="text-align:center;"><a href="/copyright/" style="text-decoration:none;" title="Copyright">Copyright</a> © {{ site.time | date: "%Y" }} - Johannes Findeisen (<a href="https://xw3.org/hanez" class="user">hanez</a>)</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,4 +15,15 @@
|
|||
<script src="/js/jquery-3.6.4.min.js" charset="{{ site.charset }}"></script>
|
||||
<script src="/js/js-cookie-3.0.5.min.js" charset="{{ site.charset }}"></script>
|
||||
<script src="/js/matomo.min.js" charset="{{ site.charset }}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const links = document.querySelectorAll('a');
|
||||
links.forEach(link => {
|
||||
if (link.hostname !== window.location.hostname) {
|
||||
link.setAttribute('target', '_blank');
|
||||
link.setAttribute('rel', 'noopener noreferrer');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ layout:
|
|||
</div>
|
||||
|
||||
<p style="text-align: center; color: #e8e8e8; font-size: 12px;">
|
||||
Page generated at: {{ site.time | date: "%b %-d, %Y" }} using <a href="https://jekyllrb.com/">Jekyll</a> and <a href="https://git.xw3.org/xw3/xw3">xw3</a> on {{ site.os }}<br>
|
||||
Page generated at: {{ site.time | date: "%b %-d, %Y" }} using <a href="https://jekyllrb.com/">Jekyll</a> and <a href="https://xw3.org/xw3/xw3">xw3</a> on {{ site.os }}<br>
|
||||
<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/" style="text-decoration:none;" title="License"><img src="/images/by-nc-sa-88x31.png"></a></p>
|
||||
|
||||
<script src="/js/sorttable-2.min.js" charset="utf-8"></script>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
title: Init Fun!
|
||||
description: Blog - Init Fun!
|
||||
date: 2025-10-05
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Announcing Fun 0.37.62
|
||||
description: Homepage and Blog
|
||||
|
|
@ -20,7 +20,7 @@ tags:
|
|||
- language
|
||||
---
|
||||
|
||||
### First at all! Special thanks go to my wonderful girlfriend Sabine, my friends [Armin](https://m2m.pm/){:class="ext"} and [Ole](https://schizzoteam.org/){:class="ext"}, who motivated me over the past month to implement this. Cheers! Markus, I miss YOU, R.I.P!
|
||||
### First at all! Special thanks go to my wonderful friends Sabine, [Armin](https://m2m.pm/){:class="ext"} and Ole, who motivated me over the past month to implement this. Cheers! Markus, I miss YOU, R.I.P!
|
||||
|
||||
We're excited to announce the release of Fun version 0.37.62, the latest iteration of our experimental programming language that prioritizes simplicity, consistency, and most importantly — joy in coding.
|
||||
|
||||
|
|
@ -142,12 +142,12 @@ Important: Fun is not developed on third-party Git servers like GitHub or GitLab
|
|||
|
||||
If you're curious about Fun, check out:
|
||||
|
||||
- [Git Repository](https://git.xw3.org/fun/fun){:class="git"}
|
||||
- [Git Repository](https://xw3.org/fun/fun){:class="git"}
|
||||
- [Fun Handbook](https://fun-lang.xyz/documentation/handbook/)
|
||||
- [Fun REPL Guide](https://fun-lang.xyz/documentation/repl/)
|
||||
- [Specification v0.4](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.4.md){:class="git"}
|
||||
- [Examples](https://git.xw3.org/fun/fun/src/branch/main/examples){:class="git"}
|
||||
- [Standard Library](https://git.xw3.org/fun/fun/src/branch/main/lib){:class="git"}
|
||||
- [Specification v0.4](https://xw3.org/fun/fun/src/branch/main/spec/v0.4.md){:class="git"}
|
||||
- [Examples](https://xw3.org/fun/fun/src/branch/main/examples){:class="git"}
|
||||
- [Standard Library](https://xw3.org/fun/fun/src/branch/main/lib){:class="git"}
|
||||
- [Community](https://fun-lang.xyz/community/)
|
||||
|
||||
The examples directory contains demonstrations of most Fun features, from basic "Hello, World!" to threading, networking, classes, and more. The lib directory includes modules written in Fun itself - and in the future, most enhancements will be written in Fun rather than C.
|
||||
|
|
@ -169,3 +169,4 @@ Fun is and will always be 100% free under the terms of the [Apache-2.0 License](
|
|||
Happy hacking, and remember: Fun is Fun!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Moving the Website to the Project Root
|
||||
description: Relocating the fun-lang.xyz source code for easier maintenance.
|
||||
|
|
@ -22,7 +22,7 @@ We've recently completed a significant change to our project structure: the sour
|
|||
|
||||
### Why the Move?
|
||||
|
||||
The main reason for merging this code into the root of the Fun project is that it is much easier for me to maintain the whole project this way.
|
||||
The main reason for merging this code into the root of the Fun project is that it is much easier for me to maintain the whole project this way.
|
||||
|
||||
Having everything in one place simplifies our development workflow:
|
||||
|
||||
|
|
@ -43,3 +43,4 @@ For most users, nothing changes! The website remains available at [https://fun-l
|
|||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Announcing Fun 0.40.5
|
||||
description: Nested functions, Rust integration, and CI automation
|
||||
date: 2026-04-17
|
||||
date_updated: 2026-04-18 00:17:13
|
||||
date_updated: 2026-04-18
|
||||
categories:
|
||||
- news
|
||||
- project
|
||||
|
|
@ -85,8 +85,9 @@ The Fun VM is now more flexible than ever. Key parameters like `MAX_FRAMES`, `ST
|
|||
|
||||
As always, Fun remains an experiment, but with version 0.40.5, we are closer than ever to a stable 1.0. We've focused on cleaning up experimental features that didn't fit our minimalism philosophy while adding the powerful primitives needed for real-world hacking.
|
||||
|
||||
Want to try it out? Head over to our [Git Repository](https://git.xw3.org/fun/fun){:class="git"} or check out the updated [Documentation](https://fun-lang.xyz/documentation/).
|
||||
Want to try it out? Head over to our [Git Repository](https://xw3.org/fun/fun){:class="git"} or check out the updated [Documentation](https://fun-lang.xyz/documentation/).
|
||||
|
||||
Happy hacking, and remember: Fun is Fun!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Call for Participants and Sponsors
|
||||
description: Join us in shaping the future of the Fun programming language
|
||||
|
|
@ -46,7 +46,7 @@ If you or your organization are interested in supporting Fun, please reach out t
|
|||
|
||||
If you're interested in participating or sponsoring, please:
|
||||
|
||||
- Visit our [Git Repository](https://git.xw3.org/fun/fun){:class="git"} and check out the open issues.
|
||||
- Visit our [Git Repository](https://xw3.org/fun/fun){:class="git"} and check out the open issues.
|
||||
- Join our [community discussions](https://fun-lang.xyz/community/).
|
||||
- Contact Johannes Findeisen (hanez) directly via [email](mailto:hanez@fun-lang.xyz){:class="mail"}.
|
||||
|
||||
|
|
@ -55,3 +55,4 @@ Every contribution, no matter how small, helps us move closer to our goal of mak
|
|||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: hanez@fun-lang.xyz
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Announcing the Fun API Documentation
|
||||
description: A new API documentation hub is now available at fun-lang.xyz/api to explore the Fun VM internals and get contributors started faster.
|
||||
|
|
@ -46,7 +46,7 @@ This is actually a work in progress, and we are continuously improving and expan
|
|||
### How to get involved
|
||||
|
||||
- Browse the API docs at [https://api.fun-lang.xyz/](https://api.fun-lang.xyz/){:class="ext"} and send feedback.
|
||||
- Open issues and ideas in our Git repository: [https://git.xw3.org/fun/fun](https://git.xw3.org/fun/fun){:class="git"}.
|
||||
- Open issues and ideas in our Git repository: [https://xw3.org/fun/fun](https://xw3.org/fun/fun){:class="git"}.
|
||||
- Join the discussion: [https://fun-lang.xyz/community/](https://fun-lang.xyz/community/).
|
||||
- Reach out directly: [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}.
|
||||
|
||||
|
|
@ -55,3 +55,4 @@ This is just the beginning - we will keep iterating on structure, coverage, and
|
|||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: hanez@fun-lang.xyz
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: What is Fun?
|
||||
description: Fun is a serious, focused programming language and VM aimed at professional environments — used for serial and smartcard communication and, potentially, TCP/Unix sockets.
|
||||
|
|
@ -72,4 +72,5 @@ If you work with serial devices, smartcards, or lightweight networked tools and
|
|||
|
||||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - hanez@fun-lang.xyz
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
noToc: false
|
||||
title: Announcing Fun 0.42.1
|
||||
description: Maintenance release with documentation updates and a small test script fix
|
||||
|
|
@ -55,4 +55,5 @@ Thanks to everyone testing, filing issues, and contributing examples and docs. I
|
|||
|
||||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) — hanez@fun-lang.xyz
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
|
|
|
|||
|
|
@ -93,62 +93,6 @@ blockquote {
|
|||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
background: $grey-color-dark;
|
||||
/*background-image: -webkit-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -moz-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -ms-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -o-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: linear-gradient($grey-color-dark 50%, $grey-color-light 50%);*/
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat;
|
||||
background-size: 2.8em 2.8em;
|
||||
border: 0px $grey-color solid;
|
||||
border-radius: 8px;
|
||||
counter-reset: line-numbering;
|
||||
font-family: $base-font-family;
|
||||
font-size: $base-font-size;
|
||||
// https://stackoverflow.com/questions/5840328/styling-each-line-inside-pre-with-css
|
||||
line-height: 1.4em;
|
||||
margin: 0 0 0 0;
|
||||
overflow: auto;
|
||||
overflow-x: auto;
|
||||
padding: 8px;
|
||||
tab-size: 2;
|
||||
-moz-tab-size: 2;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*pre::before {
|
||||
content: counter(linex);
|
||||
counter-increment: linex;
|
||||
padding-right: 1em;
|
||||
width: 1.5em;
|
||||
text-align: right;
|
||||
opacity: 0.5;
|
||||
}*/
|
||||
|
||||
/*pre > code {
|
||||
font-family: $base-font-family;
|
||||
font-size: $base-font-size;
|
||||
counter-increment: line;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0;
|
||||
}*/
|
||||
|
||||
/**pre > code:before{
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 2.0em;
|
||||
border-right: 1px solid $grey-color;
|
||||
padding-right: .7em;
|
||||
margin-right: .7em;
|
||||
color: #bc6c21;
|
||||
-webkit-user-select: none;
|
||||
text-align: right;
|
||||
}**/
|
||||
|
||||
audio {
|
||||
height: 24px;
|
||||
width: 100%;
|
||||
|
|
|
|||
26
web/_sass/_code.scss
Normal file
26
web/_sass/_code.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
code {
|
||||
//-moz-tab-size: 2;
|
||||
//tab-size: 2;
|
||||
//border-radius: 12px;
|
||||
font-family: "CourierPrimeSans";
|
||||
font-size: $base-font-size;
|
||||
//margin: 0 0 0 0;
|
||||
//padding: 0 0 0 0;
|
||||
//border: 1px $grey-color solid;
|
||||
//line-height: 1.2em;
|
||||
//background: $grey-color-dark;
|
||||
//background-image: -webkit-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
//background-image: -moz-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
//background-image: -ms-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
//background-image: -o-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
//background-image: linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
//background-position: 0 0;
|
||||
//background-repeat: repeat;
|
||||
//background-size: 2.8em 2.8em;
|
||||
//white-space: pre-wrap;
|
||||
//white-space: -moz-pre-wrap;
|
||||
//white-space: -o-pre-wrap;
|
||||
//word-wrap: break-word;
|
||||
//width: 100%;
|
||||
}
|
||||
|
||||
|
|
@ -7,3 +7,38 @@
|
|||
font-family: "MDI";
|
||||
src: url("../fonts/materialdesignicons-webfont.woff2") format("woff2");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'OpenSans-Regular';
|
||||
src: url('/fonts/OpenSans-Regular.woff') format('woff');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MDI';
|
||||
src: url('/fonts/materialdesignicons-webfont.woff2') format('woff2');
|
||||
}
|
||||
|
||||
.mdi:before,
|
||||
.mdi-set {
|
||||
display: inline-block;
|
||||
font: normal normal normal 24px/1 "MDI";
|
||||
font-size: inherit;
|
||||
text-rendering: auto;
|
||||
line-height: inherit;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.mdi-hamburger::before {
|
||||
content: "\F0685";
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Graph-35-pix';
|
||||
src: url('/fonts/Graph-35-pix.ttf');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CourierPrimeSans';
|
||||
src: url('/fonts/Courier-Prime-Sans.ttf');
|
||||
}
|
||||
|
|
|
|||
45
web/_sass/_pre.scss
Normal file
45
web/_sass/_pre.scss
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
pre {
|
||||
//counter-reset: line;
|
||||
-moz-tab-size: 2;
|
||||
tab-size: 2;
|
||||
border-radius: 12px;
|
||||
//color: #363c3e;
|
||||
font-family: "CourierPrimeSans";
|
||||
//font-weight: bold;
|
||||
font-size: $base-font-size;
|
||||
margin: 0 0 0 0;
|
||||
padding: 0 0 0 0.5em;
|
||||
border: 1px $grey-color solid;
|
||||
//overflow: auto;
|
||||
overflow-x:auto;
|
||||
line-height: 1.2em;
|
||||
// https://stackoverflow.com/questions/5840328/styling-each-line-inside-pre-with-css
|
||||
background: $grey-color-dark;
|
||||
background-image: -webkit-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -moz-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -ms-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: -o-linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-image: linear-gradient($grey-color-dark 50%, $grey-color-light 50%);
|
||||
background-position: 0 0;
|
||||
background-repeat: repeat;
|
||||
background-size: 2.4em 2.4em;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
//pre > code {
|
||||
// margin: 0 0 0 0;
|
||||
// padding: 0 0 0 0;
|
||||
//}
|
||||
|
||||
/*pre.highlight > code:before{
|
||||
content: counter(line);
|
||||
display: inline-block;
|
||||
width: 2.0em;
|
||||
border-right: 1px solid $grey-color;
|
||||
padding-right: .7em;
|
||||
margin-right: .7em;
|
||||
color: #bc6c21;
|
||||
-webkit-user-select: none;
|
||||
text-align: right;
|
||||
}*/
|
||||
|
||||
|
|
@ -31,4 +31,4 @@ $on-laptop: 1024px;
|
|||
@content;
|
||||
}
|
||||
}
|
||||
@import "base", "fonts", "forms", "highlight", "isso", "layout", "links", "toc";
|
||||
@import "base", "code", "fonts", "forms", "highlight", "isso", "layout", "links", "pre", "toc";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tags:
|
|||
---
|
||||
|
||||
|
||||
This document summarizes the Fun bytecode format. For deep VM details, see [internals](./internals/).
|
||||
This document summarizes the Fun bytecode format. For deep VM details, see [internals](/documentation/internals/).
|
||||
|
||||
## Goals
|
||||
- Compact representation for fast loading and dispatch
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ This file serves as an index of the documents in this directory. Links are relat
|
|||
- [REPL](./repl/) - REPL user guide: how to build/launch, editing and history, completions, REPL-on-error, and tips.
|
||||
- [API](https://api.fun-lang.xyz/){:class="ext"} - API documentation for the Fun language and VM.
|
||||
- Specification
|
||||
- [v0.4](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.4/){:class="git"}
|
||||
- [v0.3](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.3/){:class="git"}
|
||||
- [v0.2](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.2/){:class="git"}
|
||||
- [v0.1](https://git.xw3.org/fun/fun/src/branch/main/spec/v0.1/){:class="git"}
|
||||
- [Examples](https://git.xw3.org/fun/fun/src/branch/main/examples){:class="git"}
|
||||
- [Standard Library](https://git.xw3.org/fun/fun/src/branch/main/lib){:class="git"}
|
||||
- [v0.4](https://xw3.org/fun/fun/src/branch/main/spec/v0.4.md){:class="git"}
|
||||
- [v0.3](https://xw3.org/fun/fun/src/branch/main/spec/v0.3.md){:class="git"}
|
||||
- [v0.2](https://xw3.org/fun/fun/src/branch/main/spec/v0.2.md){:class="git"}
|
||||
- [v0.1](https://xw3.org/fun/fun/src/branch/main/spec/v0.1.md){:class="git"}
|
||||
- [Examples](https://xw3.org/fun/fun/src/branch/main/examples){:class="git"}
|
||||
- [Standard Library](https://xw3.org/fun/fun/src/branch/main/lib){:class="git"}
|
||||
|
||||
The examples directory contains demonstrations of most Fun features, from basic "Hello, World!" to threading, networking, classes, and more. The lib directory includes modules written in Fun itself.
|
||||
|
||||
|
|
@ -86,3 +86,4 @@ Documentation for optional, build-time selectable integrations lives in [./exten
|
|||
- For a broader project overview and quickstart, see the repository root [README](./../README/).
|
||||
- Crypto examples:
|
||||
- If built with `-DFUN_WITH_OPENSSL=ON`, try `examples/crypto/openssl_md5.fun`.
|
||||
|
||||
|
|
|
|||
|
|
@ -96,3 +96,4 @@ The website can be deployed using the Makefile:
|
|||
make release</pre>
|
||||
|
||||
The `release` target builds the site, minifies the assets, and syncs the files to [fun-lang.xyz](https://fun-lang.xyz) via `rsync`. Ensure you have the necessary SSH permissions for the sync to succeed.
|
||||
|
||||
|
|
|
|||
BIN
web/fonts/Glass_TTY_VT220.ttf
Normal file
BIN
web/fonts/Glass_TTY_VT220.ttf
Normal file
Binary file not shown.
BIN
web/fonts/Graph-35-pix.ttf
Normal file
BIN
web/fonts/Graph-35-pix.ttf
Normal file
Binary file not shown.
BIN
web/fonts/OpenSans-Regular.woff
Normal file
BIN
web/fonts/OpenSans-Regular.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-Black.woff
Normal file
BIN
web/fonts/Vollkorn-Black.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-BlackItalic.woff
Normal file
BIN
web/fonts/Vollkorn-BlackItalic.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-Bold.woff
Normal file
BIN
web/fonts/Vollkorn-Bold.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-BoldItalic.woff
Normal file
BIN
web/fonts/Vollkorn-BoldItalic.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-ExtraBold.woff
Normal file
BIN
web/fonts/Vollkorn-ExtraBold.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-ExtraBoldItalic.woff
Normal file
BIN
web/fonts/Vollkorn-ExtraBoldItalic.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-Italic.woff
Normal file
BIN
web/fonts/Vollkorn-Italic.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-Medium.woff
Normal file
BIN
web/fonts/Vollkorn-Medium.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-MediumItalic.woff
Normal file
BIN
web/fonts/Vollkorn-MediumItalic.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-Regular.woff
Normal file
BIN
web/fonts/Vollkorn-Regular.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-SemiBold.woff
Normal file
BIN
web/fonts/Vollkorn-SemiBold.woff
Normal file
Binary file not shown.
BIN
web/fonts/Vollkorn-SemiBoldItalic.woff
Normal file
BIN
web/fonts/Vollkorn-SemiBoldItalic.woff
Normal file
Binary file not shown.
BIN
web/images/supportme.png
Normal file
BIN
web/images/supportme.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -19,6 +19,8 @@ permalink: /
|
|||
|
||||
<h2>What is Fun?</h2>
|
||||
|
||||
<p>This is open-source software maintained by volunteers. Contributions are welcome. However, personal attacks, memes, and expressions of blanket outrage in issue threads are unwelcome. Please be respectful.</p>
|
||||
|
||||
<p>Fun is a small, strict, and simple programming language that runs on a compact stack-based virtual machine. The C core is intentionally minimal; most functionality and standard libraries are implemented in Fun itself.</p>
|
||||
|
||||
<p>Fun is dynamically typed with optional static type annotations, <a href="/features/">featuring first-class functions</a>, classes with inheritance, pattern matching, and a rich standard library. It supports everything from basic scripting to TCP sockets, serial communication, threading, cryptography (MD5, SHA-1/256/384/512, CRC-32, AES-256), and a built-in debugger.</p>
|
||||
|
|
@ -33,7 +35,7 @@ permalink: /
|
|||
|
||||
<!--<p><a href="/about/">More about Fun</a><p>-->
|
||||
|
||||
<h2>Blog (<a href="/feed/" title="Blog Feed" class="rss">Feed</a>)</h2>
|
||||
<h2>News (<a href="/feed/" title="Blog Feed" class="rss">Feed</a>)</h2>
|
||||
|
||||
{% for post in site.tags.pinned %}
|
||||
<article class="post-content">
|
||||
|
|
@ -63,3 +65,4 @@ permalink: /
|
|||
{% if count == paginator.page %}<a href="/{{ count }}/" class="active" title="Page {{ count }}">{{ count }}</a>{% else %}<a href="/{{ count }}/" title="Page {{ count }}">{{ count }}</a>{% endif %}
|
||||
{% endfor %}
|
||||
</div><br>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue