Part I — Foundations

How Quoin code is written and how the most basic pieces fit together: comments, literals, names, message sends, and operators.

Each section opens with a Rules box (the terse version for lookup) followed by prose and examples. ⚠ Gotcha boxes flag behavior that is surprising or a common source of wrong code.

Nav: Foundations · Blocks & control · Objects · Patterns & errors · Concurrency & iteration · Networking & the web · Types · Tooling · Library & reference · Appendices


1. Mental model

Rules

Quoin is a small, uniformly object-oriented language in the Smalltalk lineage. If you can read "send the message m to the object x" for every x.m, you can read almost all of it. A method with no explicit receiver is sent to self, written with a bare leading dot:

.print: 'hello'        "* sends print: to self
person.greet           "* sends greet to person
(1..5).collect:{ |n| n * 10 }

Because control flow is just methods (Part II), there is very little dedicated syntax to memorize — most of this document is about which messages exist, not about statements.


2. Lexical structure

Rules

Comments

"* A line comment — everything to the end of this line is ignored.

"
A block comment. It opens with a quote that is not followed by '*',
spans as many lines as you like, and closes at the next quote.
"

var x = 1 "this trailing block comment ends here" + 2   "* x is 3

Separators

A newline ends a statement when the result is unambiguous. A ; is needed when the next line would otherwise continue the previous expression — specifically when it begins with . (a message send) or an infix operator. The formatter (qn fmt) will generally figure this out for you, but a common gotcha is:

A <- {
    method -> {};          "* the ; ends the statement
    .mix:Mixin
}

Without the ; on the method line, this parses as (method -> {}).mix:Mixin — the .mix: send attaches to the method definition instead of starting a new statement.


3. Literals & data types

Rules

KindSyntaxExample
Integerdigits (0, or 19 then digits)42, 1000000
Doubledigits with a . and fractional digits3.14, 42.0, .5
String'…' (single quotes only)'hello'
Symbol#name, #multi:part:, #'…'#x, #when:do:, #'+:'
List#( … ) space-separated#(1 2 3), #()
Map#{ key: value … }#{ 'a': 1 'b': 2 }
Set#< … > space-separated, unique#<1 2 3>, #<>
Rangea..b (half-open)1..5, 5..1
Regex#/…/#/^[a-z]+$/
User string#Name'…'#ANSI'…'
User list#Name( … )
Block{ … }{ |n| n * 2 } (Part II)
Booleans / nilreserved identifierstrue, false, nil

⚠ Gotcha — inside #< … >, a bare > ends the set. Because the closing > would otherwise collide with the greater-than operator, > and >= are not treated as operators inside a set literal — the first bare > terminates it. To use them in an element, parenthesize: #<(a > b) c> is a two-element set. Every other operator works unparenthesized (#<a + b c>).


4. Variables, scope & destructuring

Rules

var x = 10
let greeting = 'hi'
x = x + 1                   "* reassign a `var`

var n: Integer = 42         "* typed local

var a b c = #(1 2 3)        "* a=1, b=2, c=3
var first *rest = #(1 2 3 4) "* first=1, rest=#(2 3 4)
var p q *_ = #(1 2 3 4 5)   "* p=1, q=2, rest discarded
var head (x2 y) = #(1 #(2 3)) "* head=1, x2=2, y=3  (nested)

Pi <- 3.14159               "* a constant; a second `Pi <- …` would throw

Only one splat is allowed per pattern level (a compile error otherwise; each nested ( … ) sub-pattern may carry its own), but it may lead, sit in the middle, or trail. Targets after a splat bind from the end of the list:

var a *mid z = #(10 20 30 40 50)
#(a mid z)                       "* -> #(10 #(20 30 40) 50)
var *init last = #('x' 'y')
#(init last)                     "* -> #(#(x) y)
var lead (p *q) *tail = #(1 #(2 3 4) 5 6)
#(lead p q tail)                 "* -> #(1 2 #(3 4) #(5 6))

Destructuring never errors on length: a missing position reads nil and a splat clamps to #() (on a too-short list the end-relative targets can re-read leading elements). _ (and *_) ignore the corresponding element(s).


5. Messages & call syntax

Rules

42.abs                         "* unary
'a,b,c'.split:','              "* one keyword arg
scores.at:'amy' put:95         "* selector is at:put:, two args
.print:'sum =' and:(2 + 2)     "* leading dot = self; parenthesize operator args

Because a keyword argument greedily consumes a full expression, you usually parenthesize an operator expression that you want to pass as a single argument (as with (2 + 2) above). Multi-part selectors let methods read like prose: coll.when:cond do:action is a single send of when:do:.


6. Operators & precedence

Rules

Desugaring

You writeCompiles toNotes
a + b a - b a * b a / b a % bSend("+:"…) etc.the overridable +: -: *: /: %: method on the receiver's type (resolved class-first; no global fallback)
a == b a != b a < b a <= b a > b a >= bSend("==:"…) etc.overridable ==: !=: <: <=: >: >=: methods
a ~ bSend("~:"…)the match protocol — dispatches ~: on the left operand (Part IV)
a .. bSend("..:"…)builds a NumberRange
a && b a || bshort-circuit jumpsnot method sends; right side is skipped when the left decides the result
-xSend("-")unary minus is the no-arg - method (binary - is -:); +x is Send("+"), the identity + method
!xSend("!")boolean negation (Object#'!' / Nil#'!' / the booleans)

Operators are therefore per-type customizable: define +: on your class and + works on its instances.

Note — precedence is conventional, with two specifics worth knowing. Multiplicative (* / %) binds tighter than additive (+ -), which binds tighter than comparison, which binds tighter than &&/|| — as you'd expect, so 2 + 3 * 4 is 14 and 1 + 2 == 3 is true. Beyond that: range .. is looser than arithmetic, so 2 .. n + 1 means 2 .. (n + 1); and postfix .method binds tighter than every infix operator, so 1 .. list.count is 1 .. (list.count) and a.x * b.y is (a.x) * (b.y).


Next: Part II — Blocks & control flow — closures, if:/whileDo:, truthiness, and the ^ / ^^ return operators.