Skip to content

Syntax

Jiang syntax is organized around declarations, explicit types, and visible low-level operations. When reading a file, start with imports and top-level declarations, then move into statements and expressions inside function bodies.

The project uses these conventions:

  • Type names use PascalCase.
  • Function names, variable names, field names, enum members, and module aliases use snake_case.
  • Primitive types such as Int, Bool, and UInt8 are written like ordinary type names.
enum TokenKind {
kw,
string_lit,
left_paren,
}
struct SourceFile {
UInt8[]& file_path;
Int start_offset;
}
UInt8[]& read_source(UInt8[]& file_path) {
return file_path;
}

Self is a special name in type position. self is available in instance methods and init bodies; functions without a self parameter do not have self.

A source file is a sequence of top-level items:

  • import
  • alias
  • const
  • global declarations
  • functions
  • struct, enum, union, trait
  • extend
  • comptime blocks
import math = "utils/math.jiang";
alias Byte = UInt8;
alias answer = math.answer;
public const Int default_limit = 1024;
public struct Pair {
Int left;
Int right;
}
public Int add(Int left, Int right) {
return left + right;
}

public marks a declaration as visible outside the module.

public const exports a compile-time value through the module interface. comptime { ... } is a top-level compile-time block for source selection; see Const and Comptime.

Some keywords can carry options. Jiang writes these options with square brackets after the keyword:

enum [UInt8] ByteTag {
block = 1,
item,
}

The old keyword(...) form is not used for keyword options. Parentheses remain ordinary expression or type syntax, so keyword options stay visually separate from function calls.

Blocks contain declarations, destructuring statements, assignments, call statements, control-flow statements, and defer.

Int main() {
Int total! = 0;
for i in 0..10 {
total = total + i;
}
return total;
}

Most statements end with semicolons. Control-flow forms that own a block, such as if, switch, while, and for, do not need an extra semicolon after the block.

A block has a value only when it ends with a tail expression without a semicolon:

Int value = {
Int base = 40;
base + 2
}

Without a tail expression, the block value is ().

Jiang expressions include literals, names, calls, field access, indexing, slicing, arithmetic, comparisons, if, switch, try catch, and blocks.

Int value = if flag {
1
} else {
2
}

Field literals are written with their type path:

Point origin = Point(x: 0, y: 0)

$ enters the implicit operation layer for a value or type:

Int value = 42;
Int& ref = value$.ref();
Int copied = ref$.get();
UInt size = Int$.size();

Use parentheses when applying $ to a compound expression:

Int raw = (left + right)$.as(Int);

Prefer safe target-type initialization such as Float(value) for ordinary conversions. Do not use value$.as(Type) as the everyday conversion spelling; it is a low-level cast form for raw pointers, integer addresses, FFI, and similar code.

is performs pattern matching. Optional values use .some(...):

if maybe is .some(value) {
return value;
} else {
return 0;
}

Mutable payload bindings write the payload type explicitly:

if maybe is .some(Int value!) {
value = value + 1;
}

switch uses the same style of patterns for optional, variant, and literal matches.