Making C Look ✨Pretty✨and Lua/Lisp/Python-esque
See the code#+TITLE:Pretty C: ✨Pretty✨ Scripting on Top of C
Pretty C is a new scripting language compatible with C. Pretty C
boosts your programs with dynamic typing, generic iteration, resource
tracking and other niceties. And it's backwards-compatible with C and
all of its libraries! Inspired by Lua, Python, JavaScript, and
Lisp. Here's how a naive re-implementation of ~head~ utility might
look like with Pretty C:
#+begin_src C
#include "pretty.h"
int main (int argc, string argv[])
{
if (argc above 1)
with (f, fclose, fopen(argv[1], "r"))
fortimes (line, 10)
with (buf, free, vector(200, char, 0))
when (fgets(buf, 200, f))
then print(buf)
otherwise 0;
else
println("Please provide an input file");
return EXIT_SUCCESS;
}
#+end_src
The goals for Pretty C are:
- Provide so much syntactic sugar as to cause any C developer a
diabetes-induced heart attack.
- Deprecate Lua, Python, JavaScript, Ruby and a dozen other languages,
because Pretty C is the ultimate scripting language, but
lightning-fast and strongly typed!
- Including only one header (yes, Pretty C is a header-only library
~#include~-able from arbitrary C file!) to turn any codebase into a
beginner friendly one.
* Getting Started
Checkout the repository
#+begin_src sh
git clone https://github.com/aartaka/pretty.c
#+end_src
Or simply copy the ~pretty.h~ file—Pretty C is a header-only
library, so you can
#+begin_src C
#include "pretty.h"
#+end_src
from any file in the directory you drop ~pretty.h~ to. Or from any
file really, if you specify the path to Pretty C as an include (~-I~)
path.
* Provided Conveniences
Here are all the pretty changes making C hip again.
** Included Standard Headers
- [[https://en.cppreference.com/w/c/types/boolean][stdbool.h]] for pretty booleans: ~true~, ~false~, and ~bool~.
- [[https://en.cppreference.com/w/c/types/integer][stdint.h]] for fixed-width integer types like ~uint64_t~.
- [[https://en.cppreference.com/w/c/language/operator_alternative#Operator_macros.28C95.29][iso646.h]] for readable alternatives to regular operators,
including ~and~ for ~&&~ and ~or~ for ~||~. Neat!
** Simple macros
Everyone defines these, so why not provide them?
- ~max~ and ~min~ of two numbers.
- ~len~ for array length.
- ~default~ for providing a fallback value.
- ~limit~ to ensure proper value range.
- ~between~ to check whether the number falls in a range.
- ~divisible~ to check whether a number is modulo divisible by another number.
** New types
Type aliases:
- ~string~ == ~char*~.
- ~byte~ == ~char~.
- ~bytes~ == ~char*~.
- ~any~ == ~void*~.
- Integer shortcuts:
- ~uchar~.
- ~ushort~.
- ~uint~.
- ~ulong~.
** New aliases
Mainly modeled after Lua and Lisp:
- ~eq~, because ~iso646.h~ only has ~not_eq~.
- ~is~ means ~==~ too.
- ~bitnot~ and ~bitxor~ for operations that used to be called
inconsistently (~compl~ and ~xor~ respectively) in ~iso646.h~.
- ~success~ and ~fail~ / ~failure~ for pattern of ~success == 0~.
- ~below~, ~above~, ~upto~, and ~downto~ comparison operators.
- ~even~, ~odd~, ~positive~, ~negative~, ~zero~, and ~empty~ as predicates for numbers/data.
- ~nil~ for ~NULL~.
- ~until~ for negated ~while~.
- ~elif~ for ~else if~.
- ~ifnt~ for ~if(!...)~ and ~elifnt~ (you guessed it.)
- ~repeat~ from Lua as an alias for ~do~.
- ~done~/~finish~ and ~pass~ as aliases for ~break~ and ~continue~, respectively.
- ~always~, ~forever~, ~loop~, and ~indefinitely~ so that you can make infinite (event? server?) loops
#+begin_src C
always println("After all this time?");
#+end_src
- and ~never~ and ~comment~ to comment out some code with just one
keyword, while still allowing the compiler to analyze/optimize it
(similar to [[https://clojuredocs.org/clojure.core/comment][Clojure ~comment~ form]]):
#+begin_src C
never println("This never runs, but never gets outdated, because compiler will shout at you if it does.");
#+end_src
** Type inference (GCC, Clang, or C23+)
Yes, you can do
#+begin_src C
var t = time(0);
let lt = localtime(&t);
local at = asctime(lt);
println(at);
#+end_src
With Pretty C.
** Generic printing (C11+)
~print~ prints whatever you feed it. ~println~ adds a newline after it.
#+begin_src C
println(3.1);
print("Hello world!\n");
#+end_src
** Generic equality (C11+)
Compare all the things!
#+begin_src C
equal("NA", line); // true
equal(0.3, 0.2 + 0.1); // true
#+end_src
** Pretty ternaries
Ternaries are frightening, so it won't hurt to add some plain
text. ~if~ and ~else~ are taken, but there are proper linguistic
alternatives that look quite Python/Lisp-like:
#+begin_src C
return when some_condition
then do_something()
other do_something_else();
#+end_src
It's ternaries underneath:
- ~when~ expands to empty string and is only provided for readability.
- ~unless~ expands to ~not~ to be a negative version of ~when~.
- ~then~ expands to ~?~.
- ~other~ / ~otherwise~ expands to ~:~.
There's also ~only~ for when the ~otherwise~ clause is
unnecessary:
#+begin_src C
return when done()
then 42 only;
#+end_src
and ~otherwhen~ for the next condition
#+begin_src C
return when c is 'A'
then 'a'
otherwhen c is 'B'
then 'b' only;
#+end_src
** ~for~ Macros
These macros are aliases for certain ~for~ loop pattern, each
abstracting away some of the frequent ~for~ loop uses.
*** ~foreach (var, type, length, ...)~
This one walks an array or memory region initialized to the vararg
expression. Every time it iterates, ~var~ is set to a pointer to the
respective array element. Yes, pointer—so that you can modify the
element in place if you have to.
#+begin_src C
foreach (i, int, 10, vector(10, int, 1, 2, 3, 3, 4, 5))
println(*i);
#+end_src
Also shows the use of ~vector~.
*** ~forthese (var, type, ...)~
Iterates over the provided varargs, binding each of these to ~type~-d
~var~. The loop above can be translated as:
#+begin_src C
forthese (i, int, 1, 2, 3, 3, 4, 5)
println(i);
#+end_src
*** ~fortimes (var, times)~
A frequent case of going from 0 to some positive number. Saves you
quite some time for your
#+begin_src C
for (int i = 0; i < 28; i++)
println(i+1);
#+end_src
turning it into a simple
#+begin_src C
fortimes (i, 28)
println(i+1);
println("28 stab wounds, you didn't want to leave him a chance, huh?");
#+end_src
*** ~forrange (var, init, target)~
Iterate over a range of numbers from ~init~ to
~target~. Pythonesque. Here's Celsius to Fahrenheit conversion loop
with ~forrange~:
#+begin_src C
forrange (c, -10, 20)
printf("Celsius %i = Fahrenheit %f\n", c, (32 + (c * 1.8)));
#+end_src
Note that ~init~ and ~target~ are arbitrary integers, signed and
unsigned. And ~init~ might be greater than ~target~ in which case the
iteration step decreases the variable.
*** ~forrangeby (var, type, init, target, by)~
Iterate ~type~-d ~var~ from ~iter~ to ~target~, stepping by ~by~ every
time. Pythonesque.
#+begin_src C
forrangeby (x, double, 1.0, 10.0, 0.5)
println(x);
#+end_src
** Allocation macros
These allow quick-and-dirty allocation for typical patterns. Mostly
modeled after C++.
*** ~new (type, ...)~
C++ ~new~ operator is nice, so it won't hurt having something similar
in C, right? Ask no more:
#+begin_src C
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode *node = new(struct ListNode, 2, new(struct ListNode, 1, nil));
#+end_src
Or, if you fancy, you can add even more syntax on top:
#+begin_src C
#define cons(val, ...) new(struct ListNode, val, __VA_ARGS__)
cons(2, cons(1, nil));
#+end_src
*** ~vector (length, type, ...)~
C++ again. ~std::vector~ is an extremely useful and versatile data
structure that's easy to reason about. While this macro is not even
remotely as featureful as C++ counterpart, it simplifies a frequent
pattern of "allocate an array of that much elements and with these
contents":
#+begin_src C
double *vec = vector(10, double, 1, 2, 3, 4, 5);
#+end_src
*** ~delete (...)~
In case you don't like ~free~-ing the resources and prefer a fancier C++ name.
Otherwise the same as ~free~.
** Block utilities
These establish new local bindings, ensure deferred computations, or
otherwise act on the block after them.
*** ~lambda (ret, name, ...)~ (GCC, Clang, or C++)
Nested functions/lambdas/closures, now in C!
#+begin_src C
int *arr = vector(10, int, 23423, 23423, 234, 5233, 6, 4, 34, 643, 3, 9);
lambda (int, cmp, int *a, int *b) {
return *a - *b;
};
qsort(arr, 10, sizeof(int), cmp);
// arr becomes {3, 4, 6, 9, 34, 234, 643, 5233, 23423, 23423}
#+end_src
*** ~with (var, close, ...)~
This ensures that you never have use-after-free, because you provide
the freeing procedure (~close~) upfront. Especially useful for
dynamically allocated objects and file designators.
#+begin_src C
with (file, fclose, fopen("hello.txt", "w"))
fprintf(file, "Hello world!\n");
#+end_src
One of the downsides is that the bound ~var~ is a ~void *~, so you
might need to coerce it to your type before using it.
*** ~defer~ REMOVED
Use [[https://gustedt.wordpress.com/2026/02/15/defer-available-in-gcc-and-clang/][newly available defer]] instead.
*** ~try~ and ~catch~
Fancy error handling, now in C. Refactored example from [[https://en.cppreference.com/w/c/error/errno][errno reference]]:
#+begin_src C
try log(0.0);
catch (NOERR)
println("No error.");
catch (EDOM, ERANGE)
println("Math error!");
#+end_src
~NOERR~ and ~NOERROR~ are also provided by Pretty C, for convenience
of error switch-casing.
* Contributing
- Please follow [[https://www.kernel.org/doc/html/v4.10/process/coding-style.html][Linux Kernel Style Guide]] for functions you add.
- Run ~make indent~ before commiting, that should handle most of the style details.
- It is preferable that you follow [[https://aartaka.me/commitmsg][A's Commit Message Guide]], but it's okay if you don't want to.
Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.
1,316 followers · starred Nov 2024
261 followers · starred Sep 2024
541 followers · starred Oct 2024
80 followers · starred Oct 2024
Making C Look ✨Pretty✨and Lua/Lisp/Python-esque
See the code#+TITLE:Pretty C: ✨Pretty✨ Scripting on Top of C
Pretty C is a new scripting language compatible with C. Pretty C
boosts your programs with dynamic typing, generic iteration, resource
tracking and other niceties. And it's backwards-compatible with C and
all of its libraries! Inspired by Lua, Python, JavaScript, and
Lisp. Here's how a naive re-implementation of ~head~ utility might
look like with Pretty C:
#+begin_src C
#include "pretty.h"
int main (int argc, string argv[])
{
if (argc above 1)
with (f, fclose, fopen(argv[1], "r"))
fortimes (line, 10)
with (buf, free, vector(200, char, 0))
when (fgets(buf, 200, f))
then print(buf)
otherwise 0;
else
println("Please provide an input file");
return EXIT_SUCCESS;
}
#+end_src
The goals for Pretty C are:
- Provide so much syntactic sugar as to cause any C developer a
diabetes-induced heart attack.
- Deprecate Lua, Python, JavaScript, Ruby and a dozen other languages,
because Pretty C is the ultimate scripting language, but
lightning-fast and strongly typed!
- Including only one header (yes, Pretty C is a header-only library
~#include~-able from arbitrary C file!) to turn any codebase into a
beginner friendly one.
* Getting Started
Checkout the repository
#+begin_src sh
git clone https://github.com/aartaka/pretty.c
#+end_src
Or simply copy the ~pretty.h~ file—Pretty C is a header-only
library, so you can
#+begin_src C
#include "pretty.h"
#+end_src
from any file in the directory you drop ~pretty.h~ to. Or from any
file really, if you specify the path to Pretty C as an include (~-I~)
path.
* Provided Conveniences
Here are all the pretty changes making C hip again.
** Included Standard Headers
- [[https://en.cppreference.com/w/c/types/boolean][stdbool.h]] for pretty booleans: ~true~, ~false~, and ~bool~.
- [[https://en.cppreference.com/w/c/types/integer][stdint.h]] for fixed-width integer types like ~uint64_t~.
- [[https://en.cppreference.com/w/c/language/operator_alternative#Operator_macros.28C95.29][iso646.h]] for readable alternatives to regular operators,
including ~and~ for ~&&~ and ~or~ for ~||~. Neat!
** Simple macros
Everyone defines these, so why not provide them?
- ~max~ and ~min~ of two numbers.
- ~len~ for array length.
- ~default~ for providing a fallback value.
- ~limit~ to ensure proper value range.
- ~between~ to check whether the number falls in a range.
- ~divisible~ to check whether a number is modulo divisible by another number.
** New types
Type aliases:
- ~string~ == ~char*~.
- ~byte~ == ~char~.
- ~bytes~ == ~char*~.
- ~any~ == ~void*~.
- Integer shortcuts:
- ~uchar~.
- ~ushort~.
- ~uint~.
- ~ulong~.
** New aliases
Mainly modeled after Lua and Lisp:
- ~eq~, because ~iso646.h~ only has ~not_eq~.
- ~is~ means ~==~ too.
- ~bitnot~ and ~bitxor~ for operations that used to be called
inconsistently (~compl~ and ~xor~ respectively) in ~iso646.h~.
- ~success~ and ~fail~ / ~failure~ for pattern of ~success == 0~.
- ~below~, ~above~, ~upto~, and ~downto~ comparison operators.
- ~even~, ~odd~, ~positive~, ~negative~, ~zero~, and ~empty~ as predicates for numbers/data.
- ~nil~ for ~NULL~.
- ~until~ for negated ~while~.
- ~elif~ for ~else if~.
- ~ifnt~ for ~if(!...)~ and ~elifnt~ (you guessed it.)
- ~repeat~ from Lua as an alias for ~do~.
- ~done~/~finish~ and ~pass~ as aliases for ~break~ and ~continue~, respectively.
- ~always~, ~forever~, ~loop~, and ~indefinitely~ so that you can make infinite (event? server?) loops
#+begin_src C
always println("After all this time?");
#+end_src
- and ~never~ and ~comment~ to comment out some code with just one
keyword, while still allowing the compiler to analyze/optimize it
(similar to [[https://clojuredocs.org/clojure.core/comment][Clojure ~comment~ form]]):
#+begin_src C
never println("This never runs, but never gets outdated, because compiler will shout at you if it does.");
#+end_src
** Type inference (GCC, Clang, or C23+)
Yes, you can do
#+begin_src C
var t = time(0);
let lt = localtime(&t);
local at = asctime(lt);
println(at);
#+end_src
With Pretty C.
** Generic printing (C11+)
~print~ prints whatever you feed it. ~println~ adds a newline after it.
#+begin_src C
println(3.1);
print("Hello world!\n");
#+end_src
** Generic equality (C11+)
Compare all the things!
#+begin_src C
equal("NA", line); // true
equal(0.3, 0.2 + 0.1); // true
#+end_src
** Pretty ternaries
Ternaries are frightening, so it won't hurt to add some plain
text. ~if~ and ~else~ are taken, but there are proper linguistic
alternatives that look quite Python/Lisp-like:
#+begin_src C
return when some_condition
then do_something()
other do_something_else();
#+end_src
It's ternaries underneath:
- ~when~ expands to empty string and is only provided for readability.
- ~unless~ expands to ~not~ to be a negative version of ~when~.
- ~then~ expands to ~?~.
- ~other~ / ~otherwise~ expands to ~:~.
There's also ~only~ for when the ~otherwise~ clause is
unnecessary:
#+begin_src C
return when done()
then 42 only;
#+end_src
and ~otherwhen~ for the next condition
#+begin_src C
return when c is 'A'
then 'a'
otherwhen c is 'B'
then 'b' only;
#+end_src
** ~for~ Macros
These macros are aliases for certain ~for~ loop pattern, each
abstracting away some of the frequent ~for~ loop uses.
*** ~foreach (var, type, length, ...)~
This one walks an array or memory region initialized to the vararg
expression. Every time it iterates, ~var~ is set to a pointer to the
respective array element. Yes, pointer—so that you can modify the
element in place if you have to.
#+begin_src C
foreach (i, int, 10, vector(10, int, 1, 2, 3, 3, 4, 5))
println(*i);
#+end_src
Also shows the use of ~vector~.
*** ~forthese (var, type, ...)~
Iterates over the provided varargs, binding each of these to ~type~-d
~var~. The loop above can be translated as:
#+begin_src C
forthese (i, int, 1, 2, 3, 3, 4, 5)
println(i);
#+end_src
*** ~fortimes (var, times)~
A frequent case of going from 0 to some positive number. Saves you
quite some time for your
#+begin_src C
for (int i = 0; i < 28; i++)
println(i+1);
#+end_src
turning it into a simple
#+begin_src C
fortimes (i, 28)
println(i+1);
println("28 stab wounds, you didn't want to leave him a chance, huh?");
#+end_src
*** ~forrange (var, init, target)~
Iterate over a range of numbers from ~init~ to
~target~. Pythonesque. Here's Celsius to Fahrenheit conversion loop
with ~forrange~:
#+begin_src C
forrange (c, -10, 20)
printf("Celsius %i = Fahrenheit %f\n", c, (32 + (c * 1.8)));
#+end_src
Note that ~init~ and ~target~ are arbitrary integers, signed and
unsigned. And ~init~ might be greater than ~target~ in which case the
iteration step decreases the variable.
*** ~forrangeby (var, type, init, target, by)~
Iterate ~type~-d ~var~ from ~iter~ to ~target~, stepping by ~by~ every
time. Pythonesque.
#+begin_src C
forrangeby (x, double, 1.0, 10.0, 0.5)
println(x);
#+end_src
** Allocation macros
These allow quick-and-dirty allocation for typical patterns. Mostly
modeled after C++.
*** ~new (type, ...)~
C++ ~new~ operator is nice, so it won't hurt having something similar
in C, right? Ask no more:
#+begin_src C
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode *node = new(struct ListNode, 2, new(struct ListNode, 1, nil));
#+end_src
Or, if you fancy, you can add even more syntax on top:
#+begin_src C
#define cons(val, ...) new(struct ListNode, val, __VA_ARGS__)
cons(2, cons(1, nil));
#+end_src
*** ~vector (length, type, ...)~
C++ again. ~std::vector~ is an extremely useful and versatile data
structure that's easy to reason about. While this macro is not even
remotely as featureful as C++ counterpart, it simplifies a frequent
pattern of "allocate an array of that much elements and with these
contents":
#+begin_src C
double *vec = vector(10, double, 1, 2, 3, 4, 5);
#+end_src
*** ~delete (...)~
In case you don't like ~free~-ing the resources and prefer a fancier C++ name.
Otherwise the same as ~free~.
** Block utilities
These establish new local bindings, ensure deferred computations, or
otherwise act on the block after them.
*** ~lambda (ret, name, ...)~ (GCC, Clang, or C++)
Nested functions/lambdas/closures, now in C!
#+begin_src C
int *arr = vector(10, int, 23423, 23423, 234, 5233, 6, 4, 34, 643, 3, 9);
lambda (int, cmp, int *a, int *b) {
return *a - *b;
};
qsort(arr, 10, sizeof(int), cmp);
// arr becomes {3, 4, 6, 9, 34, 234, 643, 5233, 23423, 23423}
#+end_src
*** ~with (var, close, ...)~
This ensures that you never have use-after-free, because you provide
the freeing procedure (~close~) upfront. Especially useful for
dynamically allocated objects and file designators.
#+begin_src C
with (file, fclose, fopen("hello.txt", "w"))
fprintf(file, "Hello world!\n");
#+end_src
One of the downsides is that the bound ~var~ is a ~void *~, so you
might need to coerce it to your type before using it.
*** ~defer~ REMOVED
Use [[https://gustedt.wordpress.com/2026/02/15/defer-available-in-gcc-and-clang/][newly available defer]] instead.
*** ~try~ and ~catch~
Fancy error handling, now in C. Refactored example from [[https://en.cppreference.com/w/c/error/errno][errno reference]]:
#+begin_src C
try log(0.0);
catch (NOERR)
println("No error.");
catch (EDOM, ERANGE)
println("Math error!");
#+end_src
~NOERR~ and ~NOERROR~ are also provided by Pretty C, for convenience
of error switch-casing.
* Contributing
- Please follow [[https://www.kernel.org/doc/html/v4.10/process/coding-style.html][Linux Kernel Style Guide]] for functions you add.
- Run ~make indent~ before commiting, that should handle most of the style details.
- It is preferable that you follow [[https://aartaka.me/commitmsg][A's Commit Message Guide]], but it's okay if you don't want to.
Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.
1,316 followers · starred Nov 2024
261 followers · starred Sep 2024
541 followers · starred Oct 2024
80 followers · starred Oct 2024