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.
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.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
`\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.Void … ffi.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:
- net — TCP and UDP:
Listen,Dial, connections withRead,WriteandClose. - http — client and server:
Get,Post,NewServeMux, a server withListenAndServe, request and response objects,ParseURL. - errno — the system error numbers by name, for the calls that return one.
- syscall — the layer everything above stands on: open, read, write, socket, spawn, time, the environment. Written against a shared object of its own, no C library of the host in the way.
- ref — a box holding a value, for the times a closure has to share one rather than capture a copy.
- runtime — what the machine will say about itself:
NumCPU,OS,Arch. - buffer — a growing byte buffer with
Write,StringandBytes.