aartaka/pretty.c

Making C Look ✨Pretty✨and Lua/Lisp/Python-esque

C

641

360 commits

updated Feb 17, 2026

See the code

See what people are saying

SourceMessageScoreDate

C Until It Is No Longer C

1

Nov 4, 2024

C Until It Is No Longer C

19

Nov 2, 2024

C Until It Is No Longer C

13

Sep 30, 2024

C Until It Is No Longer C

1

Sep 24, 2024

C Until It Is No Longer C

2

Sep 24, 2024

README

#+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.

c
lisp
lua
macros
pretty
python
ruby

aartaka/pretty.c

Making C Look ✨Pretty✨and Lua/Lisp/Python-esque

C

641

360 commits

updated Feb 17, 2026

See the code

See what people are saying

SourceMessageScoreDate

C Until It Is No Longer C

1

Nov 4, 2024

C Until It Is No Longer C

19

Nov 2, 2024

C Until It Is No Longer C

13

Sep 30, 2024

C Until It Is No Longer C

1

Sep 24, 2024

C Until It Is No Longer C

2

Sep 24, 2024

README

#+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.

c
lisp
lua
macros
pretty
python
ruby

Significant stargazers

Jared Forsyth

1,316 followers · starred Nov 2024

Colin Dean

261 followers · starred Sep 2024

Aaron Pham

541 followers · starred Oct 2024

Enno

80 followers · starred Oct 2024