Add chibicc

This program popped up on Hacker News recently. It's the only modern
compiler I've ever seen that doesn't have dependencies and is easily
modified. So I added all of the missing GNU extensions I like to use
which means it might be possible soon to build on non-Linux and have
third party not vendor gcc binaries.
This commit is contained in:
Justine Tunney 2020-12-05 12:20:41 -08:00
parent e44a0cf6f8
commit 8da931a7f6
298 changed files with 19493 additions and 11950 deletions

View file

@ -1,5 +1,7 @@
#include "third_party/chibicc/chibicc.h"
#define LOOKINGAT(TOK, OP) lookingat(TOK, OP, strlen(OP))
// Input file
static File *current_file;
@ -30,17 +32,13 @@ static void verror_at(char *filename, char *input, int line_no, char *loc,
// Find a line containing `loc`.
char *line = loc;
while (input < line && line[-1] != '\n') line--;
char *end = loc;
while (*end && *end != '\n') end++;
// Print out the line.
int indent = fprintf(stderr, "%s:%d: ", filename, line_no);
fprintf(stderr, "%.*s\n", (int)(end - line), line);
// Show the error message.
int pos = str_width(line, loc - line) + indent;
fprintf(stderr, "%*s", pos, ""); // print pos spaces.
fprintf(stderr, "^ ");
vfprintf(stderr, fmt, ap);
@ -49,9 +47,9 @@ static void verror_at(char *filename, char *input, int line_no, char *loc,
void error_at(char *loc, char *fmt, ...) {
int line_no = 1;
for (char *p = current_file->contents; p < loc; p++)
for (char *p = current_file->contents; p < loc; p++) {
if (*p == '\n') line_no++;
}
va_list ap;
va_start(ap, fmt);
verror_at(current_file->name, current_file->contents, line_no, loc, fmt, ap);
@ -59,10 +57,14 @@ void error_at(char *loc, char *fmt, ...) {
}
void error_tok(Token *tok, char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
verror_at(tok->file->name, tok->file->contents, tok->line_no, tok->loc, fmt,
ap);
va_list ap, va;
va_start(va, fmt);
for (Token *t = tok; t; t = t->origin) {
va_copy(ap, va);
verror_at(t->file->name, t->file->contents, t->line_no, t->loc, fmt, ap);
va_end(ap);
}
va_end(va);
exit(1);
}
@ -73,19 +75,29 @@ void warn_tok(Token *tok, char *fmt, ...) {
ap);
}
forceinline int compare_strings(const char *a, const char *b, size_t n) {
size_t i = 0;
if (!n-- || a == b) return 0;
while (a[i] == b[i] && b[i] && i < n) ++i;
return (a[i] & 0xff) - (b[i] & 0xff);
}
static int is_space(int c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\f' ||
c == '\v';
}
static bool lookingat(const char *a, const char *b, size_t n) {
return !compare_strings(a, b, n);
}
// Consumes the current token if it matches `op`.
bool equal(Token *tok, char *op) {
return strlen(op) == tok->len && !strncmp(tok->loc, op, tok->len);
bool equal(Token *tok, char *op, size_t n) {
return n == tok->len && !compare_strings(tok->loc, op, tok->len);
}
// Ensure that the current token is `op`.
Token *skip(Token *tok, char *op) {
if (!equal(tok, op)) error_tok(tok, "expected '%s'", op);
return tok->next;
}
bool consume(Token **rest, Token *tok, char *str) {
if (equal(tok, str)) {
bool consume(Token **rest, Token *tok, char *str, size_t n) {
if (n == tok->len && !compare_strings(tok->loc, str, n)) {
*rest = tok->next;
return true;
}
@ -93,6 +105,12 @@ bool consume(Token **rest, Token *tok, char *str) {
return false;
}
// Ensure that the current token is `op`.
Token *skip(Token *tok, char *op) {
if (!EQUAL(tok, op)) error_tok(tok, "expected '%s'", op);
return tok->next;
}
// Create a new token and add it as the next token of `cur`.
static Token *new_token(TokenKind kind, char *start, char *end) {
Token *tok = calloc(1, sizeof(Token));
@ -103,15 +121,10 @@ static Token *new_token(TokenKind kind, char *start, char *end) {
tok->filename = current_file->display_name;
tok->at_bol = at_bol;
tok->has_space = has_space;
at_bol = has_space = false;
return tok;
}
static bool starts_with(char *p, char *q) {
return strncmp(p, q, strlen(q)) == 0;
}
// Read an identifier and returns a pointer pointing to the end
// of an identifier.
//
@ -119,11 +132,12 @@ static bool starts_with(char *p, char *q) {
static char *read_ident(char *p) {
uint32_t c = decode_utf8(&p, p);
if (!is_ident1(c)) return NULL;
for (;;) {
char *q;
c = decode_utf8(&q, p);
if (!is_ident2(c)) return p;
if (!('a' <= c && c <= 'f') && !is_ident2(c)) {
return p;
}
p = q;
}
}
@ -136,7 +150,6 @@ static int from_hex(char c) {
static bool is_keyword(Token *tok) {
static HashMap map;
if (map.capacity == 0) {
static char *kw[] = {
"return", "if", "else",
@ -155,18 +168,17 @@ static bool is_keyword(Token *tok) {
"typeof", "asm", "_Thread_local",
"__thread", "_Atomic", "__attribute__",
};
for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++)
for (int i = 0; i < sizeof(kw) / sizeof(*kw); i++) {
hashmap_put(&map, kw[i], (void *)1);
}
}
return hashmap_get2(&map, tok->loc, tok->len);
}
static int read_escaped_char(char **new_pos, char *p) {
if ('0' <= *p && *p <= '7') {
// Read an octal number.
int c = *p++ - '0';
unsigned c = *p++ - '0';
if ('0' <= *p && *p <= '7') {
c = (c << 3) + (*p++ - '0');
if ('0' <= *p && *p <= '7') c = (c << 3) + (*p++ - '0');
@ -174,20 +186,18 @@ static int read_escaped_char(char **new_pos, char *p) {
*new_pos = p;
return c;
}
if (*p == 'x') {
// Read a hexadecimal number.
p++;
if (!isxdigit(*p)) error_at(p, "invalid hex escape sequence");
int c = 0;
for (; isxdigit(*p); p++) c = (c << 4) + from_hex(*p);
unsigned c = 0;
for (; isxdigit(*p); p++) {
c = (c << 4) + from_hex(*p); /* TODO(jart): overflow here unicode_test */
}
*new_pos = p;
return c;
}
*new_pos = p + 1;
switch (*p) {
case 'a':
return '\a';
@ -225,14 +235,12 @@ static Token *read_string_literal(char *start, char *quote) {
char *end = string_literal_end(quote + 1);
char *buf = calloc(1, end - quote);
int len = 0;
for (char *p = quote + 1; p < end;) {
if (*p == '\\')
buf[len++] = read_escaped_char(&p, p + 1);
else
buf[len++] = *p++;
}
Token *tok = new_token(TK_STR, start, end + 1);
tok->ty = array_of(ty_char, len + 1);
tok->str = buf;
@ -250,13 +258,11 @@ static Token *read_utf16_string_literal(char *start, char *quote) {
char *end = string_literal_end(quote + 1);
uint16_t *buf = calloc(2, end - start - 1);
int len = 0;
for (char *p = quote + 1; p < end;) {
if (*p == '\\') {
buf[len++] = read_escaped_char(&p, p + 1);
continue;
}
uint32_t c = decode_utf8(&p, p);
if (c < 0x10000) {
// Encode a code point in 2 bytes.
@ -268,7 +274,6 @@ static Token *read_utf16_string_literal(char *start, char *quote) {
buf[len++] = 0xdc00 + (c & 0x3ff);
}
}
Token *tok = new_token(TK_STR, start, end + 1);
tok->ty = array_of(ty_ushort, len + 1);
tok->str = (char *)buf;
@ -283,14 +288,12 @@ static Token *read_utf32_string_literal(char *start, char *quote, Type *ty) {
char *end = string_literal_end(quote + 1);
uint32_t *buf = calloc(4, end - quote);
int len = 0;
for (char *p = quote + 1; p < end;) {
if (*p == '\\')
buf[len++] = read_escaped_char(&p, p + 1);
else
buf[len++] = decode_utf8(&p, p);
}
Token *tok = new_token(TK_STR, start, end + 1);
tok->ty = array_of(ty, len + 1);
tok->str = (char *)buf;
@ -300,16 +303,13 @@ static Token *read_utf32_string_literal(char *start, char *quote, Type *ty) {
static Token *read_char_literal(char *start, char *quote, Type *ty) {
char *p = quote + 1;
if (*p == '\0') error_at(start, "unclosed char literal");
int c;
if (*p == '\\')
c = read_escaped_char(&p, p + 1);
else
c = decode_utf8(&p, p);
char *end = strchr(p, '\'');
if (!end) error_at(p, "unclosed char literal");
Token *tok = new_token(TK_NUM, start, end + 1);
tok->val = c;
tok->ty = ty;
@ -318,7 +318,6 @@ static Token *read_char_literal(char *start, char *quote, Type *ty) {
static bool convert_pp_int(Token *tok) {
char *p = tok->loc;
// Read a binary, octal, decimal or hexadecimal number.
int base = 10;
if (!strncasecmp(p, "0x", 2) && isxdigit(p[2])) {
@ -330,22 +329,19 @@ static bool convert_pp_int(Token *tok) {
} else if (*p == '0') {
base = 8;
}
int64_t val = strtoul(p, &p, base);
// Read U, L or LL suffixes.
bool l = false;
bool u = false;
if (starts_with(p, "LLU") || starts_with(p, "LLu") || starts_with(p, "llU") ||
starts_with(p, "llu") || starts_with(p, "ULL") || starts_with(p, "Ull") ||
starts_with(p, "uLL") || starts_with(p, "ull")) {
if (LOOKINGAT(p, "LLU") || LOOKINGAT(p, "LLu") || LOOKINGAT(p, "llU") ||
LOOKINGAT(p, "llu") || LOOKINGAT(p, "ULL") || LOOKINGAT(p, "Ull") ||
LOOKINGAT(p, "uLL") || LOOKINGAT(p, "ull")) {
p += 3;
l = u = true;
} else if (!strncasecmp(p, "lu", 2) || !strncasecmp(p, "ul", 2)) {
p += 2;
l = u = true;
} else if (starts_with(p, "LL") || starts_with(p, "ll")) {
} else if (LOOKINGAT(p, "LL") || LOOKINGAT(p, "ll")) {
p += 2;
l = true;
} else if (*p == 'L' || *p == 'l') {
@ -355,9 +351,7 @@ static bool convert_pp_int(Token *tok) {
p++;
u = true;
}
if (p != tok->loc + tok->len) return false;
// Infer a type.
Type *ty;
if (base == 10) {
@ -385,7 +379,6 @@ static bool convert_pp_int(Token *tok) {
else
ty = ty_int;
}
tok->kind = TK_NUM;
tok->val = val;
tok->ty = ty;
@ -402,11 +395,9 @@ static bool convert_pp_int(Token *tok) {
static void convert_pp_number(Token *tok) {
// Try to parse as an integer constant.
if (convert_pp_int(tok)) return;
// If it's not an integer, it must be a floating point constant.
char *end;
long double val = strtold(tok->loc, &end);
Type *ty;
if (*end == 'f' || *end == 'F') {
ty = ty_float;
@ -417,9 +408,7 @@ static void convert_pp_number(Token *tok) {
} else {
ty = ty_double;
}
if (tok->loc + tok->len != end) error_tok(tok, "invalid numeric constant");
tok->kind = TK_NUM;
tok->fval = val;
tok->ty = ty;
@ -438,7 +427,6 @@ void convert_pp_tokens(Token *tok) {
static void add_line_numbers(Token *tok) {
char *p = current_file->contents;
int n = 1;
do {
if (p == tok->loc) {
tok->line_no = n;
@ -461,32 +449,27 @@ Token *tokenize_string_literal(Token *tok, Type *basety) {
// Tokenize a given string and returns new tokens.
Token *tokenize(File *file) {
current_file = file;
char *p = file->contents;
Token head = {};
Token *cur = &head;
at_bol = true;
has_space = false;
while (*p) {
// Skip line comments.
if (starts_with(p, "//")) {
if (LOOKINGAT(p, "//")) {
p += 2;
while (*p != '\n') p++;
has_space = true;
continue;
}
// Skip block comments.
if (starts_with(p, "/*")) {
if (LOOKINGAT(p, "/*")) {
char *q = strstr(p + 2, "*/");
if (!q) error_at(p, "unclosed block comment");
p = q + 2;
has_space = true;
continue;
}
// Skip newline.
if (*p == '\n') {
p++;
@ -494,14 +477,12 @@ Token *tokenize(File *file) {
has_space = false;
continue;
}
// Skip whitespace characters.
if (isspace(*p)) {
if (is_space(*p)) {
p++;
has_space = true;
continue;
}
// Numeric literal
if (isdigit(*p) || (*p == '.' && isdigit(p[1]))) {
char *q = p++;
@ -516,42 +497,36 @@ Token *tokenize(File *file) {
cur = cur->next = new_token(TK_PP_NUM, q, p);
continue;
}
// String literal
if (*p == '"') {
cur = cur->next = read_string_literal(p, p);
p += cur->len;
continue;
}
// UTF-8 string literal
if (starts_with(p, "u8\"")) {
if (LOOKINGAT(p, "u8\"")) {
cur = cur->next = read_string_literal(p, p + 2);
p += cur->len;
continue;
}
// UTF-16 string literal
if (starts_with(p, "u\"")) {
if (LOOKINGAT(p, "u\"")) {
cur = cur->next = read_utf16_string_literal(p, p + 1);
p += cur->len;
continue;
}
// Wide string literal
if (starts_with(p, "L\"")) {
if (LOOKINGAT(p, "L\"")) {
cur = cur->next = read_utf32_string_literal(p, p + 1, ty_int);
p += cur->len;
continue;
}
// UTF-32 string literal
if (starts_with(p, "U\"")) {
if (LOOKINGAT(p, "U\"")) {
cur = cur->next = read_utf32_string_literal(p, p + 1, ty_uint);
p += cur->len;
continue;
}
// Character literal
if (*p == '\'') {
cur = cur->next = read_char_literal(p, p, ty_int);
@ -559,29 +534,25 @@ Token *tokenize(File *file) {
p += cur->len;
continue;
}
// UTF-16 character literal
if (starts_with(p, "u'")) {
if (LOOKINGAT(p, "u'")) {
cur = cur->next = read_char_literal(p, p + 1, ty_ushort);
cur->val &= 0xffff;
p += cur->len;
continue;
}
// Wide character literal
if (starts_with(p, "L'")) {
if (LOOKINGAT(p, "L'")) {
cur = cur->next = read_char_literal(p, p + 1, ty_int);
p += cur->len;
continue;
}
// UTF-32 character literal
if (starts_with(p, "U'")) {
if (LOOKINGAT(p, "U'")) {
cur = cur->next = read_char_literal(p, p + 1, ty_uint);
p += cur->len;
continue;
}
// Identifier or keyword
char *q;
if ((q = read_ident(p)) != NULL) {
@ -589,38 +560,32 @@ Token *tokenize(File *file) {
p = q;
continue;
}
// Three-letter punctuators
if (starts_with(p, "<<=") || starts_with(p, ">>=") ||
starts_with(p, "...")) {
if (LOOKINGAT(p, "<<=") || LOOKINGAT(p, ">>=") || LOOKINGAT(p, "...")) {
cur = cur->next = new_token(TK_RESERVED, p, p + 3);
p += 3;
continue;
}
// Two-letter punctuators
if (starts_with(p, "==") || starts_with(p, "!=") || starts_with(p, "<=") ||
starts_with(p, ">=") || starts_with(p, "->") || starts_with(p, "+=") ||
starts_with(p, "-=") || starts_with(p, "*=") || starts_with(p, "/=") ||
starts_with(p, "++") || starts_with(p, "--") || starts_with(p, "%=") ||
starts_with(p, "&=") || starts_with(p, "|=") || starts_with(p, "^=") ||
starts_with(p, "&&") || starts_with(p, "||") || starts_with(p, "<<") ||
starts_with(p, ">>") || starts_with(p, "##")) {
if (LOOKINGAT(p, "==") || LOOKINGAT(p, "!=") || LOOKINGAT(p, "<=") ||
LOOKINGAT(p, ">=") || LOOKINGAT(p, "->") || LOOKINGAT(p, "+=") ||
LOOKINGAT(p, "-=") || LOOKINGAT(p, "*=") || LOOKINGAT(p, "/=") ||
LOOKINGAT(p, "++") || LOOKINGAT(p, "--") || LOOKINGAT(p, "%=") ||
LOOKINGAT(p, "&=") || LOOKINGAT(p, "|=") || LOOKINGAT(p, "^=") ||
LOOKINGAT(p, "&&") || LOOKINGAT(p, "||") || LOOKINGAT(p, "<<") ||
LOOKINGAT(p, ">>") || LOOKINGAT(p, "##")) {
cur = cur->next = new_token(TK_RESERVED, p, p + 2);
p += 2;
continue;
}
// Single-letter punctuators
if (ispunct(*p)) {
cur = cur->next = new_token(TK_RESERVED, p, p + 1);
p++;
continue;
}
error_at(p, "invalid token");
}
cur = cur->next = new_token(TK_EOF, p, p);
add_line_numbers(head.next);
return head.next;
@ -629,7 +594,6 @@ Token *tokenize(File *file) {
// Returns the contents of a given file.
static char *read_file(char *path) {
FILE *fp;
if (strcmp(path, "-") == 0) {
// By convention, read from stdin if a given filename is "-".
fp = stdin;
@ -637,11 +601,9 @@ static char *read_file(char *path) {
fp = fopen(path, "r");
if (!fp) return NULL;
}
int buflen = 4096;
int nread = 0;
char *buf = calloc(1, buflen);
// Read the entire file.
for (;;) {
int end = buflen - 2; // extra 2 bytes for the trailing "\n\0"
@ -653,15 +615,12 @@ static char *read_file(char *path) {
buf = realloc(buf, buflen);
}
}
if (fp != stdin) fclose(fp);
// Make sure that the last logical line is properly terminated with '\n'.
if (nread > 0 && buf[nread - 1] == '\\')
buf[nread - 1] = '\n';
else if (nread == 0 || buf[nread - 1] != '\n')
buf[nread++] = '\n';
buf[nread] = '\0';
return buf;
}
@ -682,7 +641,6 @@ File *new_file(char *name, int file_no, char *contents) {
// Replaces \r or \r\n with \n.
static void canonicalize_newline(char *p) {
int i = 0, j = 0;
while (p[i]) {
if (p[i] == '\r' && p[i + 1] == '\n') {
i += 2;
@ -694,19 +652,16 @@ static void canonicalize_newline(char *p) {
p[j++] = p[i++];
}
}
p[j] = '\0';
}
// Removes backslashes followed by a newline.
static void remove_backslash_newline(char *p) {
int i = 0, j = 0;
// We want to keep the number of newline characters so that
// the logical line number matches the physical one.
// This counter maintain the number of newlines we have removed.
int n = 0;
while (p[i]) {
if (p[i] == '\\' && p[i + 1] == '\n') {
i += 2;
@ -718,7 +673,6 @@ static void remove_backslash_newline(char *p) {
p[j++] = p[i++];
}
}
p[j] = '\0';
}
@ -734,9 +688,8 @@ static uint32_t read_universal_char(char *p, int len) {
// Replace \u or \U escape sequences with corresponding UTF-8 bytes.
static void convert_universal_chars(char *p) {
char *q = p;
while (*p) {
if (starts_with(p, "\\u")) {
if (LOOKINGAT(p, "\\u")) {
uint32_t c = read_universal_char(p + 2, 4);
if (c) {
p += 6;
@ -744,7 +697,7 @@ static void convert_universal_chars(char *p) {
} else {
*q++ = *p++;
}
} else if (starts_with(p, "\\U")) {
} else if (LOOKINGAT(p, "\\U")) {
uint32_t c = read_universal_char(p + 2, 8);
if (c) {
p += 10;
@ -759,27 +712,22 @@ static void convert_universal_chars(char *p) {
*q++ = *p++;
}
}
*q = '\0';
}
Token *tokenize_file(char *path) {
char *p = read_file(path);
if (!p) return NULL;
canonicalize_newline(p);
remove_backslash_newline(p);
convert_universal_chars(p);
// Save the filename for assembler .file directive.
static int file_no;
File *file = new_file(path, file_no + 1, p);
// Save the filename for assembler .file directive.
input_files = realloc(input_files, sizeof(char *) * (file_no + 2));
input_files[file_no] = file;
input_files[file_no + 1] = NULL;
file_no++;
return tokenize(file);
}