
Let's Destroy C - shakna
https://gist.github.com/shakna-israel/4fd31ee469274aa49f8f9793c3e71163#lets-destroy-c
======
senozhatsky

       > printf("%s\n", "Hello, World!");
       >
       > That's an awful lot of symbolic syntax.
    

Well... Because it should have been

    
    
        printf("Hello, World!\n");
    

in the first place?

One can do something like

    
    
        printf("%s,%s%c\n", "Hello", "World", '!');
    

and claim that C is awful and that

    
    
        displayln("Hello, World!");
    

is so much better.

~~~
shakna
displayln is a _Generic.

All of these are valid:

    
    
        displayln("Hello, World!");
        displayln(100);
        displayln(1.8);
    

The point is, for simple things, to not have to specify how they appear.

> Well... Because it should have been

> printf("Hello, World!\n");

No. You don't really want to do that. If you're doing that, use puts [0] . All
this requires is a modification to one string in memory and you have an
injection vulnerability.

[0]
[http://www.cplusplus.com/reference/cstdio/puts/](http://www.cplusplus.com/reference/cstdio/puts/)

~~~
senozhatsky
> No. You don't really want to do that. If you're doing that, use puts

This is not the case for RO strings, is it?

~~~
shakna
You're not guaranteed to have a RO string by the standard are you?

~~~
senozhatsky
Good point. I suppose that you are talking about overwriting terminating NUL,
do I get it right? puts() is vulnurable in exactly same way.

~~~
shakna
No, a format string attack. If you replace the start of the string with
various specifiers, you can lift out pointer addresses and write to them. You
can't do that with puts. Worst you can do with puts is read.

~~~
senozhatsky
OK, understood. So what you meant was more like

    
    
       puts()'s attack surface is smaller than printf()'s.
    

This is not how your message appeared to me - "printf() is vulnerable to
injections, use puts() instead". Both are vulnerable to "unintended read()-s".

~~~
shakna
Not really.

printf is vulnerable to both read and _write_ attacks when you misuse it by
only supplying the single argument. It's vulnerable to injections that can
lead to remote execution and all sorts of CVEs.

puts is sometimes vulnerable to read attacks, but not often.

------
cperciva
While I'm not a fan of the specific changes shown in this article, I do agree
with the principle of "remove boilerplate when possible". I found that the
largest amount of boilerplate I ever wrote was in main() to parse the command
line, and to that end I wrote a "magic getopt", which handles both short and
long options in-line without external tables
([https://www.daemonology.net/blog/2015-12-06-magic-
getopt.htm...](https://www.daemonology.net/blog/2015-12-06-magic-getopt.html)
); and PARSENUM, which handles the common cases of parsing human-readable
values while appropriately checking for overflow
([https://github.com/Tarsnap/libcperciva/blob/master/util/pars...](https://github.com/Tarsnap/libcperciva/blob/master/util/parsenum.h)).

------
roywiggins
Ah, finally a modern successor to Bournegol:
[http://oldhome.schmorp.de/marc/bournegol.html](http://oldhome.schmorp.de/marc/bournegol.html)

~~~
shakna

        #define TYPE typedef
        #define STRUCT TYPE struct
        #define UNION TYPE union
    

Well, that's extremely opinionated. Which I guess is the point. It does seem
to add a BASIC-ness to the code.

However, when I see landmines like these:

    
    
        #define TRUE (-1)
        #define FALSE 0
    

I might just hide instead of touching it.

~~~
stiray
Uh... This is serious landmine... I suggest author to change this.

~~~
thrwaway69
Can you explain why?

~~~
shakna
To emphasise: This is about Bournegol. Nothing to do with this post.

> Can you explain why?

Because it isn't how most C libraries expect true/false to be defined.

stdbool in C99 standardized things a bit, but before then what was generally
accepted was:

> true is 1

> false is !true (Often 0 in practice).

Which means that any trivial:

    
    
        if(true) { ... }
    

Won't work under Bournegol. Instead you _need_ to compare when doing an if
statement.

So the C-programmer is easily tripped up. False will work as expected, but
True won't... All the time. There may be times it does work. Leaving the
programmer throwing their hands in the air.

~~~
kaetemi
Most cases I've seen accept anything as true as long as the LSB is 1, and
quite strictly 0 as false. Everything else is up in the air. The value -1 is
definitely true. :)

~~~
ufo
One could say that -1 is the truest true, because its two's-complement
representation has the most 1s.

~~~
kaetemi
True.

------
bregma
This just in: you can write bad code in any language.

In this case, that language is the C Preprocessor, already well documented and
widely accepted as a Bad Language To Start With.

The title of the article is misleading: it's not "destroying C" it's writing
Bad Code using the C Preprocessor. It's not destroying C any more than a
series of bad puns destroys the English language.

~~~
mumblemumble
> you can write bad code in any language.

But it's more fun to do in C.

~~~
tatersolid
It’s _much easier_ to do it in C, even when you think you’re not writing bad
code.

------
dilippkumar
I've been a C programmer for several years now (out of necessity - C is still
the best language for firmware and embedded development)

There's been a lot of noise on the internet about Rust and C++20 and "modern"
languages. I recently had an opportunity to try some of these first hand.

Honestly the new language features are all terrible with few exceptions. In
general, anything that was added to a language to support generics or to hide
pointers and memory management from the developers on a language that isn't a
lisp has only produced more harm than good.

I grew up as a programmer always thinking about the underlying CPU and the
underlying memory and how my code would interact with both of them. It
requires greater care as a programmer and tools like static analysers, code
reviews and rigorous testing are extremely important. Improving these tools
and coming up with new ones is far more useful (in my opinion) than trying to
update C.

Yes C has it's flaws. Sometimes, those flaws are important and a replacement
language is useful. I find that Go is a very interesting replacement for C at
sufficiently higher up the stack. Something like Haskell probably will best
fill in the gaps between C and Go.

I'm not convinced that fixing C with something like C++ was a good idea.
Similarly, anything that's trying to fix C++'s problems is unlikely to come up
with a decent way to have generics and hide memory and pointers from
developers.

If I was going to create my own language, I'd keep it like C, remove some of
the undefined behaviors, add support for Posits, 128 bit and arbitrarily wide
signed and unsigned integers, features for explicit cache management (!!!),
container classes as part of a standard library, Go-lang style interfaces and
bake in something like cppcheck into the compiler.

/rant

~~~
lhweroiuwer
> remove some of the undefined behaviors,

There was a great talk from a C/C++ compiler writer about why you can't remove
undefined behaviors while at the same time keeping C like speed.

Simple example: accessing an array past it's end it's undefined behaviour. If
you want to remove this, you need to add bounds checking (either to raise an
error or to just return 0).

~~~
audunw
> There was a great talk from a C/C++ compiler writer about why you can't
> remove undefined behaviors while at the same time keeping C like speed.

I don't think this is true. Look at Zig, they don't seem to have a problem
removing a lot of C's undefined behaviour, while still being able to surpass C
in speed in many cases.

> Simple example: accessing an array past it's end it's undefined behaviour.
> If you want to remove this, you need to add bounds checking

Zig does indeed do bounds checking. I think the way it can still compete with
C is: \- Zig should be better at propagating constants (Better module system,
Link-Time Optimization by default, and I think avoiding undefined behaviour
helps here too). Arrays/slices do often have constant bounds. \- You can
choose to build with or without bounds checks (--release-safe, --release-
fast). This means you're more likely to discover out of bounds problems during
debugging, since you'll always get errors in those cases. But you have the
option to release a fast version.

Julia has another interesting solution to bounds checking, where you can mark
a piece of code with @inbounds to declare that you assume array access is
within bounds.

I think some undefined behaviour can also be detrimental to performance. If
you pass two pointers to a function, and it's undefined whether they alias or
not, there are optimisations you can't do.

~~~
slrz
> I think some undefined behaviour can also be detrimental to performance. If
> you pass two pointers to a function, and it's undefined whether they alias
> or not, there are optimisations you can't do.

I think this results from a misunderstanding of how undefined behaviour works
in C. When a program exhibits undefined behaviour it is not a valid C program.
The compiler may just assume (instead of having to prove) that it doesn't
happen.

Example: the memcpy(3) standard library function. C says the behaviour is
undefined if the given areas overlap. That means the implementation can
perform optimizations "knowing" that there is no overlap. A valid C program
can't possible invoke memcpy with buffers aliasing each other (because then,
the program would be invalid). The compiler is not required to issue a
diagnostic about these kinds of incorrect programs and just compiles your code
assuming they don't exist.

------
taneq
Didn't one version of the "Ten Commandments of C" have a rule stating "Thou
shalt not use the preprocessor to turn C into a different language"? :P

------
kccqzy
GNU lambdas are incredibly evil. In order for lambdas to capture their
environment and yet still be available as a function pointer, the compiler
makes the stack executable and stores the trampoline code on the stack.

C++ lambdas don't suffer from this problem.

~~~
shakna
GNU doesn't really have lambdas. This is an abuse of the compound statement
macro.

IIRC compound statements, like I've presented, don't use trampolines. Nested
functions definitely do, but that isn't quite what we're doing here.

GCC _should_ compile using descriptors for the compound statements that lambda
is expanding to instead of using trampolines.

    
    
        gcc -fno-trampolines -I. examples/lambda.c
    

Works. There's no trampoline present.

~~~
PatrickMDev

      #define lambda(ret_type, _body) ({ ret_type _ _body _; })
    

I found this one really fascinating, they're using _both_ statement expression
_and_ nested functions. Their (reformatted) example of:

    
    
      int (*max)(int, int) =
        lambda(int, (int x, int y) { return x > y ? x : y; });
    

macro-expands to

    
    
      int (*max)(int, int) =
        ({ int _(int x, int y) { return x > y ? x : y;}; });
    

So they have a statement-expression with just one statement in it - the value
of that statement is the value of the whole statement-expression. And the one
statement inside the statement expression is a declaration of a nested
function named _. Statement-expressions decay their return types, so that
function gets converted to a function pointer. And thus your "lambda" is a
pointer to a GCC nested function.

~~~
nwellnhof
It's slightly different.

    
    
        { ret_type _ _body _; }
    

Notice the second underscore at the end? The compound statement contains a
nested function definition followed by an expression statement which "returns"
the function just defined. The function definition alone wouldn't work.

------
garaetjjte

        // Original macro hack by Robert Elder (c) 2016
    

Described also back in 2000 on
[https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html](https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html)

~~~
juped
And as Simon Tatham points out there, Tom Duff invented it in the 80s, but
only hinted at it rather than publish it.

------
wruza
My friend served as a military officer and told me a story. Every evening
before lights out, he had to count soldiers via a roll call, a 10-minute
routine. If someone coughed during that (no matter what intent), then suddenly
many soldiers began to cough, and that ruined everything. The only solution he
figured out to remain legal/moral was to pause and start it all over if
someone coughs.

~~~
MaxBarraclough
Wouldn't it make sense to roll-call different sub-groups independently?

------
agentultra
Those defines remind me of how Bourne shell was originally written using such
a system to make the source look more like Algol 68. It might even still be
maintained that way in BSD to this day [0].

[0]
[https://books.google.ca/books?id=9f9uAQAAQBAJ&pg=PA9&lpg=PA9...](https://books.google.ca/books?id=9f9uAQAAQBAJ&pg=PA9&lpg=PA9&dq=expert+c+programming+bourne+shell&source=bl&ots=UJafm8jP8B&sig=ACfU3U1Nb-
leW-01XLRyLB6xsKOBrJf3hw&hl=en&sa=X&ved=2ahUKEwi_6dDe0KvnAhVuRN8KHQDTBgUQ6AEwAHoECAoQAQ#v=onepage&q=expert%20c%20programming%20bourne%20shell&f=false)

~~~
JdeBP
"to this day" in 1994 is very much _not_ "to this day" in 2020.

The various BSDs use the Almquist and Korn shells for /bin/sh nowadays.
Indeed, M. Van der Linden was out of date even back in 1994. At that time, BSD
had already been largely freed of AT&T code, such as the Bourne shell, for 3
years. It's now approaching 3 decades.

~~~
agentultra
I've forgotten how old that book is. Geez time flies. Thanks for the
clarification!

------
ffwff
With enough macros you can even bring high-level programming to C a la
libcello: [http://libcello.org/](http://libcello.org/)

------
nothrabannosir
Big fan. I don’t see support for exceptions, maybe this can help:
[https://github.com/hraban/c-exceptions](https://github.com/hraban/c-exceptions)

(I ended up using this in a job once. They didn’t hire me back...)

~~~
shakna
That's horrifying. I've opened a ticket to implement it. [0]

[0]
[https://todo.sr.ht/~shakna/CNoEvil3/9](https://todo.sr.ht/~shakna/CNoEvil3/9)

------
gricardo99
Reminds me of Arthur Whitney's k.h[1]

Example:

    
    
      // remove more clutter
      #define O printf
      #define R return
      #define Z static
      #define P(x,y) {if(x)R(y);}
      #define U(x) P(!(x),0)
      #define SW switch
      #define CS(n,x) case n:x;break;
      #define CD default
    
    

1 -
[https://github.com/KxSystems/kdb/blob/master/c/c/k.h](https://github.com/KxSystems/kdb/blob/master/c/c/k.h)

~~~
spopejoy
Or the J incunabulum[1] ...

    
    
      typedef char C;typedef long I;
      typedef struct a{I t,r,d[3],p[2];}*A;
      #define P printf
      #define R return
      #define V1(f) A f(w)A w;
      #define V2(f) A f(a,w)A a,w;
      #define DO(n,x) {I i=0,_n=(n);for(;i<_n;++i){x;}}
      I *ma(n){R(I*)malloc(n*4);}mv(d,s,n)I *d,*s;{DO(n,d[i]=s[i]);}
      tr(r,d)I *d;{I z=1;DO(r,z=z*d[i]);R z;}
      A ga(t,r,d)I *d;{A z=(A)ma(5+tr(r,d));z->t=t,z->r=r,mv(z->d,d,r);
       R z;}
      V1(iota){I n=*w->p;A z=ga(0,1,&n);DO(n,z->p[i]=i);R z;}
      V2(plus){I r=w->r,*d=w->d,n=tr(r,d);A z=ga(0,r,d);
       DO(n,z->p[i]=a->p[i]+w->p[i]);R z;}
      ...
    

1 -
[https://code.jsoftware.com/wiki/Essays/Incunabulum](https://code.jsoftware.com/wiki/Essays/Incunabulum)

------
artsr
When I started learning C++, there was't a C++ compiler available for my Atari
ST (late 80s), there was only Borland C. So instead I used the C preprocessor
to emulate classes, virtual functions etcetera. Not everything could be
implemented this way, but it looked like C++ close enough for me to learn it.

~~~
shakna
That's basically how C++ was originally created, or the prototype, "C with
Classes", albeit with a custom preprocessor. [0]

> In October of 1979 I had a pre− processor, called Cpre, that added Simula−
> like classes to C run-ning and in March of 1980 this pre− processor had been
> refined to the point where it supported onereal project and several
> experiments.

Recognising that you could do it that way is kinda awesome. Maybe not entirely
uncommon, but you drafted a powerful bit of software yourself.

[0] [http://www.stroustrup.com/hopl2.pdf](http://www.stroustrup.com/hopl2.pdf)

~~~
gpderetta
Thanks, awesome paper (which remind me that I should read the ARM at some
point).

Interesting stuff:

"Cfront [the fist c++ compiler] was (and is) a traditional compiler front− end
performing a complete check of the syntax and semantics of the language"

"[...] the C compiler is used as a code generator only. [...]. I stress this
because there has been a long history of confusion about what Cfront was/is.
It has been called a preprocessor because it generates C, and for people in
the C community (and elsewhere) that has been taken as proof that Cfront was a
rather simple program – something like a macro preprocessor."

Cfront being a preprocessor is something that gets repeated often. As you
correctly point out, cpre was the preprocessor and it wasn't realy C++ yet.

~~~
userbinator
I believe the "modern" term for Cfront would be "transpiler".

~~~
msla
I think it could be called a "compiler" if C-- is anything to go by:

[https://en.wikipedia.org/wiki/C--](https://en.wikipedia.org/wiki/C--)

> C-- (pronounced cee minus minus) is a C-like programming language. Its
> creators, functional programming researchers Simon Peyton Jones and Norman
> Ramsey, designed it to be generated mainly by compilers for very high-level
> languages rather than written by human programmers. Unlike many other
> intermediate languages, its representation is plain ASCII text, not bytecode
> or another binary format.[1][2]

> There are two main branches of C--. One is the original C-- branch, with the
> final version 2.0 released in May 2005.[3] The other is the Cmm fork
> actively used by the Glasgow Haskell Compiler as its intermediate
> representation.[4]

~~~
gpderetta
Yes. In addition to C--, there is a long tradition of FP (and not only)
languages targeting C as a (portable) back end representation. That's how many
scheme compilers worked for example. LLVM itself at some point had a C
backend.

------
bjornjajajaja
C has its place; every language has its quirks. I think a lot of emphasis
needs to be put on testing code. Like SQLite uses Tcl to script tests. C is
easy to test because the “software units” are only structures and functions.

~~~
shakna
> C has its place; every language has its quirks. I think a lot of emphasis
> needs to be put on testing code.

The library that this post is a subset of, actually does have a testsuite. It
compiles every example, and it's own documentation. The testsuite still needs
to grow, and check evaluations rather than whether something "works", but it's
getting there.

------
ggerules
Well there is the International Obfuscated C Code Contest....

Details here.... [https://www.ioccc.org/](https://www.ioccc.org/)

~~~
kragen
The IOCCC was inspired by Steve Bourne using precisely this kind of tricks in
the C source for the Bourne Shell to make C look like Algol-60.

------
vparikh
A really good example of this is the implementation of Objective-C. I believe
even the original C++ implementation by Bjarne Stroustrup were also C macros.

------
dnpp123
I guess this article is to be taken lightly as kind of a "joke" ?

The first proposed macro displayln is the archetype of malpractice. What if I
want to do:

if(some_condition) displayln("enjoy debugging that");

etc etc... almost all proposed changes seem to be awful ?

~~~
shakna
I think I was pretty explicit:

> What if, for a moment, we forgot all the rules we know. That we ignore every
> good idea, and accept all the terrible ones.

~~~
dnpp123
Ow alrighty then. Looking at comments here I'm not sure some other people saw
that sentence haha

------
ubertakter
The end result reminds me of Julia. Enough to make me wonder if there's
something like it underneath Julia somewhere, or if that's how it started
(unfortunately I don't have time to really dig in to it right now).

------
Koshkin
In some subtle way C was already "destroyed" with the introduction of C89. The
K&R C had simplicity and beauty to it. Include files were small as they only
contained the essential stuff (mostly definitions of structs). UNIX version 7
- the last "true" edition of UNIX - including its entire userspace - was still
written in it. X Window system was written in it. Vi was written in it. Now
all people do is complain.

~~~
rramadass
>Now all people do is complain

Amen, brother! For a language that has been so supremely successful in the
"real world", i simply don't understand the HN crowd's disdain for it.

As an aside, "printf format strings" are actually a DSL and hence the
complexity. In their book, _The Practice of Programming_ Kernighan & Pike
actually show how to implement something similar for Network Packet
encoding/decoding. It is quite neat and validates the power of this approach.

------
MaxBarraclough
I'm reminded of a brilliant talk, _Can I Has Grammar?_ [0][1], which does
similar violence to C++.

[0] [https://youtu.be/tsG95Y-C14k?t=277](https://youtu.be/tsG95Y-C14k?t=277)

[1]
[https://news.ycombinator.com/item?id=18832953](https://news.ycombinator.com/item?id=18832953)

------
JabavuAdams
I used to be really interested in new languages and compilers. Now ... let's
just get on with making Machine Programmers / CASE tools. I don't want to be
futzing around with individual lines of code anyway. I guess I want to be like
a very technical PM.

------
macintux
I haven't looked closely at either, but the spirit of this effort reminds me
of Cello, recently discussed here:
[https://news.ycombinator.com/item?id=22102533](https://news.ycombinator.com/item?id=22102533)

------
chrisbrandow
Reminds me a lot of how the first version of objective-C was implemented

------
thrower123
I can't help but be reminded of some of the horrifying basic.h and pascal.h
macro-filled headers I can remember from long, long ago. Except people
actually used those...

------
awalias
the best thing about this was discovering
[https://git.sr.ht/](https://git.sr.ht/)

~~~
trashcan
I love the simple and lightweight interface, but everything is hard to
discover.

Sourcehut? Sounds great- I'd love to replace my Gogs/gitlab instance with
something more lightweight. Let's download the source and run it. I guess
click on "git" on [https://git.sr.ht/](https://git.sr.ht/)? Wait that's where
I already am with no indication that that is the selected tab. Ok maybe the
link for sourcehut? [https://sourcehut.org/](https://sourcehut.org/) Cool.
There's some links about pricing, ignore that and click on "100% free and open
source software" Now I am just at a list of what appears to be about 20 repos,
all with helpful names like sr.ht-apkbuilds.

I can tell that this person has put a ton of work into making something that
is probably fantastic, but it is really all presented in an undiscoverable
way. I still have no idea what language this project is written in, how to
deploy or maintain it.

I assume
[https://git.sr.ht/~sircmpwn/git.sr.ht](https://git.sr.ht/~sircmpwn/git.sr.ht)
might be what I want, but it still looks like the inscrutable mess that
reminds me of hgweb. There's not even a readme on the first page, let alone
the source or anything useful. There's a link to
[https://man.sr.ht/git.sr.ht/installation.md](https://man.sr.ht/git.sr.ht/installation.md).
Which looks like it might be what I want, but I guess I'm old and at this
point I've lost interest.

I'm definitely being crotchety, but I wish this information was organized in a
more useful way. I can tell Drew has put a lot of time into it, but I don't
feel like it is being shared in an effective way. And I would definitely never
pay for software like this. Please someone tell me I'm crazy and this UI makes
perfect sense to them.

~~~
shakna
It sounds like you're going to the main page, expecting to download it and
deploy it yourself.

It isn't surprising that the main interface is pointing you to _use_ it,
rather than deploy it.

If you click on the help hub, _man_, you'll find what you're looking for
straight away:

> Hacking on or deploying sourcehut yourself? Resources here.

\---

> I guess click on "git" on [https://git.sr.ht/](https://git.sr.ht/)? Wait
> that's where I already am with no indication that that is the selected tab.

That's incorrect. If you look to the left of the nav bar, you'll see some text
with red highlighting exactly where you are.

> I still have no idea what language this project is written in, how to deploy
> or maintain it.

If you click the dev resources link, then you'll find this nice and obvious
quote:

> sr.ht core is a Python package that provides shared functionality across all
> sr.ht services. It also contains the default templates and stylesheets that
> give sr.ht a consistent look and feel.

~~~
irishsultan
> If you look to the left of the nav bar, you'll see some text with red
> highlighting exactly where you are.

That looks part of the "logo", not part of the navbar. There is at most a
minimal difference in the actual "tabs". Of course the reason is that this
isn't actually a navbar/tabs but a list of applications offered by sourcehut,
this is really noticeable if you click on git when you're at an actual git
repository (e.g.
[https://git.sr.ht/~sircmpwn/scdoc](https://git.sr.ht/~sircmpwn/scdoc)) note
that the red text highlighting where I am already says git, so clicking on git
shouldn't do anything if it was actually a tab bar, but in reality it
navigates to [https://git.sr.ht/](https://git.sr.ht/)

------
ipsum2
This is amazing. Are there any larger examples that uses more of the
functionality of the library that I could read?

~~~
shakna
Whilst it uses an older version of CNoEvil, and I haven't updated it yet, the
largest application is probably evilshell [0].

There's also all the examples [1].

[0] [https://git.sr.ht/~shakna/evilshell](https://git.sr.ht/~shakna/evilshell)

[1]
[https://git.sr.ht/~shakna/cnoevil3/tree/master/examples](https://git.sr.ht/~shakna/cnoevil3/tree/master/examples)

~~~
mst
I've already said this a couple other places but I really wanted to say: "This
is amazing and it would be hilarious if you wrote a CNoEvil vs. BOURNEGOL head
to head article".

<3

------
kazinator
> _Windows has a different line ending_

But not when printing to stdio.h text streams; \n turns into the right line
ending.

~~~
shakna
> But not when printing to stdio.h text streams; \n turns into the right line
> ending.

Only on some, but not all, compilers. I got varying behaviour until I went
ahead and did it myself.

------
exabrial
I really don't think the result is better than the beginning.

------
0xdead
Perfect usage of the title to get on HN frontpage.

~~~
non-entity
I really expected this to be another aggressive Rust marketing peice at first

~~~
shakna
Well, thanks to Rust's more powerful macro system, I'm sure someone could
butcher it even more than the C-PreProcessor lets me do to C, here.

------
pininja
Great writing and clever hacking!

------
davidhyde
This reminds me of Arduino.

evil.h == arduino.h

~~~
shakna
I've used CNoEvil in a couple Arduino projects. It makes about as much sense
when something goes wrong as with the Arduino library.

------
progx
Return of Turbo Pascal ;-)

------
tus88
This hurt my brain.

~~~
newnewpdro
It hurt my eyes.

------
_pmf_
You invented Ada.

~~~
shakna
Unsurprisingly, I adore Ada.

However, Ada's main benefits - the incredible type system, don't exist at all
here. CNoEvil is a giant shotgun pointed directly between your legs.

~~~
_pmf_
Ada is definitely optimized for readability (not you,
Ada.Strings.Unbounded.To_Unbounded_String!)

------
archivist1
That's a work of art!

------
32gbsd
I hope we get some new stuff out of this. The world is lacking in new stuff
because everyone is in the business of creating slavery apis.

------
Pete_D
Off topic, but in today's bitter and divided world, it's reassuringly
wholesome that people can still get together to recite the alphabet in GitHub
comments.

