Show HN: A work-in-progress C compiler from scratch
github.com
github.com
I used Flex and Bison since the project was in C. Getting up and running and understanding how the tools have to be set up with different options was a bit tricky, but after that, my parser was up and running in about two hours, compared to probably four times that for the hand written DDL. Our DML subset was also much larger and more complex than our DDL, so I was very happy with the development speed increase.
I had this idea that using a parser generator was slow and wasteful since many modern tutorials online write them by hand and speak against parser generators (possibly because there isn't a catch all for all languages). Turns out dev speed is way more important to me up front, because in the case that I notice parsing speed actually being an issue I should be happy that my MVP has gotten enough use.
It's also nice because a lexer and parser can be pretty easily black-boxed and swapped out for your hand written, just keep the AST and API the same and you should be good.
All that said, that's personal preferences and writing the parser by hand is definitely good experience and more extensible, especially for error handling. Nice work!
Having written many parsers, I can attest that the time spent writing and debugging the parser is about 0.0000001% of the time you'll invest in the compiler.
Heck, I spent more time trying to figure out how to integrate the tag name space from ImportC into the host D compiler's symbol table mechanism, than I did writing the C parser.
(When I took compilers, the initial weedout/attention-getting assignment was artificial: write a string/symbol table manager module that could handle strings of arbitrary size, up to megabytes or larger for each entry. Though I don't know how well this first assignment worked for setting expectations, or if it could've been improved. After that school term, I heard from someone else in the class that he and I had been the only two people in the class to actually "finish" our compilers. Not that we were smarter or harder-working, but we both had a lot of prior programming experience, so a big head start. Setting expectations for a project-heavy systems CS class seems hard to do, without discouraging people who could do it if they invest the time, and I suppose just noticing a couple nerds who had a big head start could be discouraging to everyone else. One of the secret keys to learning/accomplishing something hard is to feel you're doing well enough to stick with it long enough to do well.)
Tag symbols are relatively rare, so I was willing to trade lookup times for reduced memory consumption. Wound up adding a pointer to a hash table in each symbol table, where the tag symbols would be stored. Only when tag symbols are actually used is it ever accessed or even initialized. It works a treat.
> a compiler somewhat resembling C
> Add new or borrow from other language(s) features ontop of C, deviate away from just C
function strlen(const char *s)
This does not appear to be a C compiler. The title should reflect this.I'm not sure whether I'll incorporate everything from C or just make it compatible with C and behind the scenes do things differently e.g instead of carrying const char */string pointers everywhere always carry the length of said data structure along with it. Or for instance push an additional pointer on the stack for memory zones, where an allocator is always present and knows it's context, sort of like __thiscall with class methods for this->.
But then if we look beyond the syntax almost any C implementation is “its own language” in that it defines behaviour the standard doesn’t specify, and frequently also changes behaviour the standard does, if in minor ways. Like how the Windows ABI forces a noncompliant wchar_t.
(Even if we look at the preprocessor, the classic algorithm implemented in most places [that I can’t be bothered to find a link for] in fact expands more than the standard strictly guarantees, e.g. you can sometimes get recursive expansion out of it by creative use of token pasting,—I remember reading the ANSI committee thought the case was too “perverse” and didn’t specify anything [no link here as well, sorry... poke me again if you actually care about this]. The standalone preprocessor mcpp has warnings about this specification hole, but other implementations don’t as far as I know. And you’d think the preprocessor, an almost purely syntactic thing, wouldn’t have implementation differences worth keeping around.)
The compiler is kencc which is why it is different. Plan 9 also still uses a.out for binary images.
Below taken from https://en.wikipedia.org/wiki/Alef_(programming_language):
Alef appeared in the first and second editions of Plan 9, but was abandoned during development of the third edition.[1][2] Rob Pike later explained Alef's demise by pointing to its lack of automatic memory management, despite Pike's and other people's urging Winterbottom to add garbage collection to the language;[3] also, in a February 2000 slideshow, Pike noted: "…although Alef was a fruitful language, it proved too difficult to maintain a variant language across multiple architectures, so we took what we learned from it and built the thread library for C."[4]
https://github.com/riicchhaarrd/ocean/commit/0618e0810c8d437...
Then again in C it would work aswell if you type casted the types.
const char *f()
{
return (const char*)123;
}
int main()
{
int i = (int)f();
return 0;
}Contributes absolutely nothing to the conversation, here.
1. Some code seems to be duplicated (although with slightly different formatting) across multiple files, like dd/dw/db in elf.c and x86.c for example. I'd suggest consolidating those.
2. It helps to define types when you have variables that can take a limited set of possible values; typedef works but enums are better. I'm thinking of variables like the `type` parameter to `primitive_data_type_size`, for example. The compiler can help you detect a missing case statement if you use an enum, but not an int.
3. It's a matter of preference, but typedef'ing your structs can make your code more concise and not have it littered with struct keywords.
4. You seem to have a mix of tabs and spaces in some files (e.g. in `elf.c`). I would recommend configuring your editor to use just one of the two or it'll start to be an issue as your code base grows.
5. On a related point, I'd suggest picking a formatting style and sticking to it. You have functions like `accept` in `ast.c` declared without spaces after the opening `(` and others like `read_file` in the same file that has spaces.
Good luck! This is a great way to learn.
I don't mean this in a bad way, but that tokenizer looks a bit ... simple. ;-)
I say that because ~26 years ago I had a go at writing a C preprocessor with the goal of creating a ToC and Index for my C projects. I sat down with a BNF of C and lex/yacc and got ground up in the details, then found some software that did exactly what I wanted and moved on. I just remember a few parts of the C syntax that don't fit nicely into BNF and hadn't studied parsers enough to dig myself out of the hole.
Good luck doing it manually, that's gonna be tricky as heck.
https://eli.thegreenplace.net/2007/11/24/the-context-sensiti...
I decided to write my own lexer/parser/compiler it's pretty straightforward.
Sources I use(d) to program this, e.g look at resulting assembly from other compilers or look at how the AST is generated for other languages.
https://en.wikipedia.org/wiki/Recursive_descent_parser
https://en.cppreference.com/w/c/language/operator_precedence
I didn't start writing C yesterday, and most of the things are just stuff I've learned over the years and seeing how other languages work and then just use what I know to try to program in a logical manner.
Also I've written a implementation of a scripting language (compiler and VM) before in similar fashion.
At some point ANTLR [3] looked promising, but these days I'd probably write a lexer and recursive descent parser by hand, then generate LLVM IR [4].
[1] https://github.com/westes/flex
[2] https://www.gnu.org/software/bison/
* for a subset of the full C89 spec
Aspiration to create a language that I would love to use some day.
Have your compiler generate high quality assembly language for a DSP.
Motivation is key though.