Documentation / Language basics

Language basics

Moth uses a compact set of syntax rules across the whole language. Colons open blocks, semicolons close them and square brackets build strings rather than index collections.

These choices differ from C-like and Python-like languages, but they keep each delimiter tied to one job. Start here before moving to bindings, control flow and user-defined types.

Choose the explanation level: Blocks and statements

Blocks and statements

Most Moth blocks have a colon at the start and a semicolon at the end.

greet |name String|:
    io.line([: Hello, [name]])
;

The colon opens the function body. The semicolon closes it.

Semicolons do not end every line. Add one only when the surrounding block is finished.

Use indentation to make nesting easy to see:

if ready:
    io.line("Ready!")
;

Moth uses explicit block delimiters.

A colon opens a block. A semicolon closes that block.

clamp_to_zero |value Int| -> Int:
    if value < 0:
        return 0
    ;

    return value
;

Contract

  • : opens a function body, branch, loop or another block-form construct.
  • ; closes the active block.
  • A semicolon does not terminate an ordinary statement.
  • Statements inside a block are evaluated in source order unless control flow exits or redirects the block.
  • Child blocks create child scopes.
  • Indentation is for readability. Explicit delimiters define block boundaries.
  • The language has no general authored lexical-block construct or control-flow labels.
  • Immediate identifier: headers in executable statement position are reserved for declared regions. Full declared-region semantics remain deferred.
  • Exact _: is invalid. Keyword-led semantic scopes such as async: remain distinct.

Reading the shape

The final semicolon in the example closes the function. The earlier semicolon closes the if block.

Nested language features may add their own structural rules. Branching, loops, functions, declared regions and template control flow document those forms in detail.

Choose the explanation level: Comments and naming

Comments and naming

In ordinary Moth code, comments begin with two minus signs:

-- This source-code line is ignored by the compiler.
score = 10

Template and moth template bodies are content. Two minus signs inside those bodies are text rather than source comments.

Use regular_snake_case for values and functions:

player_score = 10

double_score |score Int| -> Int:
    return score * 2
;

Use PascalCase for named types:

PlayerScore = |
    value Int,
|

Clear names beat short names. player_score is easier to understand than ps, especially when the code grows.

Config and its keyword-shadow variants are reserved for build configuration. Import is an ordinary type name.

In ordinary Moth code, -- starts a single-line comment.

-- Explain why this value exists.
retry_count = 3

Everything after -- on that source-code line is comment text.

Template and moth template text

Template bodies are content, not ordinary code-token context.

Inside a template body, -- is ordinary body text. A moth template .mtf file is the body of an implicit compile-time Markdown template, so -- is text there too.

Use template directives such as $note or $todo when template-authored content should be ignored.

Naming conventions

  • Variables and functions use regular_snake_case.
  • Structs, choices, type aliases and generic parameters use PascalCase.
  • Trait names use all-caps identifiers such as DISPLAY_TEXT.
  • Names should describe their role rather than their storage or implementation detail.
retry_count = 3

load_profile |user_id String| -> String:
    return "profile"
;

UserProfile = |
    display_name String,
|

Name collisions

A visible name cannot be redeclared while it remains in scope.

The complete no-shadowing contract is documented by Values and bindings. Dependency aliases and module API names have additional collision rules in the Packages documentation.

Reserved configuration names

Accepted deferred. The config identifier family is compiler-reserved. Keyword-shadow matching is case-insensitive and strips leading underscores, so config, Config, CONFIG, _config, _Config and equivalent shadows cannot be user declarations. This reserves one vocabulary for config.moth, entry config: blocks and #Config.

import is not a keyword. Import is an ordinary valid PascalCase user name:

Import = |
    source String,
|

Ordinary dependency clauses do not use an import keyword.

block, group and region are not keywords. They follow ordinary identifier policy. In executable statement position, each corresponding name: header has the same reserved declared-region shape as any other valid identifier.

Choose the explanation level: Core values

Core values

Moth can usually work out a value's type from the value itself.

enabled = true
count = 42
ratio = 0.5
letter = '🦋'
name = "Moth"

You can write the type when it helps the reader or gives the compiler needed context:

count Int = 42
ratio Float = 0.5
name String = "Moth"

The most common core types are:

  • Bool for true and false
  • Int for whole numbers
  • Float for decimal numbers
  • Char for one character
  • String for text

Collections, structs, choices and options build larger values from these ideas.

Values have a semantic type. A binding may infer that type from its initializer or state it explicitly.

enabled = true
count Int = 42
ratio Float = 0.5
letter Char = '🦋'
label String = "Moth"

Core forms

  • true and false are Bool values.
  • Whole-number literals are naturally Int.
  • Decimal-point literals are naturally Float.
  • Single-quoted literals create Char values.
  • Quoted slices and string templates use the String type.
  • none requires an optional T? receiving context.

The Numbers documentation owns exact numeric grammar and arithmetic behaviour. The Strings and characters section owns the implemented string source forms. Options are documented with Errors and options.

Composite values

Collections use braces. String templates use square brackets.

names = {"Priya", "Rob"}

message = [:
    Hello from a template.
]

Structs, choices, collections, maps and generic instances add richer value shapes without changing the basic annotation form.

Binding mutability and access mode are separate from semantic type identity. See Values and bindings for that contract.

Choose the explanation level: Strings and characters

Strings and characters

Use double quotes for normal text:

greeting = "Hello"

Expression-position backtick strings aren't implemented. Use an ordinary string with escapes when the text contains backslashes:

windows_path = "C:\\docs\\site"

Use single quotes for one Unicode scalar value:

seedling = '🦋'

Char follows scalar values rather than UTF-16 code units. A user-perceived character made from more than one scalar value needs a String.

Use a template when text needs values or a larger structure:

name = "Priya"

greeting = [:
    Hello, [name]
]

Joining strings

Use templates to join strings together. Put the parts inside square brackets:

first = "Hello, "
second = "world"
message = [first, second]

Don't use + to join strings. + is for numbers only. Use templates to concatenate strings.

Read-only strings

A quoted string like "Hello" is read-only content. You can reassign a mutable binding to point at a different string, but you can't change the string itself.

Square brackets belong to templates. Collections use curly braces instead.

Moth has two string source forms and one character form. Both string forms share one semantic String type at ordinary typed boundaries.

escaped_slice = "Hello\nworld"
letter = '🦋'

name = "Priya"
message = [:
    Hello, [name]
]

Quoted string slices

Double quotes create escaped string slices. They support exactly these escapes:

  • \\ for a backslash
  • \" for a double quote
  • \n for a newline
  • \r for a carriage return
  • \t for a tab

Every other escape is invalid. A backslash can't continue a quoted string across a physical newline.

A quoted string slice is read-only content. It has no character or substring mutation surface and isn't concatenated with +. A slice may be read, compared, passed, returned, stored and inserted into templates. A mutable binding that currently holds a slice may be reassigned. That doesn't make the slice contents mutable.

name ~= "Priya"
name = "Rob"  -- reassigns the mutable binding, doesn't mutate the slice

Characters

Single quotes create Char values.

Template strings

Square brackets create string templates. Templates produce owned string values and may fold at compile time or lower to runtime string construction.

Templates are the idiomatic and canonical way to:

  • concatenate strings
  • interpolate values
  • build runtime or compile-time text
  • produce strings that need full template composition
first = "Hello, "
second = "world"
message = [first, second]

name = "Priya"
greeting = [: Hello, [name]]

Use [left, right] for concatenation. Don't use +:

-- VALID: template concatenation
joined = [left, right]

-- INVALID: + is a numeric operator only
joined = left + right

Source String + String is invalid because + is a numeric operator only. Runtime equality compares content for every String origin, and String map keys use the same content rule. Non-JS map lowering remains target-gated. See Progress matrix for current target support.

One semantic String type

Quoted slices and template-produced strings share one String type at ordinary typed boundaries. They share:

  • parameter and return compatibility
  • equality
  • choice payload equality
  • collection and map value use
  • String map-key use
  • casts
  • IO and external StringContent boundaries
  • template insertion
  • aliases and generic use where String is concrete

Construction origin doesn't create hidden equality, hashing, map-key or call-compatibility rules.

Equality

String supports is and is not. String doesn't support ordering operators (<, <=, >, >=).

slice = "hello"
template = [: hello]

-- VALID: equality across both forms
same = slice is template

-- INVALID: ordering is not supported
-- ordered = slice < template

Binding reassignment versus content mutation

A mutable binding that holds a string can be reassigned. Quoted slices are immutable. Templates produce owned String values. The current source surface does not yet expose in-place string mutation operations.

text ~= "initial"
text = "replacement"  -- valid: reassigns the binding

-- No mutation API exists:
-- text.append("more")  -- invalid
-- text[0] = 'X'        -- invalid

Templates

Templates own interpolation, formatting, slots, wrappers and structured content. See Templates for the complete system.

Quoted slices and templates both participate in the language's String surface. Expression-position raw backtick slices aren't implemented. Inside a template body, backticks, backslashes and physical newlines are ordinary body text available to the active formatter.