API Reference
Every entry is a Wyn-callable function written as Namespace.method(args) -> ReturnType. Call them with dot syntax, e.g. Crypto.sha256("hello").
221 callable functions across 29 modules.
Args
3 functions
Args.get(name) -> stringArgs.has(name) -> boolArgs.positional() -> array
Base64
2 functions
Base64.encode(data) -> stringBase64.decode(data) -> string
Crypto
8 functions
Crypto.hash32(data) -> intCrypto.hash64(data) -> intCrypto.sha1(data) -> stringCrypto.sha1_base64(data) -> stringCrypto.sha256(data) -> stringCrypto.md5(data) -> stringCrypto.hmac_sha256(key, data) -> stringCrypto.random_bytes(n) -> string
Csv
8 functions
Csv.parse(text) -> intCsv.row_count(handle) -> intCsv.get(handle, row, col) -> stringCsv.get_field(handle, row, header) -> stringCsv.col_count(handle, row) -> intCsv.header(handle, col) -> stringCsv.header_count(handle) -> intCsv.write(path, rows)
Data
2 functions
Data.save(path, map)Data.load(path) -> HashMap
DateTime
16 functions
DateTime.now() -> intDateTime.millis() -> intDateTime.micros() -> intDateTime.format(timestamp, fmt) -> stringDateTime.sleep(seconds)DateTime.diff(a, b) -> intDateTime.add_seconds(t, n) -> intDateTime.to_iso(timestamp) -> stringDateTime.format_duration(ms) -> stringDateTime.day_of_week(timestamp) -> intDateTime.year(timestamp) -> intDateTime.month(timestamp) -> intDateTime.day(timestamp) -> intDateTime.hour(timestamp) -> intDateTime.minute(timestamp) -> intDateTime.second(timestamp) -> int
Db
11 functions
Db.open(path) -> intDb.exec(handle, sql) -> intDb.query(handle, sql) -> stringDb.query_one(handle, sql) -> stringDb.exec_p(handle, sql, params) -> intDb.query_p(handle, sql, params) -> stringDb.last_insert_id(handle) -> intDb.error(handle) -> stringDb.close(handle)Db.escape(str) -> stringDb.table_exists(handle, table_name) -> int
Encoding
5 functions
Encoding.base64_encode(data) -> stringEncoding.base64_decode(data) -> stringEncoding.hex_encode(data) -> stringEncoding.hex_decode(hex) -> stringEncoding.csv_parse(csv) -> string
Env
3 functions
Env.get(name) -> stringEnv.set(name, value) -> intEnv.all()- does not work as documented. The checker types the resultint, so assigning it fails to build (initializing 'long long' from 'WynArray'). Filed.
File
23 functions
File.read(p) -> stringFile.read_lines(p) -> stringFile.write(p, d) -> intFile.exists(p) -> intFile.delete(p) -> intFile.copy(s, d) -> intFile.move(s, d) -> intFile.size(p) -> intFile.is_dir(p) -> intFile.is_file(p) -> intFile.mkdir(p) -> intFile.list_dir(p) -> stringFile.append(p, d) -> intFile.cwd() -> stringFile.open(path, mode) -> intFile.read_line(handle) -> stringFile.write_line(handle, data) -> intFile.eof(handle) -> intFile.close(handle)File.rename(old_path, new_path) -> intFile.glob(pattern) -> stringFile.error() -> string— why the most recent File operation failed, or""if it did not
Telling failure from an empty success
File.read returns "" for a file that does not exist and for a file that is empty; File.size returns 0 for both. File.error() is how you tell them apart — it carries the operating system's own reason.
text = File.read("config.toml")
if File.error() != "" {
print("could not read config: ${File.error()}")
} else {
print("read ${text.len()} bytes")
}could not read config: Cannot read file 'config.toml': No such file or directoryIt takes no argument and reports the last File call on this thread, so check it immediately after the call you care about — a later File operation overwrites it, and a successful one clears it.
""always means "nothing failed", soFile.error() != ""is the test to write.
File.walk_dir(path) -> stringFile.temp_file() -> string
HashMap
Create with HashMap(), then use string keys and values:
m = HashMap.new()
m.set("name", "Wyn")
print(m.get("name")) // Wyn
print(m.len().to_string()) // 1
print(m.has("name").to_string()) // true
m.remove("name")
keys = m.keys() // []Methods (dot syntax on a HashMap value):
.set(key, value)- set a key-value pair.get(key) -> string- value for a key.has(key) -> bool- whether a key exists.len() -> int- number of entries.remove(key)- remove a key.keys() -> array- all keys
HashSet
1 function
HashSet.new() -> HashSet
Input (global builtins)
3 functions - not namespaced, callable anywhere.
input_line() -> string- read one line from stdin, newline strippedinput() -> int- read one line and parse it as an integerinput_float() -> float- read one line and parse it as a float
input() and input_float() validate the whole line and panic on anything else (changed in v1.21.0 - they previously returned 0 at exit 0, which turned bad input into a wrong answer):
$ echo abc | ./prog
panic: input(): "abc" is not a valid integer; use input_line() to read text
$ printf '' | ./prog
panic: input(): end of input (expected an integer)12abc is rejected rather than read as 12, the trailing newline is consumed (so a following input_line() returns the next line rather than ""), and 64-bit values survive. Set WYN_LENIENT=1 to warn and return 0 instead of aborting, if you are migrating a program that relied on the old behaviour. Use input_line() when the input is text.
Http
Reading a request
Http.accept hands you ONE packed record, "METHOD|PATH|BODY|TOKEN". Read it with these accessors, never by splitting on "|" at a fixed index. The body is whatever the client sent, so a pipe in it shifts every later field: curl -d 'a|b' against a server that used req.split_at("|", 3).to_int() made that field "b" and the panic took the process down. These resolve the final field from the right, so extra pipes anywhere earlier cannot shift it.
Http.method(req) -> stringHttp.path(req) -> string— includes the query string, if anyHttp.req_body(req) -> string— the body, intact, pipes and allHttp.fd(req) -> int— the valueHttp.respondexpects. It is a capability token this process minted, not a raw descriptor, and it returns-1for a token it does not recognise, so a client cannot name a file descriptor of its choosing.
Client
Http.get(url) -> string— returns the response bodyHttp.post(url, body) -> stringHttp.status() -> int— the HTTP status code of the most recent request on this thread, or0if the request never got a responseHttp.error() -> string— why the most recent request failed, or""if it did notHttp.free(resp)
body = Http.get("https://example.com/thing")
if Http.status() == 200 {
print(body)
} else if Http.status() == 0 {
print("request failed: ${Http.error()}")
} else {
print("server said ${Http.status()}")
}
Http.status()andHttp.error()take no argument. They report the last request rather than taking a response handle, becauseHttp.gethands you the body as a plain string and there is no handle to pass. Check them straight after the call that interests you — a later request overwrites both, and two requests running concurrently share them.
Http.headerdoes not exist. By the timeHttp.getreturns, the response headers have been parsed and dropped, so there is nothing for it to read; calling it is a compile-time error rather than a wrong answer. (Until v1.23Http.statusandHttp.headerwere bound to a second, struct-based C client that no Wyn expression can produce a value for:Http.statusreturned1347634514— the ASCII bytesHTTPread as an integer — andHttp.headercrashed. That was issue #508.)
Http.get_json(url) -> intHttp.post_json(url, data) -> intHttp.set_timeout(seconds)Http.timeout() -> int
Server
Http.serve(port) -> intHttp.accept(server_fd) -> string- blocks for the next request, returning the packed record described above. Since v1.21.0 it skips a connection that sends no request and keeps accepting, so it never returns"". Before that it did, and the then-documented.split_at("|", 3).to_int()idiom panicked on the empty string - one unauthenticated packet, or an ordinary TCP health check, port scan or browser preconnect, killed the server process. That idiom is no longer what this reference publishes; see "Reading a request".Http.respond(client_fd, status, content_type, body)-client_fdisHttp.fd(req)Http.respond_json(fd, status, json)Http.respond_html(fd, status, html)Http.close_client(fd)Http.close_server(fd)Http.route_match(pattern, path, params) -> intHttp.parse_request(raw) -> HashMapHttp.ctx_fd(ctx) -> int
Json
19 functions
Json.new() -> JsonJson.set(j, k, v)Json.set_string(j, k, v)Json.set_int(j, k, v)Json.set_bool(j, k, v)Json.stringify(j) -> stringJson.parse(text) -> intJson.get(root, key) -> stringJson.get_int(root, key) -> intJson.has(root, key) -> intJson.array_len(node) -> intJson.array_get(node, index) -> intJson.node_str(node) -> stringJson.keys(root) -> stringJson.get_float(root, key) -> floatJson.get_bool(root, key) -> intJson.get_array(root, key) -> intJson.get_object(root, key) -> intJson.to_pretty_string(j) -> string
Log
5 functions
Log.set_level(level)Log.debug(msg)Log.info(msg)Log.warn(msg)Log.error(msg)
Math
26 functions
Math.sin(x) -> floatMath.cos(x) -> floatMath.tan(x) -> floatMath.sqrt(x) -> floatMath.pow(base, exp) -> floatMath.floor(x) -> floatMath.ceil(x) -> floatMath.round_to(x, places) -> floatMath.atan2(y, x) -> floatMath.pi() -> floatMath.e() -> floatMath.round(x) -> floatMath.abs(x) -> intMath.max(a, b) -> floatMath.min(a, b) -> floatMath.checked_add(a, b) -> intMath.checked_sub(a, b) -> intMath.checked_mul(a, b) -> intMath.random() -> floatMath.log(x) -> floatMath.log10(x) -> floatMath.exp(x) -> floatMath.clamp(x, lo, hi) -> intMath.sign(x) -> intMath.lerp(a, b, t) -> floatMath.map_range(x, in_min, in_max, out_min, out_max) -> float
Net
6 functions
Net.listen(port) -> intNet.connect(host, port) -> intNet.send(sockfd, data) -> intNet.recv(sockfd) -> stringNet.close(sockfd) -> intNet.resolve(hostname) -> string
Os
6 functions
Os.platform() -> stringOs.arch() -> stringOs.hostname() -> stringOs.pid() -> intOs.temp_dir() -> stringOs.home_dir() -> string
Path
4 functions
Path.basename(p) -> stringPath.dirname(p) -> stringPath.extension(p) -> stringPath.join(a, b) -> string
Process
2 functions
Process.exec_capture(cmd) -> stringProcess.exec_status(cmd) -> int
Regex
5 functions
Regex.match(s, p) -> bool- whether the pattern matches anywhereRegex.replace(s, p, r) -> string- replace every matchRegex.find(s, p) -> int- byte offset of the first match, or -1Regex.find_all(s, p) -> string- one string, one match per lineRegex.split(s, p) -> string- one string, one field per line
find_all and split return newline-joined strings, not arrays. Iterate them with .trim().split("\n"):
fn main() {
text = "mail [email protected] or [email protected]"
emails = Regex.find_all(text, "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}")
for email in emails.trim().split("\n") { println(email) }
}There are no capture groups. Regex.match returns a bool - there is no match object, no .group(n), no named groups. Extract fields with .split() or .substring() after matching.
The shorthand classes \d, \w and \s are not supported, and they fail silently - \d is matched as a literal d, so Regex.replace(s, "\\d+", "N") turns "disk" into "Nisk" and leaves the digits alone. Use explicit character classes, which work correctly:
| Instead of | Write |
|---|---|
\d | [0-9] |
\w | [A-Za-z0-9_] |
\s | [ \t\n] |
Supported: literals, ., character classes (including escapes inside them, e.g. [ \t]), *, +, ?, {n}, {n,m}, alternation (a|b), and the ^/$ anchors. Not supported: backreferences, lookaround, capture-group extraction, and the shorthand classes above.
Socket
8 functions
Socket.connect(host, port) -> intSocket.send(sock, data, len) -> intSocket.recv(sock, max_len) -> stringSocket.close(sock)Socket.set_timeout(sock, seconds) -> intSocket.set_nonblocking(sock) -> intSocket.poll_read(sock, timeout_ms) -> intSocket.read_line(sock) -> string
StringBuilder
8 functions
StringBuilder.new() -> intStringBuilder.append(handle, s)StringBuilder.to_string(handle) -> stringStringBuilder.len(handle) -> intStringBuilder.clear(handle)StringBuilder.free(handle)StringBuilder.append_int(handle, val)StringBuilder.append_line(handle, s)
System
9 functions
System.shell_escape(s) -> stringSystem.exec(cmd) -> stringSystem.exec_code(cmd) -> intSystem.exit(code)System.env(key) -> stringSystem.set_env(key, value) -> intSystem.args() -> arraySystem.gc()System.load_env(path)
Task
12 functions
Task.value(initial) -> intTask.get(handle) -> intTask.set(handle, value)Task.add(handle, amount)Task.free_value(handle)Task.channel(capacity) -> intTask.send(handle, value)Task.recv(handle) -> intTask.close(handle)Task.try_recv(handle) -> int?-Some(v)when a value is ready,nonewhen empty (changed in v1.20; the old two-argument out-param form no longer exists)Task.select_2(ch1, ch2) -> intTask.select_3(ch1, ch2, ch3) -> int
Toml
3 functions
Toml.parse(text) -> intToml.get(handle, key) -> stringToml.parse_file(path) -> int
Url
2 functions
Url.encode(str) -> stringUrl.decode(str) -> string
Uuid
2 functions
Uuid.generate() -> stringUuid.v4() -> string
Ws
4 functions. All four currently fail to link (Ws.connect, Ws.send, Ws.recv, Ws.close) - they type-check and then the build fails at the linker. Filed; do not build on these yet.
Ws.connect(url) -> intWs.send(sock, msg) -> intWs.recv(sock) -> stringWs.close(sock)