14 ms·
C4: C in Four Functions (2014)
- jessermeyer 7y agoFaster than CPython? ;)
- jancsika 7y agoWhy all the printf copy/pasta around the opcode enum? Can't the author use pre-processor string concatenation to write once and then leverage that to map enum to string and back? At least I'm doing that in a little scripting language parser and it looks portable. Edit: I'm actually using pre-processor string concatenation for something unrelated. You don't even need that to do the mapping.
- dmytrish 7y agoc4 is meant to be self-hosted and it does not implement a preprocessor. It was easier to re-use printf creatively.
- BadMrFrosty 7y agoSurprisingly succinct.
- sedatk 7y agoI guess switch/case support is in order.
- abecedarius 7y agoAn earlier version had this, and basic structs too. It was only a little longer, at least in line count, but harder to figure out. It took me four evenings with a printout and a red pen before I was satisfied with my understanding. (I have mixed feelings about this code: it has both good ideas and pointless obscurity. I guess the newer version would've taken me 'only' three evenings.)
- ConradKilroy 7y agoThanks for commenting the code. WTF sarcasm
- michaelfeathers 7y agoIt looks like a great base for a refactoring exercise.
- abecedarius 7y agoI considered doing that once. Trouble is, how do you test it? It comes with one tiny example in the C subset, plus one substantial one (itself). Its behavior is generally undefined when the input departs from the subset. It seemed like more work to address these issues than the fun you could have in messing with the code. So I sketched my refactorings without bothering to check them or to publish it.
- michaelfeathers 7y agoThis is a way you can approach it. https://michaelfeathers.silvrback.com/characterization-testing https://michaelfeathers.silvrback.com/characterization-testi...
- schoen 7y ago(2014)?
- keanzu 7y agohttps://news.ycombinator.com/item?id=8558822 https://news.ycombinator.com/item?id=8558822
- tptacek 7y agoI unironically love this code. We used it as a starting point (or rather, the same design; we wrote in Go) for reversing challenges at our last startup, symbolically evaluating the stack machine bytecode to generate AVR. The trick to reading it: * next() is the lexer * expr() is a precedence-climbing expression parser * stmt() parses statements and generates code * main() has the virtual machine loop.
- mzs 7y agoI mean I guess, it's only a small subset of C though (no floating point or structs even): $ cc float.c && ./a.out && ./c4 -s float.c && ./c4 float.c && echo 'struct { int a; } s;' >struct.c && cc -c struct.c && ./c4 -s struct.c hello, world 1.250000 1: #include <stdio.h> 2: 3: int main() 4: { 5: printf("hello, world %f\n", 1.25); ENT 0 IMM 83656704 PSH IMM 1 PSH IMM 25 PSH PRTF ADJ 3 6: return 0; IMM 0 LEV 7: } LEV hello, world 0.000000 exit(0) cycle = 13 1: bad global declaration $
- dkersten 7y agoSo not really C in 4 functions then. Just some mini language that’s a subset of C, in four functions. That’s a much simpler target to reach, I guess.
- sansnomme 7y agoOoh explain more about your challenges?
- wolf550e 7y agoStarfighter was shut down but you can Google it.
- lifthrasiir 7y agoC4 really has some good ideas by its own. * It never bothers to produce a machine code. The use of stack-based VM simplifies the single-pass compilation, and it never makes use of unknown library functions so no machine-specific knowledge is required (like dlsym in Bellard's OTCC [1]). * It chose its primitives wisely. It never implements structs and returning with values, for example, but the code is carefully structured that the lack of them doesn't make it harder to read. * And yet it has tons of little tricks. Switching from r-value to l-value (triggered when infix `=` or postfix `++`/`--` are read) is a single opcode fix. Reserved words are initialized from an imaginary source code. The type is represented by a single number 2n+k where n is the number of indirections. [1] https://bellard.org/otcc/ https://bellard.org/otcc/
- skrebbel 7y agoI really like this, but I wonder why somehow "four functions" has to imply "needlessly cryptic variable names". Is that just part of the exercise in minimalism, i.e. art?
- marcofiset 7y agoAs far as I'm concerned, it could be a single function if they really wanted.
- norswap 7y agoAnd no comments :'(
- FpUser 7y agoI do not think this code needs any. It is very readable and understandable as it is
- GuB-42 7y agoI found the code surprisingly readable. Most variables names are what I expected them to mean despite their shortness: pc, sp, bp are registers, a is the accumulator, fd is a file descriptor (of the input file, what else?), tk is for the token, t is temporary, etc... For the less obvious ones, it is usually not that hard to infer their meaning from either the code or comments. Because yes, they are comments, not many, but they are helpful. For example, the VM has unusual instructions (for me) like LEV and ADJ, and they are commented. The "obvious" ones like MUL and SHR are not. The variable names are not "needlessly cryptic". I've seen (and written, not proud of it) a lot of needlessly cryptic variable names, and believe me, these are crystal clear by comparison. Here, there is a clear influence from assembly mnemonics that really helps understanding. Now on the why. This is minimalism, and minimizing the number of comments and variable name length is part of it. It is actually a very interesting exercise. The golden rule in making understandable code is making it as short as possible. There is a limited amount of space on your screen and in your mind, and the shorter your code is, the more you can see/understand at once. Of course, too much is too much, you don't want to do things IOCCC style, and striking a balance is difficult. So once in a while, reading or writing very compact code can help you understand where shaving off characters is fine and where it really hurts understanding.
- jacobvosmaer 7y agoClang on my macOS doesn't like the fact that main() takes and returns long long. But it appears GCC on Debian 8 (random linux I have lying around) doesn't mind. I get it to compile on macOS if I remove the int define but then it segfaults when you run it. I wonder if there is some magic flag to make "main does not return int" a non-fatal error on Clang? It's fun to read this code but running it is even more fun, you can see the VM code it generates, with source line annotations and all.
- coolreader18 7y agoI just undef'ed and re-define-d int before and after the main declaration with clang, and it seems to work fine.
- keanzu 7y agoFor mac users: gcc -Wno-all -arch i386 -o c4 c4.c https://news.ycombinator.com/item?id=8560127 https://news.ycombinator.com/item?id=8560127 You can compile it on 64 bit OS X with clang's -m32 option and it should work. https://news.ycombinator.com/item?id=8559044 https://news.ycombinator.com/item?id=8559044
- saagarjha 7y agoNeither of those work with a recent toolchain :(
- flohofwoe 7y agoThe "#define int long long" is annoying indeed. A quick hack to make it work is: #include <stdint.h> ...and then replace the two "int" in main() with int32_t.
- psychoslave 7y agoDoesn't that make it fail to maintain the self hosting property that is most likely behind the introduction of this define?
- 6nf 7y agoThis is awesome, less than 500 lines for a self-compiling C compiler!
- saagarjha 7y agoHere's a simpler self-compiling C compiler: int main(int argc, char **argv) { } Sadly, it doesn't support that much…
- TickleSteve 7y ago...and virtual-machine.
- dntbnmpls 7y agohello dot c my old friend, i've come to compile you again...
- zelphirkalt 7y agoThe repo says "An exercise in minimalism." – Looking at the code, there is nothing minimalistic about it. Perhaps in terms of C it can be called that, but in terms of programming languages in general, this cannot be called "minimal" at all. The code quality is terrible. Few explaining comments (actually mos of those are only 1 or 2 words, so not explaining anything at all) and almost all variable names consist of 1 or 2 characters, which do not say what the thing actually is. Then to achieve this arbitrary goal of doing it "in 4 functions" (actually procedures) loads of stuff was apparently stuffed into 4 those 4 functions, so much so, that they are longer than a whole page of code. Most of it looks like gigantic switch statements. It's horribly written code. It looks like what I think of as a C nightmare. I will admit, I could not write such a thing myself. I lack the knowledge for writing such a low level stuff, do not use C, and if I had that knowledge to do it, my inner drive to do a cleaner job than anything remotely looking like that code, would prevent me from ever sharing such a thing with anyone in public. At least group cases into procedures as it makes sense. Even people writing this low level type of code should be aware of how unreadable that code is, right?
- jabbequbs 7y agoI've only skimmed the code but it seems to me that the repo is an art piece just as much or more than it is a software project. The medium is code but that doesn't mean that it needs to adhere to professional standards anymore than someone's woodworking project needs to adhere to building codes.
- jfkebwjsbx 7y agoThe code is obviously not intended for production, but it is straightforward nevertheless for anyone with C experience.
- joppy 7y agoGigantic switch statements are pretty much the best way to do a parsing style thing in a straightforward basic manner, and it is after all, “an _exercise_ in minimalism”. There are short variable names but again there are not many variables used in the whole program. This was not code written to be approachable by any other random team member without further context, it was written for a different purpose entirely. Perhaps people would be less reluctant to show their code in public if it were not so often criticised for falling short of some criteria that the author was never intending to meet?
- nighthawk7000 7y agoSeeing this made my day. There's something calming about seeing C used so...hmm....elegantly? Well maybe that's not the right word. But the minimalism of it is soothing.
- hereisdx 7y agoCan someone please explain to me what this code does? I know some C, but I couldn't understand this.
- vfinn 7y agoAFAIK after taking a quick glance, it takes c code as input, then translates it to internal format by doing tokenization and parsing, and then executes the code. So, it's an interpreter. You could call it with other names as well. You can also have the program itself as an input, and have many layers of execution.
- Iv 7y agoconsidering the code is translated into a kind of simple, non-native opcode, I think it is also correct to call it a VM.
- bluetomcat 7y agoA one-pass compiler for a subset of C, relying on a recursive-descent parser, doing the lexing, parsing and code generation in lockstep. The generated code, consisting of abstract machine instructions, is then executed by an instruction fetch and execute loop. BTW, the code looks relatively short because many semicolon-separated statements are crammed on a single line, and short variable names make that somewhat manageable and even visually symmetric. If you were to unfold it with each statement on its own line, I guess it would be at least 3 times the size.
- epicide 7y ago> A one-pass compiler for a subset of C, relying on a recursive-descent parser, doing the lexing, parsing and code generation in lockstep. The generated code, consisting of abstract machine instructions, is then executed by an instruction fetch and execute loop. This should be added to the README.
- hereisdx 7y agoThanks a lot for the explanation!
- shyammm 7y agohttps://coincircle.com/l/MfCCgWqnhZ https://coincircle.com/l/MfCCgWqnhZ
- rini17 7y agoAnything like that for C++? /s
- Iv 7y agoAm I the only one annoyed by the fact that the main loop has a series of if..else instead of a giant switch statement? But yes, many people rely on parsers and VMs without really knowing how it works and assume some black magic, whereas it can be really simple and elegant.
- lifthrasiir 7y agoIt is self-hosted so having switch in the code means yet another code implementing switch.
- asdasdasdasdwd 7y agoWouldn't the compiler optimize it either way?
- Iv 7y agoHmmm, gcc may be smart enough indeed.