| name | Language Syntax |
| description | Documentation of Syntax and APIs for Chemical Programming Language, A must read before implementing something in chemical programming language.s |
Chemical Language Syntax Reference
This document provides a comprehensive reference for Chemical language syntax, designed to help AI systems generate correct Chemical code. All syntax described here is based on analysis of the parser implementation and existing codebase.
Table of Contents
- Core C-like Features
- Standard Library
- Advanced Features
- Best Practices for AI Generation
Basic Structure
File Extensions
- Source files:
.ch
- Module descriptor:
chemical.mod
Module Declaration
module module_name
source "src" // Include all .ch files in src directory (recursive)
import module_name // Import other modules
Important: No import statements are required at the top of .ch files. Any .ch file can access anything in the current module and from any modules imported in chemical.mod. Import statements are only valid in build.lab or chemical.mod files.
Namespaces
Chemical supports C++-style namespaces for organizing types and functions and for avoiding name collisions.
Declaring a namespace
namespace cool {
struct Pair2 {
var a : int
var b : int
}
func pair2_sum(p : *Pair2) : int {
return p.a + p.b;
}
}
Qualified access with ::
Use namespace_name::identifier to refer to types/functions inside a namespace from outside that namespace.
var p : cool::Pair2 { a : 0, b : 0 }
var x = cool::pair2_sum(&p)
Exposing a namespace to other modules
You must make the namespace public if you want to expose anything inside it to other modules.
This is a hard rule. Without it you'll get symbol resolution errors. By default every namespace is internal, Everything inside it
is only accessible from inside the same module.
Anything inside the namespace (struct/function/variant) must be also made public if you want to expose it to other modules.
Extending (re-opening) a namespace
The same namespace can appear multiple times in the same file; later blocks extend the namespace and can use earlier declarations.
namespace cool {
func pair2_mul(p : *Pair2) : int {
return p.a * p.b;
}
}
Unqualified use from inside the same namespace
Within a namespace X { ... } block, names inside X can be referenced without X::.
namespace cool {
func pair2_sum_call(p : *Pair2) : int {
return pair2_sum(p);
}
}
using declarations
Bring a single symbol into the current scope:
using closed_bro::bring_me_in;
func demo() : int {
return bring_me_in();
}
Bring all symbols from a namespace into the current scope:
using namespace all_closed;
func demo2() : int {
return check_im_closed();
}
Practical guidance (especially for codegen and edits)
- Prefer qualification: Use
X::name in generated code when possible; it reduces ambiguity and avoids name collisions.
- Use
using sparingly: using namespace X; is convenient, but it can hide where a symbol comes from and can create conflicts if multiple namespaces define the same identifier.
- Be consistent when editing: A typo in the namespace name (e.g.
cool vs coool) silently creates a different namespace and breaks lookups.
- Type aliases in namespaces: Type aliases can live in a namespace and are referenced with
:: the same way as types.
// Mutable variables
var name : type = value
var name = value // Type inference
// Immutable constants
const name : type = value
const name = value // Type inference
// Compile-time variables
comptime var name : type = value
comptime const name : type = value
Variable Initialization Rules
- Local variables must be initialized at declaration
- Global/top-level variables can be declared without initialization if type is specified
const variables must be initialized at declaration
- Type inference available when value is provided
Examples
var x : int = 42
var message = "Hello, World!"
const PI = 3.14159
comptime var COMPTIME_VAL = 100
Basic Types
Primitive Types
// Chemical integer types
i8, i16, i32, i64, int128
u8, u16, u32, u64, uint128
// C-compatible types
char, short, int, long, longlong
uchar, ushort, uint, ulong, ulonglong
// Floating point
float, double, longdouble, float128
// Other types
bool, void, any
Pointers and References
var ptr : *int // Pointer to int
var ptr_ptr : **int // Pointer to pointer to int
var func_ptr : () => int // Function pointer
var ref : &int // Reference to int
Common Mistake
Using const in pointer type is not recommended, for example
// Invalid Code (const doesn't exist)
var ptr : *const int
Every pointer is by default non-mutable, you use *mut int for mutability
*char in Chemical means const char* in C
How to take a pointer of an object
Use &raw obj or &raw mut obj to take a pointer or mutable pointer respectively
func i_take_ptr(ptr : *mut Point) {}
func i_send_ptr(p : Point) {
i_take_ptr(&raw mut p)
}
How to take a reference
func i_take_ref(ptr : &mut Point) {}
func i_send_ref(p : Point) {
i_take_ref(&mut p)
}
Arrays
var arr : [10]int // Fixed-size array
var multi : [10][20]int // Multi-dimensional array
Control Flow
If Statements
if(condition) {
// then block
} else if(other_condition) {
// else if block
} else {
// else block
}
// If as expression
// must not use braces to specify values
var result = if(condition) value1 else value2
Switch Statements
switch(value) {
case1 => {
// case 1 block
// break statement NOT allowed
}
case2 => {
// case 2 block
}
default => {
// default block
}
}
// Switch as expression
var result = switch(value) {
case1, case2 => value
default => default_value
}
Loops
// While loop
while(condition) {
// loop body
}
// Do-while loop
do {
// loop body
} while(condition)
// For loop
for(var i = 0; i < 10; i++) {
// loop body
}
// Loop as expression
var result = loop {
// loop body
break value
}
Break and Continue
for(var i = 0; i < 10; i++) {
if(condition) {
break
}
if(other_condition) {
continue
}
}
Functions
Basic Function Definition
func function_name(param1 : type1, param2 : type2) : return_type {
// function body
return value
}
// Public functions (exposed to other modules)
public func public_function(param : type) : return_type {
// implementation
}
Function Parameters
// Regular parameters
func func(param : type) : return_type
// Default values
func func(param : type = default_value) : return_type
// Variadic parameters, only for c extern defined functions
func func(param : type, ...) : return_type
Structs
Basic Struct Definition
// public before would expose to other modules
struct StructName {
// Members with access specifiers
public var member1 : int
private const member2 : std::string
// Default values
// A member is by default public (like C++ struct)
var member3 : float = 0.0
// Methods
func method_name(&self) : return_type {
// method body
}
}
How to initialize a struct
Chemical has three struct annotation combinations that affect initialization:
| Annotations | T{} works? | T.make() works? | T{field: val} works? |
|---|
@direct_init only | Yes (all fields required) | No | Yes (all fields required) |
@make only | No | Yes | No |
@direct_init + @make | Yes (all fields required) | Yes | Yes (all fields required) |
| Neither | Yes (all fields required) | No | Yes (all fields required) |
Critical: A struct with @make but without @direct_init cannot use {} syntax at all. The compiler errors with "struct has a constructor, use @direct_init to allow direct initialization". You must use T.make() instead.
// @direct_init only:
var s = StructName { member1 : 0, member2 : 0 } // All fields required
// @make only:
var s = StructName.make() // Works
// var s = StructName{} // ERROR: struct has a constructor
// var s = StructName{member1: 0} // ERROR: struct has a constructor
// @direct_init + @make:
var s = StructName{} // Works (all fields zeroed)
var s = StructName{member1: 0} // Works (all fields required)
var s = StructName.make() // Works
Calling the constructor
When calling the constructor, You must not use the name of the function of the constructor, for example
// compiler will automatically determine the constructor
// like c++
var p = Point(10, 20)
If you use the name of the constructor function, chemical won't complain, but for example library author changes the
name of the constructor, that would cause your project to break at compilation.
Creating a zeroed struct
// this will set all values to zero
// if the struct is destructible or has a constructor, this may not work, and compiler may force you to use annotations
var s = zeroed<StructName>()
Common Mistake
When initializing struct, members without a constructor or a default value MUST be initialized, otherwise there would be
compilation errors.
vector<T>{} standalone init
vector<T>{} aggregate init only works inside @direct_init struct init context. Standalone var x = vector<T>{} fails. Use the constructor:
// WRONG (outside struct init):
// var file_data = vector<u8>{}
// CORRECT:
var file_data = vector<u8>()
Struct Members
- Fields:
var name : type or const name : type
- Methods:
func name(params) : return_type
- Constructors: Use
@constructor or @make annotation
Destructors
struct ManagedResource {
var resource : *void
@delete
func destruct(&self) {
unsafe {
// dealloc statement just calls free under the hood
// use dealloc instead of free, because it can be optimized out
// when users are using a garbage collected runtime
dealloc self.resource
// even better would be using delete
// delete ptr; which calls the destructor (if any) and then calls free
}
}
}
Access Specifiers
public
private
- Top Level decls only accessible in current file
- Struct members/functions only accessible in current struct
internal
- Only exposed to the current module
protected
- Top level decls not exposed to other modules but public at link time
- Struct members/functions visible to inheriting structs only
By default every top level decl (struct/function/variant/namespace) is internal
by default every struct member/function is public
Unsafe Code
Unsafe Blocks
func unsafe_operation() {
unsafe {
// Unsafe operations here
var ptr = malloc(100) as *int
*ptr = 42
// dealloc statement just calls free under the hood
dealloc ptr;
// even better would be using delete
// delete ptr; which calls the destructor (if any) and then calls free
}
}
Memory Management
func manual_memory() {
unsafe {
var ptr = malloc(100) as *int
*ptr = 42
// Use pointer
// dealloc statement just calls free under the hood
dealloc ptr;
// for structs use delete instead of dealloc
// delete ensures that destructors are called
}
}
New and Dealloc
func dynamic_allocation() {
unsafe {
var ptr = new int // Allocate and construct
*ptr = 42
dealloc ptr // Deallocate
// for structs use delete instead of dealloc
// delete ensures that destructors are called
}
}
Overview of dealloc, destruct and delete
dealloc just calls free
destruct calls destructor only (no free)
delete calls the destructor then cals free
Variants and Pattern Matching
Basic Variant Definition
variant VariantName {
Case1(param1 : type1, param2 : type2)
Case2(param : type)
Case3() // Empty case
}
Pattern Matching
Strict Rule: Pattern Matching only works with variants
Variable Pattern Matching
variant Option<T> {
Some(value : T)
None()
}
func get_value(opt : Option<int>) : int {
var Some(value) = opt else -1
return value
}
Pattern Matching with Return
func get_value_or_return(opt : Option<int>) : int {
var Some(value) = opt else return 0
return value
}
Pattern Matching with Unreachable
func unwrap(opt : Option<int>) : int {
var Some(value) = opt else unreachable
return value
}
Pattern Matching in If Statements
func check_option(opt : Option<int>) : bool {
if(var Some(value) = opt) {
return value > 0
}
return false
}
Switch Pattern Matching
func process_variant(variant : MyVariant) : int {
switch(variant) {
Case1(value) => {
return value * 2
}
Case2 => return -1
}
}
Type Testing with is
func is_some_case(opt : Option<int>) : bool {
return opt is Option.Some
}
func is_none_case(opt : Option<int>) : bool {
return opt is Option.None
}
Note: These core C-like features are sufficient to write good, performant Chemical code. Stick to these features for better reliability and performance.
Standard Library
The Chemical standard library provides essential data structures and utilities. Below are the key APIs that AI systems should know.
std::string
Important: You cannot use the + operator between std::string or *char. Chemical doesn't perform support addition of strings with +.
Constructor
var s = std::string() // Empty string
Methods
// Append a single character
s.append('x')
// Append a string view
s.append_view(std::string_view("wow"))
s.append_view("hello world") // string_view has implicit constructor for *char
// Append another std::string
s.append_string(other_string)
// Append another std::string (by converting to view)
s.append_view(other_string.to_view())
Note about destructible containers
Chemical does implicit moves, which means a variable becomes inaccessible if moved.
var s = std::string("hello world")
var y = s
// using s is invalid here
// s.append_view("x") <-- causes compilation error
// because std::string has a destructor
// you can copy a string explicitly using
// var copy = s.copy()
Usage Examples
var s = std::string()
s.append('H')
s.append_view("ello")
s.append(' ')
s.append_view("World")
// WRONG - This will not work:
// var result = s + " more text" // + operator not overloaded
// CORRECT:
s.append_view(" more text")
std::string_view
A lightweight view into string data without ownership.
Constructor
var view = std::string_view("hello") // From *char literal
var view2 = std::string_view(string_obj) // From std::string
Usage
func process_text(text : std::string_view) {
// Process text without copying
}
process_text("hello") // Works due to implicit constructor
std::vector
Dynamic array container similar to C++ std::vector.
Constructor
var vec = std::vector<int>() // Empty vector of int
var vec2 = std::vector<std::string>() // Empty vector of strings
Methods
// Add elements
vec.push(42)
vec.push(100)
// Access elements
var first = vec[0] // No bounds checking
var safe_first = vec.at(0) // With bounds checking
// Size
var size = vec.size()
// Check if empty
var empty = vec.empty()
// Clear all elements
vec.clear()
// Reserve capacity
vec.reserve(100) // Allocate space for 100 elements
Usage Examples
var numbers = std::vector<int>()
for(var i = 0; i < 10; i++) {
numbers.push(i * i)
}
var strings = std::vector<std::string>()
strings.push(std::string())
strings[0].append_view("first")
std::unordered_map
Hash map container for key-value pairs.
Does not support [key] operator for lookups or assignment.
Warning: The iterator() method scans buckets — iteration order is not deterministic. Use ordered_map if you need predictable insertion-order iteration.
Constructor
var map = std::unordered_map<std::string, int>()
var map2 = std::unordered_map<int, std::string>()
Methods
// Insert key-value pairs
map.insert("key", 42)
// Access elements
var value = map.get_ptr("key") // Returns a pointer, null if not found
// Check if key exists
var exists = map.contains("key")
// Remove elements
map.erase("key")
// Size
var size = map.size()
// Clear all elements
map.clear()
Usage Examples
var word_count = std::unordered_map<std::string, int>()
word_count.insert("hello", 1)
word_count.insert("world", 2)
if(word_count.contains("hello")) {
word_count.insert("hello", (*word_count.get_ptr("hello")) + 1)
}
std::ordered_map
Insertion-order-preserving hash map. Same API as unordered_map but iteration follows insertion order via an internal doubly-linked list.
Key constraint: Key : Hashable + Eq (same as unordered_map).
How it works
- Hash table (same bucket-chain structure as
unordered_map) for O(1) lookups
- Each node has
order_prev / order_next pointers forming a linked list in insertion order
insert appends new keys to the tail; insert of an existing key updates the value without changing position
erase splices the node out of both the hash chain and the linked list
- Iterator walks
order_next from head to tail (no bucket scanning)
- Reverse iterator walks
order_prev from tail to head
Methods
Same as unordered_map: insert, find, contains, get_ptr, erase, clear, size, empty, isEmpty
Iteration
for(var entry in map) // insertion order
for(var entry, i in map) // with index
for(var& entry in map) // by reference
for(var entry in map reversed) // reverse insertion order
Constructor
var map = std::ordered_map<std::string, int>()
var map2 = std::ordered_map<int, std::string>()
Usage Examples
var map = std::ordered_map<std::string, int>()
map.insert("first", 1)
map.insert("second", 2)
map.insert("third", 3)
// Iteration is guaranteed to yield: first→1, second→2, third→3
for(var entry in map) {
// entry.key, entry.value
}
// Erase a key
map.erase("second") // remaining order: first→1, third→3
// Update (position unchanged)
map.insert("first", 100) // still at position 1
std:: Option and Result Types
The standard library provides commonly used variant types for error handling and optional values.
std::Option
// Already available in standard library
var opt = std::Option<int>.Some(42)
var empty = std::Option<int>.None()
// Use with pattern matching
var Some(value) = opt else -1
std::Result
// Already available in standard library
var success = std::Result<int, std::string>.Ok(42)
var error = std::Result<int, std::string>.Err("failed")
// Use with pattern matching
var Ok(value) = success else -1
Performance Guidelines
- *Prefer char for simple strings - Use
*char for C-style string operations when possible
- Use string_view for read-only access - Avoid copying when you just need to read string data
- Reserve vector capacity - Pre-allocate memory when you know the expected size
- Avoid excessive string concatenation - Use
append_view() instead of + operator
- Use appropriate containers - Choose
vector for sequential access, unordered_map for key-value lookup
- Do not try to use moved variables - Chemical performs implicit moves of containers having destructors
Advanced Features
These features provide additional functionality but should be used judiciously. For most use cases, the core C-like features and standard library are sufficient.
Generics
Generic Type Parameters
struct GenericStruct<T> {
var value : T
}
func <T> generic_func(param : T) : T {
return param
}
Generic Constraints
func <T : SomeInterface> constrained_func(param : T) : T {
return param
}
Default Type Parameters
struct DefaultGeneric<T = int> {
var value : T
}
func <T = std::string> default_generic_func(param : T) : T {
return param
}
Generic Instantiation
var int_struct = GenericStruct<int> { value : 0 }
var string_struct = GenericStruct<std::string>()
var default_struct = DefaultGeneric{ value : 0 } // Uses int as default
Lambda Functions
When writing a function type, You should use colon arrow '=>' before the return type
var lambda : (param : type) => return_type = null ✅
When writing the lambda as an expression, You must not write return_type after =>
var lamb = () => int { return 33 } ❌
var lamb = () : int => 33 ✅
var lamb = () : int => { return 33 } ✅
Return type determined from the returned expression
var lamb = () => 33 ✅
var lamb = () => { return 33 } ✅
Lambda with return type
Here's a lambda with a function type
var lamb : () => int = () => 33;
Basic Lambda
var lambda = (param : type) : return_type => {
return param * 2
}
The return_type after the colon is optional, You can do this
var lambda = (param : type) => {
return param * 2
}
Capturing Lambda
When using capturing lambda, a container type like std::function is required, The lambda decays into that container type
Without the container type, capturing of variables is not possible.
var x = 10
var capturing_lambda : std::function<(p : int) => int> = |x|(param : int) : int => {
return param + x
}
Lambda without Parameters
var no_params = () : int => {
return 42
}
Function Types
// Function pointer type
var func_ptr : (a : int, b : std::string) => bool
// Lambda type with capture - only capturing functions can capture variables
var lambda_type : std::function<(param : type) => return_type>
Annotations
Built-in Annotations
@deprecated // Marks item as deprecated
@extern // External definition
@no_mangle // Don't mangle name
@static // Static member
@implicit // Implicit parameter
@constructor // Constructor method
@test // Test function
@inline // Inline function
@noinline // Don't inline
Annotation Usage
@deprecated("Use new_function instead")
func old_function() {
// implementation
}
@extern public func printf(format : *char, ...) : int
@constructor
func init(&self, param : int) {
// constructor implementation
}
@test
func test_feature() : bool {
return true
}
Conditional Compilation
comptime if(def.windows) {
// windows specific code
// the syntax is still checked
// however type checking is skipped if def.windows is false
}
Compile-time Features
Compile-time Functions
// only if function can run at compile time
comptime func calculate_at_compile_time() : int {
return 42 * 2
}
const RESULT = calculate_at_compile_time()
Compile-time Conditionals
comptime if(condition) {
// Compile-time branch
} else {
// Other compile-time branch
}
Interoperability
C Function Declarations
@extern public func printf(format : *char, ...) : int
@extern public func malloc(size : usize) : *void
@extern public func free(ptr : *void) : void
C Struct Definitions
@extern
struct CStruct {
var field1 : int
var field2 : *char
}
Type Casting
var int_val = 42
var float_val = int_val as float
var ptr_val2 = &raw mut int_val as *mut float
Move Semantics
func move_example() {
var original = MyStruct()
// implicit moves
var moved = original // Move ownership
// original is no longer valid here
}
Generic Structs and Variants
Generic Structs
struct GenericStruct<T, U> {
var value1 : T
var value2 : U
func <V> generic_method(param : V) : V {
return param
}
}
Generic Variants
variant GenericVariant<T> {
Some(value : T)
None()
}
variant MultiGeneric<T, U> {
First(a : T, b : U)
Second(value : T)
Empty()
}
Variant with Default Type Parameter
variant VariantWithDefault<T = int> {
Some(value : T)
None()
}
Variant Methods
variant MyVariant {
Case1(value : int)
Case2(text : std::string)
func get_value(&self) : int {
switch(self) {
Case1(value) => return value
Case2 => return -1
}
}
}
Library Development Gotchas
These patterns were discovered while implementing pure-Chemical libraries.
.data() Returns Immutable Pointer
vector<T>.data() returns *T (immutable). To write through the pointer, cast to *mut T:
var sptr = vec.data() as *mut i16
sptr[0] = 10000
sptr[1] = -10000
.size() Not .length()
string and vector use .size() — there is no .length() method.
Float Literals
0.5 is double in Chemical. For float parameters, use 0.5f:
public func audio_volume(audio : *mut Audio, factor : float) { ... }
audio_volume(&raw mut a, 0.5f) // WRONG: 0.5 is double
audio_volume(&raw mut a, 0.5) // TypeCheck error: double does not satisfy float
if Expressions Not Inline
Chemical does not support if as an expression inline in function arguments:
// WRONG:
// var max_samples = if(a > b) { a } else { b }
// CORRECT:
var max_samples : size_t
if(a > b) { max_samples = a } else { max_samples = b }
if Requires else
Every if block requires an else block. No bare if without else.
Variant-Captured Strings: append_string(&msg)
When capturing a string from a variant pattern, pass it by reference to append_string:
switch(self) {
InvalidFormat(msg) => {
var s = string("Error: ")
s.append_string(&msg) // NOT append_string(msg)
return s
}
}
using Declarations in Test Files
Test files at global scope need using declarations to access standard library types:
using std::Result; // For Result.Err / Result.Ok patterns
using std::string; // For string() constructor
using std::vector; // For vector<T>() constructor
Internal Functions Not Accessible Across Packages
Functions without public (internal by default) cannot be called from test modules in other packages. Mark test-relevant helper functions public:
// archive/src/endian.ch — WRONG: internal, can't be called from tests
// func read_u16_le(data : *u8, offset : size_t) : u16 { ... }
// CORRECT:
public func read_u16_le(data : *u8, offset : size_t) : u16 { ... }
memcpy With Pointer Variables
When a variable is already *mut T, never use &raw mut var with memcpy. &raw mut var takes the address of the pointer variable itself (on the stack), not what it points to:
func zip_find_entry(output : *mut ArchiveEntry, ...) {
// WRONG — &raw mut output is *mut (*mut ArchiveEntry), stack corruption!
// memcpy(&raw mut output, &raw entry, sizeof(ArchiveEntry))
// CORRECT — output is already *mut ArchiveEntry:
memcpy(output, &raw entry, sizeof(ArchiveEntry))
}
Result Pattern in @test Functions
@test
public func my_test(env : &mut TestEnv) {
var result = some_func()
if(result is Result.Err) { env.error("should succeed"); return }
var Ok(value) = result else unreachable
// Now use value safely
}
No const in Pointer Types
*const T is not valid Chemical. Use *T for immutable pointers.
Right Shift Type Matching
The right operand of shift operators must match the left operand type:
var val : u32 = 128u
var shifted = val >> 2u // CORRECT: both u32
// var shifted = val >> 2 // WRONG: int does not satisfy uint
Best Practices for AI Generation
Core Principles
- Write C-like code for performance - Chemical is very C-like, strings are
*char. For performant code, stick to C-like features with Chemical syntax.
- Use structs and destructors for better API - Combine C-style performance with modern safety through destructors.
- Add variants and pattern matching selectively - Use these for specific modern features, but don't overuse them.
- No magic behind the scenes - Chemical doesn't perform hidden allocations.
Bad AI Patterns to Avoid
- Don't use + operator with strings -
std::string and *char don't support + operator. Use append() functions specifically designed.
- No import statements in .ch files - Import statements are only valid in
build.lab or chemical.mod files.
- No defer statements - Chemical doesn't support defer (may be added in future).
Code Quality Guidelines
- Always specify types for struct members - Don't rely on inference in struct definitions
- Use proper access specifiers - Be explicit about public/private/internal
- Initialize variables - Local variables must be initialized at declaration
- Handle all variant cases - When using switch with variants, cover all cases
- Use pattern matching - Prefer pattern matching over manual variant checking
- Mark unsafe code - Always wrap unsafe operations in unsafe blocks
- Include proper annotations - Use @extern for C functions
- Follow naming conventions - Use PascalCase for types, camelCase for functions
- Handle memory properly - Use destructors for resource cleanup
- Use generics appropriately - Don't over-genericize without need
Performance Guidelines
- Prefer core C-like features - For best performance, stick to the basics
- Use pointers when appropriate - Chemical supports pointers for low-level control
- Leverage destructors for RAII - Get modern safety with C-like performance
- Use variants judiciously - Great for certain use cases but not a replacement for all enums
- Standard library when beneficial - Use std containers when they provide clear advantages
Critical Gotchas (From Implementation)
1. &key in parameter means reference, not address-of
When a function signature uses &Key as a parameter type, it means reference to Key — pass the value directly, don't prefix with &:
func contains(&self, key : &Key) : bool // key is a reference
// CORRECT:
map.contains(10)
// WRONG — taking address of r-value literal:
map.contains(&10) // ERROR: cannot apply operator '&' to r-value
The same applies to erase(key), get_ptr(key), find(key, &mut out).
Only use explicit & when the parameter type is a raw pointer (*Key or *mut Key).
1b. &arr[0] gives a reference, not a pointer — use &raw for raw pointers
Array indexing arr[i] produces a reference to the element (&T), not a raw pointer (*T).
To get a raw pointer for passing to C functions, use &raw or &raw mut:
var buf : char[2048]
// WRONG — produces &char, not *char:
// popen(&buf[0], "r") // TypeCheck error: &char does not satisfy *char
// sprintf(&buf[0], "%s", arg) // TypeCheck error: &char does not satisfy *mut char
// CORRECT — use &raw for raw pointers:
var pipe = popen(&raw buf[0], "r") // *char (immutable)
sprintf(&raw mut buf[0], "%s", arg) // *mut char (mutable)
This applies to all C interop (sprintf, popen, fgets, fwrite, fopen, etc.) and any
function expecting *char or *mut char.
1c. String literals are *char (immutable), arrays need &raw mut for *mut char
String literals (e.g., "hello") have type *char and can be passed directly to functions
expecting *char. However, char arrays are mutable local variables and require &raw mut
to get *mut char:
@extern public func sprintf(buf : *mut char, fmt : *char, ...) : int
var s = "hello" // *char — pass directly
var buf : char[64]
sprintf(&raw mut buf[0], "%s", s) // buf needs &raw mut for *mut char
2. Multi-line expressions break inside lambdas
The parser errors on expressions spanning multiple lines inside a lambda body:
// WRONG — parser error:
test("name", () => {
return a == 1 && b == 2
&& c == 3 // ERROR here
})
// CORRECT — use if/return statements:
test("name", () => {
if(a != 1) return false
if(b != 2) return false
if(c != 3) return false
return true
})
3. reversed is a keyword — cannot use as field name
The reversed keyword used in for(var entry in map reversed) conflicts with user-defined identifiers. If you name a struct field reversed, the compiler errors:
error: couldn't find value for member 'reversed'
Use a different name like reverse_dir or avoid the name entirely.
4. vector<T> does NOT support operator[]
You cannot use vec[0], vec[1], etc. to index into a std::vector. There is no index operator overload. To access elements, use .get(i) or .get_ptr(i):
var vec = std::vector<int>()
vec.push(10)
vec.push(20)
// WRONG:
// var x = vec[0] // ERROR
// CORRECT:
var x = vec.get(0)
var px = vec.get_ptr(0) // returns *mut int
5. Implementing for-in requires Iterable/ReversibleIterable
To make a custom type iterable with for(var x in obj), implement core::iterable::Iterable<T, Cursor>:
impl core::iterable::Iterable<MyNode, MyCursor> for MyStruct {
func begin(&self) : MyCursor { ... }
func valid(&self, c : MyCursor) : bool { ... }
func current(&self, c : MyCursor) : &MyNode { ... }
func next(&self, c : MyCursor) : MyCursor { ... }
}
For reversed support, additionally implement ReversibleIterable:
impl core::iterable::ReversibleIterable<MyNode, MyCursor> for MyStruct {
func rbegin(&self) : MyCursor { ... } // start from end
func previous(&self, c : MyCursor) : MyCursor { ... } // walk backward
func count(&self) : size_t { ... }
}
The cursor struct does not need a reversed flag — next() is used for forward iteration, previous() for reverse. They are never called in the same loop.
6. Hash table + linked list pattern for ordered maps
To build an insertion-order-preserving map:
- Each node has separate pointers for the hash chain (
hash_next) and the order chain (order_prev / order_next)
head / tail pointers on the map track the ordered list
insert appends new nodes to the tail (tail.order_next = newNode; tail = newNode)
erase splices the node out of the order list (prev.order_next = next; next.order_prev = prev)
resize rehashes by walking the order list (not the buckets), preserving the linked list pointers unchanged
- The
@delete destructor walks the order list (not buckets) for cleanup, then frees the table
Common Patterns
to_view pattern
This is invalid code
take_view(create_string().to_view())
with code like this you get error
error: function call is on a temporary that is destroyed at expression end, please store the temporary in a variable
To fix you must use
var str = create_string()
take_view(str.to_view())
Option Type Pattern
std::Option type is available in standard library
variant Option<T> {
Some(value : T)
None()
}
func <T> unwrap_or(opt : Option<T>, default : T) : T {
var Some(value) = opt else default
return value
}
Result Type Pattern
std::Result type is available in standard library
variant Result<T, E> {
Ok(value : T)
Err(error : E)
}
func <T, E> map_result(result : Result<T, E>, mapper : (T) => T) : Result<T, E> {
switch(result) {
Ok(value) => Result.Ok(mapper(value))
Err(error) => Result.Err(error)
}
}
Builder Pattern
struct Builder {
var field1 : int = 0
var field2 : std::string = ""
func with_field1(value : int) -> Builder {
field1 = value
return this
}
func with_field2(value : std::string) -> Builder {
field2 = value
return this
}
func build() -> MyStruct {
return MyStruct(field1, field2)
}
}
This syntax reference provides the foundation for generating correct Chemical code. The language emphasizes safety, performance, and C interoperability while maintaining modern language features.