Compare commits
No commits in common. "6ee27276a87ea5d0c5934c910f926ade38651891" and "391a7689e5ef4cd269f39aac3e080dcae4f9cd4d" have entirely different histories.
6ee27276a8
...
391a7689e5
39 changed files with 89 additions and 563 deletions
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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
370
lib/net/http.fun
|
|
@ -1,370 +0,0 @@
|
|||
/*
|
||||
* 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://xw3.org/fun/fun/
|
||||
git_short: https://xw3.org
|
||||
git: https://git.xw3.org/fun/fun/
|
||||
git_short: https://git.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://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://git.xw3.org/hanez" class="user">hanez</a>)</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,15 +15,4 @@
|
|||
<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://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://git.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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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 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!
|
||||
### 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!
|
||||
|
||||
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://xw3.org/fun/fun){:class="git"}
|
||||
- [Git Repository](https://git.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://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"}
|
||||
- [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"}
|
||||
- [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,4 +169,3 @@ 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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
noToc: false
|
||||
title: Moving the Website to the Project Root
|
||||
description: Relocating the fun-lang.xyz source code for easier maintenance.
|
||||
|
|
@ -43,4 +43,3 @@ 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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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
|
||||
date_updated: 2026-04-18 00:17:13
|
||||
categories:
|
||||
- news
|
||||
- project
|
||||
|
|
@ -85,9 +85,8 @@ 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://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://git.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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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://xw3.org/fun/fun){:class="git"} and check out the open issues.
|
||||
- Visit our [Git Repository](https://git.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,4 +55,3 @@ 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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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://xw3.org/fun/fun](https://xw3.org/fun/fun){:class="git"}.
|
||||
- Open issues and ideas in our Git repository: [https://git.xw3.org/fun/fun](https://git.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,4 +55,3 @@ 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://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
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,5 +72,4 @@ If you work with serial devices, smartcards, or lightweight networked tools and
|
|||
|
||||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
Johannes Findeisen (hanez) - hanez@fun-lang.xyz
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ layout: post
|
|||
published: true
|
||||
author: hanez
|
||||
author_email: you@hanez.org
|
||||
author_url: https://xw3.org/hanez
|
||||
author_url: https://hanez.org
|
||||
noToc: false
|
||||
title: Announcing Fun 0.42.1
|
||||
description: Maintenance release with documentation updates and a small test script fix
|
||||
|
|
@ -55,5 +55,4 @@ Thanks to everyone testing, filing issues, and contributing examples and docs. I
|
|||
|
||||
Happy hacking!
|
||||
|
||||
Johannes Findeisen (hanez) - [hanez@fun-lang.xyz](mailto:hanez@fun-lang.xyz){:class="mail"}
|
||||
|
||||
Johannes Findeisen (hanez) — hanez@fun-lang.xyz
|
||||
|
|
|
|||
|
|
@ -93,6 +93,62 @@ 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%;
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
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,38 +7,3 @@
|
|||
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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
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", "code", "fonts", "forms", "highlight", "isso", "layout", "links", "pre", "toc";
|
||||
@import "base", "fonts", "forms", "highlight", "isso", "layout", "links", "toc";
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ tags:
|
|||
---
|
||||
|
||||
|
||||
This document summarizes the Fun bytecode format. For deep VM details, see [internals](/documentation/internals/).
|
||||
This document summarizes the Fun bytecode format. For deep VM details, see [internals](./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://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"}
|
||||
- [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"}
|
||||
|
||||
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,4 +86,3 @@ 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,4 +96,3 @@ 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.
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
|
|
@ -19,8 +19,6 @@ 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>
|
||||
|
|
@ -35,7 +33,7 @@ permalink: /
|
|||
|
||||
<!--<p><a href="/about/">More about Fun</a><p>-->
|
||||
|
||||
<h2>News (<a href="/feed/" title="Blog Feed" class="rss">Feed</a>)</h2>
|
||||
<h2>Blog (<a href="/feed/" title="Blog Feed" class="rss">Feed</a>)</h2>
|
||||
|
||||
{% for post in site.tags.pinned %}
|
||||
<article class="post-content">
|
||||
|
|
@ -65,4 +63,3 @@ 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