Cool Compilable Programming Language — a small, Lua-flavored language that compiles straight to a native executable. No interpreter, no bytecode, no VM.
CCPL mixes a dynamic, Lua-like core with escape hatches to native C types, pointers and malloc, then lowers everything to C which TinyCC turns into a native .exe in milliseconds.
-- hello world in CCPL
say("hello, world")
func fib(n)
if n <= 1 then
return n
end
return fib(n - 1) + fib(n - 2)
end
for i = 1, 10 do
say("fib", i, fib(i))
end
- Why CCPL
- Requirements
- Quick start
- Command line
- clm — the package manager
- Language tour
- Examples
- Building from source
- Project layout
- Related repositories
- License
- Truly compiled. Source lowers to C, TCC emits a native binary. There is no runtime interpreter.
- Tiny and fast to build. A whole program compiles in a few milliseconds.
- Dynamic by default. Numbers are doubles, plus strings, booleans and
nil, with Lua-styleand/or/notand..concatenation. - Native when you need it.
i8…i64,u8…u64,f32/f64,bool,charandptr, plus&/*, indexing andmalloc/free. - One self-contained C file. The compiler is a single ~2k-line C source with no dependencies beyond the Windows API and TCC.
- Windows (the compiler currently uses
windows.hfor process spawning and path handling). - TinyCC — either the bundled toolchain or a local install. The easiest route is the ready-to-run
ccpl-toolchainbundle.
Download or clone ccpl-toolchain, then:
coolc.bat hello.ccpl --rungit clone https://github.com/ccpl-lang/ccpl.git
cd ccpl
build.bat
build\coolc.exe examples\hello.ccpl --runbuild.bat looks for the TinyCC driver in this order: %TCC%, toolchain\tcc\tcc.exe, then tcc on PATH.
coolc <source.ccpl> [-o out.exe] [--run] [--fast] [--show-c]
| Flag | Meaning |
|---|---|
-o, --output <file> |
Output executable path (default: source name with .exe). |
--run |
Run the executable after a successful build. |
--fast |
Compile the generated C with -O2 -s. |
--show-c |
Print the generated C and exit (great for debugging). |
--version, -v |
Print the compiler version. |
-h, --help |
Show usage. |
clm ("Cool Library Manager") ships with the compiler and installs libraries for get. It is a pip-like tool backed by the ccpl-packages registry.
clm search <query> search the registry
clm install <name>[@<ver>] install a package (and its dependencies)
clm update [<name>] update installed package(s)
clm remove <name> uninstall a package
clm list list installed packages
clm login link this machine to a GitHub account (for publishing)
clm whoami show the linked account
clm logout forget the linked account
clm publish <dir> build a library for publishing
Packages install into <compiler-dir>\packages\, exactly where get looks. The default registry is https://cdn.jsdelivr.net/gh/ccpl-lang/ccpl-packages@main; override it with the CLM_REGISTRY environment variable (file:// works for testing).
-- a line comment
--[[ a
block comment ]]
Dynamic values are numbers (doubles), strings, booleans and nil.
var greeting = "hello"
variable count = 3
var flag = true
var nothing = nil
count = count + 1
say(greeting, count, flag, nothing) --> hello 4 true nil
Declare a variable without a value to get nil:
var x
x = 10
Add a type after : to get a native C value. Native variables are unboxed and use C semantics.
var n: i32 = 42
var big: u64 = 9000000000
var pi: f64 = 3.14159
var ok: bool = true
var p: i8* = malloc(16)
| Category | Types |
|---|---|
| Signed integers | i8, i16, i32, i64 |
| Unsigned integers | u8, u16, u32, u64 |
| Floating point | f32, f64 |
| Other | bool, char, ptr |
Append * for pointers (i32*, u8**, …). ptr is a generic void *.
From lowest to highest precedence:
| Operators | Notes |
|---|---|
or |
Short-circuits, returns an operand |
and |
Short-circuits, returns an operand |
== ~= < <= > >= |
Comparisons |
.. |
String concatenation (right associative) |
+ - |
Add, subtract |
* / % |
Multiply, divide, modulo |
- Dynamic numbers are doubles, so
/always yields a fraction and%is floor-mod (-7 % 3is2). - Native integers keep C semantics for
/(truncating) but%is also floor-mod. -negates,notis logical negation,#is not used. Cast with(type)expr, dereference with*, address-of with&, index withbase[i].
if x < 0 then
say("negative")
elseif x == 0 then
say("zero")
else
say("positive")
end
while n > 0 do
n = n - 1
end
repeat
n = n + 1
until n >= 10
for i = 0, 10 do -- inclusive, optional step
say(i)
end
for i = 10, 0, -1 do -- count down
say(i)
end
break and continue work inside loops.
func add(a, b)
return a + b
end
-- native parameters and return type
func mul(a: i32, b: i32): i32
return a * b
end
-- pointers work too
func scale(p: f64*, k: f64): f64
return *p * k
end
Functions are dynamically typed unless annotated. Native arguments are converted automatically when needed.
var x: i32 = 42
var p: i32* = &x
*p = 99
say("now x =", x) --> now x = 99
var buf: i8* = malloc(16)
buf[0] = 104
buf[1] = 105
say("bytes:", buf[0], buf[1])
free(buf)
malloc, calloc, realloc and free are built in. Because native values are unboxed, you can take their address and pass them anywhere a pointer is expected.
| Builtin | Description |
|---|---|
say(...) |
Print values separated by tabs, then a newline. |
hey(...) |
Print values to stderr. |
oh(msg) |
Print msg to stderr and abort. |
tonumber(x) |
Convert to a number, or nil if it cannot. |
tostring(x) |
Convert to a string. |
malloc(n) / calloc(n, size) / realloc(p, n) / free(p) |
Native memory helpers. |
get pulls another .ccpl file into the current program.
get "mathlib" -- imports the whole file
say(square(7))
Resolution order:
- relative to the importing file,
<compiler-dir>\packages\,<compiler-dir>\..\packages\.
You can also import a single function:
get "mathlib/square"
Each module is loaded at most once, so diamond imports are safe.
The examples/ folder contains runnable programs:
| File | Shows off |
|---|---|
hello.ccpl |
Printing and variables |
controlflow.ccpl |
Loops, if/else, string concat |
features.ccpl |
Hex/float literals, floor-mod, and/or/not |
fib.ccpl |
Recursion |
lowlevel.ccpl |
Native types, pointers, casts, malloc |
getdemo.ccpl + mathlib.ccpl |
Modules with get |
bench.ccpl |
Tight loops and native-speed output |
coolc examples\bench.ccpl --run --fastRequirements: Windows and TinyCC. TCC is tiny (~1 MB), so it is often easier to grab a release.
build.batOr invoke TCC directly:
tcc src\compiler.c -o build\coolc.exe
copy src\runtime.h build\runtime.hThe compiler emits C to a temporary file, runs tcc on it, and deletes it. Use --show-c to inspect the generated C.
ccpl/
├── src/
│ ├── compiler.c # the whole compiler: lexer, parser, codegen, CLI
│ ├── clm.c # the clm package manager
│ └── runtime.h # runtime helpers injected into every program
├── examples/ # sample .ccpl programs
├── docs/ # language reference and guides
├── build.bat # build script for the compiler and clm
└── LICENSE # GNU GPL v3
| Project | Description | License |
|---|---|---|
| ccpl | Compiler and language implementation (this repo) | GPL-3.0 |
| ccpl-toolchain | Ready-to-run Windows toolchain bundle | GPL-3.0 |
| ccpl-packages | The clm package registry |
MIT |
| vscode-ccpl | VS Code syntax highlighting | MIT |
CCPL is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. See LICENSE.
The bundled TinyCC toolchain is distributed under its own
(LGPL) terms — see the notices in the ccpl-toolchain repository.