Nodora
Language

Types

The Nodora type system

Nodora is statically typed. Every expression has a type that the compiler checks before any code runs. Type errors are reported with a source location at compile time.

Primitive types

TypeLiteral examplesNotes
string"hello", ""Double-quoted. Supports \n, \t, \r, \", \\.
number0, 42, 3.14Stored as 64-bit float internally.
booltrue, false
object{ a: 1, "b": "x" }String keys, heterogeneous values.
array[1, 2, 3], ["a", "b"]Written as array<T> in built-in signatures.

There is no separate integer type — 1 and 1.0 are the same value. The built-in int(x) truncates a number to its integer part.

Compound types

Arrays

Arrays are homogeneous in signatures but can hold mixed values at runtime. Built-in argument types are written array<T> (for example array<string> or array<any>).

xs = [1, 2, 3]
first = xs[0]          // 1
flat  = arrays::flatten([[1, 2], [3]])

Indexing an array out of bounds is an evaluation error. Indexing with a non-integer is also an error.

Objects

Objects are unordered string-keyed maps. Keys can be bare identifiers or string literals; values are any expression.

user = {
    name: "Alice",
    "age": 30,
    tags: ["admin", "ops"]
}

n = user.name           // dot access
a = user["age"]         // bracket access
t = user.tags[0]        // chained

Accessing a missing key yields undefined.

undefined

undefined is a distinct value that represents absence. It is what you get from:

  • A missing object field (input.nope).
  • A division or modulo by zero.
  • Any operation whose operand is itself undefined (propagation).

Outputs whose value is undefined are omitted from the evaluation result rather than serialized as null. This makes it safe to write optional outputs without guard expressions.

any

any is the top type. Argument slots typed any accept values of any type, and any is assignable from anything. You will mostly see it in built-in signatures like is_defined(value: any) -> bool.

Union types

Some built-ins accept a union of types. Unions are written A|B|... in signatures, for example:

len(value: union<string|array<any>|object>) -> number
min(value: union<array<number>|array<string>>) -> union<number|string>

A value is assignable to a union if it is assignable to at least one member.

Lambda types

Higher-order built-ins (some, every, arrays::find_index, arrays::group_by) take a lambda. Lambdas are written with pipes:

positive = |x| x > 0
has_pos  = some(input.numbers, |x| x > 0)
idx      = arrays::find_index(input.items, |it| it.flagged)

Lambda types are written |T1, T2, ...| -> R.

Lambdas are first-class — you can bind one to a name and pass it later — but they cannot be stored in JSON inputs or outputs. They only exist during a single evaluation.

Type checking and coercion

Nodora does not implicitly coerce between types. Comparing a string to a number is a type error; concatenating a number to a string with + is a type error. Use sprintf or conv::atoi / conv::atof for explicit conversion.

Undefined propagation rules

SituationResult
Any operand of + - * / % is undefinedundefined
Any operand of < <= > >= is undefinedundefined
== or != with an undefined operandundefined
&& / || with an undefined operandundefined
a.b where a is undefinedundefined
arr[i] where arr or i is undefinedundefined
Division or modulo by 0undefined
Most built-ins receiving undefinedundefined

Use is_defined(x) to test explicitly.

On this page