Skip to content

Reference

Keywords ​

Echo reserves thirty-eight words. The list is short on purpose. There is no new, no this, no import, no switch, no try, no throw and no sizeof.

echo
function greet(string $name) : void
{
    echo $name;
}

greet("Echo");      // Echo

Six words in that program and two of them are reserved. Below is the full list, the words that look reserved and aren't, and every token that means something on its own.

The reserved words ​

Reserved means the lexer takes the word before anything else gets a look at it, so it can't be a function name, a struct name or a member name. Variables are safe either way, because a variable always carries a $.

Matching is whole-word. forward, classify, enumerate, nullable and constructor are all ordinary identifiers, and nothing about them collides with for, class, enum, null or const.

Declarations ​

WordWhat it doesCovered in
functiondeclares a function, a method or a constructorFunctions
returnleaves a function with a valueFunctions
structdeclares a value typeStructs
classdeclares a reference-counted heap typeClasses
interfacedeclares a set of requirements a type can answerInterfaces
destructordeclares the method that runs when a value diesStructs
operatordeclares an operator, infix, prefix or []Operators
namespaceputs the rest of the file in a namespaceNamespaces
usebinds a shorter name for a namespace, type, function or constant, for this fileNamespaces
externa C surface: extern { } of functions and incomplete types, extern struct Name;, or the C function-pointer type extern function<R(P...)>C interop
consta read-only variable, a constant, or a const ifConstants
privatenarrows a declaration to its file, or a member to its own typeVisibility
internalnarrows a declaration to its module, which is also the defaultVisibility
publicwidens a declaration to every moduleVisibility
statica method or property that belongs to the type, not to a valueStructs
testdeclares a test block, compiled only by echoc testTesting
enumdeclares a set of casesEnums
casedeclares one case of an enumEnums

Control flow ​

WordWhat it doesCovered in
if / elsethe ordinary branchControl flow
whileloop while a condition holdsControl flow
forinitializer, condition, stepControl flow
foreachwalk anything iterable, with asIteration
asa written destination ($x as T), and the binding half of a foreachCasts, Iteration
break / continueleave the loop, or jump to its next stepControl flow
guardunwrap, optionally bind, or leave the scopeNullability

Values ​

WordWhat it doesCovered in
true / falsethe two bool literalsTypes
nullthe absent value, only where a T? admits oneNullability
mvmove out of a variable, or declare a parameter that takes ownershipOwnership
strongturn a weak<T> back into a handle, if the object is still thereOwnership
instanceofask whether an interface value holds a particular typeInterfaces

Types ​

WordWhat it doesCovered in
ptrthe raw pointer type ptr<T>, and the ptr(...) conversionPointers
weakthe non-owning class handle weak<T>Ownership

Statements ​

WordWhat it doesCovered in
matchread which case an enum is holding. An expression, and a statement when its arms are blocksEnums
echoprint one value and a newline. A statement, not a functionYour first program
unsafeopen a block where raw storage may be promoted to a typed borrowUnsafe

Words that look reserved and are not ​

die, assert, dprint and exit read like keywords and are ordinary functions. They are declared in Echo, in the standard library, and the compiler answers them at the call site instead of emitting a call:

echo
assert(1 + 1 == 2);
echo "still here";      // still here

The full set of names the compiler answers this way: size_of, align_of, is_trivially_copyable, needs_destruction, take, init, die, assert, ref_count, weak_count, dprint, alloc_bytes, realloc_bytes, free_bytes, live_allocations, process_argc, process_argv, process_envp and exit. Nothing reserves them, so you can declare a function called assert and shadow it. I would not.

There is no print. echo is the only output statement in the language.

constructor is the other one, and its asymmetry with destructor is deliberate rather than an oversight:

echo
struct Point
{
    int32 $x;

    constructor(int32 $x) { $this->x = $x; }
    destructor() { }
}

echo Point(3)->x;       // 3

destructor is a real keyword so that no member can be named destructor and collide with the mangled name of the actual one. constructor is an ordinary identifier the type parser recognises by value, which means a struct can have a property called constructor and nothing breaks.

init in a type body is the same kind of word. init { $this->encoded = ...; } is recognised only there, and mem::init is still a function. Structs is the chapter.

A few more words are contextual in the same way, recognised only in one position and ordinary identifiers everywhere else:

  • type, inside an interface body, declaring an associated type.
  • map, inside an enum body, or at file scope as map dhd : int32 for Glyph { ... }. It names a second encoding of the cases. map<K, V> is still the stdlib type. Enums is the chapter.
  • self, in self::NAME, reaching a constant on the enclosing type.
  • left and right, inside an operator(45, left) precedence clause.
  • numeric, integer, signed, unsigned and floating, as generic constraint aliases.

Primitive type names are not keywords either ​

int32, usize, bool and the rest are plain identifiers that the type parser matches by string. That's why they can never be shadowed (the primitive is checked first) and also why they don't appear in the tables above. Primitive types has the full list.

string, array, map, slice and range aren't even that. They are structs in stdlib/core/, and with --no-stdlib they simply don't exist.

The tokens ​

TokenMeansCovered in
::namespace separator, nested type, self::NAMENamespaces
->member and method accessStructs
?->member access that stops at a nullNullability
??value on the left, or the right if it is absentNullability
?nullable suffix on a type, as in int32?Nullability
=>the key half of foreach ($m as $k => $v)Maps
:return type, attribute value, generic constraintFunctions
:$reach the pointer itself rather than what it points atPointers
&a reference, or bitwise and. Whitespace decides, see belowPointers
!negation, and the presence test on a nullableExpressions
**exponentiationExpressions
<< >>shift left, shift rightExpressions
&& ||logical and, logical orExpressions
| ^bitwise or, bitwise xorExpressions
~bitwise not, prefix-only like !Expressions
< >comparison, and the brackets around type argumentsGenerics
# [ ]an attribute is # followed by a bracketed valueAttributes
// /* */comments. No doc-comment form

Three things are missing from that table on purpose.

There is no ternary operator. ? is the nullable suffix and nothing else. ?? covers the case people usually reach for a ternary to write.

$x as T is a written destination. (int32)$x does not parse and int32($x) is not a function. See Casts.

.. and ..= are not tokens. They are ordinary operator declarations in stdlib/core/range.eco:

echo
foreach (0 .. 3 as $i) {
    echo $i;        // 0, then 1, then 2
}

0 .. 3 lexes as an integer, a dot, a dot and an integer, and the parser puts them back together the same way it does for any symbol a user declares. That's not a trick played for ranges. Declare <=> yourself and it goes through the identical path.

& is the one place whitespace changes meaning ​

& glued to what follows is a reference. & with a space after it is the bitwise operator:

echo
uint32 $h = 12;
uint32 $mask = 7;

echo $h & $mask;    // 4, bitwise and

The rule is exact rather than a matter of taste: & is a reference when the very next character is $ or any of A-Z, a-z, 0-9, _. Otherwise it is bitwise and.

The consequence to remember is that a digit counts. $mask &1 is not "bitwise-and one", it is a reference to something starting with 1, and the message you get will be about the wrong thing entirely. Put spaces around a binary &.

Unpicking this would be a lexer change, not a parser one, and it would change what &$a[$i] means everywhere. I'd rather write the space.

Two more places spacing matters ​

A binary - needs spaces, because - glues to a following digit and becomes part of the literal:

echo
echo 1 - 2;         // -1

Written 1-2 that is two integer literals in a row, and the compiler says unexpected '-2' - two expressions with no operator between them. Which is technically accurate and takes a moment to read.

#[name: $value] needs the space after the colon, because :$ is a single token that will eat both characters. #[cache:$ttl] loses its value silently. This one is a deliberate decision rather than an accident: :$ is worth more than the compact attribute spelling.

Next ​

Echo is a work in progress. Nothing here is a promise of stability.