Quadrate Language Specification
Version 2.0.0-alpha
This document provides a complete specification of the Quadrate programming language, intended for implementers who wish to create their own Quadrate compiler or interpreter.
Table of Contents
- Introduction
- Lexical Structure
- Type System
- Stack Machine Model
- Syntax and Grammar
- Control Flow
- Functions
- Structs
- Module System
- Error Handling
- Memory Management
- Built-in Instructions
- Standard Library Interface
- Runtime Requirements
- Appendix A: Grammar Summary
- Appendix B: Operator Reference
1. Introduction
Quadrate is a stack-based programming language that compiles to native code via LLVM. It combines the simplicity of stack-based execution with modern features like generics, closures, and a module system.
1.1 Design Principles
- Stack-based execution: All operations manipulate an explicit runtime stack
- Postfix notation: Operators follow their operands
- Strong typing: Types are checked at compile time with runtime validation
- Reference counting: Automatic memory management for heap objects
- Module system: Code organization with public/private visibility
1.2 Execution Model
A Quadrate program consists of:
1. Module imports (use statements)
2. Constant declarations
3. Struct declarations
4. Function declarations
5. A main function (entry point)
Execution begins at main, which operates on a shared runtime stack.
2. Lexical Structure
2.1 Character Set
Quadrate source files are UTF-8 encoded. Identifiers may contain ASCII letters, digits, and underscores.
2.2 Comments
// Line comment - extends to end of line
/* Block comment
Can span multiple lines
/* Can be nested */
*/
2.3 Tokens
2.3.1 Keywords
fn pub struct use import
if else for while loop
switch break continue return defer
const test ctx as
2.3.2 Predefined Constants
These identifiers are reserved and evaluate to integer literals:
| Constant | Value | Description |
|---|---|---|
true |
1 |
Boolean true |
false |
0 |
Boolean false |
Ok |
1 |
Success result |
Err |
0 |
Error result |
Note: true/Ok and false/Err are interchangeable pairs.
2.3.3 Identifiers
identifier := letter (letter | digit | '_')*
letter := 'a'..'z' | 'A'..'Z'
digit := '0'..'9'
Naming conventions:
- Structs: PascalCase (e.g., Point, HttpRequest)
- Constants: PascalCase (e.g., MaxSize, DefaultTimeout)
- Functions: snake_case (e.g., get_value, parse_input)
- Variables: snake_case (e.g., file_path, line_count)
- Type parameters: Single uppercase letters (T, U, V)
2.3.4 Literals
Integer literals:
integer := digit+
Examples: 42, 0, 1000
Float literals:
float := digit+ '.' digit+
Examples: 3.14, 0.0, 100.5
String literals:
string := '"' character* '"'
Escape sequences: \n, \r, \t, \\, \"
Examples: "hello", "line1\nline2"
2.3.5 Operators and Punctuation
Arithmetic operators:
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulo
Comparison operators:
< Less than
> Greater than
== Equal
!= Not equal
<= Less than or equal
>= Greater than or equal
Other operators:
++ Increment
-- Decrement
<< Shift left
>> Shift right
-> Local variable binding
:: Scope resolution
@ Field access (read)
. Field set (write)
& Function pointer prefix
! Abort-on-error suffix
Punctuation:
: Type annotation
, Type parameter separator
( ) Function signatures
{ } Blocks
[ ] Array literals
Note: Bitwise operators use named forms: and, or, xor, not, shl, shr.
2.4 Whitespace
Whitespace (spaces, tabs, newlines) separates tokens but is otherwise insignificant. Indentation is not syntactically meaningful.
3. Type System
3.1 Primitive Types
Quadrate has four primitive types:
| Type | Description | Size |
|---|---|---|
i64 |
Signed 64-bit integer | 8 bytes |
f64 |
IEEE 754 double-precision float | 8 bytes |
str |
Immutable UTF-8 string | pointer |
ptr |
Generic pointer | 8 bytes |
Note: Quadrate uses only 64-bit integers and floats. There are no 8/16/32-bit types in the language itself.
3.2 Composite Types
3.2.1 Structs
User-defined composite types with named fields:
struct Point {
x:f64
y:f64
}
struct Person {
name:str
age:i64
}
Structs are represented as ptr on the stack (pointer to heap or stack allocated data).
3.2.2 Arrays
Dynamic arrays created via array literals or make<T> instruction:
[1 2 3 4 5] // Integer array
[1.0 2.0 3.0] // Float array
["a" "b" "c"] // String array
[[1 2] [3 4]] // Nested array
Arrays are represented as ptr on the stack.
3.3 Generic Types
Functions and structs can be parameterized over types:
fn identity<T>(x:T -- y:T) {
-> val
val
}
struct Box<T> {
value:T
}
struct Pair<A, B> {
first:A
second:B
}
Type parameters are single uppercase letters or short uppercase names.
3.4 Function Types
Function pointers are represented as ptr:
fn apply(x:i64 f:ptr -- result:i64) {
-> func -> val
val func call
}
// Get function pointer
&double -> func_ptr
3.5 Special Types
| Type | Description |
|---|---|
any |
Wildcard type for operations accepting any type |
UNKNOWN |
Internal: unresolved type during compilation |
TAINTED |
Internal: error-tainted value from fallible function |
TYPEVAR |
Internal: type variable in generic context |
3.6 Type Coercion
Explicit casting via cast<Type>:
3.14 cast<i64> // Float to int (truncates toward zero)
42 cast<f64> // Int to float
100 cast<str> // Any to string
"42" cast<i64> // String to int (parse)
Implicit coercion: None. All type conversions must be explicit.
4. Stack Machine Model
4.1 Runtime Stack
The Quadrate runtime maintains a single data stack shared across all function calls.
Stack element structure:
struct qd_stack_element {
union {
int64_t i; // Integer value
double f; // Float value
void* p; // Pointer value
qd_string_t* s; // String reference
} value;
qd_stack_type type; // Type tag: INT=0, FLOAT=1, PTR=2, STR=3
bool is_error_tainted; // Error propagation flag
};
4.2 Stack Operations
All operations are postfix. Arguments are popped from the stack, results are pushed.
Duplication:
| Operation | Stack Effect | Description |
|---|---|---|
dup |
( a -- a a ) |
Duplicate top |
dup2 |
( a b -- a b a b ) |
Duplicate top two |
dupd |
( a b -- a a b ) |
Duplicate second |
Swapping:
| Operation | Stack Effect | Description |
|---|---|---|
swap |
( a b -- b a ) |
Swap top two |
swap2 |
( a b c d -- c d a b ) |
Swap pairs |
swapd |
( a b c -- b a c ) |
Swap under top |
Removal:
| Operation | Stack Effect | Description |
|---|---|---|
drop |
( a -- ) |
Remove top |
drop2 |
( a b -- ) |
Remove top two |
nip |
( a b -- b ) |
Remove second |
nipd |
( a b c -- a c ) |
Remove second under top |
clear |
( ... -- ) |
Clear entire stack |
Rearrangement:
| Operation | Stack Effect | Description |
|---|---|---|
over |
( a b -- a b a ) |
Copy second to top |
over2 |
( a b c d -- a b c d a b ) |
Copy second pair |
overd |
( a b c -- a b a c ) |
Copy third |
rot |
( a b c -- b c a ) |
Rotate three |
tuck |
( a b -- b a b ) |
Copy top below second |
pick |
( ... n -- ... x ) |
Copy nth to top |
roll |
( ... n -- ... ) |
Move nth to top |
nth |
( array n -- elem ) |
Get array element |
depth |
( -- n ) |
Push stack depth |
4.3 Stack Effect Signatures
Functions declare their stack effects:
fn name(input1:type1 input2:type2 -- output1:type1 output2:type2)
- Left of
--: Parameters consumed from stack (bottom to top) - Right of
--: Values produced on stack (bottom to top)
Examples:
fn add(a:i64 b:i64 -- sum:i64) // Consumes 2, produces 1
fn swap_pair(a:i64 b:i64 -- b:i64 a:i64) // Consumes 2, produces 2
fn print_value(x:i64 -- ) // Consumes 1, produces 0
fn get_constant( -- c:i64) // Consumes 0, produces 1
4.4 Local Variables
The -> operator pops values into named local variables:
fn example(x:i64 y:i64 -- result:i64) {
-> b // Pop top into b
-> a // Pop next into a
a b + // Use variables
}
Variables are stored in function-local allocas and can be referenced multiple times.
Binding multiple values:
10 20 30 -> z -> y -> x // Each -> binds one value: z=30, y=20, x=10
Note: Each -> binds exactly one value. Multiple values require multiple -> operators.
5. Syntax and Grammar
5.1 Program Structure
program := declaration*
declaration := use_statement
| const_declaration
| struct_declaration
| function_declaration
| import_declaration
| test_declaration
5.2 Use Statements
use module_name // Import module by name
use "path/to/file.qd" // Import file by path
use "./relative/path" // Relative import
5.3 Constant Declarations
const NAME = literal
const PI = 3.14159
const MESSAGE = "hello"
const FROM_ENV = env("VAR_NAME")
const WITH_DEFAULT = env("VAR", "default")
5.4 Struct Declarations
[pub] struct Name [<TypeParams>] {
field1:type1
field2:type2 = default_value
field3:*StructName // Pointer type
}
5.5 Function Declarations
[pub] fn name [<TypeParams>] (params -- returns) [!] {
body
}
pub: Makes function publicly accessible from other modules<TypeParams>: Generic type parameters!: Marks function as fallible (can fail with error)
5.6 Import Declarations (FFI)
import "library.a" as "namespace" {
[pub] fn funcname(params -- returns) [!]
...
}
5.7 Test Declarations
test "test name" {
// test body
}
6. Control Flow
6.1 If-Else
Condition is popped from stack; non-zero is truthy:
condition if {
// then branch
} else {
// else branch (optional)
}
Chained conditions:
x 10 > if {
"greater than 10"
} else {
x 5 > if {
"greater than 5"
} else {
"5 or less"
}
}
6.2 For Loops
start end step for iterator {
// body - iterator is available as local
}
Stack: Pops step, end, start from stack.
0 10 1 for i {
i print nl
}
// Reverse iteration
10 0 -1 for i {
i print nl
}
6.3 While Loops
condition while {
// body
condition // last expression becomes condition for next iteration
}
The initial condition is on the stack before while. The block executes if the condition is true (non-zero). The block's last expression becomes the condition for the next iteration.
Example:
0 -> i
i 5 < while {
i print nl
i 1 + -> i
i 5 < // condition for next iteration
}
6.4 Infinite Loops
loop {
// body
// must use 'break' to exit
}
6.5 Switch-Case
value switch {
1 { "one" }
2 { "two" }
3 { "three" }
_ { "other" } // Default case (wildcard)
}
Cases can match:
- Integer literals
- String literals
- Constants (including scoped: module::Constant)
- Wildcard _ for default
6.6 Break and Continue
break // Exit innermost loop
continue // Skip to next iteration
6.7 Defer
Execute code when scope exits (LIFO order):
fn process() {
resource_open -> handle
defer { handle resource_close }
// ... work with handle ...
// cleanup runs automatically
}
Multiple defers execute in reverse order:
fn example() {
defer { "third" print nl }
defer { "second" print nl }
defer { "first" print nl }
}
// Prints: first, second, third
7. Functions
7.1 Function Definition
[pub] fn name [<T, U>] (input_params -- output_params) [!] {
body
}
7.2 Function Calls
Functions are called by name; arguments must already be on stack:
10 20 add // Call 'add' with 10 and 20 on stack
"hello" print // Call 'print' with string on stack
Module-qualified calls:
3.14 math::sin // Call 'sin' from 'math' module
"hello" str::len // Call 'len' from 'str' module
7.3 Generic Functions
fn identity<T>(x:T -- y:T) {
-> val
val
}
fn pair<A, B>(a:A b:B -- first:A second:B) {
-> b -> a
a b
}
Type parameters are inferred from arguments at call sites.
7.4 Methods
Methods are functions where the first parameter is a struct receiver:
struct Vec2 { x:f64 y:f64 }
fn length(v:Vec2 -- len:f64) {
-> v
v @x v @x * v @y v @y * + math::sqrt
}
// Call as method
vec length
7.5 Anonymous Functions (Closures)
fn (params -- returns) { body }
Implicit capture: Variables from the enclosing scope are automatically captured when referenced inside the closure body. No explicit capture list is needed.
Example:
42 -> x
fn ( -- r:i64) { x } -> get_x // x is automatically captured
get_x call print nl // Prints 42
Multiple captures:
10 -> a
20 -> b
fn ( -- r:i64) { a b + } -> sum // Both a and b are captured
sum call print nl // Prints 30
Closure without captures (plain function pointer):
fn (x:i64 y:i64 -- r:i64) { -> b -> a a b + } -> add_fn
3 4 add_fn call print nl // Prints 7
7.6 Function Pointers
&function_name // Get function pointer
pointer call // Invoke function via pointer
8. Structs
8.1 Struct Definition
[pub] struct Name [<TypeParams>] {
field1:type1
field2:type2 = default_value
}
8.2 Struct Construction
StructName {
field1 = expr1
field2 = expr2
}
Generic struct construction:
Box<i64> { value = 42 }
Pair<str, i64> { first = "key" second = 100 }
8.3 Field Access (Read)
Use @ operator:
point @x // Read field 'x' from point
point @x @y // Chained access (if x is a struct)
8.4 Field Set (Write)
Use . operator:
value struct.field // Set field to value
Example:
Point { x = 0.0 y = 0.0 } -> p
5.0 p.x // Set p.x to 5.0
10.0 p.y // Set p.y to 10.0
8.5 Generic Structs
struct Box<T> {
value:T
}
struct Result<T, E> {
value:T
error:E
is_ok:i64
}
9. Module System
9.1 Module Structure
A module is either:
- A single .qd file
- A directory containing .qd files (all files are loaded and merged into the module namespace)
Test files (*_test.qd) are excluded from module loading.
9.2 Module Resolution
When use modulename is encountered, search order:
- Local path:
./modulename/*.qd(all.qdfiles in directory) - Include paths (from
-Iflags) - Third-party packages:
~/.quadrate/modules/ $QUADRATE_PATHenvironment variable$QUADRATE_ROOTenvironment variable- Relative to executable:
../share/quadrate/ $HOME/quadrate/
9.3 Public and Private
By default, declarations are private to their module.
pub fn public_function() { } // Accessible from other modules
fn private_function() { } // Only accessible within module
pub struct PublicStruct { }
struct PrivateStruct { }
pub const PUBLIC_CONST = 1
const PRIVATE_CONST = 2
9.4 Scoped Access
Use :: to access module members:
use math
use io
math::sin // Function
math::Pi // Constant
io::Read // Constant
io::File // Struct
9.5 FFI Imports
Import C functions from static libraries:
import "libfoo.a" as "foo" {
pub fn bar(x:i64 -- y:i64)
pub fn baz(s:str -- )! // Fallible
}
// Usage
42 foo::bar
9.6 Multi-File Modules
mymodule/
├── api.qd // Public API functions
├── helpers.qd // Helper functions
└── types.qd // Type definitions
All .qd files in the directory are automatically loaded and merged into the same module namespace. No file has special significance - they are all equal. No explicit imports between files in the same module are needed.
// api.qd
pub fn main_feature() {
helper_function // Can call functions from helpers.qd
}
// helpers.qd
fn helper_function() { }
10. Error Handling
10.1 Fallible Functions
Functions that can fail are marked with !:
fn divide(a:i64 b:i64 -- result:i64)! {
dup 0 == if {
drop drop
"division by zero" -1 panic
}
/
}
10.2 The panic Instruction
Signal an error from within a fallible function:
"error message" error_code panic
- Stack effect:
(msg:str code:i64 -- ) - Sets runtime error state
- Only valid in fallible functions
Alternative: Error literal syntax:
error { code = 100 message = "must be non-negative" } panic
The error { code = N message = "..." } syntax creates an anonymous error value that can be used with panic.
10.3 Error Handling at Call Site
Abort on error (! operator):
10 0 divide! // Aborts program if divide fails
Check with if-else:
10 0 divide if {
// Success - result on stack
print nl
} else {
// Failure - handle error
drop
"Division failed" print nl
}
Check with switch:
path io::Read io::open switch {
Ok {
-> handle
// use handle
handle io::close
}
io::ErrNotFound {
drop
"File not found" print nl
}
_ {
drop
"Unknown error" print nl
}
}
10.4 Error Constants
By convention:
- Ok = 1 (success)
- Err = 0 (generic error)
- Module-specific errors start at 2
Example from io module:
pub const ErrNotFound = 2
pub const ErrPermission = 3
pub const ErrInvalidHandle = 4
10.5 The err Instruction
Retrieve error information after a failed call:
err // Stack effect: ( -- msg:str code:i64 )
11. Memory Management
11.1 Allocation Strategy
Stack allocation: - Local structs that don't escape their function - Faster, no reference counting overhead - Automatic cleanup when function returns
Heap allocation: - Structs returned from functions - Strings - Arrays - Captured closure variables
11.2 Reference Counting
Heap objects use atomic reference counting:
struct qd_refcounted {
atomic_size_t refcount; // Initially 1
// ... object data ...
};
Operations: - Retain: Increment refcount (when sharing) - Release: Decrement refcount; free when reaches 0
11.3 String Memory
Strings are reference-counted and immutable:
struct qd_string {
char* data;
size_t length;
size_t capacity;
atomic_size_t refcount;
};
- Copying a string increments its refcount (shallow copy)
- String operations that modify create new strings
- In-place mutation only when refcount == 1
11.4 Struct Memory
Heap-allocated structs have a hidden header:
struct qd_struct_header {
size_t magic; // Validation marker
atomic_size_t refcount;
void (*destructor)(void*);
};
Destructors: - Automatically generated for structs with string/struct fields - Called when refcount reaches 0 - Release nested references
11.5 Array Memory
struct qd_array {
size_t magic;
atomic_size_t refcount;
size_t length;
size_t capacity;
qd_array_type elemType; // INT, FLOAT, STR, PTR
union {
int64_t* i;
double* f;
void** p;
} data;
};
- Dynamic resizing with 2x growth factor
- Element-type-aware cleanup (strings released, nested arrays/structs released)
11.6 Closure Captures
Variables referenced inside a closure are automatically detected and captured (implicit capture). Captured variables are heap-allocated with reference counting:
struct captured_var {
int64_t refcount;
qd_stack_element_t elem;
};
- Closure increments refcount of captured variables
- Variables freed when all closures using them are released
11.7 Defer for Cleanup
Use defer for explicit cleanup:
fn process(path:str -- )! {
path io::Read io::open!
-> file
defer { file io::close }
// ... process file ...
// close runs automatically on scope exit
}
12. Built-in Instructions
12.1 Arithmetic
| Instruction | Alias | Stack Effect | Description |
|---|---|---|---|
add |
+ |
(a b -- c) |
Addition |
sub |
- |
(a b -- c) |
Subtraction |
mul |
* |
(a b -- c) |
Multiplication |
div |
/ |
(a b -- c) |
Division |
mod |
% |
(a b -- c) |
Modulo |
neg |
(a -- b) |
Negation | |
inc |
++ |
(a -- b) |
Increment by 1 |
dec |
-- |
(a -- b) |
Decrement by 1 |
12.2 Comparison
| Instruction | Alias | Stack Effect | Description |
|---|---|---|---|
eq |
== |
(a b -- bool) |
Equal |
neq |
!= |
(a b -- bool) |
Not equal |
lt |
< |
(a b -- bool) |
Less than |
gt |
> |
(a b -- bool) |
Greater than |
lte |
<= |
(a b -- bool) |
Less than or equal |
gte |
>= |
(a b -- bool) |
Greater than or equal |
within |
(x lo hi -- bool) |
lo <= x < hi |
12.3 Bitwise
| Instruction | Alias | Stack Effect | Description |
|---|---|---|---|
and |
(a b -- c) |
Bitwise AND | |
or |
(a b -- c) |
Bitwise OR | |
xor |
(a b -- c) |
Bitwise XOR | |
not |
(a -- b) |
Bitwise NOT | |
shl |
<< |
(a n -- b) |
Shift left |
shr |
>> |
(a n -- b) |
Shift right |
12.4 Stack Manipulation
See Section 4.2 for complete list.
12.5 Array Operations
| Instruction | Stack Effect | Description |
|---|---|---|
make<T> |
(n -- arr) |
Create typed array of size n |
append |
(arr elem -- arr) |
Append element |
set |
(arr idx elem -- ) |
Set element at index |
nth |
(arr idx -- elem) |
Get element at index |
len |
(arr -- n) |
Get array length |
12.6 Type Operations
| Instruction | Stack Effect | Description |
|---|---|---|
cast<T> |
(a -- b) |
Cast to type T |
sizeof |
(T -- n) |
Size of type in bytes |
12.7 I/O Operations
| Instruction | Stack Effect | Description |
|---|---|---|
print |
(x -- ) |
Print value |
printv |
(x -- x) |
Print value, keep on stack |
prints |
( -- ) |
Print entire stack contents (debug) |
printsv |
( -- ) |
Print entire stack with type info (debug) |
nl |
( -- ) |
Print newline |
read |
( -- s) |
Read line from stdin |
12.8 Error Handling
| Instruction | Stack Effect | Description |
|---|---|---|
panic |
(msg code -- ) |
Signal error (fallible functions only) |
err |
( -- msg code) |
Get last error info |
12.9 Function Calls
| Instruction | Stack Effect | Description |
|---|---|---|
call |
(ptr -- ...) |
Call function via pointer |
13. Standard Library Interface
The Quadrate standard library provides these core modules:
13.1 math
Mathematical functions and constants.
Constants: Pi, E, Tau, Phi, Sqrt2, Ln2, etc.
Functions:
- Trigonometric: sin, cos, tan, asin, acos, atan, atan2
- Hyperbolic: sinh, cosh, tanh, asinh, acosh, atanh
- Exponential: exp, exp2, pow, sqrt, cbrt, log, log2, log10
- Rounding: ceil, floor, round, trunc
- Utility: abs, min, max, clamp, lerp, sign
13.2 str
String manipulation.
Functions:
- len, concat, contains, starts_with, ends_with
- index_of, last_index_of, substring!
- upper, lower, trim, trim_left, trim_right
- split!, join!, replace!, repeat, reverse
- char_at!, from_char, compare, count
13.3 io
File and stream I/O.
Constants: Read, Write, Append, ReadWrite, SeekSet, SeekCur, SeekEnd
Error codes: ErrNotFound, ErrPermission, ErrRead, ErrWrite, etc.
Functions:
- open!, close, read!, write!, seek!, tell!, eof
- readline!, read_file!, write_file!
13.4 fmt
Formatted output.
Functions: printf, sprintf
13.5 os
Operating system interface.
Functions:
- Process: exit, system, getpid, exec!, popen!
- Environment: getenv, setenv
- Filesystem: exists, delete!, rename!, copy!, mkdir!, rmdir!
- Directory: list!, walk!, glob!, cwd!
13.6 time
Time operations.
Constants: Duration units, weekday/month names
Functions:
- unix, now, sleep
- year, month, day, hour, minute, second, weekday
- is_leap_year, days_in_month, add, sub, before, after
13.7 thread
Threading primitives.
Structs: Thread, Mutex, Channel, WaitGroup
Functions:
- Thread: spawn!, join!, detach!, is_alive
- Mutex: mutex_new!, lock!, unlock!, try_lock
- Channel: chan_new!, send!, recv!, close
- WaitGroup: wg_new!, add, done, wait!
13.8 mem
Low-level memory.
Functions:
- alloc!, realloc!, free
- set_byte, get_byte, set_i64, get_i64
- copy, zero, fill
13.9 Additional Modules
sb- StringBuilder for efficient string buildingbytes- Byte buffer operationsbits- Bit manipulation utilitiesrand- Random number generation (xorshift64*)path- File path manipulationstrconv- String/number conversionunicode- Unicode character constantslimits- Numeric type limitsterm- Terminal controlsignal- Signal handlingflag- Command-line flag parsingtesting- Unit testing utilities
14. Runtime Requirements
14.1 Context Structure
The runtime must maintain an execution context:
typedef struct {
qd_stack* st; // Data stack
int64_t error_code; // Current error (0 = none)
char* error_msg; // Error message
int argc; // Command-line arg count
char** argv; // Command-line args
// Call stack for debugging
const char* call_stack[256];
const char* call_stack_files[256];
size_t call_stack_lines[256];
size_t call_stack_depth;
} qd_context;
14.2 Stack Implementation
typedef struct {
qd_stack_element_t* data;
size_t capacity;
size_t size;
} qd_stack;
14.3 Required Runtime Functions
Context:
- qd_create_context(capacity) - Create new context
- qd_free_context(ctx) - Destroy context
Stack:
- qd_push_i(ctx, int64) - Push integer
- qd_push_f(ctx, float64) - Push float
- qd_push_s(ctx, string) - Push string (copy)
- qd_push_s_ref(ctx, string) - Push string (reference)
- qd_push_p(ctx, ptr) - Push pointer
- qd_stack_pop(stack, elem_ptr) - Pop element
Operations:
- qd_add, qd_sub, qd_mul, qd_div, qd_mod - Arithmetic
- qd_lt, qd_gt, qd_eq, qd_neq, qd_lte, qd_gte - Comparison
- qd_and, qd_or, qd_xor, qd_not, qd_shl, qd_shr - Bitwise
Memory:
- qd_struct_alloc(size, destructor) - Allocate struct
- qd_struct_retain(ptr) - Increment refcount
- qd_struct_release(ptr) - Decrement refcount
- qd_string_retain(str) - Retain string
- qd_string_release(str) - Release string
- qd_array_create(capacity, type) - Create array
- qd_array_release(arr) - Release array
Debug:
- qd_push_call(ctx, func, file, line) - Push call frame
- qd_pop_call(ctx) - Pop call frame
- qd_print_stack_trace(ctx) - Print trace
Appendix A: Grammar Summary
program = { declaration } ;
declaration = use_stmt | const_decl | struct_decl | fn_decl | import_decl | test_decl ;
use_stmt = "use" ( identifier | string ) ;
const_decl = "const" identifier "=" expression ;
struct_decl = ["pub"] "struct" identifier [type_params] "{" { field_decl } "}" ;
fn_decl = ["pub"] "fn" identifier [type_params] signature ["!"] block ;
import_decl = "import" string "as" string "{" { import_fn } "}" ;
test_decl = "test" string block ;
type_params = "<" identifier { "," identifier } ">" ;
signature = "(" [ param_list ] "--" [ param_list ] ")" ;
param_list = param { param } ;
param = identifier ":" type ;
field_decl = identifier ":" type [ "=" expression ] ;
import_fn = ["pub"] "fn" identifier signature ["!"] ;
block = "{" { statement } "}" ;
statement = expression | if_stmt | for_stmt | while_stmt | loop_stmt
| switch_stmt | defer_stmt | "break" | "continue" ;
if_stmt = expression "if" block [ "else" block ] ;
for_stmt = expression expression expression "for" identifier block ;
while_stmt = expression "while" block ; /* block's last expr is next condition */
loop_stmt = "loop" block ;
switch_stmt = expression "switch" "{" { case_clause } "}" ;
case_clause = ( literal | identifier | "_" ) block ;
defer_stmt = "defer" ( block | statement ) ;
expression = literal | identifier | scoped_id | struct_const | array_lit
| field_access | field_set | local_bind | fn_call | anon_fn
| error_lit | operator | instruction ;
error_lit = "error" "{" "code" "=" expression "message" "=" expression "}" ;
literal = integer | float | string | "true" | "false" | "Ok" | "Err" ;
scoped_id = identifier "::" identifier ;
struct_const = identifier [type_args] "{" { field_init } "}" ;
array_lit = "[" { expression } "]" ;
field_access = "@" identifier ;
field_set = expression identifier "." identifier ;
local_bind = "->" identifier ;
anon_fn = "fn" signature block ; /* captures are implicit */
fn_call = identifier [ "!" ] ;
type = "i64" | "f64" | "str" | "ptr" | identifier [type_args] | "*" type ;
type_args = "<" type { "," type } ">" ;
operator = "+" | "-" | "*" | "/" | "%" | "++" | "--"
| "<" | ">" | "==" | "!=" | "<=" | ">="
| "<<" | ">>" ;
instruction = "dup" | "swap" | "drop" | "over" | "rot" | "nip" | "tuck"
| "pick" | "roll" | "nth" | "len" | "append" | "set"
| "make" "<" type ">" | "cast" "<" type ">"
| "print" | "nl" | "read" | "call" | "panic" | "err" | ... ;
Appendix B: Operator Reference
B.1 Arithmetic Operators
| Operator | Named | Stack Effect | Description |
|---|---|---|---|
+ |
add |
(a b -- c) |
a + b |
- |
sub |
(a b -- c) |
a - b |
* |
mul |
(a b -- c) |
a * b |
/ |
div |
(a b -- c) |
a / b |
% |
mod |
(a b -- c) |
a mod b |
++ |
inc |
(a -- b) |
a + 1 |
-- |
dec |
(a -- b) |
a - 1 |
B.2 Comparison Operators
| Operator | Named | Stack Effect | Result |
|---|---|---|---|
== |
eq |
(a b -- bool) |
1 if a == b, else 0 |
!= |
neq |
(a b -- bool) |
1 if a != b, else 0 |
< |
lt |
(a b -- bool) |
1 if a < b, else 0 |
> |
gt |
(a b -- bool) |
1 if a > b, else 0 |
<= |
lte |
(a b -- bool) |
1 if a <= b, else 0 |
>= |
gte |
(a b -- bool) |
1 if a >= b, else 0 |
B.3 Bitwise Operators
| Operator | Named | Stack Effect | Description |
|---|---|---|---|
<< |
shl |
(a n -- b) |
a << n |
>> |
shr |
(a n -- b) |
a >> n |
and |
(a b -- c) |
a & b | |
or |
(a b -- c) |
a | b | |
xor |
(a b -- c) |
a ^ b | |
not |
(a -- b) |
~a |
B.4 Special Operators
| Operator | Description |
|---|---|
-> |
Bind top of stack to local variable |
:: |
Scope resolution (module::member) |
@ |
Field access (read) |
. |
Field set (write) |
& |
Get function pointer |
! |
Abort on error (after fallible call) |
? |
Check error (after fallible call) |
Document History
- 2.0.0-alpha: Initial specification document
This specification is provided for implementers of Quadrate compilers and interpreters. For user documentation, see the Quadrate Documentation.