slate

Lexical structure

What the compiler reads before it reads anything else: files, whitespace, names and literals.

Files

A slate program is a .sl file. A file that writes elements is conventionally .slx, after .tsx — but the extension says nothing to the compiler, which parses both the same way, and elements are read in every file. It is a name for a reader and an editor.

A file with a #! line on its first byte is a command. slate skips that line and everything after the program’s name on the command line belongs to the program:

#!/usr/bin/env slate

import { args, exit } from slate:process

if args.len() == 0
    print("usage: greet <name>...")

for name in args
    print("Hello, " + name + "!")
usage: greet <name>...

#! is read at the very first byte and nowhere else. # is not a comment in slate.

Comments

// to the end of the line. There is no block comment.

Indentation is structure

slate is off-side ruled, as Python and sysl are. A block is opened by indenting under the line that introduces it and closed by returning to the outer column.

A newline inside brackets means nothing, which is what lets an argument list or an array literal be split across lines. Two tokens suspend that rule, and only where they end a line: -> and match. Either one at the end of a line opens a block even inside brackets, which is what lets a callback with a real body be written where it is passed:

forEach([1, 2, 3], x ->
    val doubled = x * 2
    print(x, doubled))
1 2
2 4
3 6

An arrow written mid-line would hand the block to whatever line came next, so it does not open one.

A block lambda has to be the last argument, because its block runs to the end of its last line and a , arriving there has nothing to mean.

A trailing operator does not continue a line. a + followed by b on the next line is two statements, not a sum. Where an expression has to span lines, brackets are what say so — inside them the off-side rule is suspended and the continuation is unambiguous:

isShort(n) = len(n) < 4
isKnown(n) = n == "ada"

val name = "ada"
val ok = (
    isShort(name) ||
    isKnown(name))

print(ok)
true

end, and the closing words

A block that has grown long enough to want a closing marker takes one: end if, end while, end for, end loop, end <name> for a definition or a class. end is a soft word — a program may still call something end — and so are class, data and type.

Names

An identifier is a letter or _ followed by letters, digits and _. Names are case-sensitive.

The type words are not keywords. null, boolean, integer, real, number, string, array, object, function, date, time, zone and the rest of the twenty-one listed in Patterns are ordinary names in expression position — val int = 3 and val date = readIt() both work — and are read as type tests only in pattern position.

There are no abbreviations. It is boolean and function, never bool and fn; a short form is refused with the long one named.

A keyword may name a FIELD, as it may in JavaScript — after a ., in an object literal, in a with, and as a pattern’s key. A word the grammar happens to use is not a word a program controls: somebody else’s JSON has an if in it.

val o = { with: 1, if: 2, match: 3 }

print(o.with, o.if, (o with { if: 9 }).if)

val { if: n } = o

print(n)
1 2 9
2

A keyword still may not name a BINDING, so the { name } shorthand has nothing to introduce where the key is one, and says so:

val { with } = { with: 1 }
is a word the grammar has taken

Literals

val text = "it"

print(null, true, false)
print(42, -7, 1_000_000)
print(0xff, 0b1011, 0o17)
print(3.14, 2e10, 1e-3)
print("text", s"interpolated ${text}")
print([1, 2, 3], [0; 5])
print({ a: 1, b: 2 })
print(0..<3, 1..10)
null true false
42 -7 1000000
255 11 15
3.14 2e+10 0.001
text interpolated it
[1, 2, 3] [0, 0, 0, 0, 0]
{a: 1, b: 2}
0..<3 1..10

An integer may be written in hexadecimal, binary or octal, and _ may be written between digits of any of them.

Integers and reals are separate values, not one numeric type. An integer is 64 bits, wraps, and divides towards zero.

Every comma list takes a trailing comma: an array, an object, an argument list, a parameter list.

[v; n] is an array of n copies of v.

Strings

A string literal is double-quoted with the usual escapes (\n, \t, \\, \", \u{...}). There is no single-quoted form and no character type — a single character is a string of one.

An s-string interpolates:

val w = 3
val h = 4
val point = { x: 7 }

print(s"${w} by ${h}")
print(s"$w by $h")           // the same thing
print(s"$point.x")           // interpolates `point`, then `.x` is text
print(s"${point.x}")         // says the other thing
print(s"costs 5$")           // a `$` that begins no name is just itself
print(s"$$")                 // a literal `$`
3 by 4
3 by 4
{x: 7}.x
7
costs 5$
$

A hole that is a single name needs no braces. The short form is identifier-only and stops at the end of the name, which is why $point.x and ${point.x} differ.

There is no raw string. A backslash in a string is an escape wherever it appears, so a regular expression written as a literal doubles its backslashes: regex("\\d+").

What a program never writes

There is no undefined, no NaN that spreads, and no Invalid Date. Each of those is a value that travels through a program silently changing what everything downstream computes, and slate has none of them: the equivalent situation is either a null the program can test or a fault it can catch.

Search

Esc
to navigate to open Esc to close