Skip to content

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) -> string
  • Args.has(name) -> bool
  • Args.positional() -> array

Base64 ​

2 functions

  • Base64.encode(data) -> string
  • Base64.decode(data) -> string

Crypto ​

8 functions

  • Crypto.hash32(data) -> int
  • Crypto.hash64(data) -> int
  • Crypto.sha1(data) -> string
  • Crypto.sha1_base64(data) -> string
  • Crypto.sha256(data) -> string
  • Crypto.md5(data) -> string
  • Crypto.hmac_sha256(key, data) -> string
  • Crypto.random_bytes(n) -> string

Csv ​

8 functions

  • Csv.parse(text) -> int
  • Csv.row_count(handle) -> int
  • Csv.get(handle, row, col) -> string
  • Csv.get_field(handle, row, header) -> string
  • Csv.col_count(handle, row) -> int
  • Csv.header(handle, col) -> string
  • Csv.header_count(handle) -> int
  • Csv.write(path, rows)

Data ​

2 functions

  • Data.save(path, map)
  • Data.load(path) -> HashMap

DateTime ​

16 functions

  • DateTime.now() -> int
  • DateTime.millis() -> int
  • DateTime.micros() -> int
  • DateTime.format(timestamp, fmt) -> string
  • DateTime.sleep(seconds)
  • DateTime.diff(a, b) -> int
  • DateTime.add_seconds(t, n) -> int
  • DateTime.to_iso(timestamp) -> string
  • DateTime.format_duration(ms) -> string
  • DateTime.day_of_week(timestamp) -> int
  • DateTime.year(timestamp) -> int
  • DateTime.month(timestamp) -> int
  • DateTime.day(timestamp) -> int
  • DateTime.hour(timestamp) -> int
  • DateTime.minute(timestamp) -> int
  • DateTime.second(timestamp) -> int

Db ​

11 functions

  • Db.open(path) -> int
  • Db.exec(handle, sql) -> int
  • Db.query(handle, sql) -> string
  • Db.query_one(handle, sql) -> string
  • Db.exec_p(handle, sql, params) -> int
  • Db.query_p(handle, sql, params) -> string
  • Db.last_insert_id(handle) -> int
  • Db.error(handle) -> string
  • Db.close(handle)
  • Db.escape(str) -> string
  • Db.table_exists(handle, table_name) -> int

Encoding ​

5 functions

  • Encoding.base64_encode(data) -> string
  • Encoding.base64_decode(data) -> string
  • Encoding.hex_encode(data) -> string
  • Encoding.hex_decode(hex) -> string
  • Encoding.csv_parse(csv) -> string

Env ​

3 functions

  • Env.get(name) -> string
  • Env.set(name, value) -> int
  • Env.all() - does not work as documented. The checker types the result int, so assigning it fails to build (initializing 'long long' from 'WynArray'). Filed.

File ​

23 functions

  • File.read(p) -> string
  • File.read_lines(p) -> string
  • File.write(p, d) -> int
  • File.exists(p) -> int
  • File.delete(p) -> int
  • File.copy(s, d) -> int
  • File.move(s, d) -> int
  • File.size(p) -> int
  • File.is_dir(p) -> int
  • File.is_file(p) -> int
  • File.mkdir(p) -> int
  • File.list_dir(p) -> string
  • File.append(p, d) -> int
  • File.cwd() -> string
  • File.open(path, mode) -> int
  • File.read_line(handle) -> string
  • File.write_line(handle, data) -> int
  • File.eof(handle) -> int
  • File.close(handle)
  • File.rename(old_path, new_path) -> int
  • File.glob(pattern) -> string
  • File.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.

wyn
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 directory

It 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", so File.error() != "" is the test to write.

  • File.walk_dir(path) -> string
  • File.temp_file() -> string

HashMap ​

Create with HashMap(), then use string keys and values:

wyn
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 stripped
  • input() -> int - read one line and parse it as an integer
  • input_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) -> string
  • Http.path(req) -> string — includes the query string, if any
  • Http.req_body(req) -> string — the body, intact, pipes and all
  • Http.fd(req) -> int — the value Http.respond expects. It is a capability token this process minted, not a raw descriptor, and it returns -1 for 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 body
  • Http.post(url, body) -> string
  • Http.status() -> int — the HTTP status code of the most recent request on this thread, or 0 if the request never got a response
  • Http.error() -> string — why the most recent request failed, or "" if it did not
  • Http.free(resp)
wyn
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() and Http.error() take no argument. They report the last request rather than taking a response handle, because Http.get hands 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.header does not exist. By the time Http.get returns, 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.23 Http.status and Http.header were bound to a second, struct-based C client that no Wyn expression can produce a value for: Http.status returned 1347634514 — the ASCII bytes HTTP read as an integer — and Http.header crashed. That was issue #508.)

  • Http.get_json(url) -> int
  • Http.post_json(url, data) -> int
  • Http.set_timeout(seconds)
  • Http.timeout() -> int

Server ​

  • Http.serve(port) -> int
  • Http.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_fd is Http.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) -> int
  • Http.parse_request(raw) -> HashMap
  • Http.ctx_fd(ctx) -> int

Json ​

19 functions

  • Json.new() -> Json
  • Json.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) -> string
  • Json.parse(text) -> int
  • Json.get(root, key) -> string
  • Json.get_int(root, key) -> int
  • Json.has(root, key) -> int
  • Json.array_len(node) -> int
  • Json.array_get(node, index) -> int
  • Json.node_str(node) -> string
  • Json.keys(root) -> string
  • Json.get_float(root, key) -> float
  • Json.get_bool(root, key) -> int
  • Json.get_array(root, key) -> int
  • Json.get_object(root, key) -> int
  • Json.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) -> float
  • Math.cos(x) -> float
  • Math.tan(x) -> float
  • Math.sqrt(x) -> float
  • Math.pow(base, exp) -> float
  • Math.floor(x) -> float
  • Math.ceil(x) -> float
  • Math.round_to(x, places) -> float
  • Math.atan2(y, x) -> float
  • Math.pi() -> float
  • Math.e() -> float
  • Math.round(x) -> float
  • Math.abs(x) -> int
  • Math.max(a, b) -> float
  • Math.min(a, b) -> float
  • Math.checked_add(a, b) -> int
  • Math.checked_sub(a, b) -> int
  • Math.checked_mul(a, b) -> int
  • Math.random() -> float
  • Math.log(x) -> float
  • Math.log10(x) -> float
  • Math.exp(x) -> float
  • Math.clamp(x, lo, hi) -> int
  • Math.sign(x) -> int
  • Math.lerp(a, b, t) -> float
  • Math.map_range(x, in_min, in_max, out_min, out_max) -> float

Net ​

6 functions

  • Net.listen(port) -> int
  • Net.connect(host, port) -> int
  • Net.send(sockfd, data) -> int
  • Net.recv(sockfd) -> string
  • Net.close(sockfd) -> int
  • Net.resolve(hostname) -> string

Os ​

6 functions

  • Os.platform() -> string
  • Os.arch() -> string
  • Os.hostname() -> string
  • Os.pid() -> int
  • Os.temp_dir() -> string
  • Os.home_dir() -> string

Path ​

4 functions

  • Path.basename(p) -> string
  • Path.dirname(p) -> string
  • Path.extension(p) -> string
  • Path.join(a, b) -> string

Process ​

2 functions

  • Process.exec_capture(cmd) -> string
  • Process.exec_status(cmd) -> int

Regex ​

5 functions

  • Regex.match(s, p) -> bool - whether the pattern matches anywhere
  • Regex.replace(s, p, r) -> string - replace every match
  • Regex.find(s, p) -> int - byte offset of the first match, or -1
  • Regex.find_all(s, p) -> string - one string, one match per line
  • Regex.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"):

wyn
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 ofWrite
\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) -> int
  • Socket.send(sock, data, len) -> int
  • Socket.recv(sock, max_len) -> string
  • Socket.close(sock)
  • Socket.set_timeout(sock, seconds) -> int
  • Socket.set_nonblocking(sock) -> int
  • Socket.poll_read(sock, timeout_ms) -> int
  • Socket.read_line(sock) -> string

StringBuilder ​

8 functions

  • StringBuilder.new() -> int
  • StringBuilder.append(handle, s)
  • StringBuilder.to_string(handle) -> string
  • StringBuilder.len(handle) -> int
  • StringBuilder.clear(handle)
  • StringBuilder.free(handle)
  • StringBuilder.append_int(handle, val)
  • StringBuilder.append_line(handle, s)

System ​

9 functions

  • System.shell_escape(s) -> string
  • System.exec(cmd) -> string
  • System.exec_code(cmd) -> int
  • System.exit(code)
  • System.env(key) -> string
  • System.set_env(key, value) -> int
  • System.args() -> array
  • System.gc()
  • System.load_env(path)

Task ​

12 functions

  • Task.value(initial) -> int
  • Task.get(handle) -> int
  • Task.set(handle, value)
  • Task.add(handle, amount)
  • Task.free_value(handle)
  • Task.channel(capacity) -> int
  • Task.send(handle, value)
  • Task.recv(handle) -> int
  • Task.close(handle)
  • Task.try_recv(handle) -> int? - Some(v) when a value is ready, none when empty (changed in v1.20; the old two-argument out-param form no longer exists)
  • Task.select_2(ch1, ch2) -> int
  • Task.select_3(ch1, ch2, ch3) -> int

Toml ​

3 functions

  • Toml.parse(text) -> int
  • Toml.get(handle, key) -> string
  • Toml.parse_file(path) -> int

Url ​

2 functions

  • Url.encode(str) -> string
  • Url.decode(str) -> string

Uuid ​

2 functions

  • Uuid.generate() -> string
  • Uuid.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) -> int
  • Ws.send(sock, msg) -> int
  • Ws.recv(sock) -> string
  • Ws.close(sock)

See Also ​

MIT License - v1.22.0