G

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

AuthorJakob Wakeling <[email protected]>
Date2023-07-01 07:14:24
Commit3f9a543df8693f7ebeb7492e4077a716ed3cfdc0
Parent828da145ae807f2023e1ef42127b5650c80e40c3

Implement array subscripting

Diffstat

M examples/main.g | 6 +++---
M src/analyse.c | 8 ++++++++
M src/llvm.c | 156 +++++++++++++++++++++++++++++++++++--------------------------------------------
M src/parse.c | 22 ++++++++++++++++------
M src/parse.h | 2 +-

5 files changed, 98 insertions, 96 deletions

diff --git a/examples/main.g b/examples/main.g
index 5cfdb4f..b4c85f3 100644
--- a/examples/main.g
+++ b/examples/main.g
@@ -1,5 +1,5 @@
 main :: proc() -> u8 {
-	var : u8 = u8(0b11111101);
-	inv : u8 = ~var;
-	return inv;
+	var : [1]u8;
+	var[0] = 1;
+	return var[0];
 }
diff --git a/src/analyse.c b/src/analyse.c
index 2f275d6..4f2a4a9 100644
--- a/src/analyse.c
+++ b/src/analyse.c
@@ -143,6 +143,7 @@ static void analyse_stmt_for(ast *a, syt *st) {
 static void analyse_expr(ast *a, syt *st) {
 	switch (A.k) {
 	case AK_CALL: {
+		assert(CL >= 1);
 		assert(C[0]->k == AK_ID);
 		assert(C[0]->h != 0);
 		assert(C[0]->s != NULL);
@@ -155,6 +156,13 @@ static void analyse_expr(ast *a, syt *st) {
 			if (CL > 2) { note("TODO", A.ln, A.cl, 0, "Type casts must only have a single argument"); }
 		}
 	} break;
+	case AK_SUBS: {
+		assert(CL == 2);
+		
+		if (!is_int(ast_type(C[1], st))) {
+			note("TODO", A.ln, A.cl, 0, "Expression must have integral type");
+		}
+	} break;
 	case AK_PROC:   { analyse_expr_proc(a, st);       } break;
 	case AK_OP_DRF: { A.t = ast_type(C[0], st)->base; } break;
 	default: { for (UINT i = 0; i < CL; i += 1) { analyse_expr(C[i], st); }} break;
diff --git a/src/llvm.c b/src/llvm.c
index 01b71a3..77096a3 100644
--- a/src/llvm.c
+++ b/src/llvm.c
@@ -30,13 +30,12 @@ static LLVMBuilderRef llvm_builder = NULL;
 static LLVMValueRef llvm_stmt(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_compound(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_decl(ast *a, syt *st);
-static LLVMValueRef llvm_stmt_assn(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_expr(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_return(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_if(ast *a, syt *st);
 static LLVMValueRef llvm_stmt_for(ast *a, syt *st);
 
-static LLVMValueRef llvm_expr(ast *a, syt *st);
+static LLVMValueRef llvm_expr(ast *a, syt *st, bool load);
 static LLVMValueRef llvm_expr_proc(ast *a, syt *st);
 static LLVMValueRef llvm_expr_cast(ast *a, syt *st);
 
@@ -123,11 +122,8 @@ static LLVMValueRef llvm_stmt(ast *a, syt *st) {
 	case AK_RETURN: { return llvm_stmt_return(a, st);   } break;
 	case AK_IF:     { return llvm_stmt_if(a, st);       } break;
 	case AK_FOR:    { return llvm_stmt_for(a, st);      } break;
-	case AK_ASSIGN: case AK_AS_ADD: case AK_AS_SUB: case AK_AS_MUL: case AK_AS_DIV: case AK_AS_MOD:
-		{ return llvm_stmt_assn(a, st); } break;
-	case AK_HASH_SYSCALL:
-		{ return llvm_hash(a, st); } break;
-	default:        { return llvm_expr(a, st);          } break;
+	case AK_HASH_SYSCALL: { return llvm_hash(a, st);    } break;
+	default:        { return llvm_expr(a, st, true);    } break;
 	}
 }
 
@@ -154,12 +150,12 @@ static LLVMValueRef llvm_stmt_decl(ast *a, syt *st) {
 	else {
 		if (A.p->k == AK_PROG) { /* Global */
 			LLVMValueRef v = LLVMAddGlobal(llvm_module, llvm_type(A.t), "");
-			LLVMSetInitializer(v, CL ? llvm_expr(C[0], st) : llvm_ival(A.t));
+			LLVMSetInitializer(v, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t));
 			A.llvm_v = v;
 		}
 		else { /* Local */
 			LLVMValueRef v = LLVMBuildAlloca(llvm_builder, llvm_type(A.t), "");
-			LLVMBuildStore(llvm_builder, CL ? llvm_expr(C[0], st) : llvm_ival(A.t), v);
+			LLVMBuildStore(llvm_builder, CL ? llvm_expr(C[0], st, true) : llvm_ival(A.t), v);
 			A.llvm_v = v;
 		}
 	}
@@ -167,48 +163,6 @@ static LLVMValueRef llvm_stmt_decl(ast *a, syt *st) {
 	return NULL;
 }
 
-/* Generate IR for an assignment statement. */
-static LLVMValueRef llvm_stmt_assn(ast *a, syt *st) {
-	assert(
-		a->k == AK_ASSIGN || a->k == AK_AS_ADD || a->k == AK_AS_SUB ||
-		a->k == AK_AS_MUL || a->k == AK_AS_DIV || a->k == AK_AS_MOD
-	);
-	
-	ast *v = syt_search(st, C[0]->s);
-	if (v == NULL) { note(file_name, a->ln, a->cl, 0, "Undefined variable %s (LLVM Failsafe)", a->s); }
-	
-	switch (a->k) {
-	case AK_ASSIGN: {
-		return LLVMBuildStore(llvm_builder, llvm_expr(C[1], st), v->llvm_v);
-	} break;
-	case AK_AS_ADD: {
-		LLVMValueRef vr = LLVMBuildLoad2(llvm_builder, llvm_type(v->t), v->llvm_v, v->s);
-		LLVMValueRef rr = LLVMBuildAdd(llvm_builder, vr, llvm_expr(C[1], st), "as_add");
-		return LLVMBuildStore(llvm_builder, rr, v->llvm_v);
-	} break;
-	case AK_AS_SUB: {
-		LLVMValueRef vr = LLVMBuildLoad2(llvm_builder, llvm_type(v->t), v->llvm_v, v->s);
-		LLVMValueRef rr = LLVMBuildSub(llvm_builder, vr, llvm_expr(C[1], st), "as_sub");
-		return LLVMBuildStore(llvm_builder, rr, v->llvm_v);
-	} break;
-	case AK_AS_MUL: {
-		LLVMValueRef vr = LLVMBuildLoad2(llvm_builder, llvm_type(v->t), v->llvm_v, v->s);
-		LLVMValueRef rr = LLVMBuildMul(llvm_builder, vr, llvm_expr(C[1], st), "as_mul");
-		return LLVMBuildStore(llvm_builder, rr, v->llvm_v);
-	} break;
-	case AK_AS_DIV: {
-		LLVMValueRef vr = LLVMBuildLoad2(llvm_builder, llvm_type(v->t), v->llvm_v, v->s);
-		LLVMValueRef rr = LLVMBuildSDiv(llvm_builder, vr, llvm_expr(C[1], st), "as_div");
-		return LLVMBuildStore(llvm_builder, rr, v->llvm_v);
-	} break;
-	case AK_AS_MOD: {
-		LLVMValueRef vr = LLVMBuildLoad2(llvm_builder, llvm_type(v->t), v->llvm_v, v->s);
-		LLVMValueRef rr = LLVMBuildSRem(llvm_builder, vr, llvm_expr(C[1], st), "as_mod");
-		return LLVMBuildStore(llvm_builder, rr, v->llvm_v);
-	} break;
-	}
-}
-
 /* Generate IR for an expression statement. */
 static LLVMValueRef llvm_stmt_expr(ast *a, syt *st) {
 
@@ -216,7 +170,7 @@ static LLVMValueRef llvm_stmt_expr(ast *a, syt *st) {
 
 /* Generate IR for a return statement. */
 static LLVMValueRef llvm_stmt_return(ast *a, syt *st) {
-	return LLVMBuildRet(llvm_builder, a->c.al > 0 ? llvm_expr(C[0], st) : NULL);
+	return LLVMBuildRet(llvm_builder, a->c.al > 0 ? llvm_expr(C[0], st, true) : NULL);
 }
 
 /* Generate IR for an if statement. */
@@ -230,7 +184,7 @@ static LLVMValueRef llvm_stmt_for(ast *a, syt *st) {
 }
 
 /* Generate IR for an expression. */
-static LLVMValueRef llvm_expr(ast *a, syt *st) {
+static LLVMValueRef llvm_expr(ast *a, syt *st, bool load) {
 	reset:; switch (A.k) {
 	case AK_BOOL: { return LLVMConstInt(llvm_type(a->t), a->v_bool, false); } break;
 	case AK_INT:  { return LLVMConstInt(llvm_type(a->t), a->v_int, false);  } break;
@@ -240,29 +194,40 @@ static LLVMValueRef llvm_expr(ast *a, syt *st) {
 	case AK_CAST: { return llvm_expr_cast(a, st); } break;
 	case AK_ID: {
 		ast *sym = syt_search(st, a->s);
-		if (sym == NULL) { note(file_name, A.ln, A.cl, 0, "Undefined variable %s", A.s); }
+		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); }
 
-		if (sym->t->k == TY_ARR) { return sym->llvm_v; }
-		
-		return LLVMBuildLoad2(llvm_builder, llvm_type(sym->t), 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: {
 		ast *sym = syt_search_h(st, C[0]->h, C[0]->s);
 		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); }
 
 		/* TODO procedure call arguments */
 
 		return LLVMBuildCall2(llvm_builder, sym->llvm_t, sym->llvm_v, NULL, 0, "");
 	} 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); }
+		
+		LLVMValueRef idc[1] = { llvm_expr(C[1], st, true) };
+		LLVMValueRef vr = LLVMBuildGEP2(llvm_builder, llvm_type(sym->t), sym->llvm_v, idc, 1, "");
+		
+		if (!load) { return vr; }
+		else { return LLVMBuildLoad2(llvm_builder, llvm_type(sym->t->base), vr, ""); }
+	}
 	case AK_OP_POS: { a = C[0]; goto reset; /* no-op */ }
 	case AK_OP_NEG: {
 		type *t = ast_type(C[0], st);
 		if (t == NULL) { note(file_name, A.ln, A.cl, -1, "Subtree is missing a type (llvm:llvm_expr)"); }
 
-		if (is_int(t)) { return LLVMBuildNeg(llvm_builder, llvm_expr(C[0], st), "neg"); }
-		else if (is_flt(t)) { return LLVMBuildFNeg(llvm_builder, llvm_expr(C[0], st), "neg"); }
+		if (is_int(t)) { return LLVMBuildNeg(llvm_builder, llvm_expr(C[0], st, true), "neg"); }
+		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_BW_NOT: {
@@ -270,30 +235,30 @@ static LLVMValueRef llvm_expr(ast *a, syt *st) {
 		if (t == NULL) { note(file_name, A.ln, A.cl, -1, "Subtree is missing a type (llvm:llvm_expr)"); }
 
 		if (is_int(t)) {
-			return LLVMBuildXor(llvm_builder, llvm_expr(C[0], st), LLVMConstInt(llvm_type(t), -1, false), "");
+			return LLVMBuildXor(llvm_builder, llvm_expr(C[0], st, true), LLVMConstInt(llvm_type(t), -1, false), "");
 		}
 		else if (is_flt(t)) {
 			/* TODO should floating point numbers be invertable? */
-			LLVMValueRef bc = LLVMBuildBitCast(llvm_builder, llvm_expr(C[0], st), LLVMIntType(t->l * 8), "");
+			LLVMValueRef bc = LLVMBuildBitCast(llvm_builder, llvm_expr(C[0], st, true), LLVMIntType(t->l * 8), "");
 			LLVMValueRef vr = LLVMBuildXor(llvm_builder, bc, LLVMConstInt(LLVMIntType(t->l * 8), -1, false), "");
 			return LLVMBuildBitCast(llvm_builder, vr, llvm_type(t), "");
 		}
 		else { note(file_name, A.ln, A.cl, -1, "Expression cannot be inverted (llvm:llvm_expr)"); }
 	} break;
 	case AK_OP_ADD: {
-		return LLVMBuildAdd(llvm_builder, llvm_expr(C[0], st), llvm_expr(C[1], st), "add");
+		return LLVMBuildAdd(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "add");
 	} break;
 	case AK_OP_SUB: {
-		return LLVMBuildSub(llvm_builder, llvm_expr(C[0], st), llvm_expr(C[1], st), "sub");
+		return LLVMBuildSub(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "sub");
 	} break;
 	case AK_OP_MUL: {
-		return LLVMBuildMul(llvm_builder, llvm_expr(C[0], st), llvm_expr(C[1], st), "mul");
+		return LLVMBuildMul(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "mul");
 	} break;
 	case AK_OP_DIV: {
-		return LLVMBuildSDiv(llvm_builder, llvm_expr(C[0], st), llvm_expr(C[1], st), "div");
+		return LLVMBuildSDiv(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "div");
 	} break;
 	case AK_OP_MOD: {
-		return LLVMBuildSRem(llvm_builder, llvm_expr(C[0], st), llvm_expr(C[1], st), "mod");
+		return LLVMBuildSRem(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "mod");
 	} break;
 	case AK_OP_ADO: {
 		ast *v = syt_search(st, C[0]->s);
@@ -306,8 +271,31 @@ static LLVMValueRef llvm_expr(ast *a, syt *st) {
 		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)"); }
 
-		return LLVMBuildLoad2(llvm_builder, llvm_type(C[0]->t->base), llvm_expr(C[0], st), "");
+		return LLVMBuildLoad2(llvm_builder, llvm_type(C[0]->t->base), llvm_expr(C[0], st, true), "");
 	}
+	case AK_ASSIGN: {
+		return LLVMBuildStore(llvm_builder, llvm_expr(C[1], st, true), llvm_expr(C[0], st, false));
+	} break;
+	case AK_AS_ADD: {
+		LLVMValueRef vr = LLVMBuildAdd(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "");
+		return LLVMBuildStore(llvm_builder, vr, llvm_expr(C[0], st, false));
+	} break;
+	case AK_AS_SUB: {
+		LLVMValueRef vr = LLVMBuildSub(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "");
+		return LLVMBuildStore(llvm_builder, vr, llvm_expr(C[0], st, false));
+	} break;
+	case AK_AS_MUL: {
+		LLVMValueRef vr = LLVMBuildMul(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "");
+		return LLVMBuildStore(llvm_builder, vr, llvm_expr(C[0], st, false));
+	} break;
+	case AK_AS_DIV: {
+		LLVMValueRef vr = LLVMBuildSDiv(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "");
+		return LLVMBuildStore(llvm_builder, vr, llvm_expr(C[0], st, false));
+	} break;
+	case AK_AS_MOD: {
+		LLVMValueRef vr = LLVMBuildSRem(llvm_builder, llvm_expr(C[0], st, true), llvm_expr(C[1], st, true), "");
+		return LLVMBuildStore(llvm_builder, vr, llvm_expr(C[0], st, false));
+	} break;
 	default: { note(file_name, A.ln, A.cl, -1, "Unhandled AST kind %s (llvm:llvm_expr)", ast_ks[a->k]); } break;
 	}
 }
@@ -326,44 +314,44 @@ static LLVMValueRef llvm_expr_cast(ast *a, syt *st) {
 
 	if (is_ptr(expr_type)) {
 		if (is_int(a->t)) {
-			return LLVMBuildPtrToInt(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "");
+			return LLVMBuildPtrToInt(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "");
 		}
 	}
 	else if (is_bool(expr_type)) {
 		if (is_int(a->t)) {
-			return LLVMBuildIntCast2(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), is_sign(a->t), "");
+			return LLVMBuildIntCast2(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), is_sign(a->t), "");
 		}
 	}
 	else if (is_int(expr_type)) {
 		if (is_bool(a->t) || is_int(a->t)) {
-			return LLVMBuildIntCast2(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), is_sign(a->t), "");
+			return LLVMBuildIntCast2(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), is_sign(a->t), "");
 		}
 		else if (is_flt(a->t)) {
 			if (is_sign(expr_type)) {
-				return LLVMBuildSIToFP(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "stof");
+				return LLVMBuildSIToFP(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "stof");
 			}
 			else {
-				return LLVMBuildUIToFP(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "utof");
+				return LLVMBuildUIToFP(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "utof");
 			}
 		}
 	}
 	else if (is_flt(expr_type)) {
 		if (is_flt(a->t)) {
-			return LLVMBuildFPCast(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "cast");
+			return LLVMBuildFPCast(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "cast");
 		}
 		else if (is_int(a->t)) {
 			if (is_sign(a->t)) {
-				return LLVMBuildFPToSI(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "ftos");
+				return LLVMBuildFPToSI(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "ftos");
 			}
 			else {
-				return LLVMBuildFPToUI(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "ftou");
+				return LLVMBuildFPToUI(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "ftou");
 			}
 		}
 	}
 	else if (expr_type->k == TY_ARR) {
 		if (is_ptr(a->t)) {
 			a->t->base = expr_type->base;
-			return LLVMBuildBitCast(llvm_builder, llvm_expr(C[1], st), llvm_type(a->t), "");
+			return LLVMBuildBitCast(llvm_builder, llvm_expr(C[1], st, true), llvm_type(a->t), "");
 		}
 	}
 
@@ -376,7 +364,7 @@ static LLVMValueRef llvm_arr(ast *a, syt *st) {
 
 	LLVMValueRef *va = calloc(CL, sizeof (LLVMValueRef));
 
-	for (UINT i = 0; i < CL; i += 1) { va[i] = llvm_expr(C[i], st); }
+	for (UINT i = 0; i < CL; i += 1) { va[i] = llvm_expr(C[i], st, true); }
 
 	return LLVMConstArray(llvm_type(C[0]->t), va, CL);
 }
@@ -388,7 +376,7 @@ static LLVMValueRef llvm_hash(ast *a, syt *st) {
 	UINT arg_count = a->c.al;
 	LLVMValueRef *args = calloc(arg_count, sizeof (LLVMValueRef));
 	for (UINT i = 0; i < arg_count; i += 1) {
-		args[i] = llvm_expr(a->c.a[i], st);
+		args[i] = llvm_expr(a->c.a[i], st, true);
 	}
 
 	LLVMTypeRef unsigned_integer_type = llvm_type(&TYPE(TY_UINT));
diff --git a/src/parse.c b/src/parse.c
index 2a9b439..923ac7c 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -27,7 +27,7 @@ char *ast_ks[] = {
 
 	"AK_ASSIGN", "AK_AS_ADD", "AK_AS_SUB", "AK_AS_MUL", "AK_AS_DIV", "AK_AS_MOD",
 
-	"AK_ID", "AK_CALL", "AK_BOOL", "AK_INT", "AK_FLT", "AK_ARR",
+	"AK_ID", "AK_CALL", "AK_BOOL", "AK_INT", "AK_FLT", "AK_ARR", "AK_SUBS",
 
 	"AK_HASH_SYSCALL"
 };
@@ -107,7 +107,11 @@ type *ast_type(ast *a, syt *st) {
 	if (a->t) { return a->t; }
 
 	/* Otherwise recurse down the first child */
-	if (a->c.al) { return ast_type(a->c.a[0], st); }
+	if (a->c.al) {
+		type *rt = ast_type(a->c.a[0], st);
+		if (a->k == AK_SUBS) { return rt->base; }
+		else { return rt; }
+	}
 
 	/* If no type is found, return NULL */
 	return NULL;
@@ -275,7 +279,7 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 	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;
 	prefix: { lex_next(l); ast_push(left, parse_expr(l, st, ast_precedence(left->k))); } break;
-	default: { note(l->n, T.ln, T.cl, -1, "Unhandled expression of kind %s", tok_ks[T.k]); } break;
+	default: { note(l->n, T.ln, T.cl, 0, "Unexpected \"%s\", was expecting an expression", tok_ks[T.k]); } break;
 	}
 
 	/* Parse an infix expression if one is present */
@@ -293,7 +297,11 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 		lex_kind(l, TK_RPAREN);
 	} break;
 	case TK_LBRACK: {
-		/* TODO array access */
+		a = ast_init(AK_SUBS, T.ln, T.cl);
+		lex_next(l); ast_push(a, left);
+		
+		ast_push(a, parse_expr(l, st, 0));
+		lex_kind(l, TK_RBRACK);
 	} break;
 	case TK_ASSIGN: { a = ast_init(AK_ASSIGN, T.ln, T.cl); } goto infix;
 	case TK_AS_ADD: { a = ast_init(AK_AS_ADD, T.ln, T.cl); } goto infix;
@@ -309,8 +317,6 @@ static ast *parse_expr(lex *l, syt *st, s32 o) {
 	infix: { lex_next(l); ast_push(a, left); ast_push(a, parse_expr(l, st, ast_precedence(a->k))); } break;
 	}
 
-	if (left == NULL) { note(l->n, T.ln, T.cl, 0, "Expected an expression"); }
-	
 	return left;
 }
 
@@ -423,7 +429,7 @@ static ast *parse_flt(lex *l, syt *st) {
 
 /*
 	Expression operator precedence:
-	8 > expression group (parenthesis), function call
+	8 > expression group (parenthesis), procedure call, array subscripting
 	7 >
 	6 > positive (prefix +), negative (prefix -), bitwise not (prefix ~), address-of (prefix &), dereference (prefix *)
 	5 >
@@ -436,7 +442,7 @@ static ast *parse_flt(lex *l, syt *st) {
 /* Get the infix precedence of a token kind. */
 static s32 tok_precedence(tok_k tk) {
 	switch (tk) {
-	case TK_LPAREN: { return 8; }
+	case TK_LPAREN: case TK_LBRACK: { return 8; }
 	case TK_OP_MUL: case TK_OP_DIV: case TK_OP_MOD: { return 4; }
 	case TK_OP_ADD: case TK_OP_SUB: { return 3; }
 	case TK_ASSIGN: case TK_AS_ADD: case TK_AS_SUB: case TK_AS_MUL: case TK_AS_DIV: case TK_AS_MOD: { return 1; }
@@ -447,7 +453,7 @@ static s32 tok_precedence(tok_k tk) {
 /* Get the precedence of an AST kind. */
 static s32 ast_precedence(ast_k ak) {
 	switch (ak) {
-	case AK_CALL: { return 8; }
+	case AK_CALL: case AK_SUBS: { return 8; }
 	case AK_OP_POS: case AK_OP_NEG: case AK_BW_NOT: case AK_OP_ADO: case AK_OP_DRF: { return 6; }
 	case AK_OP_MUL: case AK_OP_DIV: case AK_OP_MOD: { return 4; }
 	case AK_OP_ADD: case AK_OP_SUB: { return 3; }
diff --git a/src/parse.h b/src/parse.h
index d875d72..d0f7da0 100644
--- a/src/parse.h
+++ b/src/parse.h
@@ -24,7 +24,7 @@ typedef enum {
 
 	AK_ASSIGN, AK_AS_ADD, AK_AS_SUB, AK_AS_MUL, AK_AS_DIV, AK_AS_MOD,
 
-	AK_ID, AK_CALL, AK_BOOL, AK_INT, AK_FLT, AK_ARR,
+	AK_ID, AK_CALL, AK_BOOL, AK_INT, AK_FLT, AK_ARR, AK_SUBS,
 
 	AK_HASH_SYSCALL
 } ast_k;