Quirrel4.41.0

Cheat sheet

The language on two pages.

Values

null true falsethe three keywords
42 -7 0x1Finteger, decimal or hex
1_000_000_ is ignored
1.5 1e3 1.0e-3float; 1 == 1.0, types differ
'A'integer 65, no char type
"text"string
@"raw\no escape"verbatim string
$"got {n} of {t}"interpolation; nests, \{ escapes
[1, 2, 3]array
{ a = 1, b = 2 }table
[...a, v], { ...t, k = v }spread: copies a or t into the literal

Value types: null, bool, integer, float. Reference types: string, array, table, function, class, instance, generator, thread, weakref, userdata.

Falsy are only null, false, 0, 0.0. "", [] and {} are true.

Bindings

let x = 1bind once; the default
local x = 1reassignable
const MAX = 10compile time, folded
enum E { A = 1 }compile time
global constvisible outside the file
let f;forward declaration

let fixes the name, not the value: let t = {} still allows t.x <- 1. Use freeze for the contents.

Control flow

if (a) { } else if (b) { } else { }
while (c) { }
do { } while (c)
for (local i = 0; i < n; i++) { }
foreach (v in arr) { }
foreach (i, v in arr) { }   // index first
foreach (k, v in tbl) { }   // no order
break   continue   return

No switch by default; #allow-switch-statement at the top of a file turns it on.

Functions

function add(a, b) { return a + b }
let mul = @(a, b) a * b        // lambda
function greet(n, hi = "hi") { }
function sum(...) { return vargv.len() }
function [pure] sq(x) { return x * x }

Types are optional, checked at compile time where inferable, else at run time:

function scale(v: number, by = 1.0): float {
  return v * by
}

int float number bool string null table array function class instance generator thread weakref userdata any, joined by |. number is int|float.

Classes

class Squad {
  name = "none"        // per instance
  static MAX = 12      // on the class only, read-only
  constructor(name) { this.name = name }
  function report() { return this.name }
}
class Tank(Squad) { }  // extends Squad

this. is required in a method; a bare name is not a field. Metamethods customize operators. == is not one.

Arrays

a.append(v, w)not push; takes many
a.insert(i, v)
a.remove(i)
a.indexof(v)not find; null if absent
a.contains(v)by value
a.slice(1, -1)negative from end; clamps
a.sort()sort(cmp), cmp gives -1 0 1
a.map(f) a.filter(f) a.each(f)f([value], [index], [container]), as many as f declares
a.reduce(f)f([accumulator], [value], [index], [container])
a.totable()values become keys, or
[[k, v], ...] becomes slots

Tables

t.keys() t.values()
t.len()
t.x <- 1create/replace a slot
t.x = 1assign; throws if absent
t.$rawdelete("x")remove; delete is off
k in tk not in t

On an array, in tests the index, not the value.

Strings

s.len()bytes, not characters
s[0]the byte, an integer
s.slice(a, b)
s.indexof(sub)null if absent
s.toupper().tolower() .strip()
s.split(",")to an array
", ".join(arr)back to a string
"".concat(a, b)
s.startswith(p).endswith(p)
"{0}/{1}".subst(a, b)

A string is bytes, so slice can cut a UTF-8 character in half.

Null safety

a?.b a?[i] a?()null instead of throwing
a ?? fallbackonly when a is null

0 and false survive ??. One ?. covers the rest of the chain.

Modules

from "math" import clamp     // compile time
import "utils.nut" as utils
let m = require("math")      // run time
let p = require_optional("p.nut")

Every import comes before any other statement. A module returns its exports: return freeze({ a, b }).

Traps

Tooling

sq f.nut          run
sq -sa f.nut      analyzer
sq --warnings-list
sq -D:<name> -sa f.nut