i started pouf with a simple idea: write a program in one language, then compile it to either python or c. python makes it easy to try things out; c lets the same project reach into freestanding and low-level work
the fun part isn’t just having two code generators. i want both targets to feel like different ways to run the same language. pouf reads the source, builds an ast, checks bindings, control flow and types, then generates python or c. the syntax stays familiar across both, while the compiler still tells you when a feature doesn’t fit the target you picked
the language bits
the basics are familiar: functions, typed parameters and return values, local bindings, loops, enums, arrays and expressions. bindings don’t change unless you mark them mut. enums work with match, including a wildcard arm, so you can see the cases right where you use them. for c, there are also types like Ptr<T>, const<T>, Volatile<T>, Array<T> and Vec<T> to make pointer and data intent visible
here’s an enum and a match in c mode:
mode c
enum Direction {
North
South
East
West
}
def describe(direction: Direction) -> Ptr<const<char>> {
return match direction {
Direction::North => "north",
Direction::South => "south",
Direction::East => "east",
_ => "west",
}
}
it’s a small example, but it gives you the feel of the syntax. the compiler turns these constructs into the right code for the target, and checks for problems along the way. the shared subset is still growing, so portability means sticking to features both sides support
what the runtime does
the runtime isn’t a big standard library of its own. it’s a little bit of support for language features that need it after compilation. in python mode, generated programs use pouf_runtime.py for things like mutable references, option/result values and collection helpers. pouf run sets up the runtime, compiles a python-mode file and runs it; pouf compile writes out the generated python if you want to look at it
in c mode, pouf emits c you can feed into a regular c toolchain. most language features turn into ordinary c declarations, expressions and control flow. the support header, pouf_runtime.h, provides a little machinery for things like Vec<T>: growing its storage, pushing and popping values, clearing it and freeing it. from there, your build can use a host compiler or a freestanding toolchain. pouf doesn’t bundle libc or take care of platform setup for you
some c types and operations don’t have a python equivalent, and python has features that don’t fit the c subset. the compiler checks against the target you chose, and the overlap is smaller than either language on its own. that’s the tradeoff: one language for quick experiments and systems code, without pretending every program will work unchanged on both
one more example
this one adds up the squares of even numbers, and can be compiled to c by just changing line 1 from mode python to mode c
mode python
import stdio
def sum_even_squares(numbers: Array<int>, count: int) -> int {
mut total: int = 0
for i in range(0, count) {
let value: int = numbers[i]
if value % 2 == 0 {
total += value * value
}
}
return total
}
def main() {
let numbers: Array<int> = [1, 2, 3, 4, 5, 6]
let result: int = sum_even_squares(numbers, 6)
printf("sum of squares of evens: %d\n", result)
}
demo projects
i've come up with a few demo projects to demo pouf's usability:
- backend for a hardware renting service
- rewriting parts of mboot userspace
- quick and dirty unix-like os
- cfd data analysis (demo pouf's functional features)
still figuring it out
there’s still plenty to build. the two targets don’t share every type or feature yet, and real projects are how i find the gaps. python makes it quick to try an idea; c lets me take pouf into constrained environments and systems projects. the goal is simple: keep one language that’s useful in both places, and be clear about where the edges are