Standard library

Small modules, one job each, written in Tau and short enough to read. Import one by name — strings = import("strings") — and only the capitalised names come out.

Every module carries its own reference, built from the comments in its source by tau doc. Read it in the terminal, or open the page with tau doc -b MODULE — the same pages are here: buffer, bufio, cmp, crypto/hmac, crypto/sha256, encoding/base64, encoding/csv, encoding/hex, encoding/json, encoding/xml, errno, errors, ffi, flag, io, list, log, maps, math, math/rand, net, net/http, os, os/exec, path, ref, regexp, runtime, strconv, strings, sync, sync/atomic, syscall, testing, time, unicode/utf8.
Modules

cmp

Comparison that goes deep. Equal walks lists, maps and objects instead of comparing identities, Compare returns -1, 0 or 1 for sorting, and Min and Max are there so you stop writing them again.

cmp = import("cmp")

println(cmp.Equal([1, {"a": 2}], [1, {"a": 2}]))
println(cmp.Compare(2, 3), cmp.Min(3, 1), cmp.Max(3, 1))
true
-1 1 3

strings

Everything you expect on top of byte-indexed strings: Contains, Count, Cut, HasPrefix, HasSuffix, Index, LastIndex, Join, Split, SplitN, SplitAfter, Fields, Repeat, Reverse, Replace, ReplaceAll, ToUpper, ToLower, ToTitle, Trim, TrimSpace, TrimPrefix, TrimSuffix, PadLeft, PadRight.

strings = import("strings")

println(strings.ToUpper("tau"), strings.Repeat("-", 8))
println(strings.Split("a,b,c", ","))
println(strings.Join(["usr", "local", "bin"], "/"))
println(strings.TrimSpace("  padded  "), strings.HasPrefix("tau-lang", "tau"))
println(strings.ReplaceAll("one two two", "two", "2"))
TAU --------
[a, b, c]
usr/local/bin
padded true
one 2 2

strconv

Text to numbers and back, with a base when you want one. ParseInt, Atoi, ParseFloat, FormatInt, FormatFloat, Itoa. A bad string comes back as an error value, not as zero.

strconv = import("strconv")

println(strconv.Atoi("42") + 1)
println(strconv.ParseInt("ff", 16), strconv.ParseFloat("2.5"))
println(strconv.FormatInt(255, 2), strconv.Itoa(7))
43
255 2.5
11111111 7

buffer

Builder grows a string without the quadratic cost of s = s + x in a loop; Buffer is the same idea over bytes, and being a reader and a writer it plugs into io and bufio.

buffer = import("buffer")

b = buffer.Builder()
b.Write("tau")
b.Write(" is ")
b.Write("small")
println(b.String(), b.Len())
tau is small 12

list

The functional trio and the ordinary helpers: Each, Map, Filter, Reduce, Index, Contains, Find, Copy, Reverse, Range, Join and Sort, which takes the less function so you decide the order.

list = import("list")

xs = list.Range(1, 6)
println(xs)
println(list.Map(xs, fn(x) { x * x }))
println(list.Filter(xs, fn(x) { x % 2 == 0 }))
println(list.Reduce(xs, 0, fn(acc, x) { acc + x }))
println(list.Sort([3, 1, 2], fn(a, b) { a < b }))
[1, 2, 3, 4, 5]
[1, 4, 9, 16, 25]
[2, 4]
15
[1, 2, 3]

maps

What the keys, delete and index builtins leave out: Has, Get with a default, Keys, Values, Len, Each, Merge and a deep Equal.

maps = import("maps")

m = {"a": 1, "b": 2}
println(maps.Has(m, "a"), maps.Get(m, "z", 0), maps.Len(m))
println(maps.Values(m))
maps.Each(m, fn(k, v) { println("{k} -> {v}") })
true 0 2
[1, 2]
a -> 1
b -> 2

os

Files and the process environment, straight over syscalls, with no dependency on the host C library. Open, Create, ReadFile, ReadFileString, WriteFile, Remove, Mkdir, MkdirAll, RemoveAll, Rename, Stat, IsDir, ReadDir, Exists, Getwd, Chdir, Getenv, Setenv, TempDir, plus Args, Stdin, Stdout and Stderr.

os = import("os")

if failed(err = os.WriteFile("/tmp/tau-demo.txt", "hello\n")) {
	exit(err)
}
println(os.Exists("/tmp/tau-demo.txt"))
println(os.ReadFileString("/tmp/tau-demo.txt"))
println(os.Getenv("HOME") != null)
os.Remove("/tmp/tau-demo.txt")
true
hello

true

io and bufio

io is the small vocabulary every reader and writer speaks: ReadAll, ReadFull, Copy, CopyN, WriteString. A reader is any object with a Read field, a writer any object with a Write — no interface declaration needed.

bufio puts a buffer in front of either: NewReader, NewWriter and NewScanner, the last one being the line-by-line loop you want most of the time.

os = import("os")
io = import("io")
bufio = import("bufio")

if failed(f = os.Create("/tmp/tau-io.txt")) {
	exit(f)
}
w = bufio.NewWriter(f, null)
io.WriteString(w, "first\nsecond\n")
w.Flush()
f.Close()

if failed(f = os.Open("/tmp/tau-io.txt", null, null)) {
	exit(f)
}
s = bufio.NewScanner(f)
for s.Scan() {
	println("> {s.Text()}")
}
f.Close()
os.Remove("/tmp/tau-io.txt")
> first
> second

path

Pure string work on slash-separated paths, no disk access: Base, Dir, Ext, Split, Clean, Join, IsAbs.

path = import("path")

p = "/usr/local/bin/tau"
println(path.Base(p), path.Dir(p), path.Ext("notes.md"))
println(path.Join(["/usr", "local", "bin"]))
println(path.Clean("/a/b/../c/./d"), path.IsAbs(p))
tau /usr/local/bin .md
/usr/local/bin
/a/c/d true

math

The C library’s maths, declared and called through ffi: the interpreter is linked against libm, so the module takes the functions off dlopen(null) and carries no C of its own. Sqrt, Cbrt, Exp, Log, Log2, Log10, the trigonometric and hyperbolic family, Floor, Ceil, Round, Trunc, Pow, Atan2, Mod, Hypot, Abs, Min, Max, Signum, Inf, NaN, IsNaN, IsInf, and the constants Pi, E, Sqrt2, Ln2, MaxInt, MinInt.

math = import("math")

println(math.Sqrt(2), math.Pi)
println(math.Floor(2.7), math.Ceil(2.1), math.Abs(-3))
println(math.Pow(2, 10), math.Max(3, 9))
1.4142135623730951 3.141592653589793
2 3 3
1024 9

time

Clocks and calendars. Now and Unix for wall time, Mono and Since for measuring, Sleep and Measure, and the calendar side: Date, FromDate, Format with strftime verbs, Parse for RFC 3339, IsLeap, plus the Millisecond, Second, Minute and Hour constants.

time = import("time")

start = time.Mono()
time.Sleep(20)
println("slept {time.Since(start)}ms")

d = time.Date(0)
println(time.Format(d, "%A %d %B %Y"))
slept 20ms
Thursday 01 January 1970

json

Marshal turns a Tau value into JSON text, Unmarshal turns JSON text into maps, lists, strings, numbers, booleans and null. Numbers keep their kind: 42 comes back an int, 3.14 a float. Broken input comes back as an error.

json = import("encoding/json")

s = json.Marshal({"name": "tau", "tags": ["small", "fast"], "ok": true})
println(s)

if failed(v = json.Unmarshal(`{"port":8080,"hosts":["a","b"]}`)) {
	exit(v)
}
println(v["port"], v["hosts"][1])
{"name":"tau","tags":["small","fast"],"ok":true}
8080 b

encoding/xml

Parse reads a document into a tree of elements and gives back the root. An element is a plain object with four fields — Name, Attr, Children and Text — and nothing else: Child, All and Get are module functions that take a node rather than methods hung on every one of the tens of thousands a document might hold. String writes a node back out and Escape quotes the five named entities. It is the XML that carries data, not the XML that marks up prose: no validation, no DTDs, no entities of your own.

xml = import("encoding/xml")

# Parse gives back the root element. An element is a plain object with four
# fields: Name, Attr, Children and Text.
doc = xml.Parse(`<catalog n="2">
	<book id="a"><title>Tau</title></book>
	<book id="b"><title>More tau</title></book>
</catalog>`)

println(doc.Name, doc.Attr["n"])

# Child, All and Get are module functions taking a node, not methods on it.
books = xml.All(doc, "book")
for i = 0; i < len(books); i++ {
	b = books[i]
	println(xml.Get(b, "id"), xml.Child(b, "title").Text)
}
catalog 2
a Tau
b More tau

regexp

A backtracking engine written in Tau. Compile returns a regexp or an error, MustCompile stops the program on a bad pattern, and the compiled object has MatchString, FindString, FindStringIndex, FindStringSubmatch, FindAllString, NumSubexp and friends. MatchString and QuoteMeta are there at module level for one-off use.

regexp = import("regexp")

re = regexp.MustCompile("(\\w+)@(\\w+)\\.com")
println(re.MatchString("write to [email protected]"))
println(re.FindString("write to [email protected]"))
println(re.FindStringSubmatch("[email protected]"))
println(regexp.MatchString("^[0-9]+$", "12345"))
true
[email protected]
[[email protected], bob, example]
true
Patterns live in raw strings — `\bcat\b` — or the backslashes have to be doubled, as in the example above.

errors

Errors are a builtin, so this module is thin on purpose: New, Wrap to add context while keeping the original message, Is to test for one, and Message to get the text out.

errors = import("errors")
strconv = import("strconv")

readPort = fn(s) {
	if failed(n = strconv.Atoi(s)) {
		return errors.Wrap(n, "reading the port")
	}
	n
}

println(readPort("8080"))
err = readPort("http")
println(errors.Message(err))
println(errors.Is(err, "reading the port"))
8080
reading the port: strconv: invalid number "http"
true

encoding/hex

Bytes to the two hex digits each of them is, and back. EncodeToString and DecodeString, with EncodedLen and DecodedLen for the sizes.

hex = import("encoding/hex")

b = bytes("tau")
println(hex.EncodeToString(b))
println(string(hex.DecodeString("746175")))
746175
tau

encoding/base64

The same round trip over the base64 alphabet. EncodeToString and DecodeString, URLEncodeToString for the URL-safe alphabet, and the EncodedLen and DecodedLen size helpers.

base64 = import("encoding/base64")

b = bytes("tau")
println(base64.EncodeToString(b))
println(string(base64.DecodeString("dGF1")))
dGF1
tau

utf8

A string counts bytes, the way it does in Go, and this is the module that counts letters. RuneCount, Runes and FromRunes go between the two, DecodeRune(s, i) walks a string one code point at a time, EncodeRune writes one, and Slice(s, from, to) cuts by letters rather than by bytes. Valid says whether the bytes are UTF-8 at all.

utf8 = import("unicode/utf8")

# A string counts bytes, the way it does in Go. The letters are here.
s = "città"
println(len(s), utf8.RuneCount(s))
println(utf8.Runes("aé€"), utf8.EncodeRune(0x1f600))
println(utf8.Slice(s, 1, 4), utf8.Valid(s))
6 5
[97, 233, 8364] 😀
itt true

rand

New(seed) makes a source that gives the same sequence every run, which is what a test wants; the module keeps one of its own, seeded from the clock, for when it should differ. Int, Intn, Float, Bool, Bytes, Perm, Shuffle and Choice on either. Crypto(n) is the separate thing: bytes from the system generator, for keys and tokens.

rand = import("math/rand")

# A source of its own, seeded, so the same run gives the same numbers.
r = rand.New(42)
println(r.Intn(100), r.Intn(100), r.Bool())
println(r.Shuffle([1, 2, 3, 4, 5]))

# The module keeps one seeded from the clock, for when the sequence should
# differ every time. Crypto is the one to use for a token.
println(len(rand.Crypto(16)), type(rand.Crypto(16)))
42 91 true
[5, 2, 4, 3, 1]
16 bytes

csv

Parse(text, sep) gives a list of records, Format(rows, sep) writes them back, and null as the separator means a comma. Quoted fields, doubled quotes inside them, embedded newlines and both kinds of line ending, as RFC 4180 says.

csv = import("encoding/csv")

rows = csv.Parse("city,people\nRoma,2.8\nMilano,1.4\n", null)
println(rows[0], rows[1])

# Quoting happens where it has to and nowhere else.
print(csv.Format([["name", "note"], ["tau", "small, fast"]], null))
[city, people] [Roma, 2.8]
name,note
tau,"small, fast"

flag

Command line flags, declared before parsing and read after it. -name value, -name=value and, for a boolean, -name on its own; -- ends the flags and -h prints the usage. flag.Parse() takes the command line of the program and returns what is left over; flag.New(name) makes a set of your own for anything else.

flag = import("flag")

name = flag.String("name", "world", "who to greet")
times = flag.Int("n", 1, "how many times")
loud = flag.Bool("loud", false, "shout it")

rest = flag.Parse()
if failed(rest) {
	println(rest)
	print(flag.Usage())
	exit(2)
}

strings = import("strings")
for i = 0; i < times.Value; ++i {
	msg = "hello, {name.Value}"
	println(if loud.Value { strings.ToUpper(msg) + "!" } else { msg })
}

if len(rest) > 0 {
	println("and also: {rest}")
}
$ tau run greet.tau -name tau -n 2 --loud extra.txt
HELLO, TAU!
HELLO, TAU!
and also: [extra.txt]

log

One line per message with the local date in front, on standard error. Print and Fatal on the module, New(w, prefix) for a logger that writes somewhere else, and SetOutput, SetPrefix, SetStamp to change the one the module keeps.

log = import("log")
os = import("os")

# One line per message, with the local date, on standard error.
log.SetOutput(os.Stdout)
log.SetStamp(false)
log.SetPrefix("worker ")

log.Print("started")
log.Print("done in {12 * 4}ms")
worker started
worker done in 48ms

exec

Runs another program and waits for it. The program and its arguments go as a list, with no shell in between, so a file named ; rm -rf . is a file name and nothing else. Run gives back the status and the output, Output is the short way when the program is expected to work, Status runs it without keeping the output, and Sh is there for when a pipeline really is what you want.

exec = import("os/exec")

# The program and its arguments, no shell in between: a file named
# "; rm -rf ." is a file name and nothing else.
r = exec.Run(["/bin/echo", "hello from a program"])
println(r.Status, r.Stdout)

# Input goes in, output comes back, and a failure is a status, not a crash.
o = exec.Options()
o.Input = "one\ntwo\nthree\n"
println(exec.RunWith(["/usr/bin/wc", "-l"], o).Stdout)
println(exec.Run(["/bin/false"]).Status)
0 hello from a program

3

1

Options() carries the rest: Input for the standard input, Stderr to keep the error output along with the output, Dir to run it somewhere else, Max for how much output to keep.

sync

The one thing two routines can touch at once without racing is a pipe, so everything in sync is a pipe underneath. Mutex is a pipe of one token — Lock puts it in, Unlock takes it back, a second Lock waits — and RWMutex, WaitGroup and Once build on it. WaitGroup.Tau counts a routine and starts it in one step, the way Go spells Go after its keyword; Wait sleeps until the count is zero. The first rule is Go’s, though: a pipe is usually the better answer, and a lock is for state rather than messages.

sync = import("sync")

# A counter every routine bumps, guarded by a mutex, with a WaitGroup to wait
# for all of them to finish. The counter lives on an object so every routine
# shares the one cell rather than closing over a copy.
mu = sync.Mutex()
wg = sync.WaitGroup()
state = new()
state.count = 0

for i = 0; i < 100; i++ {
	wg.Tau(fn() {
		mu.Lock()
		state.count = state.count + 1
		mu.Unlock()
	})
}

wg.Wait()
println("count", state.count)

# Once runs a thing once, however many routines ask for it.
once = sync.Once()
setup = fn() { once.Do(fn() { println("setup ran") }) }
setup()
setup()
setup()
count 100
setup ran

sync/atomic

Smaller still than a mutex: a single read, write or addition no other routine can see half of, one processor instruction rather than a wait and a wake. Int is a counter every routine bumps — Add returns the value after its own addition, so it works as a ticket — and Bool is a flag one routine raises and the others watch. It is the one corner of sync with a shared object of its own, because these instructions are not something pipes can build.

atomic = import("sync/atomic")
sync = import("sync")

# A counter every routine bumps, without a lock: one instruction, not a wait
# and a wake. hits.Add returns the value after its own addition.
hits = atomic.Int(0)
wg = sync.WaitGroup()

for i = 0; i < 1000; i++ {
	wg.Tau(fn() { hits.Add(1) })
}

wg.Wait()
println("hits", hits.Load())

# Bool is a flag one routine raises and the others watch.
stop = atomic.Bool(false)
println(stop.Load())
stop.Store(true)
println(stop.Load(), stop.CompareAndSwap(true, false), stop.Load())
hits 1000
false
true true false

ffi

Calling C with the types written down. dlopen(path) opens a shared object and the dot on it is the symbol, which can be called with no declaration at all — quick, and it takes your word for the types. ffi is the other half: give a symbol a C declaration and the arguments travel as those types, the result comes back as a tau value.

ffi.Func(sym, signature) is one function, ffi.Bind(lib, [signatures]) a whole library named the way each signature names it — lib being a handle or the name of one, which is then opened here — and ffi.Sig(text) the parse on its own, returning [result code, [argument codes], name] with the codes spelled ffi.Voidffi.CString. The signature is a C declaration: names optional, const and friends ignored, int and long and size_t the width they have here, char * a tau string and any other pointer an address.

ffi.Export(signature, f) goes the other way and makes a tau function C can call, for the libraries whose interface is a handler.

ffi.Lib(name) opens a library by the name it has here — Lib("m") is libm.so.6 on glibc and libm.dylib on macOS — and is what Bind uses when it is given a name rather than a handle.

Memory comes from the C library rather than from the language, reached through dlopen(null), the handle of the program itself: ffi.Alloc and ffi.Free are malloc and free, ffi.Write is memcpy into a pointer, ffi.String reads a C string up to its NUL, and ffi.Read(p, n) is bytes(p, n).

ffi = import("ffi")

# Memory C owns, taken from the C library rather than from the language: the
# collector knows nothing about it, so Free is yours to call.
p = ffi.Alloc(32)
println(ffi.Write(p, "hello"), ffi.Read(p, 5))

# String reads up to the NUL, so a NUL has to be there.
ffi.Write(p, bytes([116, 97, 117, 0]))
println(ffi.String(p))
ffi.Free(p)

# A whole library, by name, with the declarations that matter.
m = ffi.Bind("libm.so.6", [
		"double pow(double, double)",
		"double sqrt(double)"
	])
println(m.pow(2, 10), m.sqrt(2))
5 [104, 101, 108, 108, 111]
tau
1024 1.4142135623730951
ffi = import("ffi")

# The library name is the system's; this is a glibc machine.
libm = dlopen("libm.so.6")
libc = dlopen("libc.so.6")

# One function, with its C declaration.
pow = ffi.Func(libm.pow, "double pow(double, double)")
println(pow(2.0, 10.0))

# A whole library at once, each function named the way its signature names it.
m = ffi.Bind(libm, ["double sqrt(double)", "double floor(double x)"])
println(m.sqrt(2.0), m.floor(3.7))

# A char * goes out as a tau string and comes back as one.
strchr = ffi.Func(libc.strchr, "char *strchr(const char *s, int c)")
println(strchr("hello, world", 119))

# The parse on its own: the result code, the argument codes, the name.
println(ffi.Sig("int puts(const char *s)"), ffi.Int32, ffi.CString)

# A signature that says too little is an error where it is written.
println(ffi.Func(libc.printf, "int printf(const char *fmt, ...)"))
1024
1.4142135623730951 3
world
[6, [13], puts] 6 13
ffi: "int printf(const char *fmt, ...)" is variadic, say the types of the arguments this call passes instead

The module is written in Tau: it reads the declaration and hands the numbers to the cfunc builtin, which prepares the call once. The two layers, and what each costs, are in Tooling.

crypto/sha256

sha256.Sum(data) gives the digest as bytes and sha256.Hex(data) as the sixty-four digits it is usually written with.

sha256 = import("crypto/sha256")

println(sha256.Hex("abc"))
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad

crypto/hmac

The keyed hash of RFC 2104 over SHA-256. hmac.Sum(key, msg) signs, hmac.Hex gives the same as digits, and hmac.Equal compares two signatures in constant time, so the comparison says nothing about the key.

hmac = import("crypto/hmac")

tag = hmac.Hex("key", "the message")
println(tag)

# Compared in constant time, so the comparison says nothing about the key.
println(hmac.Equal(hmac.Sum("key", "the message"), hmac.Sum("key", "the message")))
a562bc61d1c9dd0f0751c9017482806dc6d50b033a1692c7507b10a0f1e4c4ef
true

testing

The harness behind tau test. testing.Main takes a list of [name, function] pairs and exits non-zero when one fails; testing.Run runs a single case if you want to drive it yourself.

testing = import("testing")

testing.Main([
		["it adds", fn(t) {
				t.AssertEq(1 + 1, 2)
			}],
		["it fails loudly", fn(t) {
				t.AssertError(error("boom"))
			}]
	])
$ tau run demo_test.tau
--- PASS: it adds (0ms)
--- PASS: it fails loudly (0ms)
ok      2 passed of 2 (0ms)

And the rest

The modules above are the ones with a story to tell. These are the others, and each one reads in a few minutes: