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:
#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;
}
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.
Checkout the repository
git clone https://github.com/aartaka/pretty.c
Or simply copy the pretty.h
file—Pretty C is a header-only
library, so you can
#include "pretty.h"
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.
Here are all the pretty changes making C hip again.
- stdbool.h for pretty booleans:
true
,false
, andbool
. - stdint.h for fixed-width integer types like
uint64_t
. - iso646.h for readable alternatives to regular operators,
including
and
for&&
andor
for||
. Neat!
Everyone defines these, so why not provide them?
max
andmin
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.
Type aliases:
string
==char*
.byte
==char
.bytes
==char*
.any
==void*
.- Integer shortcuts:
uchar
.ushort
.uint
.ulong
.
Mainly modeled after Lua and Lisp:
eq
, becauseiso646.h
only hasnot_eq
.is
means==
too.
bitnot
andbitxor
for operations that used to be called inconsistently (compl
andxor
respectively) iniso646.h
.success
andfail
/failure
for pattern ofsuccess == 0
.below
,above
,upto
, anddownto
comparison operators.even
,odd
,positive
,negative
,zero
, andempty
as predicates for numbers/data.nil
forNULL
.until
for negatedwhile
.elif
forelse if
.ifnt
forif(!...)
andelifnt
(you guessed it.)repeat
from Lua as an alias fordo
.done~/~finish
andpass
as aliases forbreak
andcontinue
, respectively.always
,forever
,loop
, andindefinitely
so that you can make infinite (event? server?) loops
always println("After all this time?");
- and
never
andcomment
to comment out some code with just one keyword, while still allowing the compiler to analyze/optimize it (similar to Clojurecomment
form):
never println("This never runs, but never gets outdated, because compiler will shout at you if it does.");
Yes, you can do
var t = time(0);
let lt = localtime(&t);
local at = asctime(lt);
println(at);
With Pretty C.
print
prints whatever you feed it. println
adds a newline after it.
println(3.1);
print("Hello world!\n");
Compare all the things!
equal("NA", line); // true
equal(0.3, 0.2 + 0.1); // true
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:
return when some_condition
then do_something()
other do_something_else();
It’s ternaries underneath:
when
expands to empty string and is only provided for readability.unless
expands tonot
to be a negative version ofwhen
.
then
expands to?
.other
/otherwise
expands to:
.
There’s also only
for when the otherwise
clause is
unnecessary:
return when done()
then 42 only;
and otherwhen
for the next condition
return when c is 'A'
then 'a'
otherwhen c is 'B'
then 'b' only;
These macros are aliases for certain for
loop pattern, each
abstracting away some of the frequent for
loop uses.
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.
foreach (i, int, 10, vector(10, int, 1, 2, 3, 3, 4, 5))
println(*i);
Also shows the use of vector
.
Iterates over the provided varargs, binding each of these to type
-d
var
. The loop above can be translated as:
forthese (i, int, 1, 2, 3, 3, 4, 5)
println(i);
A frequent case of going from 0 to some positive number. Saves you quite some time for your
for (int i = 0; i < 28; i++)
println(i+1);
turning it into a simple
fortimes (i, 28)
println(i+1);
println("28 stab wounds, you didn't want to leave him a chance, huh?");
Iterate over a range of numbers from init
to
target
. Pythonesque. Here’s Celsius to Fahrenheit conversion loop
with forrange
:
forrange (c, -10, 20)
printf("Celsius %i = Fahrenheit %f\n", c, (32 + (c * 1.8)));
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.
Iterate type
-d var
from iter
to target
, stepping by by
every
time. Pythonesque.
forrangeby (x, double, 1.0, 10.0, 0.5)
println(x);
These allow quick-and-dirty allocation for typical patterns. Mostly modeled after C++.
C++ new
operator is nice, so it won’t hurt having something similar
in C, right? Ask no more:
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode *node = new(struct ListNode, 2, new(struct ListNode, 1, nil));
Or, if you fancy, you can add even more syntax on top:
#define cons(val, ...) new(struct ListNode, val, __VA_ARGS__)
cons(2, cons(1, nil));
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”:
double *vec = vector(10, double, 1, 2, 3, 4, 5);
In case you don’t like free
-ing the resources and prefer a fancier C++ name.
Otherwise the same as free
.
These establish new local bindings, ensure deferred computations, or otherwise act on the block after them.
Nested functions/lambdas/closures, now in 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}
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.
with (file, fclose, fopen("hello.txt", "w"))
fprintf(file, "Hello world!\n");
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.
Offloads the code to be executed after the following block. Not at the
end of function as in Go, because that’s impossible hard to
implement in C. Still, Pretty C defer
is useful enough.
Fancy error handling, now in C. Refactored example from errno reference:
try log(0.0);
catch (NOERR)
println("No error.");
catch (EDOM, ERANGE)
println("Math error!");
NOERR
and NOERROR
are also provided by Pretty C, for convenience
of error switch-casing.
- Please follow Linux Kernel Style Guide for functions you add.
- Run
make indent
before commiting, that should handle most of the style details.
- Run
- It is preferable that you follow A’s Commit Message Guide, but it’s okay if you don’t want to.