Compare commits
	
		
			33 Commits
		
	
	
		
			84785dc3cf
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 99489dfeb6 | ||
|  | e7da8dfe38 | ||
|  | f6b74f8f97 | ||
|  | 6f91a71306 | ||
|  | 4d5cd93354 | ||
|  | 7773cc6c14 | ||
|  | 45feb3fe1d | ||
|  | ba8d2f0702 | ||
|  | befce544e7 | ||
|  | 0d5313a063 | ||
|  | 22f71d7e56 | ||
|  | a706ea6a3f | ||
|  | 0b2741f73f | ||
|  | cda56d5b9c | ||
|  | 46e7487cad | ||
|  | 18d6e7b7df | ||
|  | 1f47b5e16c | ||
|  | dfe1ac90e8 | ||
|  | d8b470f0eb | ||
|  | cf93109f1e | ||
|  | 850dafbbc9 | ||
|  | 803368a264 | ||
|  | b4c369e1d9 | ||
|  | dd67a1bf5d | ||
|  | b58810e822 | ||
|  | f02dae603d | ||
|  | 92c4c5c991 | ||
|  | 9bd4d4e0c4 | ||
| bb75b78a36 | |||
|  | ca232fbf6a | ||
|  | 6bdc4e3210 | ||
|  | d67008cfbf | ||
|  | a0842424ec | 
							
								
								
									
										5
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								Makefile
									
									
									
									
									
								
							| @@ -1,3 +1,6 @@ | ||||
| ifneq ($(OS),Windows_NT) | ||||
| CPPFLAGS = -D_POSIX_C_SOURCE=200112L | ||||
| endif | ||||
| CFLAGS  = -ggdb -std=c11 -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition | ||||
| #CFLAGS  = -pg -std=c11 -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition | ||||
| #CFLAGS  = -O3 -std=c11 -Wall -Wextra -pedantic -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition | ||||
| @@ -12,7 +15,7 @@ $(EXE): $(OBJ) | ||||
| 	$(CC) -o $@ $^ $(CFLAGS) $(LDFLAGS) | ||||
|  | ||||
| %.o: %.c | ||||
| 	$(CC) -c -o $@ $< $(CFLAGS) | ||||
| 	$(CC) -c -o $@ $< $(CPPFLAGS) $(CFLAGS) | ||||
|  | ||||
| deps.mk: $(SOURCE) $(HEADERS) | ||||
| 	@echo "# Automatically generated by $(CC) -MM." > $@ | ||||
|   | ||||
| @@ -1,3 +1 @@ | ||||
| # lang | ||||
|  | ||||
| Yet another useless programming language (a lot of stuff not yet implemented). | ||||
							
								
								
									
										21
									
								
								examples/calculator.script
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								examples/calculator.script
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| put("Enter an operation (+, -, *, /): ") | ||||
| op := getln() | ||||
| if (!(op == "+" || op == "-" || op == "*" || op == "/")) { | ||||
| 	put("Unknown operation: ") | ||||
| 	putln(op) | ||||
| } else { | ||||
| 	put("1st number: ") | ||||
| 	n1 := float(getln()) | ||||
| 	put("2nd number: ") | ||||
| 	n2 := float(getln()) | ||||
|  | ||||
| 	put("Result: ") | ||||
| 	if (op == "+") | ||||
| 		putln(n1 + n2) | ||||
| 	else if (op == "-") | ||||
| 		putln(n1 - n2) | ||||
| 	else if (op == "*") | ||||
| 		putln(n1 * n2) | ||||
| 	else if (op == "/") | ||||
| 		putln(n1 / n2) | ||||
| } | ||||
| @@ -7,6 +7,6 @@ while i { | ||||
| 	z := x + y | ||||
| 	y = x | ||||
| 	x = z | ||||
| 	print(z) | ||||
| 	putln(z) | ||||
| 	i = i - 1 | ||||
| } | ||||
							
								
								
									
										86
									
								
								examples/mandelbrot.script
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								examples/mandelbrot.script
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| width := 280 | ||||
| height := 100 | ||||
|  | ||||
| iterations := 100 | ||||
| xmin := -2.0 | ||||
| xmax := 0.5 | ||||
| ymin := -1.0 | ||||
| ymax := 1.0 | ||||
|  | ||||
| // Some further coordinates: | ||||
| /*iterations := 1000 | ||||
| xmin := -0.9072945999 | ||||
| xmax := -0.8984310833 | ||||
| ymin := 0.2304178999 | ||||
| ymax := 0.2370858666*/ | ||||
|  | ||||
| /*iterations := 100 | ||||
| xmin := -0.193596288 | ||||
| xmax := -0.119260320 | ||||
| ymin := 1.006960992 | ||||
| ymax := 1.062687264*/ | ||||
|  | ||||
| /*iterations := 800 | ||||
| xmin := -0.1675326254 | ||||
| xmax := -0.1675148625 | ||||
| ymin := 1.0413005672 | ||||
| ymax := 1.0413138086*/ | ||||
|  | ||||
| /*iterations := 918 | ||||
| xmin := -0.7506201104 | ||||
| xmax := -0.7503409687 | ||||
| ymin := 0.0170447020 | ||||
| ymax := 0.0172540583*/ | ||||
|  | ||||
| /*iterations := 400 | ||||
| xmin := -0.7548484315 | ||||
| xmax := -0.7540548595 | ||||
| ymin := 0.0530077004 | ||||
| ymax := 0.0536039518*/ | ||||
|  | ||||
| y := 0 | ||||
| while y < height { | ||||
| 	c_im := (float(height - y) / float(height)) * (ymax - ymin) + ymin | ||||
| 	x := 0 | ||||
| 	while x < width { | ||||
| 		c_re := (float(x) / float(width)) * (xmax - xmin) + xmin | ||||
|  | ||||
| 		z_re := 0.0 | ||||
| 		z_im := 0.0 | ||||
|  | ||||
| 		it := 0 | ||||
| 		loop := true | ||||
| 		while it < iterations && loop { | ||||
| 			/* z = z*z + c */ | ||||
| 			/* (a + bi)^2 = a^2 + 2abi - b^2 */ | ||||
| 			z_re_tmp := z_re * z_re - z_im * z_im + c_re | ||||
| 			z_im = 2.0 * z_re * z_im + c_im | ||||
| 			z_re = z_re_tmp | ||||
|  | ||||
| 			/* Break if the number shoots off to infinity. */ | ||||
| 			if z_re * z_re + z_im * z_im > 4.0 { | ||||
| 				loop = false | ||||
| 			} | ||||
|  | ||||
| 			it = it + 1 | ||||
| 		} | ||||
|  | ||||
| 		if it <= iterations / 5 | ||||
| 			put(' ') | ||||
| 		else if it <= iterations / 5 * 2 | ||||
| 			put('.') | ||||
| 		else if it <= iterations / 5 * 3 | ||||
| 			put(',') | ||||
| 		else if it <= iterations / 5 * 4 | ||||
| 			put('*') | ||||
| 		else if it < iterations | ||||
| 			put('+') | ||||
| 		else if it == iterations | ||||
| 			put('#') | ||||
|  | ||||
| 		x = x + 1 | ||||
| 	} | ||||
| 	put('\n') | ||||
|  | ||||
| 	y = y + 1 | ||||
| } | ||||
| @@ -13,4 +13,5 @@ while k < iterations { | ||||
| 	k = k + 1 | ||||
| } | ||||
| 
 | ||||
| print(sum) | ||||
| put("π ≈ ") | ||||
| putln(sum) | ||||
							
								
								
									
										285
									
								
								ir.c
									
									
									
									
									
								
							
							
						
						
									
										285
									
								
								ir.c
									
									
									
									
									
								
							| @@ -4,142 +4,231 @@ | ||||
| #include <stdlib.h> | ||||
|  | ||||
| const char *irinstr_str[IRInstrEnumSize] = { | ||||
| 	[IRSet] = "set", | ||||
| 	[IRNeg] = "neg", | ||||
| 	[IRAdd] = "add", | ||||
| 	[IRSub] = "sub", | ||||
| 	[IRMul] = "mul", | ||||
| 	[IRDiv] = "div", | ||||
| 	[IREq]  = "eq", | ||||
| 	[IRLt]  = "lt", | ||||
| 	[IRLe]  = "le", | ||||
| 	[IRNot] = "not", | ||||
| 	[IRJmp] = "jmp", | ||||
| 	[IRJnz] = "jnz", | ||||
| 	[IRSet]          = "set", | ||||
| 	[IRNeg]          = "neg", | ||||
| 	[IRAdd]          = "add", | ||||
| 	[IRSub]          = "sub", | ||||
| 	[IRMul]          = "mul", | ||||
| 	[IRDiv]          = "div", | ||||
| 	[IREq]           = "eq", | ||||
| 	[IRNeq]          = "neq", | ||||
| 	[IRLt]           = "lt", | ||||
| 	[IRLe]           = "le", | ||||
| 	[IRNot]          = "not", | ||||
| 	[IRAnd]          = "and", | ||||
| 	[IROr]           = "or", | ||||
| 	[IRJmp]          = "jmp", | ||||
| 	[IRJnz]          = "jnz", | ||||
| 	[IRCallInternal] = "calli", | ||||
| 	[IRAddrOf]       = "addrof", | ||||
| 	[IRArrMake]      = "mkarr", | ||||
| }; | ||||
|  | ||||
| #define IRTOKS_INIT_CAP_LONG 4096 | ||||
| #define IRTOKS_INIT_CAP_SHORT 16 | ||||
| #define IRLIST_INIT_CAP_LONG 4096 | ||||
| #define IRLIST_INIT_CAP_SHORT 16 | ||||
|  | ||||
| static void irtoks_init_with_cap(IRToks *v, size_t cap); | ||||
| static void irlist_init_with_cap(IRList *v, size_t cap); | ||||
| static IRItem *irlist_new_item(IRList *v); | ||||
|  | ||||
| static void irtoks_init_with_cap(IRToks *v, size_t cap) { | ||||
| 	v->toks = xmalloc(sizeof(IRTok) * cap); | ||||
| static void irlist_init_with_cap(IRList *v, size_t cap) { | ||||
| 	v->begin = NULL; | ||||
| 	v->end = NULL; | ||||
| 	v->p = pool_new(sizeof(IRItem) * cap); | ||||
| 	v->index = NULL; | ||||
| 	v->len = 0; | ||||
| 	v->cap = cap; | ||||
| } | ||||
|  | ||||
| void irtoks_init_long(IRToks *v) { | ||||
| 	irtoks_init_with_cap(v, IRTOKS_INIT_CAP_LONG); | ||||
|  | ||||
| static IRItem *irlist_new_item(IRList *v) { | ||||
| 	IRItem *ret = pool_alloc(v->p, sizeof(IRItem)); | ||||
| 	ret->next = NULL; | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| void irtoks_init_short(IRToks *v) { | ||||
| 	irtoks_init_with_cap(v, IRTOKS_INIT_CAP_SHORT); | ||||
| void irlist_init_long(IRList *v) { | ||||
| 	irlist_init_with_cap(v, IRLIST_INIT_CAP_LONG); | ||||
| } | ||||
|  | ||||
| void irtoks_term(IRToks *v) { | ||||
| 	for (size_t i = 0; i < v->len; i++) { | ||||
| 		if (v->toks[i].instr == IRCallInternal) | ||||
| 			free(v->toks[i].CallI.args); | ||||
| 	} | ||||
| 	free(v->toks); | ||||
| void irlist_init_short(IRList *v) { | ||||
| 	irlist_init_with_cap(v, IRLIST_INIT_CAP_SHORT); | ||||
| } | ||||
|  | ||||
| void irtoks_app(IRToks *v, IRTok t) { | ||||
| 	if (v->len+1 > v->cap) | ||||
| 		v->toks = xrealloc(v->toks, sizeof(IRTok) * (v->cap *= 2)); | ||||
| 	v->toks[v->len++] = t; | ||||
| static void free_irparam(IRParam *v, bool purge); | ||||
|  | ||||
| /* if purge is set, even statically allocated literals are freed */ | ||||
| static void free_irparam(IRParam *v, bool purge) { | ||||
| 	if (v->kind == IRParamLiteral) | ||||
| 		free_value(&v->Literal, purge); | ||||
| } | ||||
|  | ||||
| void irtoks_eat_irtoks(IRToks *v, IRToks *other, size_t jmp_offset) { | ||||
| 	if (v->len+other->len > v->cap) | ||||
| 		v->toks = xrealloc(v->toks, sizeof(IRTok) * (other->len + (v->cap *= 2))); | ||||
| 	for (size_t i = 0; i < other->len; i++) { | ||||
| 		/* correct for changed jump addresses */ | ||||
| 		if (other->toks[i].instr == IRJmp) | ||||
| 			other->toks[i].Jmp.iaddr += jmp_offset; | ||||
| 		else if (other->toks[i].instr == IRJnz) | ||||
| 			other->toks[i].CJmp.iaddr += jmp_offset; | ||||
|  | ||||
| 		v->toks[v->len++] = other->toks[i]; | ||||
| 	} | ||||
| 	/* We're not calling irtoks_term() because we don't want associated items | ||||
| 	 * (for example function arguments) to get deallocated as well. */ | ||||
| 	free(other->toks); | ||||
| } | ||||
|  | ||||
| static void print_val(const Value *v); | ||||
| static void print_irparam(const IRParam *p); | ||||
|  | ||||
| static void print_val(const Value *v) { | ||||
| 	switch (v->type.kind) { | ||||
| 		case TypeFloat: | ||||
| 			printf("%f", v->Float); | ||||
| 			break; | ||||
| 		case TypeInt: | ||||
| 			printf("%zd", v->Int); | ||||
| 			break; | ||||
| 		case TypeBool: | ||||
| 			printf("%s", v->Bool ? "true" : "false"); | ||||
| 			break; | ||||
| 		default: | ||||
| 			printf("(unknown type)"); | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void print_irparam(const IRParam *p) { | ||||
| 	if (p->kind == IRParamLiteral) { | ||||
| 		print_val(&p->Literal); | ||||
| 	} else if (p->kind == IRParamAddr) { | ||||
| 		printf("%%%zd", p->Addr); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void print_ir(IRToks *v, const BuiltinFunc *builtin_funcs) { | ||||
| 	for (size_t i = 0; i < v->len; i++) { | ||||
| 		printf("%04zx ", i); | ||||
| 		printf("%s", irinstr_str[v->toks[i].instr]); | ||||
| 		switch (v->toks[i].instr) { | ||||
| void irlist_term(IRList *v) { | ||||
| 	for (IRItem *i = v->begin; i; i = i->next) { | ||||
| 		switch (i->tok.instr) { | ||||
| 			case IRSet: | ||||
| 			case IRNeg: | ||||
| 				printf(" %%%zx ", v->toks[i].Unary.addr); | ||||
| 				print_irparam(&v->toks[i].Unary.val); | ||||
| 			case IRNot: | ||||
| 			case IRAddrOf: | ||||
| 				free_irparam(&i->tok.Unary.val, true); | ||||
| 				break; | ||||
| 			case IRAdd: | ||||
| 			case IRSub: | ||||
| 			case IRDiv: | ||||
| 			case IRMul: | ||||
| 			case IREq: | ||||
| 			case IRNeq: | ||||
| 			case IRLt: | ||||
| 			case IRLe: | ||||
| 				printf(" %%%zx ", v->toks[i].Binary.addr); | ||||
| 				print_irparam(&v->toks[i].Binary.lhs); | ||||
| 				printf(" "); | ||||
| 				print_irparam(&v->toks[i].Binary.rhs); | ||||
| 			case IRAnd: | ||||
| 			case IROr: | ||||
| 				free_irparam(&i->tok.Binary.lhs, true); | ||||
| 				free_irparam(&i->tok.Binary.rhs, true); | ||||
| 				break; | ||||
| 			case IRJmp: | ||||
| 				printf(" %zx", v->toks[i].Jmp.iaddr); | ||||
| 				break; | ||||
| 			case IRJnz: | ||||
| 				free_irparam(&i->tok.CJmp.condition, true); | ||||
| 				break; | ||||
| 			case IRCallInternal: | ||||
| 				for (size_t j = 0; j < i->tok.CallI.n_args; j++) | ||||
| 					free_irparam(&i->tok.CallI.args[j], true); | ||||
| 				free(i->tok.CallI.args); | ||||
| 				break; | ||||
| 			case IRArrMake: | ||||
| 				for (size_t j = 0; j < i->tok.ArrMake.len; j++) | ||||
| 					free_irparam(&i->tok.ArrMake.vals[j], true); | ||||
| 				free(i->tok.ArrMake.vals); | ||||
| 				break; | ||||
| 			default: | ||||
| 				ASSERT_UNREACHED(); | ||||
| 		} | ||||
| 	} | ||||
| 	pool_term(v->p); | ||||
| } | ||||
|  | ||||
| void irlist_app(IRList *v, IRTok t) { | ||||
| 	v->index = NULL; /* invalidate index */ | ||||
| 	IRItem *itm = irlist_new_item(v); | ||||
| 	itm->tok = t; | ||||
|  | ||||
| 	if (!v->begin && !v->end) | ||||
| 		v->begin = v->end = itm; | ||||
| 	else { | ||||
| 		v->end->next = itm; | ||||
| 		v->end = itm; | ||||
| 	} | ||||
|  | ||||
| 	v->len++; | ||||
| } | ||||
|  | ||||
| void irlist_eat_irlist(IRList *v, IRList *other) { | ||||
| 	v->index = NULL; /* invalidate index */ | ||||
| 	size_t jmp_offset = v->len-1; | ||||
| 	for (IRItem *i = other->begin; i; i = i->next) { | ||||
| 		/* correct for changed jump addresses */ | ||||
| 		if (i->tok.instr == IRJmp) | ||||
| 			i->tok.Jmp.iaddr += jmp_offset; | ||||
| 		else if (i->tok.instr == IRJnz) | ||||
| 			i->tok.CJmp.iaddr += jmp_offset; | ||||
|  | ||||
| 		irlist_app(v, i->tok); | ||||
| 	} | ||||
| 	/* We're not calling irlist_term() because we don't want associated items | ||||
| 	 * (for example function arguments) to get deallocated as well. */ | ||||
| 	pool_term(other->p); | ||||
| } | ||||
|  | ||||
| void irlist_update_index(IRList *v) { | ||||
| 	if (v->index) | ||||
| 		return; | ||||
| 	v->index = pool_alloc(v->p, sizeof(size_t) * v->len); | ||||
| 	size_t num_idx = 0; | ||||
| 	for (IRItem *i = v->begin; i; i = i->next, num_idx++) | ||||
| 		v->index[num_idx] = i; | ||||
| } | ||||
|  | ||||
| static void print_irparam(const IRParam *p); | ||||
|  | ||||
| static void print_irparam(const IRParam *p) { | ||||
| 	if (p->kind == IRParamLiteral) { | ||||
| 		print_value(&p->Literal, false); | ||||
| 	} else if (p->kind == IRParamAddr) { | ||||
| 		printf("%%%zd", p->Addr); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void print_ir(IRList *v, const BuiltinFunc *builtin_funcs) { | ||||
| 	size_t iaddr = 0; | ||||
| 	for (IRItem *i = v->begin; i; i = i->next, iaddr++) { | ||||
| 		printf("%04zx ", iaddr); | ||||
| 		printf("%s", irinstr_str[i->tok.instr]); | ||||
| 		switch (i->tok.instr) { | ||||
| 			case IRSet: | ||||
| 			case IRNeg: | ||||
| 			case IRNot: | ||||
| 			case IRAddrOf: | ||||
| 				printf(" %%%zx ", i->tok.Unary.addr); | ||||
| 				print_irparam(&i->tok.Unary.val); | ||||
| 				break; | ||||
| 			case IRAdd: | ||||
| 			case IRSub: | ||||
| 			case IRDiv: | ||||
| 			case IRMul: | ||||
| 			case IREq: | ||||
| 			case IRNeq: | ||||
| 			case IRLt: | ||||
| 			case IRLe: | ||||
| 			case IRAnd: | ||||
| 			case IROr: | ||||
| 				printf(" %%%zx ", i->tok.Binary.addr); | ||||
| 				print_irparam(&i->tok.Binary.lhs); | ||||
| 				printf(" "); | ||||
| 				print_irparam(&i->tok.Binary.rhs); | ||||
| 				break; | ||||
| 			case IRJmp: | ||||
| 				printf(" %zx", i->tok.Jmp.iaddr); | ||||
| 				break; | ||||
| 			case IRJnz: | ||||
| 				printf(" "); | ||||
| 				print_irparam(&v->toks[i].CJmp.condition); | ||||
| 				printf(" %zx", v->toks[i].CJmp.iaddr); | ||||
| 				print_irparam(&i->tok.CJmp.condition); | ||||
| 				printf(" %zx", i->tok.CJmp.iaddr); | ||||
| 				break; | ||||
| 			case IRCallInternal: { | ||||
| 				const BuiltinFunc *f = &builtin_funcs[v->toks[i].CallI.fid]; | ||||
| 				printf(" %s %%%zx", f->name, v->toks[i].CallI.ret_addr); | ||||
| 				for (size_t j = 0; j < f->n_args; j++) { | ||||
| 				const BuiltinFunc *f = &builtin_funcs[i->tok.CallI.fid]; | ||||
| 				if (f->returns) | ||||
| 					printf(" %%%zx", i->tok.CallI.ret_addr); | ||||
| 				printf(" %s", f->name); | ||||
| 				for (size_t j = 0; j < i->tok.CallI.n_args; j++) { | ||||
| 					printf(" "); | ||||
| 					print_irparam(&v->toks[i].CallI.args[j]); | ||||
| 					print_irparam(&i->tok.CallI.args[j]); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			default: ASSERT_UNREACHED(); break; | ||||
| 			case IRArrMake: { | ||||
| 				printf(" %%%zx", i->tok.ArrMake.arr_addr); | ||||
| 				for (size_t j = 0; j < i->tok.ArrMake.len; j++) { | ||||
| 					printf(" "); | ||||
| 					print_irparam(&i->tok.ArrMake.vals[j]); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			default: ASSERT_UNREACHED(); | ||||
| 		} | ||||
| 		printf(" ; %zu:%zu", v->toks[i].ln, v->toks[i].col); | ||||
| 		printf(" ; %zu:%zu", i->tok.ln, i->tok.col); | ||||
| 		printf("\n"); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void optimize_ir(IRList *v) { | ||||
| 	irlist_update_index(v); | ||||
| 	for (IRItem *i = v->begin; i; i = i->next) { | ||||
| 		switch (i->tok.instr) { | ||||
| 			case IRJmp: { | ||||
| 				/* resolve jump chains (esp. produced by if-else-if... statements) */ | ||||
| 				size_t ja = i->tok.Jmp.iaddr; | ||||
| 				while (ja < v->len && v->index[ja]->tok.instr == IRJmp) | ||||
| 					ja = v->index[ja]->tok.Jmp.iaddr; | ||||
| 				i->tok.Jmp.iaddr = ja; | ||||
| 			} | ||||
| 			default: break; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
							
								
								
									
										70
									
								
								ir.h
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								ir.h
									
									
									
									
									
								
							| @@ -4,11 +4,33 @@ | ||||
| #include "tok.h" | ||||
|  | ||||
| typedef struct BuiltinFunc { | ||||
| 	char *name; | ||||
| 	enum { | ||||
| 		FuncFixedArgs, | ||||
| 		FuncVarArgs, | ||||
| 	} kind; | ||||
|  | ||||
| 	bool returns : 1; | ||||
| 	bool side_effects : 1; | ||||
| 	size_t n_args; | ||||
| 	Value (*func)(Value *args); | ||||
| 	char *name; | ||||
| 	size_t fid; /* function ID, assigned automatically */ | ||||
|  | ||||
| 	union { | ||||
| 		struct { | ||||
| 			size_t n_args; | ||||
| 			union { | ||||
| 				struct { Value (*func)(Value *args); } WithRet; | ||||
| 				struct { void  (*func)(Value *args); } NoRet; | ||||
| 			}; | ||||
| 		} FixedArgs; | ||||
|  | ||||
| 		struct { | ||||
| 			size_t min_args; | ||||
| 			union { | ||||
| 				struct { Value (*func)(size_t extra_args, Value *args); } WithRet; | ||||
| 				struct { void  (*func)(size_t extra_args, Value *args); } NoRet; | ||||
| 			}; | ||||
| 		} VarArgs; | ||||
| 	}; | ||||
| } BuiltinFunc; | ||||
|  | ||||
| enum IRInstr { | ||||
| @@ -19,12 +41,17 @@ enum IRInstr { | ||||
| 	IRMul, | ||||
| 	IRDiv, | ||||
| 	IREq, | ||||
| 	IRNeq, | ||||
| 	IRLt, | ||||
| 	IRLe, | ||||
| 	IRNot, | ||||
| 	IRAnd, | ||||
| 	IROr, | ||||
| 	IRJmp, | ||||
| 	IRJnz, | ||||
| 	IRCallInternal, | ||||
| 	IRAddrOf, | ||||
| 	IRArrMake, | ||||
| 	IRInstrEnumSize, | ||||
| }; | ||||
| typedef enum IRInstr IRInstr; | ||||
| @@ -77,22 +104,39 @@ typedef struct IRTok { | ||||
| 		struct { | ||||
| 			size_t ret_addr; | ||||
| 			size_t fid; | ||||
| 			size_t n_args; | ||||
| 			IRParam *args; | ||||
| 		} CallI; | ||||
|  | ||||
| 		struct { | ||||
| 			size_t arr_addr; | ||||
| 			size_t len, cap; | ||||
| 			IRParam *vals; | ||||
| 		} ArrMake; | ||||
| 	}; | ||||
| } IRTok; | ||||
|  | ||||
| typedef struct IRToks { | ||||
| 	size_t len, cap; | ||||
| 	IRTok *toks; | ||||
| } IRToks; | ||||
| typedef struct IRItem { | ||||
| 	struct IRItem *next; | ||||
| 	IRTok tok; | ||||
| } IRItem; | ||||
|  | ||||
| void irtoks_init_long(IRToks *v); | ||||
| void irtoks_init_short(IRToks *v); | ||||
| void irtoks_term(IRToks *v); | ||||
| void irtoks_app(IRToks *v, IRTok t); | ||||
| void irtoks_eat_irtoks(IRToks *v, IRToks *other, size_t jmp_offset); | ||||
| typedef struct IRList { | ||||
| 	IRItem *begin, *end; | ||||
| 	Pool *p; | ||||
| 	IRItem **index; /* index to pointer, irlist_update_index() must be called before use */ | ||||
| 	size_t len; | ||||
| } IRList; | ||||
|  | ||||
| void print_ir(IRToks *v, const BuiltinFunc *builtin_funcs); | ||||
| void irlist_init_long(IRList *v); | ||||
| void irlist_init_short(IRList *v); | ||||
| void irlist_term(IRList *v); | ||||
| void irlist_app(IRList *v, IRTok t); | ||||
| void irlist_eat_irlist(IRList *v, IRList *other); | ||||
| void irlist_update_index(IRList *v); /* should be used very conservatively */ | ||||
|  | ||||
| void print_ir(IRList *v, const BuiltinFunc *builtin_funcs); | ||||
|  | ||||
| void optimize_ir(IRList *v); | ||||
|  | ||||
| #endif /* IR_H */ | ||||
|   | ||||
							
								
								
									
										131
									
								
								lex.c
									
									
									
									
									
								
							
							
						
						
									
										131
									
								
								lex.c
									
									
									
									
									
								
							| @@ -2,6 +2,8 @@ | ||||
|  | ||||
| #include "util.h" | ||||
|  | ||||
| #define TAB_WIDTH 4 | ||||
|  | ||||
| typedef struct Pos { | ||||
| 	size_t ln, col;     /* current position */ | ||||
| 	size_t m_ln, m_col; /* marked position */ | ||||
| @@ -11,12 +13,15 @@ static void consume(Pos *p, char c); | ||||
| static void emit(TokList *toks, const Pos *p, Tok t); | ||||
| static void mark(Pos *p); | ||||
| static void mark_err(const Pos *p); | ||||
| static char get_esc_char(char c); | ||||
|  | ||||
| static void consume(Pos *p, char c) { | ||||
| 	if (c == '\n') { | ||||
| 		p->ln++; | ||||
| 		p->col = 1; | ||||
| 	} else | ||||
| 	} else if (c == '\t') | ||||
| 		p->col += TAB_WIDTH; | ||||
| 	else | ||||
| 		p->col++; | ||||
| } | ||||
|  | ||||
| @@ -36,6 +41,23 @@ static void mark_err(const Pos *p) { | ||||
| 	err_col = p->m_col; | ||||
| } | ||||
|  | ||||
| static char get_esc_char(char c) { | ||||
| 	switch(c) { | ||||
| 		case 'a':  return '\a'; | ||||
| 		case 'b':  return '\b'; | ||||
| 		case 'e':  return '\033'; | ||||
| 		case 'f':  return '\f'; | ||||
| 		case 'n':  return '\n'; | ||||
| 		case 'r':  return '\r'; | ||||
| 		case 't':  return '\t'; | ||||
| 		case 'v':  return '\v'; | ||||
| 		case '\\': return '\\'; | ||||
| 		case '\'': return '\''; | ||||
| 		case '"':  return '\"'; | ||||
| 		default:   return 0; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TokList lex(const char *s) { | ||||
| 	TokList toks; | ||||
| 	toklist_init(&toks); | ||||
| @@ -59,9 +81,9 @@ TokList lex(const char *s) { | ||||
| 			else if (streq_0_n("while", start, i)) | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokWhile }); | ||||
| 			else if (streq_0_n("true", start, i)) | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = { .kind = TypeBool, }, .Bool = true, }, }); | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = TypeBool, .Bool = true }}); | ||||
| 			else if (streq_0_n("false", start, i)) | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = { .kind = TypeBool, }, .Bool = false, }, }); | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = TypeBool, .Bool = false }}); | ||||
| 			else { | ||||
| 				emit(&toks, &pos, (Tok){ | ||||
| 						.kind = TokIdent, | ||||
| @@ -124,9 +146,7 @@ TokList lex(const char *s) { | ||||
| 				emit(&toks, &pos, (Tok){ | ||||
| 						.kind = TokVal, | ||||
| 						.Val = { | ||||
| 							.type = { | ||||
| 								.kind = TypeFloat, | ||||
| 							}, | ||||
| 							.type = TypeFloat, | ||||
| 							.Float = num, | ||||
| 						}, | ||||
| 					}); | ||||
| @@ -142,9 +162,7 @@ TokList lex(const char *s) { | ||||
| 				emit(&toks, &pos, (Tok){ | ||||
| 						.kind = TokVal, | ||||
| 						.Val = { | ||||
| 							.type = { | ||||
| 								.kind = TypeInt, | ||||
| 							}, | ||||
| 							.type = TypeInt, | ||||
| 							.Int = num, | ||||
| 						}, | ||||
| 					}); | ||||
| @@ -200,15 +218,41 @@ TokList lex(const char *s) { | ||||
| 					continue; | ||||
| 				} | ||||
| 				break; | ||||
| 			case '&': | ||||
| 				consume(&pos, *(s++)); | ||||
| 				if (s[0] == '&') | ||||
| 					emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpAnd }); | ||||
| 				else { | ||||
| 					emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpAddrOf }); | ||||
| 					continue; | ||||
| 				} | ||||
| 				break; | ||||
| 			case '!': | ||||
| 				consume(&pos, *(s++)); | ||||
| 				if (s[0] == '=') | ||||
| 					emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpNeq }); | ||||
| 				else { | ||||
| 					emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpNot }); | ||||
| 					continue; | ||||
| 				} | ||||
| 				break; | ||||
| 			case '|': | ||||
| 				consume(&pos, *(s++)); | ||||
| 				if (s[0] == '|') | ||||
| 					emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpOr }); | ||||
| 				else | ||||
| 					continue; | ||||
| 				break; | ||||
| 			case '{': | ||||
| 			case '}': | ||||
| 			case '(': | ||||
| 			case ')': | ||||
| 			case '[': | ||||
| 			case ']': | ||||
| 			case ',': | ||||
| 			case '+': | ||||
| 			case '-': | ||||
| 			case '*': | ||||
| 			case '!': | ||||
| 				emit(&toks, &pos, (Tok){ | ||||
| 						.kind = TokOp, | ||||
| 						.Op = s[0], | ||||
| @@ -248,6 +292,73 @@ TokList lex(const char *s) { | ||||
| 						}); | ||||
| 				} | ||||
| 				continue; | ||||
| 			case '\'': { | ||||
| 				consume(&pos, *(s++)); | ||||
| 				char c = s[0]; | ||||
| 				if (c == '\\') { | ||||
| 					consume(&pos, *(s++)); | ||||
| 					c = get_esc_char(s[0]); | ||||
| 					if (!c) { | ||||
| 						set_err("Unrecognized escape sequence: '\\%c'", s[0]); | ||||
| 						return toks; | ||||
| 					} | ||||
| 				} | ||||
| 				consume(&pos, *(s++)); | ||||
| 				if (s[0] != '\'') { | ||||
| 					set_err("Unclosed char literal"); | ||||
| 					return toks; | ||||
| 				} | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = TypeChar, .Char = c }}); | ||||
| 				break; | ||||
| 			} | ||||
| 			case '"': { | ||||
| 				consume(&pos, *(s++)); | ||||
| 				const char *start = s; | ||||
| 				Pos start_pos = pos; | ||||
| 				size_t size = 0; | ||||
|  | ||||
| 				/* count the string size before allocating */ | ||||
| 				while (s[0] != '"') { | ||||
| 					if (!s[0]) { | ||||
| 						set_err("Unexpected EOF in string literal"); | ||||
| 						return toks; | ||||
| 					} else if (s[0] == '\\') | ||||
| 						consume(&pos, *(s++)); | ||||
| 					consume(&pos, *(s++)); | ||||
| 					size++; | ||||
| 				} | ||||
|  | ||||
| 				/* go through the actual string */ | ||||
| 				s = start; | ||||
| 				pos = start_pos; | ||||
| 				char *str = xmalloc(size); | ||||
| 				for (size_t i = 0; i < size; i++) { | ||||
| 					char c = s[0]; | ||||
| 					if (c == '\\') { | ||||
| 						consume(&pos, *(s++)); | ||||
| 						c = get_esc_char(s[0]); | ||||
| 						if (!c) { | ||||
| 							set_err("Unrecognized escape sequence: '\\%c'", s[0]); | ||||
| 							free(str); | ||||
| 							return toks; | ||||
| 						} | ||||
| 					} | ||||
| 					consume(&pos, *(s++)); | ||||
| 					str[i] = c; | ||||
| 				} | ||||
| 				emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { | ||||
| 						.type = TypeArr, | ||||
| 						.Arr = { | ||||
| 							.is_string = true, | ||||
| 							.dynamically_allocated = false, | ||||
| 							.type = TypeChar, | ||||
| 							.vals = str, | ||||
| 							.len = size, | ||||
| 							.cap = size, | ||||
| 						}, | ||||
| 					},}); | ||||
| 				break; | ||||
| 			} | ||||
| 			default: | ||||
| 				set_err("Unrecognized character: '%c'", s[0]); | ||||
| 				return toks; | ||||
|   | ||||
							
								
								
									
										189
									
								
								main.c
									
									
									
									
									
								
							
							
						
						
									
										189
									
								
								main.c
									
									
									
									
									
								
							| @@ -8,6 +8,7 @@ | ||||
| #include "ir.h" | ||||
| #include "lex.h" | ||||
| #include "parse.h" | ||||
| #include "runtime.h" | ||||
| #include "util.h" | ||||
| #include "vm.h" | ||||
|  | ||||
| @@ -33,27 +34,42 @@ static void die(const char *fmt, ...) { | ||||
| 	exit(1); | ||||
| } | ||||
|  | ||||
| static Value fn_print(Value *args) { | ||||
| 	switch (args[0].type.kind) { | ||||
| 		case TypeVoid:  printf("(void)\n");                              break; | ||||
| 		case TypeFloat: printf("%f\n", args[0].Float);                   break; | ||||
| 		case TypeInt:   printf("%zd\n", args[0].Int);                    break; | ||||
| 		case TypeBool:  printf("%s\n", args[0].Bool ? "true" : "false"); break; | ||||
| 		default: | ||||
| 			ASSERT_UNREACHED(); | ||||
| static void fn_put(size_t extra_args, Value *args) { | ||||
| 	for (size_t i = 0;; i++) { | ||||
| 		print_value(&args[i], true); | ||||
| 		if (i+1 >= extra_args) | ||||
| 			break; | ||||
| 		printf(" "); | ||||
| 	} | ||||
| 	return (Value){0}; | ||||
| } | ||||
|  | ||||
| static void fn_putln(size_t extra_args, Value *args) { | ||||
| 	fn_put(extra_args, args); | ||||
| 	printf("\n"); | ||||
| } | ||||
|  | ||||
| static Value fn_int(Value *args) { | ||||
| 	Value ret = { | ||||
| 		.type.kind = TypeInt, | ||||
| 		.type = TypeInt, | ||||
| 		.Int = 0, | ||||
| 	}; | ||||
| 	switch (args[0].type.kind) { | ||||
| 		case TypeVoid: break; | ||||
| 	switch (args[0].type) { | ||||
| 		case TypeVoid:  break; | ||||
| 		case TypeFloat: ret.Int = (ssize_t)args[0].Float; break; | ||||
| 		case TypeInt:   ret.Int = args[0].Int;            break; | ||||
| 		case TypeBool:  ret.Int = (ssize_t)args[0].Bool;  break; | ||||
| 		case TypeChar:  ret.Int = (ssize_t)args[0].Char;  break; | ||||
| 		case TypeArr: | ||||
| 			if (args[0].Arr.is_string && args[0].Arr.type == TypeChar) { | ||||
| 				ssize_t endpos; | ||||
| 				ret.Int = stoimax((char*)args[0].Arr.vals, args[0].Arr.len, 10, &endpos); | ||||
| 				if (endpos != -1) { | ||||
| 					set_err("Error converting from string to int"); | ||||
| 					return (Value){0}; | ||||
| 				} | ||||
| 			} else | ||||
| 				ASSERT_UNREACHED(); | ||||
| 			break; | ||||
| 		default: ASSERT_UNREACHED(); | ||||
| 	} | ||||
| 	return ret; | ||||
| @@ -61,29 +77,138 @@ static Value fn_int(Value *args) { | ||||
|  | ||||
| static Value fn_float(Value *args) { | ||||
| 	Value ret = { | ||||
| 		.type.kind = TypeFloat, | ||||
| 		.type = TypeFloat, | ||||
| 		.Float = 0.0, | ||||
| 	}; | ||||
| 	switch (args[0].type.kind) { | ||||
| 		case TypeVoid: break; | ||||
| 		case TypeFloat: ret.Float = args[0].Float;       break; | ||||
| 		case TypeInt:   ret.Float = (double)args[0].Int; break; | ||||
| 	switch (args[0].type) { | ||||
| 		case TypeVoid:  break; | ||||
| 		case TypeFloat: ret.Float = args[0].Float;        break; | ||||
| 		case TypeInt:   ret.Float = (double)args[0].Int;  break; | ||||
| 		case TypeBool:  ret.Float = (double)args[0].Bool; break; | ||||
| 		case TypeChar:  ret.Float = (double)args[0].Char; break; | ||||
| 		case TypeArr: | ||||
| 			if (args[0].Arr.is_string && args[0].Arr.type == TypeChar) { | ||||
| 				ssize_t endpos; | ||||
| 				ret.Float = stod((char*)args[0].Arr.vals, args[0].Arr.len, &endpos); | ||||
| 				if (endpos != -1) { | ||||
| 					set_err("Error converting from string to float"); | ||||
| 					return (Value){0}; | ||||
| 				} | ||||
| 			} else | ||||
| 				ASSERT_UNREACHED(); | ||||
| 			break; | ||||
| 		default: ASSERT_UNREACHED(); | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| static Value fn_bool(Value *args) { | ||||
| 	return (Value){ .type = TypeBool, .Bool = is_nonzero(&args[0]) }; | ||||
| } | ||||
|  | ||||
| static Value fn_char(Value *args) { | ||||
| 	Value ret = { | ||||
| 		.type = TypeChar, | ||||
| 		.Float = 0.0, | ||||
| 	}; | ||||
| 	switch (args[0].type) { | ||||
| 		case TypeVoid:  break; | ||||
| 		case TypeFloat: ret.Char = (char)args[0].Float; break; | ||||
| 		case TypeInt:   ret.Char = (char)args[0].Int;   break; | ||||
| 		case TypeBool:  ret.Char = (char)args[0].Bool;  break; | ||||
| 		case TypeChar:  ret.Char = args[0].Char;        break; | ||||
| 		default: ASSERT_UNREACHED(); | ||||
| 	} | ||||
| 	return ret; | ||||
| } | ||||
|  | ||||
| static Value fn_ptr(Value *args) { | ||||
| 	(void)args; | ||||
| 	return (Value){ .type = TypePtr, .Ptr = { .type = TypeVoid, .val = NULL }}; | ||||
| } | ||||
|  | ||||
| static Value fn_string(Value *args) { | ||||
| 	char *res = xmalloc(64); | ||||
| 	size_t len; | ||||
|  | ||||
| 	switch (args[0].type) { | ||||
| 		case TypeVoid:  strcpy(res, "(void)"); len = 6;               break; | ||||
| 		case TypeFloat: len = snprintf(res, 64, "%f", args[0].Float); break; | ||||
| 		case TypeInt:   len = snprintf(res, 64, "%zd", args[0].Int);  break; | ||||
| 		case TypeBool: | ||||
| 			if (args[0].Bool) { | ||||
| 				strcpy(res, "true"); | ||||
| 				len = 4; | ||||
| 			} else { | ||||
| 				strcpy(res, "false"); | ||||
| 				len = 5; | ||||
| 			} | ||||
| 			break; | ||||
| 		case TypeChar:  res[0] = args[0].Char; len = 1;               break; | ||||
| 		default: ASSERT_UNREACHED(); | ||||
| 	} | ||||
|  | ||||
| 	return (Value){ | ||||
| 		.type = TypeArr, | ||||
| 		.Arr = { | ||||
| 			.is_string = true, | ||||
| 			.dynamically_allocated = true, | ||||
| 			.type = TypeChar, | ||||
| 			.vals = res, | ||||
| 			.len = len, | ||||
| 			.cap = 64, | ||||
| 		}, | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| static Value fn_pow(Value *args) { | ||||
| 	if (!(args[0].type.kind == TypeFloat && args[1].type.kind == TypeFloat)) { | ||||
| 	if (!(args[0].type == TypeFloat && args[1].type == TypeFloat)) { | ||||
| 		set_err("pow() requires arguments of type float"); | ||||
| 		return (Value){0}; | ||||
| 	} | ||||
| 	return (Value) { | ||||
| 		.type.kind = TypeFloat, | ||||
| 	return (Value){ | ||||
| 		.type = TypeFloat, | ||||
| 		.Float = pow(args[0].Float, args[1].Float), | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| static void fn_sleep(Value *args) { | ||||
| 	if (!(args[0].type == TypeFloat && args[0].Float >= 0.0)) { | ||||
| 		set_err("sleep() requires a positive float"); | ||||
| 		return; | ||||
| 	} | ||||
| 	sleep_secs(args[0].Float); | ||||
| } | ||||
|  | ||||
| static Value fn_getln(Value *args) { | ||||
| 	(void)args; | ||||
|  | ||||
| 	char *line = xmalloc(64); | ||||
| 	size_t len = 0, cap = 64; | ||||
| 	for (;;) { | ||||
| 		int c = fgetc(stdin); | ||||
| 		if (c == EOF) | ||||
| 			break; | ||||
| 		else if (c == '\n') | ||||
| 			break; | ||||
| 		if (len+1 > cap) | ||||
| 			line = xrealloc(line, (cap *= 2)); | ||||
| 		line[len++] = c; | ||||
| 	} | ||||
|  | ||||
| 	return (Value){ | ||||
| 		.type = TypeArr, | ||||
| 		.Arr = { | ||||
| 			.is_string = true, | ||||
| 			.dynamically_allocated = true, | ||||
| 			.type = TypeChar, | ||||
| 			.vals = line, | ||||
| 			.len = len, | ||||
| 			.cap = cap, | ||||
| 		}, | ||||
| 	}; | ||||
| } | ||||
|  | ||||
| int main(int argc, const char **argv) { | ||||
| 	/* parse arguments */ | ||||
| 	size_t nargs = argc - 1; | ||||
| @@ -141,29 +266,37 @@ int main(int argc, const char **argv) { | ||||
| 		print_toks(&tokens); | ||||
| 	/* parse tokens into IR code */ | ||||
| 	BuiltinFunc funcs[] = { | ||||
| 		{ .name = "print", .side_effects = true,  .n_args = 1, .func = fn_print, }, | ||||
| 		{ .name = "int",   .side_effects = false, .n_args = 1, .func = fn_int,   }, | ||||
| 		{ .name = "float", .side_effects = false, .n_args = 1, .func = fn_float, }, | ||||
| 		{ .name = "pow",   .side_effects = false, .n_args = 2, .func = fn_pow,   }, | ||||
| 		{ .name = "put",    .kind = FuncVarArgs,   .returns = false, .side_effects = true,  .VarArgs   = { .min_args = 0, .NoRet.func   = fn_put,    }}, | ||||
| 		{ .name = "putln",  .kind = FuncVarArgs,   .returns = false, .side_effects = true,  .VarArgs   = { .min_args = 0, .NoRet.func   = fn_putln,  }}, | ||||
| 		{ .name = "int",    .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 1,   .WithRet.func = fn_int,    }}, | ||||
| 		{ .name = "float",  .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 1,   .WithRet.func = fn_float,  }}, | ||||
| 		{ .name = "bool",   .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 1,   .WithRet.func = fn_bool,   }}, | ||||
| 		{ .name = "char",   .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 1,   .WithRet.func = fn_char,   }}, | ||||
| 		{ .name = "ptr",    .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 0,   .WithRet.func = fn_ptr,    }}, | ||||
| 		{ .name = "string", .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 1,   .WithRet.func = fn_string, }}, | ||||
| 		{ .name = "pow",    .kind = FuncFixedArgs, .returns = true,  .side_effects = false, .FixedArgs = { .n_args = 2,   .WithRet.func = fn_pow,    }}, | ||||
| 		{ .name = "sleep",  .kind = FuncFixedArgs, .returns = false, .side_effects = true,  .FixedArgs = { .n_args = 1,   .NoRet.func   = fn_sleep,  }}, | ||||
| 		{ .name = "getln",  .kind = FuncFixedArgs, .returns = true,  .side_effects = true,  .FixedArgs = { .n_args = 0,   .WithRet.func = fn_getln,  }}, | ||||
| 	}; | ||||
| 	IRToks ir = parse(&tokens, funcs, sizeof(funcs) / sizeof(funcs[0])); | ||||
| 	IRList ir = parse(&tokens, funcs, sizeof(funcs) / sizeof(funcs[0])); | ||||
| 	if (err) { | ||||
| 		irtoks_term(&ir); | ||||
| 		irlist_term(&ir); | ||||
| 		toklist_term(&tokens); | ||||
| 		fprintf(stderr, C_IRED "Parser error" C_RESET " in " C_CYAN "%s" C_RESET ":%zu:%zu: %s\n", filename, err_ln, err_col, errbuf); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	toklist_term(&tokens); | ||||
| 	optimize_ir(&ir); | ||||
| 	if (opt_emit_ir) | ||||
| 		print_ir(&ir, funcs); | ||||
| 	/* run the IR */ | ||||
| 	if (!opt_dry) { | ||||
| 		run(&ir, funcs); | ||||
| 		if (err) { | ||||
| 			irtoks_term(&ir); | ||||
| 			irlist_term(&ir); | ||||
| 			fprintf(stderr, C_IRED "Runtime error" C_RESET " in " C_CYAN "%s" C_RESET ":%zu:%zu: %s\n", filename, err_ln, err_col, errbuf); | ||||
| 			return 1; | ||||
| 		} | ||||
| 	} | ||||
| 	irtoks_term(&ir); | ||||
| 	irlist_term(&ir); | ||||
| } | ||||
|   | ||||
							
								
								
									
										432
									
								
								parse.c
									
									
									
									
									
								
							
							
						
						
									
										432
									
								
								parse.c
									
									
									
									
									
								
							| @@ -5,8 +5,6 @@ | ||||
| #include "map.h" | ||||
| #include "runtime.h" | ||||
|  | ||||
| static BuiltinFunc *bf; | ||||
|  | ||||
| typedef struct Scope { | ||||
| 	struct Scope *parent; | ||||
| 	size_t mem_addr; | ||||
| @@ -32,10 +30,13 @@ static size_t get_ident_addr(const Scope *sc, const char *name, const Tok *errpo | ||||
| static IRParam tok_to_irparam(Scope *sc, Tok *t); | ||||
| static Scope make_scope(Scope *parent, bool with_idents); | ||||
| static void term_scope(Scope *sc); | ||||
| static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t); | ||||
| static void expr_into_addr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t, size_t addr); | ||||
| static IRParam expr_into_irparam(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t); | ||||
| static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListItem *t); | ||||
| static bool expr_flush_ir_and_maybe_return(IRList *out_ir, TokList *toks, IRTok instr, TokListItem *expr_start, Scope *expr_scope, TokListItem *t, ExprRet *out_ret); | ||||
| static void make_value_statically_allocated(Value *v); | ||||
| static ExprRet expr(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t); | ||||
| static void expr_into_addr(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t, size_t addr); | ||||
| static IRParam expr_into_irparam(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t); | ||||
| static void skip_newlns(TokList *toks, TokListItem *from); | ||||
| static void stmt(IRList *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListItem *t); | ||||
|  | ||||
| static void mark_err(const Tok *t) { | ||||
| 	err_ln = t->ln; | ||||
| @@ -47,6 +48,7 @@ static void set_irtok_dest_addr(IRTok *t, size_t addr) { | ||||
| 		case IRSet: | ||||
| 		case IRNeg: | ||||
| 		case IRNot: | ||||
| 		case IRAddrOf: | ||||
| 			t->Unary.addr = addr; | ||||
| 			break; | ||||
| 		case IRAdd: | ||||
| @@ -54,13 +56,19 @@ static void set_irtok_dest_addr(IRTok *t, size_t addr) { | ||||
| 		case IRMul: | ||||
| 		case IRDiv: | ||||
| 		case IREq: | ||||
| 		case IRNeq: | ||||
| 		case IRLt: | ||||
| 		case IRLe: | ||||
| 		case IRAnd: | ||||
| 		case IROr: | ||||
| 			t->Binary.addr = addr; | ||||
| 			break; | ||||
| 		case IRCallInternal: | ||||
| 			t->CallI.ret_addr = addr; | ||||
| 			break; | ||||
| 		case IRArrMake: | ||||
| 			t->ArrMake.arr_addr = addr; | ||||
| 			break; | ||||
| 		default: | ||||
| 			ASSERT_UNREACHED(); | ||||
| 	} | ||||
| @@ -119,6 +127,50 @@ static void term_scope(Scope *sc) { | ||||
| 		map_term(&sc->ident_addrs); | ||||
| } | ||||
|  | ||||
| /* If ir_tok is the underlying expr() call's last evaluation, this function | ||||
|  * deletes t from toks, sets *out_ret and tells the caller it can return | ||||
|  * *out_ret by returning true. | ||||
|  * | ||||
|  * If ir_tok is not the expression's last instruction, ir_tok is written to | ||||
|  * out_ir and t is replaced by a pointer to the result's memory address. | ||||
|  *  */ | ||||
| static bool expr_flush_ir_and_maybe_return(IRList *out_ir, TokList *toks, IRTok ir_tok, TokListItem *expr_start, Scope *expr_scope, TokListItem *t, ExprRet *out_ret) { | ||||
| 	if (t == expr_start && t->next->tok.kind == TokOp && op_prec[t->next->tok.Op] == PREC_DELIM) { | ||||
| 		/* ir_tok was the expression's last IR instruction. */ | ||||
|  | ||||
| 		toklist_del(toks, t, t); | ||||
|  | ||||
| 		*out_ret = (ExprRet){ | ||||
| 			.kind = ExprRetLastInstr, | ||||
| 			.LastInstr = ir_tok, | ||||
| 		}; | ||||
| 		return true; | ||||
| 	} else { | ||||
| 		/* ir_tok was not the expression's last IR instruction. */ | ||||
|  | ||||
| 		size_t dest_addr = expr_scope->mem_addr++; | ||||
|  | ||||
| 		set_irtok_dest_addr(&ir_tok, dest_addr); | ||||
| 		irlist_app(out_ir, ir_tok); | ||||
|  | ||||
| 		t->tok = (Tok){ | ||||
| 			.kind = TokIdent, | ||||
| 			.Ident = { | ||||
| 				.kind = IdentAddr, | ||||
| 				.Addr = dest_addr, | ||||
| 			}, | ||||
| 		}; | ||||
| 		return false; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void make_value_statically_allocated(Value *v) { | ||||
| 	switch (v->type) { | ||||
| 		case TypeArr: v->Arr.dynamically_allocated = false; break; | ||||
| 		default: break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| /* The job of this function is to reduce the expression to the most simple form | ||||
|  * writing the least IR instructions possible (without overanalyzing). | ||||
|  * This means that the only IR instructions it will be writing are those for | ||||
| @@ -170,7 +222,7 @@ static void term_scope(Scope *sc) { | ||||
|  *  l_op  r_op | ||||
|  *  both l_op and r_op are delimiters (their precedence is PREC_DELIM) => done | ||||
|  */ | ||||
| static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t) { | ||||
| static ExprRet expr(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t) { | ||||
| 	TokListItem *start = t; | ||||
|  | ||||
| 	Scope sc = make_scope(parent_sc, false); | ||||
| @@ -188,6 +240,10 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 				t = t->next; | ||||
| 				perform_unary = true; | ||||
| 				unary_op = IRNot; | ||||
| 			} else if (t->tok.Op == OpAddrOf) { | ||||
| 				t = t->next; | ||||
| 				perform_unary = true; | ||||
| 				unary_op = IRAddrOf; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -206,7 +262,7 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 			if (r.kind == ExprRetLastInstr) { | ||||
| 				size_t res_addr = sc.mem_addr++; | ||||
| 				set_irtok_dest_addr(&r.LastInstr, res_addr); | ||||
| 				irtoks_app(out_ir, r.LastInstr); | ||||
| 				irlist_app(out_ir, r.LastInstr); | ||||
| 				t->tok = (Tok){ | ||||
| 					.ln = t->tok.ln, | ||||
| 					.col = t->tok.col, | ||||
| @@ -223,6 +279,7 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 				ASSERT_UNREACHED(); | ||||
| 			toklist_del(toks, t->next, t->next); | ||||
| 		} | ||||
|  | ||||
| 		/* Collapse function call. */ | ||||
| 		else if (t->tok.kind == TokIdent && t->tok.Ident.kind == IdentName && t->next->tok.kind == TokOp && t->next->tok.Op == OpLParen) { | ||||
| 			/* get function */ | ||||
| @@ -240,88 +297,197 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 			bool eval_func_in_place = !func.side_effects; | ||||
|  | ||||
| 			size_t args_len = 0; | ||||
| 			size_t args_cap = 16; | ||||
| 			IRParam *args = xmalloc(sizeof(IRParam) * args_cap); | ||||
| 			for (;;) { | ||||
| 				if (args_len+1 > args_cap) | ||||
| 					args = xrealloc(args, (args_cap *= 2)); | ||||
| 				IRParam a; | ||||
| 				TRY_RET_ELSE(a = expr_into_irparam(out_ir, toks, funcs, &sc, t->next), (ExprRet){0}, free(args)); | ||||
| 				args[args_len++] = a; | ||||
| 				if (a.kind != IRParamLiteral) | ||||
| 					eval_func_in_place = false; | ||||
| 				if (t->next->tok.kind == TokOp) { | ||||
| 					if (t->next->tok.Op == OpComma) { | ||||
| 						toklist_del(toks, t->next, t->next); | ||||
| 						continue; | ||||
| 					} else if (t->next->tok.Op == OpRParen) { | ||||
| 						toklist_del(toks, t->next, t->next); | ||||
| 						break; | ||||
| 			IRParam *args = NULL; | ||||
|  | ||||
| 			if (t->next->tok.kind == TokOp && t->next->tok.Op == OpRParen) { | ||||
| 				/* no args */ | ||||
| 				toklist_del(toks, t->next, t->next); /* delete right parenthesis */ | ||||
| 			} else { | ||||
| 				/* go through the arguments, evaluate them and put them into the args array */ | ||||
| 				size_t args_cap = 16; | ||||
| 				args = xmalloc(sizeof(IRParam) * args_cap); | ||||
| 				for (;;) { | ||||
| 					if (args_len+1 > args_cap) | ||||
| 						args = xrealloc(args, (args_cap *= 2)); | ||||
| 					IRParam a; | ||||
| 					TRY_RET_ELSE(a = expr_into_irparam(out_ir, toks, funcs, &sc, t->next), (ExprRet){0}, free(args)); | ||||
| 					args[args_len++] = a; | ||||
| 					if (a.kind != IRParamLiteral) | ||||
| 						eval_func_in_place = false; | ||||
| 					if (t->next->tok.kind == TokOp) { | ||||
| 						if (t->next->tok.Op == OpComma) { | ||||
| 							toklist_del(toks, t->next, t->next); /* delete comma */ | ||||
| 							continue; | ||||
| 						} else if (t->next->tok.Op == OpRParen) { | ||||
| 							toklist_del(toks, t->next, t->next); /* delete right parenthesis */ | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 					mark_err(&t->next->tok); | ||||
| 					set_err("Expected ',' or ')' after function argument"); | ||||
| 					free(args); | ||||
| 					return (ExprRet){0}; | ||||
| 				} | ||||
| 				mark_err(&t->next->tok); | ||||
| 				set_err("Expected ',' or ')' after function argument"); | ||||
| 				free(args); | ||||
| 				return (ExprRet){0}; | ||||
| 			} | ||||
|  | ||||
| 			t = func_ident; | ||||
| 			toklist_del(toks, t->next, t->next); /* delete left parenthesis */ | ||||
|  | ||||
| 			if (func.n_args != args_len) { | ||||
| 			if (func.kind == FuncFixedArgs && args_len != func.FixedArgs.n_args) { | ||||
| 				mark_err(&func_ident->tok); | ||||
| 				const char *plural = func.n_args == 1 ? "" : "s"; | ||||
| 				set_err("Function %s() takes %zu argument%s but got %zu", func.name, func.n_args, plural, args_len); | ||||
| 				free(args); | ||||
| 				const char *plural = func.FixedArgs.n_args == 1 ? "" : "s"; | ||||
| 				set_err("Function %s() takes %zu argument%s but got %zu", func.name, func.FixedArgs.n_args, plural, args_len); | ||||
| 				if (args) | ||||
| 					free(args); | ||||
| 				return (ExprRet){0}; | ||||
| 			} else if (func.kind == FuncVarArgs && args_len < func.VarArgs.min_args) { | ||||
| 				mark_err(&func_ident->tok); | ||||
| 				const char *plural = func.VarArgs.min_args == 1 ? "" : "s"; | ||||
| 				set_err("Function %s() requires at least %zu argument%s but only got %zu", func.name, func.VarArgs.min_args, plural, args_len); | ||||
| 				if (args) | ||||
| 					free(args); | ||||
| 				return (ExprRet){0}; | ||||
| 			} | ||||
|  | ||||
| 			if (eval_func_in_place) { | ||||
| 				/* evaluate the function in place */ | ||||
| 				Value *arg_vals = xmalloc(sizeof(Value) * args_len); | ||||
| 				if (!func.returns) | ||||
| 					/* If the function had no side effects and returned nothing, | ||||
| 					 * that function would do absolutely nothing, which would | ||||
| 					 * make no sense. */ | ||||
| 					ASSERT_UNREACHED(); | ||||
| 				Value *arg_vals = args_len ? xmalloc(sizeof(Value) * args_len) : NULL; | ||||
| 				for (size_t i = 0; i < args_len; i++) | ||||
| 					arg_vals[i] = args[i].Literal; | ||||
| 				mark_err(&func_ident->tok); | ||||
| 				func_ident->tok = (Tok) { | ||||
| 					.kind = TokVal, | ||||
| 					.Val = func.func(arg_vals), | ||||
| 					.Val = func.kind == FuncVarArgs ? | ||||
| 						func.VarArgs.WithRet.func(args_len - func.VarArgs.min_args, arg_vals) | ||||
| 						: func.FixedArgs.WithRet.func(arg_vals), | ||||
| 				}; | ||||
| 				free(arg_vals); | ||||
| 				free(args); | ||||
| 				/* since we have a literal return value, we want it to be fully treated like one by the memory manager */ | ||||
| 				make_value_statically_allocated(&func_ident->tok.Val); | ||||
| 				/* immediately free any heap-allocated literals that are no longer needed */ | ||||
| 				for (size_t i = 0; i < args_len; i++) | ||||
| 					free_value(&arg_vals[i], true); | ||||
| 				/* free buffers */ | ||||
| 				if (arg_vals) | ||||
| 					free(arg_vals); | ||||
| 				if (args) | ||||
| 					free(args); | ||||
| 			} else { | ||||
| 				bool is_last_operation = t == start && t->next->tok.kind == TokOp && op_prec[t->next->tok.Op] == PREC_DELIM; | ||||
|  | ||||
| 				size_t res_addr = is_last_operation ? 0 : sc.mem_addr++; | ||||
|  | ||||
| 				/* function call instruction */ | ||||
| 				IRTok ir = { | ||||
| 				/* function call IR instruction */ | ||||
| 				IRTok ir_tok = { | ||||
| 					.ln =  func_ident->tok.ln, | ||||
| 					.col = func_ident->tok.col, | ||||
| 					.instr = IRCallInternal, | ||||
| 					.CallI = { | ||||
| 						.ret_addr = res_addr, | ||||
| 						.ret_addr = 0, | ||||
| 						.fid = func.fid, | ||||
| 						.n_args = args_len, | ||||
| 						.args = args, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| 				if (is_last_operation) { | ||||
| 					/* done */ | ||||
| 					toklist_del(toks, t, t); | ||||
| 					return (ExprRet){ .kind = ExprRetLastInstr, .LastInstr = ir }; | ||||
| 				} else { | ||||
| 					/* write IR instruction */ | ||||
| 					irtoks_app(out_ir, ir); | ||||
| 				/* return if we've just evaluated the last instruction */ | ||||
| 				ExprRet ret; | ||||
| 				if (expr_flush_ir_and_maybe_return(out_ir, toks, ir_tok, start, &sc, func_ident, &ret)) | ||||
| 					return ret; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 					/* leave new memory address as result */ | ||||
| 					t->tok = (Tok){ | ||||
| 						.kind = TokIdent, | ||||
| 						.Ident = { | ||||
| 							.kind = IdentAddr, | ||||
| 							.Addr = res_addr, | ||||
| 						}, | ||||
| 					}; | ||||
| 		/* Collapse array. */ | ||||
| 		else if (t->tok.kind == TokOp && t->tok.Op == OpLBrack) { | ||||
| 			TokListItem *lbrack = t; | ||||
| 			bool eval_immediately = true; | ||||
| 			size_t elems_len = 0; | ||||
| 			size_t elems_cap = 0; | ||||
| 			IRParam *elems = NULL; | ||||
|  | ||||
| 			if (t->next->tok.kind == TokOp && t->next->tok.Op == OpRBrack) { | ||||
| 				/* empty array */ | ||||
| 				toklist_del(toks, t->next, t->next); /* delete right bracket */ | ||||
| 			} else { | ||||
| 				elems_cap = 16; | ||||
| 				elems = xmalloc(sizeof(IRParam) * elems_cap); | ||||
| 				for (;;) { | ||||
| 					if (elems_len+1 > elems_cap) | ||||
| 						elems = xrealloc(elems, (elems_cap *= 2)); | ||||
| 					IRParam e; | ||||
| 					TRY_RET_ELSE(e = expr_into_irparam(out_ir, toks, funcs, &sc, t->next), (ExprRet){0}, free(elems)); | ||||
| 					if (e.kind != IRParamLiteral) | ||||
| 						eval_immediately = false; | ||||
| 					elems[elems_len++] = e; | ||||
| 					if (t->next->tok.kind == TokOp) { | ||||
| 						if (t->next->tok.Op == OpComma) { | ||||
| 							toklist_del(toks, t->next, t->next); /* delete comma */ | ||||
| 							continue; | ||||
| 						} else if (t->next->tok.Op == OpRBrack) { | ||||
| 							toklist_del(toks, t->next, t->next); /* delete right bracket */ | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 					mark_err(&t->next->tok); | ||||
| 					set_err("Expected ',' or ']' after array element"); | ||||
| 					free(elems); | ||||
| 					return (ExprRet){0}; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			if (eval_immediately) { | ||||
| 				/* turn array into value */ | ||||
| 				Value arr = { | ||||
| 					.type = TypeArr, | ||||
| 					.Arr = { | ||||
| 						.type = TypeVoid, | ||||
| 						.is_string = false, | ||||
| 						.dynamically_allocated = false, | ||||
| 						.vals = NULL, | ||||
| 						.len = elems_len, | ||||
| 						.cap = elems_len ? elems_cap : 0, | ||||
| 					}, | ||||
| 				}; | ||||
| 				if (elems_len) { | ||||
| 					Type arr_ty = elems[0].Literal.type; | ||||
| 					void *arr_vals = xmalloc(type_size[arr_ty] * elems_cap); | ||||
| 					for (size_t i = 0; i < elems_len; i++) { | ||||
| 						Value *v = &elems[i].Literal; | ||||
| 						if (v->type != arr_ty) { | ||||
| 							set_err("Type of array item %zu (%s) differs from array type (%s)", i, type_str[v->type], type_str[arr_ty]); | ||||
| 							free(arr_vals); | ||||
| 							free(elems); | ||||
| 							return (ExprRet){0}; | ||||
| 						} | ||||
| 						memcpy((uint8_t*)arr_vals + type_size[arr_ty] * i, &v->Void, type_size[arr_ty]); | ||||
| 					} | ||||
| 					arr.Arr.type = arr_ty; | ||||
| 					arr.Arr.vals = arr_vals; | ||||
| 				} | ||||
| 				/* set lbracket to collapsed array value */ | ||||
| 				lbrack->tok.kind = TokVal; | ||||
| 				lbrack->tok.Val = arr; | ||||
| 				/* free the now no longer needed element IRParam values */ | ||||
| 				free(elems); | ||||
| 			} else { | ||||
| 				/* array initialization IR instruction */ | ||||
| 				IRTok ir_tok = { | ||||
| 					.ln =  lbrack->tok.ln, | ||||
| 					.col = lbrack->tok.col, | ||||
| 					.instr = IRArrMake, | ||||
| 					.ArrMake = { | ||||
| 						.arr_addr = 0, | ||||
| 						.len = elems_len, | ||||
| 						.cap = elems_cap, | ||||
| 						.vals = elems, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| 				/* return if we've just evaluated the last instruction */ | ||||
| 				ExprRet ret; | ||||
| 				if (expr_flush_ir_and_maybe_return(out_ir, toks, ir_tok, start, &sc, lbrack, &ret)) | ||||
| 					return ret; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		/* Collapse unary operation. */ | ||||
| @@ -330,43 +496,29 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 			t = t->prev; /* go back to the '-' sign */ | ||||
| 			toklist_del(toks, t->next, t->next); /* again, just removing the reference */ | ||||
|  | ||||
| 			bool is_last_operation = t == start && t->next->tok.kind == TokOp && op_prec[t->next->tok.Op] == PREC_DELIM; | ||||
|  | ||||
| 			if (v->kind == TokVal) { | ||||
| 				/* immediately perform operation */ | ||||
| 				t->tok.kind = TokVal; | ||||
| 				mark_err(&t->tok); | ||||
| 				TRY_RET(t->tok.Val = eval_unary(unary_op, &v->Val), (ExprRet){0}); | ||||
| 			} else { | ||||
| 				size_t res_addr = is_last_operation ? 0 : sc.mem_addr++; | ||||
|  | ||||
| 				/* unary IR instruction */ | ||||
| 				IRParam v_irparam; | ||||
| 				TRY_RET(v_irparam = tok_to_irparam(&sc, v), (ExprRet){0}); | ||||
| 				IRTok ir = { | ||||
| 				IRTok ir_tok = { | ||||
| 					.ln = t->tok.ln, | ||||
| 					.col = t->tok.col, | ||||
| 					.instr = unary_op, | ||||
| 					.Unary = { | ||||
| 						.addr = res_addr, | ||||
| 						.addr = 0, | ||||
| 						.val = v_irparam, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| 				if (is_last_operation) { | ||||
| 					/* done */ | ||||
| 					toklist_del(toks, t, t); | ||||
| 					return (ExprRet){ .kind = ExprRetLastInstr, .LastInstr = ir }; | ||||
| 				} else { | ||||
| 					/* write IR instruction */ | ||||
| 					irtoks_app(out_ir, ir); | ||||
|  | ||||
| 					/* leave new memory address as result */ | ||||
| 					t->tok.kind = TokIdent; | ||||
| 					t->tok.Ident = (Identifier){ | ||||
| 						.kind = IdentAddr, | ||||
| 						.Addr = res_addr, | ||||
| 					}; | ||||
| 				} | ||||
| 				/* return if we've just evaluated the last instruction */ | ||||
| 				ExprRet ret; | ||||
| 				if (expr_flush_ir_and_maybe_return(out_ir, toks, ir_tok, start, &sc, t, &ret)) | ||||
| 					return ret; | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @@ -442,10 +594,13 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 				case OpMul: instr = IRMul; break; | ||||
| 				case OpDiv: instr = IRDiv; break; | ||||
| 				case OpEq:  instr = IREq;  break; | ||||
| 				case OpNeq: instr = IRNeq; break; | ||||
| 				case OpLt:  instr = IRLt;  break; | ||||
| 				case OpLe:  instr = IRLe;  break; | ||||
| 				case OpGt:  instr = IRLt; swap_operands = true; break; | ||||
| 				case OpGe:  instr = IRLe; swap_operands = true; break; | ||||
| 				case OpAnd: instr = IRAnd; break; | ||||
| 				case OpOr:  instr = IROr;  break; | ||||
| 				default: | ||||
| 					mark_err(l_op); | ||||
| 					set_err("Unknown operation: '%s'", op_str[l_op->Op]); | ||||
| @@ -457,53 +612,40 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, | ||||
| 				Value *lhs_val = swap_operands ? &rhs->Val : &lhs->Val; | ||||
| 				Value *rhs_val = swap_operands ? &lhs->Val : &rhs->Val; | ||||
| 				lhs->kind = TokVal; | ||||
| 				mark_err(l_op); | ||||
| 				TRY_RET(lhs->Val = eval_binary(instr, lhs_val, rhs_val), (ExprRet){0}); | ||||
| 			} else { | ||||
| 				bool is_last_operation = t == start && r_op_prec == PREC_DELIM; | ||||
|  | ||||
| 				IRParam lhs_irparam, rhs_irparam; | ||||
| 				TRY_RET(lhs_irparam = tok_to_irparam(&sc, lhs), (ExprRet){0}); | ||||
| 				TRY_RET(rhs_irparam = tok_to_irparam(&sc, rhs), (ExprRet){0}); | ||||
|  | ||||
| 				size_t res_addr = is_last_operation ? 0 : sc.mem_addr++; | ||||
|  | ||||
| 				IRTok ir = { | ||||
| 				/* binary IR instruction */ | ||||
| 				IRTok ir_tok = { | ||||
| 					.ln = l_op->ln, | ||||
| 					.col = l_op->col, | ||||
| 					.instr = instr, | ||||
| 					.Binary = { | ||||
| 						.addr = res_addr, | ||||
| 						.addr = 0, | ||||
| 						.lhs = swap_operands ? rhs_irparam : lhs_irparam, | ||||
| 						.rhs = swap_operands ? lhs_irparam : rhs_irparam, | ||||
| 					}, | ||||
| 				}; | ||||
|  | ||||
| 				if (is_last_operation) { | ||||
| 					/* done */ | ||||
| 					toklist_del(toks, t, t); | ||||
| 					return (ExprRet){ .kind = ExprRetLastInstr, .LastInstr = ir }; | ||||
| 				} else { | ||||
| 					/* write IR instruction */ | ||||
| 					irtoks_app(out_ir, ir); | ||||
|  | ||||
| 					/* leave new memory address as result */ | ||||
| 					lhs->kind = TokIdent; | ||||
| 					lhs->Ident = (Identifier){ | ||||
| 						.kind = IdentAddr, | ||||
| 						.Addr = res_addr, | ||||
| 					}; | ||||
| 				} | ||||
| 				/* return if we've just evaluated the last instruction */ | ||||
| 				ExprRet ret; | ||||
| 				if (expr_flush_ir_and_maybe_return(out_ir, toks, ir_tok, start, &sc, t, &ret)) | ||||
| 					return ret; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| static void expr_into_addr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t, size_t addr) { | ||||
| static void expr_into_addr(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t, size_t addr) { | ||||
| 	ExprRet r; | ||||
| 	TRY(r = expr(out_ir, toks, funcs, parent_sc, t)); | ||||
| 	if (r.kind == ExprRetLastInstr) { | ||||
| 		set_irtok_dest_addr(&r.LastInstr, addr); | ||||
| 		irtoks_app(out_ir, r.LastInstr); | ||||
| 		irlist_app(out_ir, r.LastInstr); | ||||
| 		t->tok = (Tok){ | ||||
| 			.ln = t->tok.ln, | ||||
| 			.col = t->tok.col, | ||||
| @@ -516,7 +658,9 @@ static void expr_into_addr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *par | ||||
| 	} else if (r.kind == ExprRetVal || r.kind == ExprRetIdent) { | ||||
| 		IRParam res; | ||||
| 		TRY(res = tok_to_irparam(parent_sc, &t->tok)); | ||||
| 		irtoks_app(out_ir, (IRTok){ | ||||
| 		irlist_app(out_ir, (IRTok){ | ||||
| 			.ln = t->tok.ln, | ||||
| 			.col = t->tok.col, | ||||
| 			.instr = IRSet, | ||||
| 			.Unary = { | ||||
| 				.addr = addr, | ||||
| @@ -528,14 +672,14 @@ static void expr_into_addr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *par | ||||
| 		ASSERT_UNREACHED(); | ||||
| } | ||||
|  | ||||
| static IRParam expr_into_irparam(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t) { | ||||
| static IRParam expr_into_irparam(IRList *out_ir, TokList *toks, Map *funcs, Scope *parent_sc, TokListItem *t) { | ||||
| 	ExprRet r; | ||||
| 	TRY_RET(r = expr(out_ir, toks, funcs, parent_sc, t), (IRParam){0}); | ||||
| 	if (r.kind == ExprRetLastInstr) { | ||||
| 		Scope sc = make_scope(parent_sc, false); | ||||
| 		size_t addr = sc.mem_addr++; | ||||
| 		set_irtok_dest_addr(&r.LastInstr, addr); | ||||
| 		irtoks_app(out_ir, r.LastInstr); | ||||
| 		irlist_app(out_ir, r.LastInstr); | ||||
| 		return (IRParam){ | ||||
| 			.kind = IRParamAddr, | ||||
| 			.Addr = addr, | ||||
| @@ -549,20 +693,30 @@ static IRParam expr_into_irparam(IRToks *out_ir, TokList *toks, Map *funcs, Scop | ||||
| 		ASSERT_UNREACHED(); | ||||
| } | ||||
|  | ||||
| static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListItem *t) { | ||||
| /* This WILL invalidate *from, so the caller should only call it on a | ||||
|  * TokListItem after any ones that are in use (e.g. skip_newlns(t->next)). */ | ||||
| static void skip_newlns(TokList *toks, TokListItem *from) { | ||||
| 	TokListItem *curr = from; | ||||
| 	while (curr->tok.kind == TokOp && curr->tok.Op == OpNewLn) | ||||
| 		curr = curr->next; | ||||
| 	if (curr != from) | ||||
| 		toklist_del(toks, from, curr->prev); | ||||
| } | ||||
|  | ||||
| static void stmt(IRList *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListItem *t) { | ||||
| 	TokListItem *start = t; | ||||
| 	if (t->tok.kind == TokIdent && t->tok.Ident.kind == IdentName && (t->next->tok.kind == TokDeclare || t->next->tok.kind == TokAssign)) { | ||||
| 		char *name = t->tok.Ident.Name; | ||||
| 		t = t->next; | ||||
| 		if (t->tok.kind == TokDeclare) { | ||||
| 			size_t addr = sc->mem_addr++; | ||||
| 			TRY(expr_into_addr(out_ir, toks, funcs, sc, t->next, addr)); | ||||
| 			bool replaced = map_insert(&sc->ident_addrs, name, &addr); | ||||
| 			if (replaced) { | ||||
| 				mark_err(&start->tok); | ||||
| 				set_err("'%s' already declared in this scope", name); | ||||
| 				return; | ||||
| 			} | ||||
| 			TRY(expr_into_addr(out_ir, toks, funcs, sc, t->next, addr)); | ||||
| 		} else if (t->tok.kind == TokAssign) { | ||||
| 			size_t addr; | ||||
| 			TRY(addr = get_ident_addr(sc, name, &start->tok)); | ||||
| @@ -572,11 +726,12 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 	} else if (t->tok.kind == TokOp && t->tok.Op == OpLCurl) { | ||||
| 		Scope inner_sc = make_scope(sc, true); | ||||
| 		for (;;) { | ||||
| 			skip_newlns(toks, t->next); | ||||
| 			if (t->next->tok.kind == TokOp) { | ||||
| 				if (t->next->tok.Op == OpEOF) { | ||||
| 					term_scope(&inner_sc); | ||||
| 					mark_err(&start->tok); | ||||
| 					set_err("Unclosed '{'"); | ||||
| 					term_scope(&inner_sc); | ||||
| 					return; | ||||
| 				} | ||||
| 				if (t->next->tok.Op == OpRCurl) | ||||
| @@ -596,8 +751,7 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 		 * */ | ||||
|  | ||||
| 		/* add initial jmp instruction */ | ||||
| 		size_t jmp_instr_iaddr = out_ir->len; | ||||
| 		irtoks_app(out_ir, (IRTok){ | ||||
| 		irlist_app(out_ir, (IRTok){ | ||||
| 			.ln = t->tok.ln, | ||||
| 			.col = t->tok.col, | ||||
| 			.instr = IRJmp, | ||||
| @@ -605,29 +759,32 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 				.iaddr = 0, /* unknown for now */ | ||||
| 			}, | ||||
| 		}); | ||||
| 		IRItem *jmp_instr = out_ir->end; | ||||
| 		size_t body_iaddr = out_ir->len; | ||||
|  | ||||
| 		/* parse condition */ | ||||
| 		IRToks cond_ir; | ||||
| 		irtoks_init_short(&cond_ir); | ||||
| 		IRList cond_ir; | ||||
| 		irlist_init_short(&cond_ir); | ||||
| 		IRParam cond; | ||||
| 		TRY_ELSE(cond = expr_into_irparam(&cond_ir, toks, funcs, sc, t->next), irtoks_term(&cond_ir)); | ||||
| 		TRY_ELSE(cond = expr_into_irparam(&cond_ir, toks, funcs, sc, t->next), irlist_term(&cond_ir)); | ||||
|  | ||||
| 		/* parse loop body */ | ||||
| 		TRY_ELSE(stmt(out_ir, toks, funcs, sc, t->next), irtoks_term(&cond_ir)); | ||||
| 		skip_newlns(toks, t->next); | ||||
| 		TRY_ELSE(stmt(out_ir, toks, funcs, sc, t->next), irlist_term(&cond_ir)); | ||||
|  | ||||
| 		/* finally we know where the jmp from the beginning has to jump to */ | ||||
| 		out_ir->toks[jmp_instr_iaddr].Jmp.iaddr = out_ir->len; | ||||
| 		jmp_instr->tok.Jmp.iaddr = out_ir->len; | ||||
|  | ||||
| 		/* append condition IR to program IR, then terminate condition IR stream */ | ||||
| 		irtoks_eat_irtoks(out_ir, &cond_ir, out_ir->len-1); | ||||
| 		irlist_eat_irlist(out_ir, &cond_ir); | ||||
|  | ||||
| 		/* add conditional jump */ | ||||
| 		irtoks_app(out_ir, (IRTok){ | ||||
| 		irlist_app(out_ir, (IRTok){ | ||||
| 			.ln = t->next->tok.ln, | ||||
| 			.col = t->next->tok.col, | ||||
| 			.instr = IRJnz, | ||||
| 			.CJmp = { | ||||
| 				.iaddr = jmp_instr_iaddr + 1, | ||||
| 				.iaddr = body_iaddr, | ||||
| 				.condition = cond, | ||||
| 			}, | ||||
| 		}); | ||||
| @@ -647,8 +804,7 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 		TRY(cond = expr_into_irparam(out_ir, toks, funcs, sc, t->next)); | ||||
|  | ||||
| 		/* add conditional jmp instruction */ | ||||
| 		size_t if_cjmp_instr_iaddr = out_ir->len; | ||||
| 		irtoks_app(out_ir, (IRTok){ | ||||
| 		irlist_app(out_ir, (IRTok){ | ||||
| 			.ln = t->tok.ln, | ||||
| 			.col = t->tok.col, | ||||
| 			.instr = IRJnz, | ||||
| @@ -657,22 +813,25 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 				.condition = cond, | ||||
| 			}, | ||||
| 		}); | ||||
| 		IRItem *if_cjmp_instr = out_ir->end; | ||||
|  | ||||
| 		/* parse if body */ | ||||
| 		IRToks if_body; | ||||
| 		irtoks_init_short(&if_body); | ||||
| 		TRY_ELSE(stmt(&if_body, toks, funcs, sc, t->next), irtoks_term(&if_body)); | ||||
| 		skip_newlns(toks, t->next); | ||||
| 		IRList if_body; | ||||
| 		irlist_init_short(&if_body); | ||||
| 		TRY_ELSE(stmt(&if_body, toks, funcs, sc, t->next), irlist_term(&if_body)); | ||||
|  | ||||
| 		skip_newlns(toks, t->next); | ||||
| 		if (t->next->tok.kind == TokElse) { | ||||
| 			toklist_del(toks, t->next, t->next); | ||||
|  | ||||
| 			/* parse and add else body */ | ||||
| 			TRY_ELSE(stmt(out_ir, toks, funcs, sc, t->next), irtoks_term(&if_body)); | ||||
| 			skip_newlns(toks, t->next); | ||||
| 			TRY_ELSE(stmt(out_ir, toks, funcs, sc, t->next), irlist_term(&if_body)); | ||||
| 		} | ||||
|  | ||||
| 		/* add jmp instruction to jump back to common code */ | ||||
| 		size_t else_jmp_instr_iaddr = out_ir->len; | ||||
| 		irtoks_app(out_ir, (IRTok){ | ||||
| 		irlist_app(out_ir, (IRTok){ | ||||
| 			.ln = t->tok.ln, | ||||
| 			.col = t->tok.col, | ||||
| 			.instr = IRJmp, | ||||
| @@ -680,16 +839,16 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 				.iaddr = 0, /* unknown for now */ | ||||
| 			}, | ||||
| 		}); | ||||
| 		IRItem *else_jmp_instr = out_ir->end; | ||||
|  | ||||
| 		/* set if condition jmp target */ | ||||
| 		out_ir->toks[if_cjmp_instr_iaddr].CJmp.iaddr = out_ir->len; | ||||
| 		if_cjmp_instr->tok.CJmp.iaddr = out_ir->len; | ||||
| 		 | ||||
| 		/* add if body */ | ||||
| 		irtoks_eat_irtoks(out_ir, &if_body, out_ir->len-1); | ||||
| 		irlist_eat_irlist(out_ir, &if_body); | ||||
|  | ||||
| 		/* set else jmp target */ | ||||
| 		out_ir->toks[else_jmp_instr_iaddr].CJmp.iaddr = out_ir->len; | ||||
| 	} else if (t->tok.kind == TokOp && t->tok.Op == OpNewLn) { | ||||
| 		else_jmp_instr->tok.CJmp.iaddr = out_ir->len; | ||||
| 	} else { | ||||
| 		/* assume expression */ | ||||
| 		TRY(expr_into_irparam(out_ir, toks, funcs, sc, t)); | ||||
| @@ -698,9 +857,7 @@ static void stmt(IRToks *out_ir, TokList *toks, Map *funcs, Scope *sc, TokListIt | ||||
| 	toklist_del(toks, start, t); | ||||
| } | ||||
|  | ||||
| IRToks parse(TokList *toks, BuiltinFunc *builtin_funcs, size_t n_builtin_funcs) { | ||||
| 	bf = builtin_funcs; | ||||
|  | ||||
| IRList parse(TokList *toks, BuiltinFunc *builtin_funcs, size_t n_builtin_funcs) { | ||||
| 	Map funcs; | ||||
| 	map_init(&funcs, sizeof(BuiltinFunc)); | ||||
| 	for (size_t i = 0; i < n_builtin_funcs; i++) { | ||||
| @@ -710,14 +867,15 @@ IRToks parse(TokList *toks, BuiltinFunc *builtin_funcs, size_t n_builtin_funcs) | ||||
| 			err_ln = 0; err_col = 0; | ||||
| 			set_err("Builtin function %s() declared more than once", builtin_funcs[i].name); | ||||
| 			map_term(&funcs); | ||||
| 			return (IRToks){0}; | ||||
| 			return (IRList){0}; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	IRToks ir; | ||||
| 	irtoks_init_long(&ir); | ||||
| 	IRList ir; | ||||
| 	irlist_init_long(&ir); | ||||
| 	Scope global_scope = make_scope(NULL, true); | ||||
| 	for (;;) { | ||||
| 		skip_newlns(toks, toks->begin); | ||||
| 		if (toks->begin->tok.kind == TokOp && toks->begin->tok.Op == OpEOF) | ||||
| 			break; | ||||
| 		TRY_RET_ELSE(stmt(&ir, toks, &funcs, &global_scope, toks->begin), ir, | ||||
|   | ||||
							
								
								
									
										2
									
								
								parse.h
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								parse.h
									
									
									
									
									
								
							| @@ -5,6 +5,6 @@ | ||||
| #include "tok.h" | ||||
| #include "util.h" | ||||
|  | ||||
| IRToks parse(TokList *toks, BuiltinFunc *builtin_funcs, size_t n_builtin_funcs); | ||||
| IRList parse(TokList *toks, BuiltinFunc *builtin_funcs, size_t n_builtin_funcs); | ||||
|  | ||||
| #endif /* PARSE_H */ | ||||
|   | ||||
							
								
								
									
										93
									
								
								runtime.c
									
									
									
									
									
								
							
							
						
						
									
										93
									
								
								runtime.c
									
									
									
									
									
								
							| @@ -8,7 +8,7 @@ Value eval_binary(IRInstr instr, const Value *lhs, const Value *rhs) { | ||||
| 		case IRSub: | ||||
| 		case IRMul: | ||||
| 		case IRDiv: { | ||||
| 			if (lhs->type.kind == TypeInt && rhs->type.kind == TypeInt) { | ||||
| 			if (lhs->type == TypeInt && rhs->type == TypeInt) { | ||||
| 				ssize_t res; | ||||
| 				switch (instr) { | ||||
| 					case IRAdd: res = lhs->Int + rhs->Int; break; | ||||
| @@ -18,11 +18,11 @@ Value eval_binary(IRInstr instr, const Value *lhs, const Value *rhs) { | ||||
| 					default: ASSERT_UNREACHED(); | ||||
| 				} | ||||
| 				return (Value){ | ||||
| 					.type.kind = TypeInt, | ||||
| 					.type = TypeInt, | ||||
| 					.Int = res, | ||||
| 				}; | ||||
| 			} else if (lhs->type.kind == TypeFloat && rhs->type.kind == TypeFloat) { | ||||
| 				float res; | ||||
| 			} else if (lhs->type == TypeFloat && rhs->type == TypeFloat) { | ||||
| 				double res; | ||||
| 				switch (instr) { | ||||
| 					case IRAdd: res = lhs->Float + rhs->Float; break; | ||||
| 					case IRSub: res = lhs->Float - rhs->Float; break; | ||||
| @@ -31,40 +31,69 @@ Value eval_binary(IRInstr instr, const Value *lhs, const Value *rhs) { | ||||
| 					default: ASSERT_UNREACHED(); | ||||
| 				} | ||||
| 				return (Value){ | ||||
| 					.type.kind = TypeFloat, | ||||
| 					.type = TypeFloat, | ||||
| 					.Float = res, | ||||
| 				}; | ||||
| 			} else { | ||||
| 				set_err("Unsupported types for operation '%s'", irinstr_str[instr]); | ||||
| 				set_err("Unsupported types for operation '%s': %s and %s", irinstr_str[instr], type_str[lhs->type], type_str[rhs->type]); | ||||
| 				return (Value){0}; | ||||
| 			} | ||||
| 		} | ||||
| 		case IREq: | ||||
| 		case IRNeq: | ||||
| 		case IRLt: | ||||
| 		case IRLe: | ||||
| 		case IRLe: { | ||||
| 			bool res; | ||||
| 			if (lhs->type.kind == TypeInt && rhs->type.kind == TypeInt) { | ||||
| 			if (lhs->type == TypeInt && rhs->type == TypeInt) { | ||||
| 				switch (instr) { | ||||
| 					case IREq: res = lhs->Int == rhs->Int; break; | ||||
| 					case IRLt: res = lhs->Int <  rhs->Int; break; | ||||
| 					case IRLe: res = lhs->Int <= rhs->Int; break; | ||||
| 					case IREq:  res = lhs->Int == rhs->Int; break; | ||||
| 					case IRNeq: res = lhs->Int != rhs->Int; break; | ||||
| 					case IRLt:  res = lhs->Int <  rhs->Int; break; | ||||
| 					case IRLe:  res = lhs->Int <= rhs->Int; break; | ||||
| 					default: ASSERT_UNREACHED(); | ||||
| 				}; | ||||
| 			} else if (lhs->type.kind == TypeFloat && rhs->type.kind == TypeFloat) { | ||||
| 			} else if (lhs->type == TypeFloat && rhs->type == TypeFloat) { | ||||
| 				switch (instr) { | ||||
| 					case IREq: res = lhs->Float == rhs->Float; break; | ||||
| 					case IRLt: res = lhs->Float <  rhs->Float; break; | ||||
| 					case IRLe: res = lhs->Float <= rhs->Float; break; | ||||
| 					case IREq:  res = lhs->Float == rhs->Float; break; | ||||
| 					case IRNeq: res = lhs->Float != rhs->Float; break; | ||||
| 					case IRLt:  res = lhs->Float <  rhs->Float; break; | ||||
| 					case IRLe:  res = lhs->Float <= rhs->Float; break; | ||||
| 					default: ASSERT_UNREACHED(); | ||||
| 				}; | ||||
| 			} else if (lhs->type == TypeArr && lhs->Arr.type == TypeChar && lhs->Arr.is_string && | ||||
| 					rhs->type == TypeArr && rhs->Arr.type == TypeChar && rhs->Arr.is_string) { | ||||
| 				switch (instr) { | ||||
| 					case IREq: | ||||
| 						res = lhs->Arr.len == rhs->Arr.len ? strncmp(lhs->Arr.vals, rhs->Arr.vals, lhs->Arr.len) == 0 : false; | ||||
| 						break; | ||||
| 					case IRNeq: | ||||
| 						res = lhs->Arr.len == rhs->Arr.len ? strncmp(lhs->Arr.vals, rhs->Arr.vals, lhs->Arr.len) != 0 : true; | ||||
| 						break; | ||||
| 					default: | ||||
| 						set_err("String operation '%s' not supported", irinstr_str[instr]); | ||||
| 						break; | ||||
| 				}; | ||||
| 			} else { | ||||
| 				set_err("Unsupported types for operation '%s'", irinstr_str[instr]); | ||||
| 				set_err("Unsupported types for operation '%s': %s and %s", irinstr_str[instr], type_str[lhs->type], type_str[rhs->type]); | ||||
| 				return (Value){0}; | ||||
| 			} | ||||
| 			return (Value){ | ||||
| 				.type.kind = TypeBool, | ||||
| 				.type = TypeBool, | ||||
| 				.Bool = res, | ||||
| 			}; | ||||
| 		} | ||||
| 		case IRAnd: | ||||
| 			return (Value){ | ||||
| 				.type = TypeBool, | ||||
| 				.Bool = is_nonzero(lhs) && is_nonzero(rhs), | ||||
| 			}; | ||||
| 			break; | ||||
| 		case IROr: | ||||
| 			return (Value){ | ||||
| 				.type = TypeBool, | ||||
| 				.Bool = is_nonzero(lhs) || is_nonzero(rhs), | ||||
| 			}; | ||||
| 			break; | ||||
| 		default: | ||||
| 			ASSERT_UNREACHED(); | ||||
| 	} | ||||
| @@ -76,31 +105,37 @@ Value eval_unary(IRInstr instr, const Value *v) { | ||||
| 		case IRSet: | ||||
| 			return *v; | ||||
| 		case IRNeg: | ||||
| 			if (v->type.kind == TypeInt) | ||||
| 				return (Value){ .type.kind = TypeInt, .Int = -v->Int }; | ||||
| 			else if (v->type.kind == TypeFloat) | ||||
| 				return (Value){ .type.kind = TypeFloat, .Float = -v->Float }; | ||||
| 			if (v->type == TypeInt) | ||||
| 				return (Value){ .type = TypeInt, .Int = -v->Int }; | ||||
| 			else if (v->type == TypeFloat) | ||||
| 				return (Value){ .type = TypeFloat, .Float = -v->Float }; | ||||
| 			else { | ||||
| 				set_err("Unsupported type for operation '%s'", irinstr_str[instr]); | ||||
| 				set_err("Unsupported type for operation '%s': %s", irinstr_str[instr], type_str[v->type]); | ||||
| 				return (Value){0}; | ||||
| 			} | ||||
| 		case IRNot: | ||||
| 			if (v->type.kind == TypeBool) { | ||||
| 				return (Value){ .type.kind = TypeBool, .Bool = !v->Bool }; | ||||
| 			if (v->type == TypeBool) { | ||||
| 				return (Value){ .type = TypeBool, .Bool = !v->Bool }; | ||||
| 			} else { | ||||
| 				set_err("Unsupported type for operation '%s'", irinstr_str[instr]); | ||||
| 				set_err("Unsupported type for operation '%s': %s", irinstr_str[instr], type_str[v->type]); | ||||
| 				return (Value){0}; | ||||
| 			} | ||||
| 		case IRAddrOf: | ||||
| 			set_err("Unable to take the address of a literal"); | ||||
| 			return (Value){0}; | ||||
| 		default: | ||||
| 			ASSERT_UNREACHED(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool is_nonzero(const Value *v) { | ||||
| 	switch (v->type.kind) { | ||||
| 		case TypeInt:   return v->Int   != 0; | ||||
| 		case TypeFloat: return v->Float != 0.0; | ||||
| 	switch (v->type) { | ||||
| 		case TypeInt:   return v->Int     != 0; | ||||
| 		case TypeFloat: return v->Float   != 0.0; | ||||
| 		case TypeBool:  return v->Bool; | ||||
| 		case TypeChar:  return v->Char    != 0; | ||||
| 		case TypePtr:   return v->Ptr.val != NULL; | ||||
| 		case TypeArr:   return v->Arr.len != 0; | ||||
| 		default: ASSERT_UNREACHED(); | ||||
| 	} | ||||
| } | ||||
| @@ -108,7 +143,7 @@ bool is_nonzero(const Value *v) { | ||||
| Value zero_val(Type ty) { | ||||
| 	Value ret; | ||||
| 	ret.type = ty; | ||||
| 	switch (ty.kind) { | ||||
| 	switch (ty) { | ||||
| 		case TypeInt:   ret.Int   = 0;     break; | ||||
| 		case TypeFloat: ret.Float = 0.0;   break; | ||||
| 		case TypeBool:  ret.Bool  = false; break; | ||||
|   | ||||
							
								
								
									
										152
									
								
								tok.c
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								tok.c
									
									
									
									
									
								
							| @@ -5,21 +5,132 @@ | ||||
|  | ||||
| #include "util.h" | ||||
|  | ||||
| size_t type_size[TypeEnumSize] = { | ||||
| 	[TypeVoid]  = 0, | ||||
| 	[TypeFloat] = sizeof(((Value*)NULL)->Float), | ||||
| 	[TypeInt]   = sizeof(((Value*)NULL)->Int), | ||||
| 	[TypeBool]  = sizeof(((Value*)NULL)->Bool), | ||||
| 	[TypeChar]  = sizeof(((Value*)NULL)->Char), | ||||
| 	[TypePtr]   = sizeof(((Value*)NULL)->Ptr), | ||||
| 	[TypeArr]   = sizeof(((Value*)NULL)->Arr), | ||||
| }; | ||||
|  | ||||
| const char *type_str[TypeEnumSize] = { | ||||
| 	[TypeVoid]  = "void", | ||||
| 	[TypeFloat] = "float", | ||||
| 	[TypeInt]   = "int", | ||||
| 	[TypeBool]  = "bool", | ||||
| 	[TypeChar]  = "char", | ||||
| 	[TypePtr]   = "ptr", | ||||
| 	[TypeArr]   = "arr", | ||||
| }; | ||||
|  | ||||
| /* if purge is set, even statically allocated literals will be freed */ | ||||
| void free_value(Value *v, bool purge) { | ||||
| 	switch (v->type) { | ||||
| 		case TypeArr: | ||||
| 			if (v->Arr.vals && (purge || v->Arr.dynamically_allocated)) { | ||||
| 				free(v->Arr.vals); | ||||
| 				v->Arr.vals = NULL; | ||||
| 				v->Arr.len = 0; | ||||
| 				v->Arr.cap = 0; | ||||
| 			} | ||||
| 			break; | ||||
| 		default: | ||||
| 			break; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void print_value(const Value *v, bool raw) { | ||||
| 	switch (v->type) { | ||||
| 		case TypeVoid: | ||||
| 			printf("(void)"); | ||||
| 			break; | ||||
| 		case TypeFloat: | ||||
| 			printf("%f", v->Float); | ||||
| 			break; | ||||
| 		case TypeInt: | ||||
| 			printf("%zd", v->Int); | ||||
| 			break; | ||||
| 		case TypeBool: | ||||
| 			printf("%s", v->Bool ? "true" : "false"); | ||||
| 			break; | ||||
| 		case TypeChar: | ||||
| 			if (raw) | ||||
| 				printf("%c", v->Char); | ||||
| 			else { | ||||
| 				const char *esc = unescape_char(v->Char); | ||||
| 				if (esc) printf("'%s'", esc); | ||||
| 				else     printf("'%c'", v->Char); | ||||
| 			} | ||||
| 			break; | ||||
| 		case TypePtr: { | ||||
| 			if (v->Ptr.val) { | ||||
| 				printf("ptr<%s>(", type_str[v->Ptr.type]); | ||||
| 				Value deref = { .type = v->Ptr.type }; | ||||
| 				memcpy(&deref.Void, v->Ptr.val, type_size[v->Ptr.type]); | ||||
| 				print_value(&deref, false); | ||||
| 				printf(")"); | ||||
| 			} else | ||||
| 				printf("ptr<%s>(nil)", type_str[v->Ptr.type]); | ||||
| 			break; | ||||
| 		} | ||||
| 		case TypeArr: | ||||
| 			if (v->Arr.is_string) { | ||||
| 				if (v->Arr.type != TypeChar) | ||||
| 					ASSERT_UNREACHED(); | ||||
| 				char *str = v->Arr.vals; | ||||
| 				if (!raw) | ||||
| 					printf("\""); | ||||
| 				for (size_t i = 0; i < v->Arr.len; i++) { | ||||
| 					char c = str[i]; | ||||
| 					if (raw) | ||||
| 						printf("%c", c); | ||||
| 					else { | ||||
| 						const char *esc = unescape_char(c); | ||||
| 						if (esc) printf("%s", esc); | ||||
| 						else     printf("%c", c); | ||||
| 					} | ||||
| 				} | ||||
| 				if (!raw) | ||||
| 					printf("\""); | ||||
| 			} else { | ||||
| 				printf("["); | ||||
| 				for (size_t i = 0;; i++) { | ||||
| 					size_t ty_sz = type_size[v->Arr.type]; | ||||
| 					Value ty_val = { .type = v->Arr.type }; | ||||
| 					memcpy(&ty_val.Void, (uint8_t*)v->Arr.vals + ty_sz * i, ty_sz); | ||||
| 					print_value(&ty_val, false); | ||||
| 					if (i+1 >= v->Arr.len) break; | ||||
| 					printf(", "); | ||||
| 				} | ||||
| 				printf("]"); | ||||
| 			} | ||||
| 			break; | ||||
| 		default: | ||||
| 			ASSERT_UNREACHED(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| int8_t op_prec[OperatorEnumSize] = { | ||||
| 	[OpEOF]    = PREC_DELIM, | ||||
| 	[OpNewLn]  = PREC_DELIM, | ||||
| 	[OpLCurl]  = PREC_DELIM, | ||||
| 	[OpRParen] = PREC_DELIM, | ||||
| 	[OpRBrack] = PREC_DELIM, | ||||
| 	[OpComma]  = PREC_DELIM, | ||||
| 	[OpEq]     = 0, | ||||
| 	[OpLt]     = 0, | ||||
| 	[OpGt]     = 0, | ||||
| 	[OpLe]     = 0, | ||||
| 	[OpGe]     = 0, | ||||
| 	[OpAdd]    = 1, | ||||
| 	[OpSub]    = 1, | ||||
| 	[OpMul]    = 2, | ||||
| 	[OpDiv]    = 2, | ||||
| 	[OpAnd]    = 0, | ||||
| 	[OpOr]     = 0, | ||||
| 	[OpEq]     = 1, | ||||
| 	[OpNeq]    = 1, | ||||
| 	[OpLt]     = 1, | ||||
| 	[OpGt]     = 1, | ||||
| 	[OpLe]     = 1, | ||||
| 	[OpGe]     = 1, | ||||
| 	[OpAdd]    = 2, | ||||
| 	[OpSub]    = 2, | ||||
| 	[OpMul]    = 3, | ||||
| 	[OpDiv]    = 3, | ||||
| }; | ||||
|  | ||||
| const char *op_str[OperatorEnumSize] = { | ||||
| @@ -27,6 +138,8 @@ const char *op_str[OperatorEnumSize] = { | ||||
| 	[OpRCurl]  = "}", | ||||
| 	[OpLParen] = "(", | ||||
| 	[OpRParen] = ")", | ||||
| 	[OpLBrack] = "[", | ||||
| 	[OpRBrack] = "]", | ||||
| 	[OpComma]  = ",", | ||||
| 	[OpAdd]    = "+", | ||||
| 	[OpSub]    = "-", | ||||
| @@ -36,10 +149,13 @@ const char *op_str[OperatorEnumSize] = { | ||||
| 	[OpNewLn]  = "\\n", | ||||
| 	[OpEOF]    = "EOF", | ||||
| 	[OpEq]     = "==", | ||||
| 	[OpNeq]    = "!=", | ||||
| 	[OpLt]     = "<", | ||||
| 	[OpGt]     = ">", | ||||
| 	[OpLe]     = "<=", | ||||
| 	[OpGe]     = ">=", | ||||
| 	[OpAnd]    = "&&", | ||||
| 	[OpOr]     = "||", | ||||
| }; | ||||
|  | ||||
| const char *tok_str[TokKindEnumSize] = { | ||||
| @@ -104,21 +220,9 @@ void print_toks(TokList *l) { | ||||
| 				printf(": " C_ICYAN "%s" C_RESET, op_str[i->tok.Op]); | ||||
| 				break; | ||||
| 			case TokVal: | ||||
| 				printf(C_IYELLOW "Val" C_RESET); | ||||
| 				switch (i->tok.Val.type.kind) { | ||||
| 					case TypeFloat: | ||||
| 						printf(": " C_ICYAN "%f" C_RESET, i->tok.Val.Float); | ||||
| 						break; | ||||
| 					case TypeInt: | ||||
| 						printf(": " C_ICYAN "%zd" C_RESET, i->tok.Val.Int); | ||||
| 						break; | ||||
| 					case TypeBool: | ||||
| 						printf(": " C_ICYAN "%s" C_RESET, i->tok.Val.Bool ? "true" : "false"); | ||||
| 						break; | ||||
| 					default: | ||||
| 						printf(" " C_ICYAN "(unknown type)" C_RESET); | ||||
| 						break; | ||||
| 				} | ||||
| 				printf(C_IYELLOW "Val" C_RESET ": " C_ICYAN); | ||||
| 				print_value(&i->tok.Val, false); | ||||
| 				printf(C_RESET); | ||||
| 				break; | ||||
| 			case TokIdent: | ||||
| 				printf(C_IYELLOW "Ident" C_RESET); | ||||
|   | ||||
							
								
								
									
										46
									
								
								tok.h
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								tok.h
									
									
									
									
									
								
							| @@ -2,49 +2,73 @@ | ||||
| #define __TOK_H__ | ||||
|  | ||||
| #include <stdint.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #include "util.h" | ||||
|  | ||||
| typedef struct Type { | ||||
| 	enum { | ||||
| 		TypeVoid = 0, | ||||
| 		TypeFloat, | ||||
| 		TypeInt, | ||||
| 		TypeBool, | ||||
| 	} kind; | ||||
| enum Type { | ||||
| 	TypeVoid = 0, | ||||
| 	TypeFloat, | ||||
| 	TypeInt, | ||||
| 	TypeBool, | ||||
| 	TypeChar, | ||||
| 	TypePtr, | ||||
| 	TypeArr, | ||||
| 	TypeEnumSize, | ||||
| }; | ||||
| typedef enum Type Type; | ||||
|  | ||||
| 	/*union { | ||||
| 	};*/ | ||||
| } Type; | ||||
| extern size_t type_size[TypeEnumSize]; | ||||
| extern const char *type_str[TypeEnumSize]; | ||||
|  | ||||
| typedef struct Value { | ||||
| 	Type type; | ||||
|  | ||||
| 	union { | ||||
| 		pseudo_void Void; | ||||
| 		double Float; | ||||
| 		ssize_t Int; | ||||
| 		bool Bool; | ||||
| 		char Char; | ||||
| 		struct { | ||||
| 			Type type; | ||||
| 			void *val; | ||||
| 		} Ptr; | ||||
| 		struct { | ||||
| 			Type type; | ||||
| 			bool is_string : 1; | ||||
| 			bool dynamically_allocated : 1; | ||||
| 			void *vals; | ||||
| 			size_t len, cap; | ||||
| 		} Arr; | ||||
| 	}; | ||||
| } Value; | ||||
|  | ||||
| void free_value(Value *v, bool purge); | ||||
| void print_value(const Value *v, bool raw); | ||||
|  | ||||
| enum Operator { | ||||
| 	OpLCurl  = '{', | ||||
| 	OpRCurl  = '}', | ||||
| 	OpLParen = '(', | ||||
| 	OpRParen = ')', | ||||
| 	OpLBrack = '[', | ||||
| 	OpRBrack = ']', | ||||
| 	OpComma  = ',', | ||||
| 	OpAdd    = '+', | ||||
| 	OpSub    = '-', | ||||
| 	OpMul    = '*', | ||||
| 	OpDiv    = '/', | ||||
| 	OpNot    = '!', | ||||
| 	OpAddrOf = '&', | ||||
| 	OpBeginNonchars = 256, | ||||
| 	OpEq, | ||||
| 	OpNeq, | ||||
| 	OpLt, | ||||
| 	OpGt, | ||||
| 	OpLe, | ||||
| 	OpGe, | ||||
| 	OpAnd, | ||||
| 	OpOr, | ||||
| 	OpNewLn, | ||||
| 	OpEOF, | ||||
| 	OperatorEnumSize, | ||||
|   | ||||
							
								
								
									
										34
									
								
								util.c
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								util.c
									
									
									
									
									
								
							| @@ -2,6 +2,23 @@ | ||||
|  | ||||
| #include <stdarg.h> | ||||
|  | ||||
| #ifdef WIN32 | ||||
| #include <windows.h> /* Sleep */ | ||||
| #else | ||||
| #include <time.h> /* nanosleep */ | ||||
| #endif | ||||
|  | ||||
| void sleep_secs(double secs) { | ||||
| #if defined(_WIN32) || defined(WIN32) | ||||
| 	Sleep(secs * 1000.0); | ||||
| #else | ||||
| 	struct timespec ts; | ||||
| 	ts.tv_sec = (time_t)secs; | ||||
| 	ts.tv_nsec = (secs - (double)ts.tv_sec) * 1000000000.0; | ||||
| 	nanosleep(&ts, NULL); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| char errbuf[ERRSZ]; | ||||
| bool err; | ||||
| size_t err_ln, err_col; | ||||
| @@ -168,6 +185,23 @@ double stod(const char *s, size_t n, ssize_t *endpos) { | ||||
| 	return res; | ||||
| } | ||||
|  | ||||
| const char *unescape_char(char c) { | ||||
| 	switch (c) { | ||||
| 		case '\a':   return "\\a"; | ||||
| 		case '\b':   return "\\b"; | ||||
| 		case '\033': return "\\e"; | ||||
| 		case '\f':   return "\\f"; | ||||
| 		case '\n':   return "\\n"; | ||||
| 		case '\r':   return "\\r"; | ||||
| 		case '\t':   return "\\t"; | ||||
| 		case '\v':   return "\\v"; | ||||
| 		case '\\':   return "\\\\"; | ||||
| 		case '\'':   return "\\'"; | ||||
| 		case '"':    return "\\\""; | ||||
| 		default:     return NULL; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| char *mreadfile(FILE *fp) { | ||||
| 	if (fseek(fp, 0l, SEEK_END) == -1) | ||||
| 		return NULL; | ||||
|   | ||||
							
								
								
									
										14
									
								
								util.h
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								util.h
									
									
									
									
									
								
							| @@ -6,7 +6,15 @@ | ||||
| #include <stdio.h> | ||||
| #include <stdlib.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
|  | ||||
| #if defined(_WIN32) || defined(WIN32) | ||||
| #include <windows.h> /* SSIZE_T */ | ||||
| typedef SSIZE_T ssize_t; | ||||
| #else | ||||
| #include <unistd.h> /* ssize_t */ | ||||
| #endif | ||||
|  | ||||
| typedef uint8_t pseudo_void; | ||||
|  | ||||
| /* some ANSI color codes */ | ||||
| #define C_RED     "\x1b[31m" | ||||
| @@ -27,6 +35,8 @@ | ||||
|  | ||||
| #define C_RESET   "\x1b[m" | ||||
|  | ||||
| void sleep_secs(double secs); | ||||
|  | ||||
| #define ERRSZ 4096 | ||||
| extern char errbuf[ERRSZ]; | ||||
| extern bool err; | ||||
| @@ -67,6 +77,8 @@ char *psndup(Pool *p, const char *s, size_t n); | ||||
| intmax_t stoimax(const char *s, size_t n, size_t base, ssize_t *endpos /* -1 on success */); | ||||
| /* convert a non-null-terminated string to a double */ | ||||
| double stod(const char *s, size_t n, ssize_t *endpos /* -1 on success */); | ||||
| /* return the escape sequence for a given character; return NULL if there is none */ | ||||
| const char *unescape_char(char c); | ||||
|  | ||||
| /* sets errno on failure */ | ||||
| char *mreadfile(FILE *fp); | ||||
|   | ||||
							
								
								
									
										151
									
								
								vm.c
									
									
									
									
									
								
							
							
						
						
									
										151
									
								
								vm.c
									
									
									
									
									
								
							| @@ -3,33 +3,50 @@ | ||||
| #include "runtime.h" | ||||
| #include "util.h" | ||||
|  | ||||
| #define INIT_STACK_CAP 256 | ||||
| #define INIT_STACK_CAP 128 | ||||
|  | ||||
| typedef struct Stack { | ||||
| 	Value *mem; | ||||
| 	bool *holds_value; | ||||
| 	size_t len, cap; | ||||
| } Stack; | ||||
|  | ||||
| static Stack stack_make(void); | ||||
| static void stack_term(Stack *s); | ||||
| static void stack_fit(Stack *s, size_t idx); | ||||
| static void stack_assign(Stack *s, size_t idx, const Value *v); | ||||
|  | ||||
| static Stack stack_make(void) { | ||||
| 	Stack s; | ||||
| 	s.mem = xmalloc(sizeof(Value) * INIT_STACK_CAP); | ||||
| 	s.holds_value = xmalloc(sizeof(bool) * INIT_STACK_CAP); | ||||
| 	s.cap = INIT_STACK_CAP; | ||||
| 	s.len = 0; | ||||
| 	for (size_t i = 0; i < s.cap; i++) | ||||
| 		s.holds_value[i] = false; | ||||
| 	return s; | ||||
| } | ||||
|  | ||||
| static void stack_term(Stack *s) { | ||||
| 	/* free any dynamically allocated objects still alive */ | ||||
| 	for (size_t i = 0; i < s->cap; i++) { | ||||
| 		if (s->holds_value[i]) | ||||
| 			free_value(&s->mem[i], false); | ||||
| 	} | ||||
| 	/* free the stack memory itself */ | ||||
| 	free(s->mem); | ||||
| 	free(s->holds_value); | ||||
| } | ||||
|  | ||||
| static void stack_fit(Stack *s, size_t idx) { | ||||
| 	size_t size = idx+1; | ||||
| 	if (size > s->cap) { | ||||
| 		s->mem = xrealloc(s->mem, sizeof(Value) * (size + (s->cap *= 2))); | ||||
| 		size_t new_cap = size + s->cap * 2; | ||||
| 		s->mem = xrealloc(s->mem, sizeof(Value) * new_cap); | ||||
| 		s->holds_value = xrealloc(s->holds_value, sizeof(bool) * new_cap); | ||||
| 		for (size_t i = s->cap; i < new_cap; i++) | ||||
| 			s->holds_value[i] = false; | ||||
| 		s->cap = new_cap; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -42,65 +59,161 @@ static Value *irparam_to_val(Stack *s, IRParam *v) { | ||||
| 		ASSERT_UNREACHED(); | ||||
| } | ||||
|  | ||||
| void run(const IRToks *ir, const BuiltinFunc *builtin_funcs) { | ||||
| static void stack_assign(Stack *s, size_t idx, const Value *v) { | ||||
| 	stack_fit(s, idx); | ||||
| 	if (s->holds_value[idx]) | ||||
| 		free_value(&s->mem[idx], false); /* free any overwritten heap-allocated values */ | ||||
| 	s->mem[idx] = *v; | ||||
| 	s->holds_value[idx] = true; | ||||
| } | ||||
|  | ||||
| void run(IRList *ir, const BuiltinFunc *builtin_funcs) { | ||||
| 	/* so we don't have to call malloc on every function call */ | ||||
| 	size_t fn_args_cap = 16; | ||||
| 	Value *fn_args = xmalloc(sizeof(Value) * fn_args_cap); | ||||
|  | ||||
| 	/* so we can use index-based addressing */ | ||||
| 	irlist_update_index(ir); | ||||
|  | ||||
| 	Stack s = stack_make(); | ||||
| 	for (size_t i = 0; i < ir->len;) { | ||||
| 		IRTok *instr = &ir->toks[i]; | ||||
| 	for (IRItem *i = ir->begin; i;) { | ||||
| 		IRTok *instr = &i->tok; | ||||
| 		err_ln = instr->ln; | ||||
| 		err_col = instr->col; | ||||
| 		switch (instr->instr) { | ||||
| 			case IRSet: | ||||
| 			case IRNeg: | ||||
| 			case IRNot: | ||||
| 				stack_fit(&s, instr->Unary.addr); | ||||
| 				TRY_ELSE(s.mem[instr->Unary.addr] = eval_unary(instr->instr, irparam_to_val(&s, &instr->Unary.val)), | ||||
| 			case IRNot: { | ||||
| 				Value res; | ||||
| 				TRY_ELSE(res = eval_unary(instr->instr, irparam_to_val(&s, &instr->Unary.val)), | ||||
| 					{free(fn_args); stack_term(&s);}); | ||||
| 				stack_assign(&s, instr->Unary.addr, &res); | ||||
| 				break; | ||||
| 			} | ||||
| 			case IRAddrOf: { | ||||
| 				if (instr->Unary.val.kind != IRParamAddr) { | ||||
| 					set_err("Unable to take the address of a literal"); | ||||
| 					free(fn_args); | ||||
| 					stack_term(&s); | ||||
| 					return; | ||||
| 				} | ||||
| 				Value *v = &s.mem[instr->Unary.val.Addr]; | ||||
| 				Value res = { | ||||
| 					.type = TypePtr, | ||||
| 					.Ptr = { | ||||
| 						.type = v->type, | ||||
| 						.val = &v->Void, | ||||
| 					}, | ||||
| 				}; | ||||
| 				stack_assign(&s, instr->Unary.addr, &res); | ||||
| 				break; | ||||
| 			} | ||||
| 			case IRAdd: | ||||
| 			case IRSub: | ||||
| 			case IRDiv: | ||||
| 			case IRMul: | ||||
| 			case IREq: | ||||
| 			case IRNeq: | ||||
| 			case IRLt: | ||||
| 			case IRLe: | ||||
| 				stack_fit(&s, instr->Binary.addr); | ||||
| 				TRY_ELSE(s.mem[instr->Binary.addr] = eval_binary(instr->instr, | ||||
| 			case IRAnd: | ||||
| 			case IROr: { | ||||
| 				Value res; | ||||
| 				TRY_ELSE(res = eval_binary(instr->instr, | ||||
| 					irparam_to_val(&s, &instr->Binary.lhs), | ||||
| 					irparam_to_val(&s, &instr->Binary.rhs)), | ||||
| 					{free(fn_args); stack_term(&s);}); | ||||
| 				stack_assign(&s, instr->Binary.addr, &res); | ||||
| 				break; | ||||
| 			} | ||||
| 			case IRJmp: | ||||
| 				i = instr->Jmp.iaddr; | ||||
| 				if (instr->Jmp.iaddr < ir->len) | ||||
| 					i = ir->index[instr->Jmp.iaddr]; | ||||
| 				else | ||||
| 					i = NULL; | ||||
| 				continue; | ||||
| 			case IRJnz: | ||||
| 				if (is_nonzero(irparam_to_val(&s, &instr->CJmp.condition))) { | ||||
| 					i = instr->Jmp.iaddr; | ||||
| 					if (instr->Jmp.iaddr < ir->len) | ||||
| 						i = ir->index[instr->CJmp.iaddr]; | ||||
| 					else | ||||
| 						i = NULL; | ||||
| 					continue; | ||||
| 				} | ||||
| 				break; | ||||
| 			case IRCallInternal: { | ||||
| 				const BuiltinFunc *f = &builtin_funcs[instr->CallI.fid]; | ||||
| 				size_t n_args = instr->CallI.n_args; | ||||
| 				/* make sure enough space for our arguments is allocated */ | ||||
| 				if (f->n_args > fn_args_cap) | ||||
| 					fn_args = xrealloc(fn_args, sizeof(Value) * (fn_args_cap = f->n_args)); | ||||
| 				if (n_args > fn_args_cap) | ||||
| 					fn_args = xrealloc(fn_args, sizeof(Value) * (fn_args_cap = n_args)); | ||||
| 				/* copy arguments into buffer */ | ||||
| 				for (size_t i = 0; i < f->n_args; i++) | ||||
| 				for (size_t i = 0; i < n_args; i++) | ||||
| 					fn_args[i] = *irparam_to_val(&s, &instr->CallI.args[i]); | ||||
|  | ||||
| 				stack_fit(&s, instr->CallI.ret_addr); | ||||
| 				TRY_ELSE(s.mem[instr->CallI.ret_addr] = f->func(fn_args), | ||||
| 					{free(fn_args); stack_term(&s);}); | ||||
| 				if (f->returns) { | ||||
| 					Value res; | ||||
| 					if (f->kind == FuncVarArgs) { | ||||
| 						size_t min_args = f->VarArgs.min_args; | ||||
| 						TRY_ELSE(res = f->VarArgs.WithRet.func(n_args - min_args, fn_args), | ||||
| 							{free(fn_args); stack_term(&s);}); | ||||
| 					} else if (f->kind == FuncFixedArgs) { | ||||
| 						TRY_ELSE(res = f->FixedArgs.WithRet.func(fn_args), | ||||
| 							{free(fn_args); stack_term(&s);}); | ||||
| 					} else | ||||
| 						ASSERT_UNREACHED(); | ||||
| 					stack_assign(&s, instr->CallI.ret_addr, &res); | ||||
| 				} else { | ||||
| 					if (f->kind == FuncVarArgs) { | ||||
| 						size_t min_args = f->VarArgs.min_args; | ||||
| 						TRY_ELSE(f->VarArgs.NoRet.func(n_args - min_args, fn_args), | ||||
| 							{free(fn_args); stack_term(&s);}); | ||||
| 					} else if (f->kind == FuncFixedArgs) { | ||||
| 						TRY_ELSE(f->FixedArgs.NoRet.func(fn_args), | ||||
| 							{free(fn_args); stack_term(&s);}); | ||||
| 					} else | ||||
| 						ASSERT_UNREACHED(); | ||||
| 				} | ||||
| 				break; | ||||
| 			} | ||||
| 			case IRArrMake: { | ||||
| 				size_t arr_len = instr->ArrMake.len, arr_cap = instr->ArrMake.cap; | ||||
| 				Value arr = { | ||||
| 					.type = TypeArr, | ||||
| 					.Arr = { | ||||
| 						.type = TypeVoid, | ||||
| 						.is_string = false, | ||||
| 						.dynamically_allocated = true, | ||||
| 						.vals = NULL, | ||||
| 						.len = arr_len, | ||||
| 						.cap = arr_len ? arr_cap : 0, | ||||
| 					}, | ||||
| 				}; | ||||
| 				if (arr_len) { | ||||
| 					Type arr_ty = irparam_to_val(&s, &instr->ArrMake.vals[0])->type; | ||||
| 					void *arr_vals = xmalloc(type_size[arr_ty] * arr_cap); | ||||
| 					for (size_t j = 0; j < arr_len; j++) { | ||||
| 						Value *v = irparam_to_val(&s, &instr->ArrMake.vals[j]); | ||||
| 						if (v->type != arr_ty) { | ||||
| 							set_err("Type of array item %zu (%s) differs from array type (%s)", j, type_str[v->type], type_str[arr_ty]); | ||||
| 							free(arr_vals); | ||||
| 							free(fn_args); | ||||
| 							stack_term(&s); | ||||
| 							return; | ||||
| 						} | ||||
| 						memcpy((uint8_t*)arr_vals + type_size[arr_ty] * j, &v->Void, type_size[arr_ty]); | ||||
| 					} | ||||
| 					arr.Arr.type = arr_ty; | ||||
| 					arr.Arr.vals = arr_vals; | ||||
| 				} | ||||
| 				stack_assign(&s, instr->ArrMake.arr_addr, &arr); | ||||
| 				break; | ||||
| 			} | ||||
| 			default: | ||||
| 				ASSERT_UNREACHED(); | ||||
| 		} | ||||
|  | ||||
| 		i++; | ||||
| 		i = i->next; | ||||
| 	} | ||||
| 	stack_term(&s); | ||||
| 	free(fn_args); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user