Compare commits
38 Commits
298883939b
...
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 | ||
|
|
84785dc3cf | ||
|
|
d185396a1c | ||
|
|
97e8e32ebc | ||
|
|
ef63742015 | ||
|
|
7ae9ddaee9 |
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).
|
||||
@@ -1,12 +0,0 @@
|
||||
sum := 0.0
|
||||
k := 0
|
||||
|
||||
iterations := 100
|
||||
|
||||
while k - iterations {
|
||||
k_f := float(k)
|
||||
sum = sum + 1.0 / pow(16.0, k_f) * (4.0 / (8.0 * k_f + 1.0) - 2.0 / (8.0 * k_f + 4.0) - 1.0 / (8.0 * k_f + 5.0) - 1.0 / (8.0 * k_f + 6.0))
|
||||
k = k + 1
|
||||
}
|
||||
|
||||
print(sum)
|
||||
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
|
||||
}
|
||||
17
examples/pi.script
Normal file
17
examples/pi.script
Normal file
@@ -0,0 +1,17 @@
|
||||
sum := 0.0
|
||||
k := 0
|
||||
|
||||
iterations := 100
|
||||
|
||||
while k < iterations {
|
||||
k_f := float(k)
|
||||
sum = sum + 1.0 / pow(16.0, k_f) *
|
||||
(4.0 / (8.0 * k_f + 1.0) -
|
||||
2.0 / (8.0 * k_f + 4.0) -
|
||||
1.0 / (8.0 * k_f + 5.0) -
|
||||
1.0 / (8.0 * k_f + 6.0))
|
||||
k = k + 1
|
||||
}
|
||||
|
||||
put("π ≈ ")
|
||||
putln(sum)
|
||||
255
ir.c
255
ir.c
@@ -10,116 +10,225 @@ const char *irinstr_str[IRInstrEnumSize] = {
|
||||
[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_app_irtoks(IRToks *v, IRToks *other) {
|
||||
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++)
|
||||
v->toks[v->len++] = other->toks[i];
|
||||
}
|
||||
|
||||
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;
|
||||
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:
|
||||
printf(" %%%zx ", v->toks[i].Arith.addr);
|
||||
print_irparam(&v->toks[i].Arith.lhs);
|
||||
printf(" ");
|
||||
print_irparam(&v->toks[i].Arith.rhs);
|
||||
case IREq:
|
||||
case IRNeq:
|
||||
case IRLt:
|
||||
case IRLe:
|
||||
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]);
|
||||
}
|
||||
printf(" ; %zu:%zu", v->toks[i].ln, v->toks[i].col);
|
||||
break;
|
||||
}
|
||||
default: ASSERT_UNREACHED();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
76
ir.h
76
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 {
|
||||
@@ -18,9 +40,18 @@ enum IRInstr {
|
||||
IRSub,
|
||||
IRMul,
|
||||
IRDiv,
|
||||
IREq,
|
||||
IRNeq,
|
||||
IRLt,
|
||||
IRLe,
|
||||
IRNot,
|
||||
IRAnd,
|
||||
IROr,
|
||||
IRJmp,
|
||||
IRJnz,
|
||||
IRCallInternal,
|
||||
IRAddrOf,
|
||||
IRArrMake,
|
||||
IRInstrEnumSize,
|
||||
};
|
||||
typedef enum IRInstr IRInstr;
|
||||
@@ -59,7 +90,7 @@ typedef struct IRTok {
|
||||
struct {
|
||||
size_t addr;
|
||||
IRParam lhs, rhs;
|
||||
} Arith;
|
||||
} Binary;
|
||||
|
||||
struct {
|
||||
size_t iaddr;
|
||||
@@ -73,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_app_irtoks(IRToks *v, IRToks *other);
|
||||
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 */
|
||||
|
||||
160
lex.c
160
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);
|
||||
@@ -54,8 +76,14 @@ TokList lex(const char *s) {
|
||||
}
|
||||
if (streq_0_n("if", start, i))
|
||||
emit(&toks, &pos, (Tok){ .kind = TokIf });
|
||||
else if (streq_0_n("else", start, i))
|
||||
emit(&toks, &pos, (Tok){ .kind = TokElse });
|
||||
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 = TypeBool, .Bool = true }});
|
||||
else if (streq_0_n("false", start, i))
|
||||
emit(&toks, &pos, (Tok){ .kind = TokVal, .Val = { .type = TypeBool, .Bool = false }});
|
||||
else {
|
||||
emit(&toks, &pos, (Tok){
|
||||
.kind = TokIdent,
|
||||
@@ -118,9 +146,7 @@ TokList lex(const char *s) {
|
||||
emit(&toks, &pos, (Tok){
|
||||
.kind = TokVal,
|
||||
.Val = {
|
||||
.type = {
|
||||
.kind = TypeFloat,
|
||||
},
|
||||
.type = TypeFloat,
|
||||
.Float = num,
|
||||
},
|
||||
});
|
||||
@@ -136,9 +162,7 @@ TokList lex(const char *s) {
|
||||
emit(&toks, &pos, (Tok){
|
||||
.kind = TokVal,
|
||||
.Val = {
|
||||
.type = {
|
||||
.kind = TypeInt,
|
||||
},
|
||||
.type = TypeInt,
|
||||
.Int = num,
|
||||
},
|
||||
});
|
||||
@@ -160,20 +184,71 @@ TokList lex(const char *s) {
|
||||
break;
|
||||
case ':':
|
||||
consume(&pos, *(s++));
|
||||
if (s[0] == '=') {
|
||||
if (s[0] == '=')
|
||||
emit(&toks, &pos, (Tok){ .kind = TokDeclare });
|
||||
} else {
|
||||
else {
|
||||
set_err("Expected ':='");
|
||||
return toks;
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
consume(&pos, *(s++));
|
||||
if (s[0] == '=')
|
||||
emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpEq });
|
||||
else {
|
||||
emit(&toks, &pos, (Tok){ .kind = TokAssign });
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '<':
|
||||
consume(&pos, *(s++));
|
||||
if (s[0] == '=')
|
||||
emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpLe });
|
||||
else {
|
||||
emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpLt });
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
consume(&pos, *(s++));
|
||||
if (s[0] == '=')
|
||||
emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpGe });
|
||||
else {
|
||||
emit(&toks, &pos, (Tok){ .kind = TokOp, .Op = OpGt });
|
||||
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 '-':
|
||||
@@ -217,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;
|
||||
|
||||
178
main.c
178
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,26 +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;
|
||||
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) {
|
||||
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;
|
||||
@@ -60,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) {
|
||||
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,
|
||||
.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;
|
||||
@@ -140,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);
|
||||
}
|
||||
|
||||
490
parse.c
490
parse.c
@@ -30,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;
|
||||
@@ -44,17 +47,28 @@ static void set_irtok_dest_addr(IRTok *t, size_t addr) {
|
||||
switch (t->instr) {
|
||||
case IRSet:
|
||||
case IRNeg:
|
||||
case IRNot:
|
||||
case IRAddrOf:
|
||||
t->Unary.addr = addr;
|
||||
break;
|
||||
case IRAdd:
|
||||
case IRSub:
|
||||
case IRMul:
|
||||
case IRDiv:
|
||||
t->Arith.addr = addr;
|
||||
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();
|
||||
}
|
||||
@@ -113,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
|
||||
@@ -164,17 +222,37 @@ 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);
|
||||
|
||||
for (;;) {
|
||||
/* Prepare to collapse negative factor. */
|
||||
bool negate = false;
|
||||
if (t->tok.kind == TokOp && t->tok.Op == OpSub) {
|
||||
/* Prepare to collapse unary operation. */
|
||||
bool perform_unary = false;
|
||||
IRInstr unary_op;
|
||||
if (t->tok.kind == TokOp) {
|
||||
if (t->tok.Op == OpSub) {
|
||||
t = t->next;
|
||||
negate = true;
|
||||
perform_unary = true;
|
||||
unary_op = IRNeg;
|
||||
} else if (t->tok.Op == OpNot) {
|
||||
t = t->next;
|
||||
perform_unary = true;
|
||||
unary_op = IRNot;
|
||||
} else if (t->tok.Op == OpAddrOf) {
|
||||
t = t->next;
|
||||
perform_unary = true;
|
||||
unary_op = IRAddrOf;
|
||||
}
|
||||
}
|
||||
|
||||
/* Delete newline if we're definitely expecting an operand. */
|
||||
if (t->tok.kind == TokOp && t->tok.Op == OpNewLn) {
|
||||
if (t == start)
|
||||
start = t->next;
|
||||
t = t->next;
|
||||
toklist_del(toks, t->prev, t->prev);
|
||||
}
|
||||
|
||||
/* Collapse parentheses. */
|
||||
@@ -184,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,
|
||||
@@ -201,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 */
|
||||
@@ -218,8 +297,15 @@ 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;
|
||||
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;
|
||||
IRParam *args = xmalloc(sizeof(IRParam) * args_cap);
|
||||
args = xmalloc(sizeof(IRParam) * args_cap);
|
||||
for (;;) {
|
||||
if (args_len+1 > args_cap)
|
||||
args = xrealloc(args, (args_cap *= 2));
|
||||
@@ -230,10 +316,10 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc,
|
||||
eval_func_in_place = false;
|
||||
if (t->next->tok.kind == TokOp) {
|
||||
if (t->next->tok.Op == OpComma) {
|
||||
toklist_del(toks, t->next, t->next);
|
||||
toklist_del(toks, t->next, t->next); /* delete comma */
|
||||
continue;
|
||||
} else if (t->next->tok.Op == OpRParen) {
|
||||
toklist_del(toks, t->next, t->next);
|
||||
toklist_del(toks, t->next, t->next); /* delete right parenthesis */
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -242,109 +328,197 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc,
|
||||
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);
|
||||
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),
|
||||
};
|
||||
/* 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 negative factor. */
|
||||
if (negate) {
|
||||
Tok *v = &t->tok; /* what we want to negate */
|
||||
/* Collapse unary operation. */
|
||||
if (perform_unary) {
|
||||
Tok *v = &t->tok; /* what we want to perform the operation on */
|
||||
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 negate value */
|
||||
/* immediately perform operation */
|
||||
t->tok.kind = TokVal;
|
||||
t->tok.Val = eval_unary(IRNeg, &v->Val);
|
||||
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++;
|
||||
|
||||
/* Instruction to negate. */
|
||||
/* 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 = IRNeg,
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,12 +585,22 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc,
|
||||
* because we're still using their values later on) */
|
||||
toklist_del(toks, t->next, t->next->next);
|
||||
|
||||
bool swap_operands = false;
|
||||
|
||||
IRInstr instr;
|
||||
switch (l_op->Op) {
|
||||
case OpAdd: instr = IRAdd; break;
|
||||
case OpSub: instr = IRSub; break;
|
||||
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]);
|
||||
@@ -425,54 +609,43 @@ static ExprRet expr(IRToks *out_ir, TokList *toks, Map *funcs, Scope *parent_sc,
|
||||
|
||||
if (lhs->kind == TokVal && rhs->kind == TokVal) {
|
||||
/* evaluate the constant expression immediately */
|
||||
Value *lhs_val = swap_operands ? &rhs->Val : &lhs->Val;
|
||||
Value *rhs_val = swap_operands ? &lhs->Val : &rhs->Val;
|
||||
lhs->kind = TokVal;
|
||||
TRY_RET(lhs->Val = eval_arith(instr, &lhs->Val, &rhs->Val), (ExprRet){0});
|
||||
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,
|
||||
.Arith = {
|
||||
.addr = res_addr,
|
||||
.lhs = lhs_irparam,
|
||||
.rhs = rhs_irparam,
|
||||
.Binary = {
|
||||
.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,
|
||||
@@ -485,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,
|
||||
@@ -497,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,
|
||||
@@ -518,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));
|
||||
@@ -541,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)
|
||||
@@ -565,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,
|
||||
@@ -574,36 +759,96 @@ 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 */
|
||||
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 */
|
||||
jmp_instr->tok.Jmp.iaddr = out_ir->len;
|
||||
|
||||
/* append condition IR to program IR, then terminate condition IR stream */
|
||||
irlist_eat_irlist(out_ir, &cond_ir);
|
||||
|
||||
/* add conditional jump */
|
||||
irtoks_app(&cond_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,
|
||||
},
|
||||
});
|
||||
|
||||
/* parse loop body */
|
||||
TRY_ELSE(stmt(out_ir, toks, funcs, sc, t->next), irtoks_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;
|
||||
|
||||
/* append condition IR to program IR, then terminate condition IR stream */
|
||||
irtoks_app_irtoks(out_ir, &cond_ir);
|
||||
irtoks_term(&cond_ir);
|
||||
|
||||
t = t->next;
|
||||
} else if (t->tok.kind == TokOp && t->tok.Op == OpNewLn) {
|
||||
} else if (t->tok.kind == TokIf) {
|
||||
/* How if is generally implemented in IR:
|
||||
* 0: some stuff evaluating condition xyz
|
||||
* 1: jmp to 5 if condition xyz is met
|
||||
* 2: some_code in else
|
||||
* 4: jmp to 6
|
||||
* 5: some_code in if
|
||||
* */
|
||||
|
||||
/* parse condition */
|
||||
IRParam cond;
|
||||
TRY(cond = expr_into_irparam(out_ir, toks, funcs, sc, t->next));
|
||||
|
||||
/* add conditional jmp instruction */
|
||||
irlist_app(out_ir, (IRTok){
|
||||
.ln = t->tok.ln,
|
||||
.col = t->tok.col,
|
||||
.instr = IRJnz,
|
||||
.CJmp = {
|
||||
.iaddr = 0, /* unknown for now */
|
||||
.condition = cond,
|
||||
},
|
||||
});
|
||||
IRItem *if_cjmp_instr = out_ir->end;
|
||||
|
||||
/* parse 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 */
|
||||
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 */
|
||||
irlist_app(out_ir, (IRTok){
|
||||
.ln = t->tok.ln,
|
||||
.col = t->tok.col,
|
||||
.instr = IRJmp,
|
||||
.Jmp = {
|
||||
.iaddr = 0, /* unknown for now */
|
||||
},
|
||||
});
|
||||
IRItem *else_jmp_instr = out_ir->end;
|
||||
|
||||
/* set if condition jmp target */
|
||||
if_cjmp_instr->tok.CJmp.iaddr = out_ir->len;
|
||||
|
||||
/* add if body */
|
||||
irlist_eat_irlist(out_ir, &if_body);
|
||||
|
||||
/* set else jmp target */
|
||||
else_jmp_instr->tok.CJmp.iaddr = out_ir->len;
|
||||
} else {
|
||||
/* assume expression */
|
||||
TRY(expr_into_irparam(out_ir, toks, funcs, sc, t));
|
||||
@@ -612,7 +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) {
|
||||
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++) {
|
||||
@@ -622,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 */
|
||||
|
||||
98
runtime.c
98
runtime.c
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "util.h"
|
||||
|
||||
Value eval_arith(IRInstr instr, const Value *lhs, const Value *rhs) {
|
||||
Value eval_binary(IRInstr instr, const Value *lhs, const Value *rhs) {
|
||||
switch (instr) {
|
||||
case IRAdd:
|
||||
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_arith(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,14 +31,69 @@ Value eval_arith(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: {
|
||||
bool res;
|
||||
if (lhs->type == TypeInt && rhs->type == TypeInt) {
|
||||
switch (instr) {
|
||||
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 == TypeFloat && rhs->type == TypeFloat) {
|
||||
switch (instr) {
|
||||
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': %s and %s", irinstr_str[instr], type_str[lhs->type], type_str[rhs->type]);
|
||||
return (Value){0};
|
||||
}
|
||||
return (Value){
|
||||
.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();
|
||||
}
|
||||
@@ -50,23 +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 types 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 == TypeBool) {
|
||||
return (Value){ .type = TypeBool, .Bool = !v->Bool };
|
||||
} else {
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -74,9 +143,10 @@ 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;
|
||||
default: ASSERT_UNREACHED();
|
||||
}
|
||||
return ret;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
#include "ir.h"
|
||||
|
||||
Value eval_arith(IRInstr instr, const Value *lhs, const Value *rhs);
|
||||
Value eval_binary(IRInstr instr, const Value *lhs, const Value *rhs);
|
||||
Value eval_unary(IRInstr instr, const Value *v);
|
||||
bool is_nonzero(const Value *v);
|
||||
Value zero_val(Type ty);
|
||||
|
||||
151
tok.c
151
tok.c
@@ -5,16 +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,
|
||||
[OpAdd] = 0,
|
||||
[OpSub] = 0,
|
||||
[OpMul] = 1,
|
||||
[OpDiv] = 1,
|
||||
[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] = {
|
||||
@@ -22,19 +138,31 @@ const char *op_str[OperatorEnumSize] = {
|
||||
[OpRCurl] = "}",
|
||||
[OpLParen] = "(",
|
||||
[OpRParen] = ")",
|
||||
[OpLBrack] = "[",
|
||||
[OpRBrack] = "]",
|
||||
[OpComma] = ",",
|
||||
[OpAdd] = "+",
|
||||
[OpSub] = "-",
|
||||
[OpMul] = "*",
|
||||
[OpDiv] = "/",
|
||||
[OpNot] = "!",
|
||||
[OpNewLn] = "\\n",
|
||||
[OpEOF] = "EOF",
|
||||
[OpEq] = "==",
|
||||
[OpNeq] = "!=",
|
||||
[OpLt] = "<",
|
||||
[OpGt] = ">",
|
||||
[OpLe] = "<=",
|
||||
[OpGe] = ">=",
|
||||
[OpAnd] = "&&",
|
||||
[OpOr] = "||",
|
||||
};
|
||||
|
||||
const char *tok_str[TokKindEnumSize] = {
|
||||
[TokAssign] = "=",
|
||||
[TokDeclare] = ":=",
|
||||
[TokIf] = "if",
|
||||
[TokElse] = "else",
|
||||
[TokWhile] = "while",
|
||||
};
|
||||
|
||||
@@ -92,18 +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;
|
||||
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);
|
||||
|
||||
47
tok.h
47
tok.h
@@ -2,41 +2,73 @@
|
||||
#define __TOK_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
typedef struct Type {
|
||||
enum {
|
||||
enum Type {
|
||||
TypeVoid = 0,
|
||||
TypeFloat,
|
||||
TypeInt,
|
||||
} kind;
|
||||
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,
|
||||
@@ -69,6 +101,7 @@ typedef struct Tok {
|
||||
TokAssign,
|
||||
TokDeclare,
|
||||
TokIf,
|
||||
TokElse,
|
||||
TokWhile,
|
||||
TokKindEnumSize,
|
||||
} kind;
|
||||
|
||||
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);
|
||||
|
||||
169
vm.c
169
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,54 +59,162 @@ 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:
|
||||
stack_fit(&s, instr->Unary.addr);
|
||||
TRY(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:
|
||||
stack_fit(&s, instr->Arith.addr);
|
||||
TRY(s.mem[instr->Arith.addr] = eval_arith(instr->instr,
|
||||
irparam_to_val(&s, &instr->Arith.lhs),
|
||||
irparam_to_val(&s, &instr->Arith.rhs)
|
||||
));
|
||||
case IREq:
|
||||
case IRNeq:
|
||||
case IRLt:
|
||||
case IRLe:
|
||||
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];
|
||||
Value *args = xmalloc(sizeof(Value) * f->n_args);
|
||||
for (size_t i = 0; i < f->n_args; i++)
|
||||
args[i] = *irparam_to_val(&s, &instr->CallI.args[i]);
|
||||
size_t n_args = instr->CallI.n_args;
|
||||
/* make sure enough space for our arguments is allocated */
|
||||
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 < 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(args), free(args));
|
||||
|
||||
free(args);
|
||||
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