Variables & types

Falx is dynamically typed: a variable has no declared type, only whatever value it currently holds.

let and const

Both declare a variable and require an initializer -there's no bare let x;. const just marks the variable immutable; reassigning one is a runtime error.

let count = 0;
const maxRetries = 3;

count = count + 1;  // fine, `count` is mutable
maxRetries = 5;    // runtime error: cannot assign to a const

Names follow the usual convention: a letter or underscore, then letters, digits or underscores.

The runtime types

These are the values typeof() (see the built-ins reference) can return:

Typetypeof()Literal syntax
Number"number"42, 3.14 -one numeric type; see below
String"string""hello"
Boolean"boolean"true, false
Null"null"null
Array"array"[1, 2, 3]
Map"map"{ key: value }
Tuple"tuple"(1, 2)
Function"function"define(a, b) { ... }
Module"module"the result of require(...)
Struct instancethe struct's own name, e.g. "Player"Player(...)

Numbers

There is one numeric literal form; Falx tracks whether a value is an integer or a float internally (so 2 + 2 stays an integer 4, while 2.0 + 2 becomes the float 4.0), but there is no separate int/float type at the language level -both report typeof as "number".

output(1 + 1);     // 2
output(1 + 1.0);   // 2.0
output(typeof(1.0)); // number

Only base-10 integer and decimal literals are supported (no hex/octal/binary literals, and no digit separators like 1_000).

Strings, booleans and null

Covered in full on the Strings page. Booleans are exactly true/false (lower-case keywords, not string values). null is its own type and its own keyword.

Truthiness

This is the sharpest edge in the language today -if conditions are checked differently from while conditions, &&/||, and !. This has been verified by running each example below against the interpreter, not just read from source.

if / elseif only ever recognise an actual boolean. Any condition that isn't literally true or false -a string (even "true"), a number, an array, a map, null -is silently treated as false. No error is raised.
if ("true") { output("shown"); } else { output("not shown"); }
// prints "not shown" -a string is never truthy in `if`, not even "true"

if (1) { output("shown"); } else { output("not shown"); }
// prints "not shown" -same for numbers

while, &&, || and unary ! instead call each value's own boolean conversion directly. Only two types define one:

  • Booleans convert to themselves, as expected.
  • Strings convert to true only if the string is exactly "true" (case-insensitive); every other string -including "false", "", or "hello" -converts to false.
  • Everything else (numbers, arrays, maps, tuples, null, struct instances) has no boolean conversion at all, and using one in these positions raises a runtime error: Cannot convert '<type>' to bool (or Cannot invert '<type>' for !).
let i = 0;
while ("true") {         // fine -the string "true" is truthy here
    i = i + 1;
    if (i == 2) { break; } // `==` produces a real boolean, so `if` sees it fine
}

while (1) { output(i); } // runtime error: Cannot convert 'number' to bool

In practice this means: write conditions that evaluate to an actual boolean -a comparison (i < 10), an equality check (x == true), a call to something like .contains(...) or .isEmpty() (see Arrays / Maps) -rather than handing if or while a raw number, string or array and hoping for JavaScript/Python-style truthiness. None of the example programs in the repository rely on implicit truthiness of non-booleans, which is a good sign this is worth avoiding. See E1011 and E1015 in the errors reference for exactly when this raises.