Complete reference for the PIE programming language syntax and semantics.
- Program Structure
- Types
- Variables and Declarations
- Operators
- Control Flow
- Functions
- Arrays
- Dictionaries
- Strings
- Comments
- Modules
- Type Conversion
- I/O
- Reserved Words
A PIE program is a sequence of function definitions and top-level statements.
PIE recognizes a void main() function as the program entry point:
void main() {
output("Hello, PIE!", string);
}
Top-level statements outside any function are also valid and are automatically treated as part of main:
// This is a complete, valid PIE program
int x = 10;
int y = 20;
output(x + y, int);
[import statements]
[function definitions]
[top-level statements / main() function]
Imports must appear before the code that uses them. Functions can be defined in any order — PIE resolves calls across the file.
| Type | Width | Description |
|---|---|---|
int |
32-bit | Signed integer. Range: −2,147,483,648 to 2,147,483,647. |
float |
64-bit | IEEE 754 double-precision floating point. |
char |
8-bit | Single ASCII character. |
string |
pointer | Null-terminated character string. |
bool / boolean |
1-bit | true or false. |
These types represent opaque handles to system resources.
| Type | Description |
|---|---|
file |
An open file handle returned by file_open(). |
socket |
A TCP socket handle returned by tcp_socket(). |
database |
A SQLite database handle returned by sqlite.open(). |
regex |
A compiled regular expression returned by regex_compile(). |
dict |
A hash map with string keys and mixed-type values. |
ptr |
A generic pointer, used for module-defined opaque objects. |
When using standard-library modules, more readable type aliases are available:
| Alias | Resolves to | Source |
|---|---|---|
json.object |
ptr |
import json; |
json.array |
ptr |
import json; |
http.request |
ptr |
import http; |
http.response |
ptr |
import http; |
type identifier;
type identifier = expression;
int age = 25;
float price = 19.99;
char grade = 'A';
string name = "Alice";
bool active = true;
// Declaration without initialization (value is undefined — always initialize)
int count;
null can be assigned to any pointer-like type:
string s = null;
file f = null;
database db = null;
json.object obj = null;
Check for null using ==:
if (db == null) {
output("Could not open database", string);
return;
}
Variables are scoped to the block { } in which they are declared:
void example() {
int x = 10; // function scope
if (x > 5) {
int y = 20; // block scope — only visible inside this if block
output(y, int);
}
// y is not accessible here
}
| Operator | Description | Example |
|---|---|---|
+ |
Addition (also string concatenation) | a + b |
- |
Subtraction | a - b |
* |
Multiplication | a * b |
/ |
Division | a / b |
% |
Modulo | a % b |
Integer division truncates toward zero: 7 / 2 == 3.
| Operator | Description |
|---|---|
== |
Equal |
!= |
Not equal |
< |
Less than |
> |
Greater than |
<= |
Less than or equal |
>= |
Greater than or equal |
Comparison operators return 1 (true) or 0 (false) as integers.
When a string is compared to an integer, PIE compares the string's length to the integer:
string password = "hello";
if (password < 8) {
output("Too short", string); // Triggers: strlen("hello") < 8 → 5 < 8
}
| Operator | Description |
|---|---|
&& |
Logical AND — both sides must be non-zero |
|| |
Logical OR — at least one side must be non-zero |
! |
Logical NOT (unary) — flips 0/non-zero |
if (age >= 18 && age <= 65) { ... }
if (score < 0 || score > 100) { ... }
if (!found) { ... }
x = 10;
name = "Bob";
Only postfix forms are supported:
i++; // equivalent to i = i + 1
i--; // equivalent to i = i - 1
The + operator concatenates strings. Non-string values are automatically converted:
string msg = "Score: " + 95 + ", grade: " + 'A';
// Produces: "Score: 95, grade: A"
The left-most operand determines the operation: "x" + 1 → concatenation, but 1 + 2 → integer addition.
- Unary:
-x,!x *,/,%+,-<,>,<=,>===,!=&&||=
Use parentheses to override: (a + b) * c.
if (condition) {
// ...
}
if (condition) {
// then branch
} else {
// else branch
}
if (score >= 90) {
output("A", string);
} else if (score >= 80) {
output("B", string);
} else if (score >= 70) {
output("C", string);
} else {
output("F", string);
}
int i = 0;
while (i < 10) {
output(i, int);
i++;
}
Executes the body at least once before checking the condition:
int choice = -1;
do {
output("Enter 1 or 2: ", string);
input(choice, int);
} while (choice != 1 && choice != 2);
for (initializer; condition; update) {
// ...
}
// Standard integer loop
for (int i = 0; i < n; i++) {
output(i, int);
}
// Countdown
for (int i = 10; i > 0; i--) {
output(i, int);
}
// Loop over array
for (int i = 0; i < arr_size(items); i++) {
output(items[i], string);
}
switch (expression) {
case value1:
// statements
break;
case value2:
// statements
break;
default:
// statements
break;
}
break is required to prevent fall-through. default handles all unmatched cases.
int day = 3;
switch (day) {
case 1: output("Monday", string); break;
case 2: output("Tuesday", string); break;
case 3: output("Wednesday", string); break;
default: output("Another day", string); break;
}
// break — exit the innermost loop or switch immediately
for (int i = 0; i < 100; i++) {
if (i == 50) break;
}
// continue — skip the rest of the current iteration
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) continue; // skip even numbers
output(i, int);
}
// Void function
void greet(string name) {
output("Hello, " + name, string);
return; // optional in void functions
}
// Function with return value
int add(int a, int b) {
return a + b;
}
// Early return for guard clauses
void process(database db) {
if (db == null) {
output("No database", string);
return;
}
// ... rest of function
}
return_type function_name(param_type param_name, ...) {
// body
return value; // required unless return_type is void
}
Any type can be a return type: int, float, string, char, bool, void, dict, file, database, ptr, regex, socket.
// No parameters, no return value
void print_separator() {
output("--------------------", string);
}
// Parameters and return value
int add(int a, int b) {
return a + b;
}
float circle_area(float radius) {
return pi() * radius * radius;
}
string repeat(string s, int n) {
string result = "";
for (int i = 0; i < n; i++) {
result = result + s;
}
return result;
}
// Multiple parameters of different types
void log_event(string message, int level) {
if (level > 0) {
output("[" + level + "] " + message, string);
}
}
print_separator();
int sum = add(3, 7);
float area = circle_area(5.0);
string line = repeat("=", 40);
log_event("Started", 1);
All parameters are passed by value — the function receives a copy. Modifying a parameter inside the function does not affect the caller's variable.
void double_it(int x) {
x = x * 2; // only changes the local copy
}
void main() {
int n = 5;
double_it(n);
output(n, int); // still 5
}
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
Declared with an explicit size. Size cannot change at runtime.
int nums[5]; // uninitialized
int values[5] = [10, 20, 30, 40, 50]; // with initializer
string names[3] = ["Alice", "Bob", "Charlie"];
Access is zero-indexed:
int first = values[0]; // 10
values[2] = 99; // modify in place
Declared with type name[] = [...]. The [] comes after the identifier name, not after the type.
int scores[] = [85, 92, 78, 95, 60]; // ✅ correct
// int[] scores = [85, 92, 78, 95]; // ✗ wrong syntax
string words[] = ["hello", "world"];
float prices[] = [9.99, 14.50, 3.75];
dict rows[] = []; // empty dict array
Dynamic arrays support arr_push and arr_pop:
int data[] = [1, 2, 3];
arr_push(data, 4); // [1, 2, 3, 4]
arr_push(data, 5); // [1, 2, 3, 4, 5]
int last = arr_pop(data); // removes and returns 5
int size = arr_size(data); // 4
| Function | Signature | Description |
|---|---|---|
arr_push |
arr_push(array, value) |
Append a value to a dynamic array |
arr_pop |
arr_pop(array) |
Remove and return the last element |
arr_size |
arr_size(array) → int |
Number of elements |
arr_contains |
arr_contains(array, value) → int |
1 if value found, else 0 |
arr_indexof |
arr_indexof(array, value) → int |
Index of value, or −1 |
arr_avg |
arr_avg(array) → float |
Arithmetic mean of a numeric array |
string fruits[] = ["apple", "banana", "cherry"];
for (int i = 0; i < arr_size(fruits); i++) {
output(fruits[i], string);
}
The most common pattern when working with database results or collections of records:
dict people[] = [
{"name": "Alice", "age": 30},
{"name": "Bob", "age": 25}
];
for (int i = 0; i < arr_size(people); i++) {
dict p = people[i];
string name = dict_get_string(p, "name");
int age = dict_get_int(p, "age");
output(name + " is " + age + " years old", string);
}
A dict is a hash map with string keys and values of any type.
dict empty = {};
dict person = {
"name": "Alice",
"age": 28,
"score": 95.5
};
| Function | Signature | Description |
|---|---|---|
dict_create |
() → dict |
Create an empty dictionary |
dict_get_int |
(dict, string) → int |
Get integer value by key |
dict_get_float |
(dict, string) → float |
Get float value by key |
dict_get_string |
(dict, string) → string |
Get string value by key |
dict_set |
(dict, string, value) |
Set a key to a value |
dict_has_key |
(dict, string) → int |
1 if key exists, else 0 |
dict_delete |
(dict, string) |
Remove a key |
Use the typed getter for the value's actual type — using the wrong getter causes undefined behavior:
dict d = {"id": 1, "name": "Bob", "score": 88.5};
int id = dict_get_int(d, "id"); // ✅
string name = dict_get_string(d, "name"); // ✅
float score = dict_get_float(d, "score"); // ✅
Always check dict_has_key before accessing keys from untrusted data:
if (dict_has_key(row, "email") == 1) {
string email = dict_get_string(row, "email");
output(email, string);
}
String literals use double quotes. Escape sequences:
| Sequence | Meaning |
|---|---|
\n |
Newline |
\t |
Tab |
\\ |
Backslash |
\" |
Double quote |
\0 |
Null terminator |
The + operator concatenates strings and auto-converts adjacent integers and floats:
string msg = "Count: " + 5 + ", avg: " + 88.5;
// → "Count: 5, avg: 88.5"
| Function | Returns | Description |
|---|---|---|
strlen(s) |
int |
Length of s |
string_trim(s) |
string |
Remove leading/trailing whitespace |
string_to_upper(s) |
string |
Convert to uppercase |
string_to_lower(s) |
string |
Convert to lowercase |
string_contains(s, sub) |
int |
1 if sub is in s |
string_starts_with(s, pre) |
int |
1 if s starts with pre |
string_ends_with(s, suf) |
int |
1 if s ends with suf |
string_index_of(s, sub) |
int |
Index of first sub, or −1 |
string_substring(s, start, len) |
string |
Substring at start, length len |
string_reverse(s) |
string |
Reversed copy |
string_replace_char(s, from, to) |
string |
Replace all from chars with to |
string_char_at(s, i) |
char |
Character at index i |
string_to_char(s) |
char |
First character |
string_to_int(s) |
int |
Parse as integer |
string_to_float(s) |
float |
Parse as float |
string_is_empty(s) |
int |
1 if null or zero-length |
string_count_char(s, c) |
int |
Count occurrences of c |
string_split_to_array(s, delim) |
string[] |
Split on delim, return array |
strcmp(s1, s2) |
int |
0 if equal, <0 if s1 < s2, >0 if s1 > s2 |
See the Standard Library for full signatures and examples.
// Single-line comment
/*
* Multi-line comment.
* Can span many lines.
*/
int x = 10; // inline comment
import sqlite;
import json;
import http;
import date;
Call module functions with dot notation:
database db = sqlite.open("data.sqlite");
json.object obj = json.create_object();
string resp = http.get("https://example.com");
int ts = date.now();
Create utils.pie in the same directory with exported functions:
// utils.pie
export string greet(string name) {
return "Hello, " + name + "!";
}
Then import it:
import utils;
void main() {
output(utils.greet("Alice"), string);
}
Only functions marked export are accessible from outside the module. Internal helpers without export remain private.
See Module System for the full guide.
PIE is statically typed — no implicit conversions. Use explicit conversion functions:
| Conversion | Function |
|---|---|
int → float |
int_to_float(n) |
float → int |
float_to_int(n) (truncates) |
int → string |
int_to_string(n) |
float → string |
float_to_string(n) |
char → string |
char_to_string(c) |
char → int |
char_to_int(c) (ASCII code) |
int → char |
int_to_char(n) (ASCII code) |
string → int |
string_to_int(s) |
string → float |
string_to_float(s) |
string → char |
string_to_char(s) (first char) |
float avg = int_to_float(total) / int_to_float(count);
string display = "Total: " + int_to_string(total);
int code = char_to_int('A'); // 65
Print a value to stdout followed by a newline.
output(expression, type);
output(expression, float, precision); // float with decimal places
The type argument is a keyword: int, float, string, char.
output("Hello", string); // Hello
output(42, int); // 42
output(3.14159, float, 2); // 3.14
output('A', char); // A
output("x = " + x, string); // x = 10
Read from stdin into a variable.
input(variable, type);
int age;
output("Enter your age: ", string);
input(age, int);
string name;
output("Enter your name: ", string);
input(name, string);
exit(); // terminate the program immediately
sleep(3); // pause for 3 seconds
clear(); // clear the terminal screen (ANSI)
int float char string bool boolean
void file socket database dict regex
array ptr
if else for while do switch
case default break continue return
import from as export
true false null
input output exit sleep clear
arr_push arr_pop arr_size arr_contains arr_indexof arr_avg