G

G Programming Language
git clone http://git.omkov.net/G
Log | Tree | Refs | README | Download

AuthorJakob Wakeling <[email protected]>
Date2023-07-10 12:17:59
Commit8021c03df5a011b7a8978a9c49c21f6e97146f8f
Parent2be2f4390b177036c5fa3b2ff11bf17e53003e97

Implement procedure argument mutability

Diffstat

M README.md | 7 +++----
M src/analyse.c | 2 ++
M src/lex.c | 80 ++++++++++++++++++++++++++++++++++++++++----------------------------------------
M src/lex.h | 6 +++---
M src/llvm.c | 59 ++++++++++++++++++++++-------------------------------------
M src/log.c | 10 +++++++++-
M src/log.h | 10 ++++++++++
M src/parse.c | 43 +++++++++++++++++++------------------------

8 files changed, 108 insertions, 109 deletions

diff --git a/README.md b/README.md
index afcc067..6054900 100644
--- a/README.md
+++ b/README.md
@@ -45,10 +45,8 @@ command. The second command will output an executable file, *a.out* by default.
 > Not all todo items will necesarilly be implemented and sometimes I am pretty
 > liberal with what justifies crossing something off
 
-- [x] Implement procedure declarations
-- [x] Implement procedure calls
-- [x] Implement procedure arguments
 - [x] Implement variables
+- [x] Implement procedures
 - [x] Implement booleans
 - [x] Implement integers
 - [x] Implement reals
@@ -58,6 +56,7 @@ command. The second command will output an executable file, *a.out* by default.
 - [x] Implement expressions
 - [x] Implement type casting
 - [ ] Implement type casting to pointers and arrays
+- [ ] Improve type inference and implicit casting
 - [ ] Implement the `type` type
 - [ ] Implement the `error` type
 - [ ] Implement labels and `goto`
@@ -66,6 +65,7 @@ command. The second command will output an executable file, *a.out* by default.
 - [ ] Implement `break` and `continue`
 - [ ] Implement `defer`
 - [ ] Implement `errdefer` (?)
+- [ ] Implement variadic procedure arguments
 - [ ] Implement first class strings
 - [x] Implement syscalls
 - [ ] Implement generics of some kind
diff --git a/src/analyse.c b/src/analyse.c
index 8a83441..fb1830e 100644
--- a/src/analyse.c
+++ b/src/analyse.c
@@ -165,6 +165,8 @@ static void analyse_expr(ast *a, syt *st) {
 			A.k = AK_CAST; A.t = sym->t;
 			if (CL > 2) { note("TODO", A.ln, A.cl, 0, "Type casts must only have a single argument"); }
 		}
+		
+		for (UINT i = 1; i < CL; i += 1) { analyse_expr(C[i], st); }
 	} break;
 	case AK_SUBS: {
 		assert(CL == 2);
diff --git a/src/lex.c b/src/lex.c
index e3c0946..722ea95 100644
--- a/src/lex.c
+++ b/src/lex.c
@@ -22,14 +22,14 @@ char *tok_ks[] = {
 	"TK_LPAREN", "TK_RPAREN", "TK_LBRACK", "TK_RBRACK", "TK_LBRACE", "TK_RBRACE",
 	"TK_COLON", "TK_SCOLON", "TK_COMMA", "TK_PERIOD", "TK_RARROW", "TK_QMARK", "TK_HASH",
 
-	"TK_OP_ADD", "TK_OP_SUB", "TK_OP_MUL", "TK_OP_DIV", "TK_OP_MOD",
+	"TK_ADD", "TK_SUB", "TK_MUL", "TK_DIV", "TK_MOD",
 	"TK_EQ", "TK_NE", "TK_LT", "TK_LE", "TK_GT", "TK_GE",
 
 	"TK_EMARK", "TK_LO_AND", "TK_LO_OR",
-	"TK_BW_NOT", "TK_BW_AND", "TK_BW_OR", "TK_BW_XOR", "TK_BW_SHL", "TK_BW_SHR",
+	"TK_TILDE", "TK_BW_AND", "TK_BW_OR", "TK_BW_XOR", "TK_LSHIFT", "TK_RSHIFT",
 
 	"TK_ASSIGN", "TK_AS_ADD", "TK_AS_SUB", "TK_AS_MUL", "TK_AS_DIV", "TK_AS_MOD",
-	"TK_AS_NOT", "TK_AS_AND", "TK_AS_OR", "TK_AS_XOR", "TK_AS_SHL", "TK_AS_SHR",
+	"TK_AS_NOT", "TK_AS_AND", "TK_AS_OR", "TK_AS_XOR", "TK_AS_LSHIFT", "TK_AS_RSHIFT",
 };
 
 #define is_space(c) (c == ' ' || c == '\f' || c == '\n' || c == '\r' || c == '\t' || c == '\v')
@@ -52,7 +52,6 @@ lex lex_init(const char *file, char *src, UINT len) {
 #define P  (l->p)    /* Pointer to the Current Character */
 #define Q  (l->q)    /* Pointer to EOF */
 #define C  (l->p[0]) /* Current Character */
-#define D  (l->p[1]) /* Next Character */
 #define LN (l->ln)   /* Line Index */
 #define CL (l->cl)   /* Column Index */
 #define T  (l->t)    /* Current Token */
@@ -76,12 +75,12 @@ tok lex_next(lex *l) {
 	if (P == Q) { T = (tok){ TK_EOF, LN, CL, 0 }; return t; }
 
 	/* Skip single-line and (potentially nested) multi-line comments */
-	if (C == '/') switch (D) {
+	if (C == '/') switch (P[1]) {
 		case '/': { for (P += 2; P != Q && C != '\n'; P += 1); } goto skip;
 		case '*': {
 			UINT d = 1; for (P += 2, CL += 2; P != Q && d; P += 1) {
-				if (C == '/' && D == '*') { P += 2; CL += 2; d += 1; continue; }
-				if (C == '*' && D == '/') { P += d == 1 ? 1 : 2; CL += 2; d -= 1; continue; }
+				if (C == '/' && P[1] == '*') { P += 2; CL += 2; d += 1; continue; }
+				if (C == '*' && P[1] == '/') { P += d == 1 ? 1 : 2; CL += 2; d -= 1; continue; }
 				if (C == '\n') { LN += 1; CL = 0; } else { CL += 1; }
 			}
 		} goto skip;
@@ -143,58 +142,66 @@ tok lex_next(lex *l) {
 		case '.': { T.k = TK_PERIOD; P += 1; CL += 1; } break;
 		case '?': { T.k = TK_QMARK;  P += 1; CL += 1; } break;
 		case '#': { T.k = TK_HASH;   P += 1; CL += 1; } break;
-		case '+': switch (D) {
-			default:  { T.k = TK_OP_ADD; P += 1; CL += 1; } break;
+		case '+': switch (P[1]) {
+			default:  { T.k = TK_ADD;    P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_ADD; P += 2; CL += 2; } break;
 		} break;
-		case '-': switch (D) {
-			default:  { T.k = TK_OP_SUB; P += 1; CL += 1; } break;
+		case '-': switch (P[1]) {
+			default:  { T.k = TK_SUB;    P += 1; CL += 1; } break;
 			case '>': { T.k = TK_RARROW; P += 2; CL += 2; } break;
 			case '=': { T.k = TK_AS_SUB; P += 2; CL += 2; } break;
 		} break;
-		case '*': switch (D) {
-			default:  { T.k = TK_OP_MUL; P += 1; CL += 1; } break;
+		case '*': switch (P[1]) {
+			default:  { T.k = TK_MUL;    P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_MUL; P += 2; CL += 2; } break;
 		} break;
-		case '/': switch (D) {
-			default:  { T.k = TK_OP_DIV; P += 1; CL += 1; } break;
+		case '/': switch (P[1]) {
+			default:  { T.k = TK_DIV;    P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_DIV; P += 2; CL += 2; } break;
 		} break;
-		case '%': switch (D) {
-			default:  { T.k = TK_OP_MOD; P += 1; CL += 1; } break;
+		case '%': switch (P[1]) {
+			default:  { T.k = TK_MOD;    P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_MOD; P += 2; CL += 2; } break;
 		} break;
-		case '=': switch (D) {
+		case '=': switch (P[1]) {
 			default:  { T.k = TK_ASSIGN; P += 1; CL += 1; } break;
 			case '=': { T.k = TK_EQ;     P += 2; CL += 2; } break;
 		} break;
-		case '<': switch (D) {
+		case '<': switch (P[1]) {
 			default:  { T.k = TK_LT;     P += 1; CL += 1; } break;
 			case '=': { T.k = TK_LE;     P += 2; CL += 2; } break;
+			case '<': switch (P[2]) {
+				default:  { T.k = TK_LSHIFT;    P += 2; CL += 2; } break;
+				case '=': { T.k = TK_AS_LSHIFT; P += 3; CL += 3; } break;
+			} break;
 		} break;
-		case '>': switch (D) {
+		case '>': switch (P[1]) {
 			default:  { T.k = TK_GT;     P += 1; CL += 1; } break;
 			case '=': { T.k = TK_GE;     P += 2; CL += 2; } break;
+			case '>': switch (P[2]) {
+				default:  { T.k = TK_RSHIFT;    P += 2; CL += 2; } break;
+				case '=': { T.k = TK_AS_RSHIFT; P += 3; CL += 3; } break;
+			} break;
 		} break;
-		case '!': switch (D) {
+		case '!': switch (P[1]) {
 			default:  { T.k = TK_EMARK;  P += 1; CL += 1; } break;
 			case '=': { T.k = TK_NE;     P += 2; CL += 2; } break;
 		} break;
-		case '&': switch (D) {
+		case '&': switch (P[1]) {
 			default:  { T.k = TK_BW_AND; P += 1; CL += 1; } break;
 			case '&': { T.k = TK_LO_AND; P += 2; CL += 2; } break;
 			case '=': { T.k = TK_AS_AND; P += 2; CL += 2; } break;
 		} break;
-		case '|': switch (D) {
+		case '|': switch (P[1]) {
 			default:  { T.k = TK_BW_OR;  P += 1; CL += 1; } break;
 			case '|': { T.k = TK_LO_OR;  P += 2; CL += 2; } break;
 			case '=': { T.k = TK_AS_OR;  P += 2; CL += 2; } break;
 		} break;
-		case '~': switch (D) {
-			default:  { T.k = TK_BW_NOT; P += 1; CL += 1; } break;
+		case '~': switch (P[1]) {
+			default:  { T.k = TK_TILDE; P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_NOT; P += 2; CL += 2; } break;
 		} break;
-		case '^': switch (D) {
+		case '^': switch (P[1]) {
 			default:  { T.k = TK_BW_XOR; P += 1; CL += 1; } break;
 			case '=': { T.k = TK_AS_XOR; P += 2; CL += 2; } break;
 		} break;
@@ -208,7 +215,7 @@ tok lex_next(lex *l) {
 				if (C != '\\') { *head = C; P += 1; head += 1; }
 
 				/* Escape characters are processed and re-written to head */
-				else switch (D) {
+				else switch (P[1]) {
 				case '0':  { *head = 0x00; P += 2; head += 1; } break;
 				case 'a':  { *head = '\a'; P += 2; head += 1; } break;
 				case 'b':  { *head = '\b'; P += 2; head += 1; } break;
@@ -225,8 +232,8 @@ tok lex_next(lex *l) {
 				// case 'x': {} break;
 				// case 'u': {} break;
 				default: {
-					note(l->n, l->ln, l->cl, 1, "Unknown escape sequence: \"\\%c\"", D);
-					*head = D; P += 2; head += 1;
+					note(l->n, l->ln, l->cl, 1, "Unknown escape sequence: \"\\%c\"", P[1]);
+					*head = P[1]; P += 2; head += 1;
 				} break;
 				}
 			}
@@ -257,10 +264,7 @@ tok lex_next(lex *l) {
 		} break;
 
 		/* Handle unknown characters */
-		default: {
-			note(l->n, LN, CL, 1, "Unknown character: %X '%c'", C, C);
-			P += 1; CL += 1;
-		} break;
+		default: { note(l->n, LN, CL, 1, "Unknown character: %X '%c'", C, C); P += 1; CL += 1; } break;
 	}
 
 	return t;
@@ -268,10 +272,7 @@ tok lex_next(lex *l) {
 
 /* Lex the next token if the current is of a specific type. */
 tok lex_kind(lex *l, tok_k k) {
-	if (T.k != k) {
-		note(l->n, T.ln, T.cl, 0, "Unexpected \"%s\", was expecting \"%s\"", tok_ks[T.k], tok_ks[k]);
-	}
-	
+	if (T.k != k) { note(l->n, T.ln, T.cl, 0, "Unexpected \"%s\", was expecting \"%s\"", tok_ks[T.k], tok_ks[k]); }
 	return lex_next(l);
 }
 
diff --git a/src/lex.h b/src/lex.h
index 83abccd..a0e4474 100644
--- a/src/lex.h
+++ b/src/lex.h
@@ -18,14 +18,14 @@ typedef enum {
 	TK_LPAREN, TK_RPAREN, TK_LBRACK, TK_RBRACK, TK_LBRACE, TK_RBRACE,
 	TK_COLON, TK_SCOLON, TK_COMMA, TK_PERIOD, TK_RARROW, TK_QMARK, TK_HASH,
 
-	TK_OP_ADD, TK_OP_SUB, TK_OP_MUL, TK_OP_DIV, TK_OP_MOD,
+	TK_ADD, TK_SUB, TK_MUL, TK_DIV, TK_MOD,
 	TK_EQ, TK_NE, TK_LT, TK_LE, TK_GT, TK_GE,
 
 	TK_EMARK, TK_LO_AND, TK_LO_OR,
-	TK_BW_NOT, TK_BW_AND, TK_BW_OR, TK_BW_XOR, TK_BW_SHL, TK_BW_SHR,
+	TK_TILDE, TK_BW_AND, TK_BW_OR, TK_BW_XOR, TK_LSHIFT, TK_RSHIFT,
 
 	TK_ASSIGN, TK_AS_ADD, TK_AS_SUB, TK_AS_MUL, TK_AS_DIV, TK_AS_MOD,
-	TK_AS_NOT, TK_AS_AND, TK_AS_OR, TK_AS_XOR, TK_AS_SHL, TK_AS_SHR,
+	TK_AS_NOT, TK_AS_AND, TK_AS_OR, TK_AS_XOR, TK_AS_LSHIFT, TK_AS_RSHIFT,
 } tok_k;
 
 /*
diff --git a/src/llvm.c b/src/llvm.c
index bafe6b3..b10a0da 100644
--- a/src/llvm.c
+++ b/src/llvm.c
@@ -136,11 +136,9 @@ static LLVMValueRef llvm_stmt_compound(ast *a, syt *st) {
 static LLVMValueRef llvm_stmt_decl(ast *a, syt *st) {
 	assert(a->k == AK_DECL);
 
-	if (CL && C[0]->k == AK_PROC) {
+	if (CL && C[0]->k == AK_PROC) /* AK_PROC has a scope */ {
 		LLVMTypeRef art[C[0]->c.al - 1];
-		for (UINT i = 0; i < C[0]->c.al - 1; i += 1) {
-			art[i] = llvm_type(ast_type(C[0]->c.a[i], st));
-		}
+		for (UINT i = 0; i < C[0]->c.al - 1; i += 1) { art[i] = llvm_type(ast_type(C[0]->c.a[i], &A.st)); }
 
 		LLVMTypeRef ft = LLVMFunctionType(llvm_type(C[0]->t), art, C[0]->c.al - 1, 0);
 		LLVMValueRef f = LLVMAddFunction(llvm_module, A.s, ft);
@@ -148,20 +146,20 @@ static LLVMValueRef llvm_stmt_decl(ast *a, syt *st) {
 		LLVMBasicBlockRef bb = LLVMAppendBasicBlock(f, "");
 		LLVMPositionBuilderAtEnd(llvm_builder, bb);
 
-		for (UINT i = 0; i < C[0]->c.al - 1; i += 1) {
-			C[0]->c.a[i]->llvm_v = LLVMGetParam(f, i);
-		}
+		/* Allocate scoped procedure arguments */
+		for (UINT i = 0; i < C[0]->c.al - 1; i += 1) { C[0]->c.a[i]->llvm_v = LLVMBuildAlloca(llvm_builder, art[i], ""); }
+		for (UINT i = 0; i < C[0]->c.al - 1; i += 1) { LLVMBuildStore(llvm_builder, LLVMGetParam(f, i), C[0]->c.a[i]->llvm_v); }
 
 		A.llvm_t = ft; A.llvm_v = f;
-		llvm_expr_proc(C[0], st);
+		llvm_expr_proc(C[0], &A.st);
 	}
 	else {
-		if (A.p->k == AK_PROG) { /* Global */
+		if (A.p->k == AK_PROG) /* Global */ {
 			LLVMValueRef v = LLVMAddGlobal(llvm_module, llvm_type(A.t), "");
 			LLVMSetInitializer(v, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t));
 			A.llvm_v = v;
 		}
-		else { /* Local */
+		else /* Local */ {
 			LLVMValueRef v = LLVMBuildAlloca(llvm_builder, llvm_type(A.t), "");
 			LLVMBuildStore(llvm_builder, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t), v);
 			A.llvm_v = v;
@@ -250,9 +248,7 @@ static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 		if (sym == NULL) { note(file_name, A.ln, A.cl, -1, "Undefined variable \"%s\"", A.s); }
 		if (!sym->llvm_v) { note(file_name, A.ln, A.cl, -1, "Variable \"%s\" follows (llvm:llvm_expr)", A.s); }
 
-		/* TODO fix procedure argument handling, should be assignable */
-		/* TODO fix array variable handling */
-		if (!load || sym->k == AK_ID || sym->t->k == TY_ARR) { return sym->llvm_v; }
+		if (!load || sym->t->k == TY_ARR) { return sym->llvm_v; }
 		else { return LLVMBuildLoad2(llvm_builder, llvm_type(sym->t), sym->llvm_v, ""); }
 	} break;
 	case AK_CALL: {
@@ -260,26 +256,18 @@ static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 		if (sym == NULL) { note(file_name, A.ln, A.cl, -1, "Undefined procedure \"%s\" (llvm:llvm_expr)", C[0]->s); }
 		if (!sym->llvm_v) { note(file_name, A.ln, A.cl, -1, "Procedure \"%s\" follows (llvm:llvm_expr)", C[0]->s); }
 
-		LLVMValueRef args[CL - 1];
-		for (UINT i = 1; i < CL; i += 1) {
-			args[i - 1] = llvm_expr(C[i], st, true);
-		}
-		
+		LLVMValueRef args[CL - 1]; for (UINT i = 1; i < CL; i += 1) { args[i - 1] = llvm_expr(C[i], st, true); }
 		return LLVMBuildCall2(llvm_builder, sym->llvm_t, sym->llvm_v, args, CL - 1, "");
 	} break;
 	case AK_HASH_SYSCALL:
 	case AK_HASH: { return llvm_expr_hash(a, st); } break;
 	case AK_SUBS: {
-		ast *sym = syt_search_h(st, C[0]->h, C[0]->s);
-		if (sym == NULL) { note(file_name, A.ln, A.cl, 0, "Undefined variable \"%s\"", C[0]->s); }
-		
-		if (!sym->llvm_v) { note(file_name, A.ln, A.cl, -1, "Variable \"%s\" follows (llvm:llvm_expr)", A.s); }
-		
+		/* FIXME subscripting does not work with pointers */
 		LLVMValueRef idc[1] = { llvm_expr(C[1], st, true) };
-		LLVMValueRef vr = LLVMBuildGEP2(llvm_builder, llvm_type(sym->t), sym->llvm_v, idc, 1, "");
+		LLVMValueRef vr = LLVMBuildInBoundsGEP2(llvm_builder, llvm_type(ast_type(C[0], st)), llvm_expr(C[0], st, false), idc, 1, "");
 
 		if (!load) { return vr; }
-		else { return LLVMBuildLoad2(llvm_builder, llvm_type(sym->t->base), vr, ""); }
+		else { return LLVMBuildLoad2(llvm_builder, llvm_type(ast_type(C[0], st)->base), vr, ""); }
 	}
 
 	case AK_OP_POS: { a = C[0]; goto reset; /* no-op */ } break;
@@ -291,18 +279,14 @@ static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 		else if (is_flt(t)) { return LLVMBuildFNeg(llvm_builder, llvm_expr(C[0], st, true), "neg"); }
 		else { note(file_name, A.ln, A.cl, -1, "Expression cannot be made negative (llvm:llvm_expr)"); }
 	} break;
-	case AK_OP_ADO: {
-		ast *v = syt_search(st, C[0]->s);
-		if (v == NULL) { note(file_name, A.ln, A.cl, -1, "Undefined variable (llvm:llvm_expr)"); }
-		if (v->llvm_v == NULL) { note(file_name, A.ln, A.cl, -1, "Variable follows (llvm:llvm_expr)"); }
-		
-		return v->llvm_v; /* TODO handle non-variables (?) */
-	} break;
+	case AK_OP_ADO: { return llvm_expr(C[0], st, false); } break;
 	case AK_OP_DRF: {
-		if (C[0]->t == NULL) { note(file_name, A.ln, A.cl, -1, "Child is missing a type (llvm:llvm_expr)"); }
-		if (C[0]->t->base == NULL) { note(file_name, A.ln, A.cl, -1, "Cannot be dereferenced (llvm:llvm_expr)"); }
+		type *t = ast_type(C[0], st);
+		if (t == NULL) { note(file_name, A.ln, A.cl, -1, "Child is missing a type (llvm:llvm_expr)"); }
+		if (t->base == NULL) { note(file_name, A.ln, A.cl, -1, "Cannot be dereferenced (llvm:llvm_expr)"); }
 
-		return LLVMBuildLoad2(llvm_builder, llvm_type(C[0]->t->base), llvm_expr(C[0], st, true), "");
+		if (!load) { return llvm_expr(C[0], st, true); }
+		else { return LLVMBuildLoad2(llvm_builder, llvm_type(t->base), llvm_expr(C[0], st, true), ""); }
 	}
 
 	case AK_OP_ADD: {
diff --git a/src/log.c b/src/log.c
index 8166eff..2d6184d 100644
--- a/src/log.c
+++ b/src/log.c
@@ -6,9 +6,11 @@
 #include "log.h"
 #include "util/util.h"
 
+#include <stdarg.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
+#include <stdnoreturn.h>
 
 bool log_waerr = false;
 sint log_level = 4, log_limit = 8;
@@ -17,7 +19,7 @@ static sint log_count = 0;
 
 /* Log a compiler fatal (-1), error (0), warning (1-3), or note (4). */
 void note(const char *file, UINT ln, UINT cl, sint level, const char *format, ...) {
-	if (file) { fprintf(stderr, "%s:%zu:%zu: ", file, ln + 1, cl + 1); }
+	if (file) { fprintf(stderr, "%s:%lu:%lu: ", file, ln + 1, cl + 1); }
 
 	if (level <= -1) { fprintf(stderr, "fatal: "); }
 	else if (level == 0) { fprintf(stderr, "error: "); }
@@ -32,3 +34,9 @@ void note(const char *file, UINT ln, UINT cl, sint level, const char *format, ..
 
 /* Check if there has been an error. */
 bool has_error(void) { return log_count > 0; }
+
+/* Print a panic message and exit. */
+noreturn void __panic(const char *file, UINT ln, const char *format, ...) {
+	fflush(stderr); fprintf(stderr, file != NULL ? "panic: %s:%lu: " : "panic: ", file, ln);
+	va_list ap; va_start(ap, format); vfprintf(stderr, format, ap); va_end(ap); fputc('\n', stderr); exit(-1);
+}
diff --git a/src/log.h b/src/log.h
index bfb69d5..8ca274e 100644
--- a/src/log.h
+++ b/src/log.h
@@ -6,6 +6,8 @@
 #ifndef G_LOG_H_1RPM5P9E
 #define G_LOG_H_1RPM5P9E
 
+#include <stdnoreturn.h>
+
 #include "util/util.h"
 
 extern bool log_waerr;
@@ -14,4 +16,12 @@ extern sint log_level, log_limit;
 extern void note(const char *file, UINT ln, UINT cl, sint level, const char *format, ...);
 extern bool has_error(void);
 
+#ifdef NDEBUG
+#define panic(format, ...) (__panic(NULL, 0, format __VA_OPT__(,) __VA_ARGS__))
+#else
+#define panic(format, ...) (__panic(__FILE__, __LINE__, format __VA_OPT__(,) __VA_ARGS__))
+#endif
+
+extern noreturn void __panic(const char *file, UINT ln, const char *format, ...);
+
 #endif // G_LOG_H_1RPM5P9E
diff --git a/src/parse.c b/src/parse.c
index dcf206e..f96234c 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -199,7 +199,7 @@ static ast *parse_stmt_decl(lex *l, syt *st, bool scolon) {
 	assert(T.k == TK_ID); lex_next(l); assert(T.k == TK_COLON); lex_next(l);
 
 	/* Store the declaration's type if one is specified */
-	if (T.k == TK_ID || T.k == TK_OP_MUL || T.k == TK_LBRACK) {
+	if (T.k == TK_ID || T.k == TK_MUL || T.k == TK_LBRACK) {
 		ta_pair ta = parse_type(l, st);
 		a->t = ta.t; /* TODO keep the AST component */
 	}
@@ -314,12 +314,12 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 		left->h = T.h; left->s = strdup_or_fail(T.s); lex_kind(l, TK_ID);
 	} break;
 	case TK_LPAREN: { lex_next(l); left = parse_expr(l, st, 0); lex_kind(l, TK_RPAREN); } break;
-	case TK_OP_ADD: { left = ast_init(AK_OP_POS, T.ln, T.cl); } goto prefix;
-	case TK_OP_SUB: { left = ast_init(AK_OP_NEG, T.ln, T.cl); } goto prefix;
+	case TK_ADD:    { left = ast_init(AK_OP_POS, T.ln, T.cl); } goto prefix;
+	case TK_SUB:    { left = ast_init(AK_OP_NEG, T.ln, T.cl); } goto prefix;
 	case TK_EMARK:  { left = ast_init(AK_LO_NOT, T.ln, T.cl); } goto prefix;
-	case TK_BW_NOT: { left = ast_init(AK_BW_NOT, T.ln, T.cl); } goto prefix;
+	case TK_TILDE:  { left = ast_init(AK_BW_NOT, T.ln, T.cl); } goto prefix;
 	case TK_BW_AND: { left = ast_init(AK_OP_ADO, T.ln, T.cl); } goto prefix;
-	case TK_OP_MUL: { left = ast_init(AK_OP_DRF, T.ln, T.cl); } goto prefix;
+	case TK_MUL:    { left = ast_init(AK_OP_DRF, T.ln, T.cl); } goto prefix;
 	prefix: { lex_next(l); ast_push(left, parse_expr(l, st, ast_precedence(left->k))); } break;
 	default: { note(l->n, T.ln, T.cl, 0, "Unexpected \"%s\", was expecting an expression", tok_ks[T.k]); } break;
 	}
@@ -353,17 +353,17 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 	case TK_AS_MUL: { a = ast_init(AK_AS_MUL, T.ln, T.cl); } goto infix;
 	case TK_AS_DIV: { a = ast_init(AK_AS_DIV, T.ln, T.cl); } goto infix;
 	case TK_AS_MOD: { a = ast_init(AK_AS_MOD, T.ln, T.cl); } goto infix;
-	case TK_OP_ADD: { a = ast_init(AK_OP_ADD, T.ln, T.cl); } goto infix;
-	case TK_OP_SUB: { a = ast_init(AK_OP_SUB, T.ln, T.cl); } goto infix;
-	case TK_OP_MUL: { a = ast_init(AK_OP_MUL, T.ln, T.cl); } goto infix;
-	case TK_OP_DIV: { a = ast_init(AK_OP_DIV, T.ln, T.cl); } goto infix;
-	case TK_OP_MOD: { a = ast_init(AK_OP_MOD, T.ln, T.cl); } goto infix;
-	case TK_EQ: { a = ast_init(AK_EQ, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
-	case TK_NE: { a = ast_init(AK_NE, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
-	case TK_LT: { a = ast_init(AK_LT, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
-	case TK_LE: { a = ast_init(AK_LE, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
-	case TK_GT: { a = ast_init(AK_GT, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
-	case TK_GE: { a = ast_init(AK_GE, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_ADD:    { a = ast_init(AK_OP_ADD, T.ln, T.cl); } goto infix;
+	case TK_SUB:    { a = ast_init(AK_OP_SUB, T.ln, T.cl); } goto infix;
+	case TK_MUL:    { a = ast_init(AK_OP_MUL, T.ln, T.cl); } goto infix;
+	case TK_DIV:    { a = ast_init(AK_OP_DIV, T.ln, T.cl); } goto infix;
+	case TK_MOD:    { a = ast_init(AK_OP_MOD, T.ln, T.cl); } goto infix;
+	case TK_EQ:     { a = ast_init(AK_EQ,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_NE:     { a = ast_init(AK_NE,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_LT:     { a = ast_init(AK_LT,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_LE:     { a = ast_init(AK_LE,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_GT:     { a = ast_init(AK_GT,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
+	case TK_GE:     { a = ast_init(AK_GE,     T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
 	case TK_LO_AND: { a = ast_init(AK_LO_AND, T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
 	case TK_LO_OR:  { a = ast_init(AK_LO_OR,  T.ln, T.cl); a->t = &TYPE(TY_BOOL); } goto infix;
 	infix: { lex_next(l); ast_push(a, left); ast_push(a, parse_expr(l, st, ast_precedence(a->k))); } break;
@@ -387,7 +387,7 @@ static ast *parse_expr_compound(lex *l, syt *st) {
 	lex_kind(l, TK_RBRACE); return a;
 }
 
-/* Parse a procedure expression. */
+/* Parse a procedure expression. AK_PROC has a scope. */
 static ast *parse_expr_proc(lex *l, syt *st) {
 	assert(T.k == TK_PROC);
 
@@ -429,7 +429,7 @@ static inline ta_pair parse_type(lex *l, syt *st) {
 
 	/* Parse optional pointer and array specifiers */
 	for (register type *t = NULL;;) switch (T.k) {
-	case TK_OP_MUL: { lex_next(l); t = type_ptr(NULL, 1); } goto store;
+	case TK_MUL: { lex_next(l); t = type_ptr(NULL, 1); } goto store;
 	case TK_LBRACK: {
 		lex_next(l);
 
@@ -496,8 +496,7 @@ static ast *parse_flt(lex *l, syt *st) {
 /*
 	Expression operator precedence:
 	8 > expression group (parenthesis), procedure call, array subscripting
-	7 > positive (prefix +), negative (prefix -), logical not (prefix !), bitwise not (prefix ~),
-		address-of (prefix &), dereference (prefix *)
+	7 > prefix (+, -, !, ~, &, *)
 	6 > multiplication (*), division (/), modulo (%)
 	5 > addition (+), subtraction (-)
 	4 > comparison (==, !=, <, >, <=, >=)
@@ -510,8 +509,8 @@ static ast *parse_flt(lex *l, syt *st) {
 static s32 tok_precedence(tok_k tk) {
 	switch (tk) {
 	case TK_LPAREN: case TK_LBRACK: { return 8; }
-	case TK_OP_MUL: case TK_OP_DIV: case TK_OP_MOD: { return 6; }
-	case TK_OP_ADD: case TK_OP_SUB: { return 5; }
+	case TK_MUL: case TK_DIV: case TK_MOD: { return 6; }
+	case TK_ADD: case TK_SUB: { return 5; }
 	case TK_EQ: case TK_NE: case TK_LT: case TK_LE: case TK_GT: case TK_GE: { return 4; }
 	case TK_LO_AND: { return 3; }
 	case TK_LO_OR: { return 2; }