Modules
The module system in Twinkies allows you to organize code into separate files with clear interfaces and implementations.
Module Overview
Twinkies uses a header/implementation file system similar to C:
.tlhfiles - Header files containing function declarations (interfaces).tlfiles - Source files containing function implementations
Basic Module Structure
Header File (.tlh)
Header files declare the public interface of a module:
// math.tlh
func factorial(n: int) -> int;
func gcd(a: int, b: int) -> int;
func fibonacci(n: int) -> int;
func is_prime(n: int) -> int;
Implementation File (.tl)
Source files contain the actual implementations:
// math.tl
func factorial(n: int) -> int {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}
func gcd(a: int, b: int) -> int {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
func fibonacci(n: int) -> int {
if (n <= 1) {
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
func is_prime(n: int) -> int {
if (n < 2) {
return 0;
}
let i: int = 2;
while (i * i <= n) {
if (n % i == 0) {
return 0;
}
i = i + 1;
}
return 1;
}
Including Modules
Use the #include directive to include module headers:
// main.tl
#include "math.tlh"
func main() -> int {
let result: int = factorial(5);
print(result);
return 0;
}
Note: The example file examples/modules/main.tl has a typo (#iinclude instead of #include). The correct syntax is #include.
Include Syntax
Two forms of includes are supported:
#include "local.tlh" // Local include (searched in user-specified paths)
#include <system.tlh> // System include (searched in system paths)
- Local includes (
"...") - For your own modules, searched in current directory and user-specified include paths - System includes (
<...>) - For system modules, searched in system directories
Complete Module Example
String Module Header
// string.tlh
func string_length(str: string) -> int;
func string_compare(str1: string, str2: string) -> int;
func string_substr(str: string, start: int, length: int) -> string;
func string_concat(str1: string, str2: string) -> string;
String Module Implementation
// string.tl
func string_length(str: string) -> int {
return strlen(str);
}
func string_compare(str1: string, str2: string) -> int {
return strcmp(str1, str2);
}
func string_substr(str: string, start: int, length: int) -> string {
return substr(str, start, length);
}
func string_concat(str1: string, str2: string) -> string {
return concat(str1, str2);
}
Using the Module
// main.tl
#include "string.tlh"
func main() -> int {
let str1: string = "Hello";
let str2: string = "World";
let combined: string = string_concat(str1, " ");
combined = string_concat(combined, str2);
print(combined); // "Hello World"
let len: int = string_length(str1);
print(len); // 5
return 0;
}
Compiling with Modules
Use the --modules flag and list all source files:
./compiler main.tl -o main.c math.tl string.tl --modules
The compiler will:
- Parse all header files to extract public symbols
- Build a dependency graph from
#includedirectives - Compile modules in dependency order
- Link everything together
Module Organization
Directory Structure
Organize modules in a logical directory structure:
project/
├── main.tl
├── modules/
│ ├── math.tlh
│ ├── math.tl
│ ├── string.tlh
│ └── string.tl
└── utils/
├── helpers.tlh
└── helpers.tl
Include Paths
Specify include paths with -I:
./compiler main.tl -o main.c -I modules/ -I utils/ math.tl string.tl --modules
Module Best Practices
1. Clear Interface
Keep header files focused on the public API:
// Good: Only public functions
// math.tlh
func factorial(n: int) -> int;
func gcd(a: int, b: int) -> int;
// Bad: Implementation details
// math.tlh
func factorial(n: int) -> int {
// Implementation here - should be in .tl file
}
2. Consistent Naming
Use consistent naming conventions:
// string.tlh - All functions prefixed with module name
func string_length(str: string) -> int;
func string_compare(str1: string, str2: string) -> int;
func string_concat(str1: string, str2: string) -> string;
3. One Module Per File Pair
Keep one logical module per .tlh/.tl pair:
// Good: math.tlh and math.tl for math functions
// Good: string.tlh and string.tl for string functions
// Bad: math.tlh with both math and string functions
4. Document Public Functions
Use comments to document public functions:
// math.tlh
// Calculates the factorial of n (n!)
func factorial(n: int) -> int;
// Calculates the greatest common divisor of a and b
func gcd(a: int, b: int) -> int;
Module Dependencies
Modules can depend on other modules:
// utils.tlh
#include "math.tlh"
func calculate_sum_of_factorials(n: int) -> int {
let total: int = 0;
let i: int = 1;
while (i <= n) {
total = total + factorial(i); // Uses math module
i = i + 1;
}
return total;
}
The compiler automatically resolves dependencies and compiles in the correct order.
Module Limitations
- No Circular Dependencies - The module system doesn't detect circular dependencies
- No Namespaces - All symbols are in the global namespace
- No Private Functions - All functions in a
.tlfile are accessible if declared in.tlh - No Module Aliases - Cannot rename modules when including
Example: Complete Module System
Math Module
// math.tlh
func add(x: int, y: int) -> int;
func subtract(x: int, y: int) -> int;
func multiply(x: int, y: int) -> int;
func divide(x: int, y: int) -> int;
// math.tl
func add(x: int, y: int) -> int {
return x + y;
}
func subtract(x: int, y: int) -> int {
return x - y;
}
func multiply(x: int, y: int) -> int {
return x * y;
}
func divide(x: int, y: int) -> int {
if (y == 0) {
return 0;
}
return x / y;
}
Main Program
// main.tl
#include "math.tlh"
func main() -> int {
let a: int = 10;
let b: int = 5;
print(add(a, b)); // 15
print(subtract(a, b)); // 5
print(multiply(a, b)); // 50
print(divide(a, b)); // 2
return 0;
}
Compilation
./compiler main.tl -o main.c math.tl --modules
Next Steps
- Learn about FFI for calling external libraries
- Read Functions for function syntax used in modules
- Check Examples Guide for module examples
