Compare commits

..

2 commits

Author SHA1 Message Date
5038517d72 random 2025-06-28 20:54:33 -06:00
d58f238fc9 comparison and assignment operators 2025-06-27 22:56:59 -06:00
3 changed files with 201 additions and 37 deletions

View file

@ -63,7 +63,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
value := value_nil; value := value_nil;
if var.value_expr != null { if var.value_expr != null {
value = interp_expr(i, var.value_expr, scope); value = interp_expression(i, var.value_expr, scope);
basic.assert(value != null); // @errors basic.assert(value != null); // @errors
} }
@ -72,7 +72,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
case .assign; case .assign;
assign := stmt.(*Node_Assign); assign := stmt.(*Node_Assign);
src := interp_expr(i, assign.src, scope); src := interp_expression(i, assign.src, scope);
basic.assert(src != null); // @errors basic.assert(src != null); // @errors
dst := interp_lvalue(i, assign.dst, scope); dst := interp_lvalue(i, assign.dst, scope);
@ -80,10 +80,8 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
basic.assert(dst.kind == .value); // @errors basic.assert(dst.kind == .value); // @errors
// @todo: typechecking // @todo: typechecking
if assign.op.kind == { basic.assert(assign.op.kind == .equal, "unexpected assign op: %", assign.op.kind);
case .equal; dst.val.* = src.*;
dst.val.* = src.*;
}
case .procedure; case .procedure;
proc := stmt.(*Node_Procedure); proc := stmt.(*Node_Procedure);
@ -96,7 +94,7 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
case .print; case .print;
print := stmt.(*Node_Print); print := stmt.(*Node_Print);
expr := interp_expr(i, print.expr, scope); expr := interp_expression(i, print.expr, scope);
if expr == null return; if expr == null return;
if expr.kind == { if expr.kind == {
@ -111,8 +109,15 @@ interp_statement :: (i: *Interp, stmt: *Node, scope: *Interp_Scope) {
basic.print("\n"); basic.print("\n");
case .assert;
assert := stmt.(*Node_Assert);
expr := interp_expression(i, assert.expr, scope);
basic.assert(expr != null, "runtime assertion failed");
basic.assert(expr.kind == .bool, "non-boolean expression given to assert");
basic.assert(expr.b, "runtime assertion failed");
case; case;
interp_expr(i, stmt, scope); interp_expression(i, stmt, scope);
// basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors // basic.assert(false, "unhandled node kind: %", stmt.kind); // @errors
} }
} }
@ -133,7 +138,7 @@ interp_lvalue :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Valu
return null; return null;
} }
interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value { interp_expression :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value {
if expr.kind == { if expr.kind == {
case .procedure_call; case .procedure_call;
call := expr.(*Node_Procedure_Call); call := expr.(*Node_Procedure_Call);
@ -153,14 +158,14 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value
proc_scope := make_scope(scope); proc_scope := make_scope(scope);
for proc.arguments { for proc.arguments {
kv.set(*proc_scope.bindings, it.symbol.str, interp_expr(i, args[it_index], scope)); kv.set(*proc_scope.bindings, it.symbol.str, interp_expression(i, args[it_index], scope));
} }
for expr: proc.block.body { for expr: proc.block.body {
if expr.kind == .return_ { if expr.kind == .return_ {
ret := expr.(*Node_Return); ret := expr.(*Node_Return);
if ret.values.count != 0 { if ret.values.count != 0 {
result = interp_expr(i, ret.values[0], proc_scope); result = interp_expression(i, ret.values[0], proc_scope);
} }
break; break;
@ -187,7 +192,7 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value
} }
un := expr.(*Node_Unary); un := expr.(*Node_Unary);
rhs := interp_expr(i, un.right, scope); rhs := interp_expression(i, un.right, scope);
res := make_interp_value(i, rhs.kind); res := make_interp_value(i, rhs.kind);
if un.op.kind == { if un.op.kind == {
case .plus; do_unop(#code ifx right < 0 then -right else right); case .plus; do_unop(#code ifx right < 0 then -right else right);
@ -199,8 +204,8 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value
case .binary; case .binary;
bin := expr.(*Node_Binary); bin := expr.(*Node_Binary);
lhs := interp_expr(i, bin.left, scope); lhs := interp_expression(i, bin.left, scope);
rhs := interp_expr(i, bin.right, scope); rhs := interp_expression(i, bin.right, scope);
basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors basic.assert(lhs.kind == rhs.kind, "type mismatch % vs. %", lhs.kind, rhs.kind); // @errors
do_binop :: (code: Code) #expand { do_binop :: (code: Code) #expand {
@ -220,16 +225,38 @@ interp_expr :: (i: *Interp, expr: *Node, scope: *Interp_Scope) -> *Interp_Value
res := make_interp_value(i, lhs.kind); res := make_interp_value(i, lhs.kind);
if bin.op.kind == { if bin.op.kind == {
case .plus; do_binop(#code left + right); case .plus; do_binop(#code left + right);
case .minus; do_binop(#code left - right); case .minus; do_binop(#code left - right);
case .star; do_binop(#code left * right); case .star; do_binop(#code left * right);
case .f_slash; case .f_slash;
basic.assert(rhs.i != 0, "divide by zero"); // @errors basic.assert(rhs.i != 0, "divide by zero"); // @errors
do_binop(#code left / right); do_binop(#code left / right);
case .percent; case .percent;
basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind); basic.assert(lhs.kind == .int, "cannot use binary operator '%%' on values of type '%'", lhs.kind);
res.i = lhs.i % rhs.i; res.i = lhs.i % rhs.i;
// @todo: typechecking
case .equal_equal;
res.kind = .bool;
res.b = lhs.i == rhs.i;
case .bang_equal;
res.kind = .bool;
res.b = lhs.i != rhs.i;
case .less;
res.kind = .bool;
res.b = lhs.i < rhs.i;
case .less_equal;
res.kind = .bool;
res.b = lhs.i <= rhs.i;
case .more;
res.kind = .bool;
res.b = lhs.i > rhs.i;
case .more_equal;
res.kind = .bool;
res.b = lhs.i >= rhs.i;
case; basic.assert(false, "unhandled binary operator '%'", bin.op.str); case; basic.assert(false, "unhandled binary operator '%'", bin.op.str);
} }

View file

@ -28,6 +28,7 @@ strings :: #import "String"; // @future
var x = 21.0 var x = 21.0
var y = 22.0 var y = 22.0
var z = x + y
x = x + 1.0 / 2.0 x = x + 1.0 / 2.0
print x print x
@ -35,7 +36,53 @@ strings :: #import "String"; // @future
x = add(x, div(1.0, 2.0)) x = add(x, div(1.0, 2.0))
print x print x
print add(x, y) print x == x
print x == y
print x == z
assert x == y
assert x != z
// def (
// Add = poly[T] proc(x T, y T) T do return x + y end
// Sub = poly[T] proc(x T, y T) T do return x - y end
// Mul = poly[T] proc(x T, y T) T do return x * y end
// Div = poly[T] proc(x T, y T) T do return x / y end
// )
// def (
// Addi = Add[int]
// Addf = Add[float]
// Subi = Sub[int]
// Subf = Sub[float]
// Muli = Mul[int]
// Mulf = Mul[float]
// Divi = Div[int]
// Divf = Div[float]
// )
// def Foo = struct {
// x int = 1
// y int = 2
// z int = 3
// }
// def Value = union {
// i int
// f float
// b bool
// }
// def Kind = enum {
// a
// b
// c
// d
// }
// var foo = Foo{ x = 10, y = 20, z = 30 }
// var val = Value{ f = 3.14 }
// var kind = Kind.a
END); END);
interp: Interp; interp: Interp;

View file

@ -31,6 +31,23 @@ Token :: struct {
kw_false; kw_false;
kw_print; kw_print;
kw_assert;
equal_equal; // ==
bang_equal; // !=
and_equal; // &=
or_equal; // |=
less_equal; // <=
more_equal; // >=
plus_equal; // +=
minus_equal; // -=
star_equal; // *=
f_slash_equal; // /=
percent_equal; // %=
and_and_equal; // &&=
or_or_equal; // ||=
equal :: #char "="; equal :: #char "=";
plus :: #char "+"; plus :: #char "+";
@ -39,8 +56,11 @@ Token :: struct {
percent :: #char "%"; percent :: #char "%";
bang :: #char "!"; bang :: #char "!";
and :: #char "&"; and :: #char "&";
or :: #char "|";
f_slash :: #char "/"; f_slash :: #char "/";
b_slash :: #char "\\"; b_slash :: #char "\\";
less :: #char "<";
more :: #char ">";
l_paren :: #char "("; l_paren :: #char "(";
r_paren :: #char ")"; r_paren :: #char ")";
@ -52,6 +72,7 @@ Token :: struct {
dot :: #char "."; dot :: #char ".";
colon :: #char ":"; colon :: #char ":";
semicolon :: #char ";"; semicolon :: #char ";";
} }
} }
@ -66,6 +87,7 @@ Node :: struct {
stmt_start; stmt_start;
print; print;
assert;
return_; return_;
assign; assign;
stmt_end; stmt_end;
@ -218,6 +240,13 @@ Node_Print :: struct {
expr: *Node; expr: *Node;
} }
Node_Assert :: struct {
#as using n: Node;
n.kind = .assert;
expr: *Node;
}
Node_Procedure_Call :: struct { Node_Procedure_Call :: struct {
#as using n: Node; #as using n: Node;
n.kind = .procedure_call; n.kind = .procedure_call;
@ -353,6 +382,18 @@ parse_statement :: (p: *Parser) -> *Node {
node.expr = expr; node.expr = expr;
return node; return node;
// assert(cond)
// assert cond
case .kw_assert;
consume_token(p);
expr := parse_expression(p);
basic.assert(expr != null, "expected expression"); // @errors
node := make_node(p, Node_Assert);
node.expr = expr;
return node;
// fn symbol(arg0, ..argN) do ... end // fn symbol(arg0, ..argN) do ... end
case .kw_fn; case .kw_fn;
consume_token(p); consume_token(p);
@ -412,7 +453,12 @@ parse_simple_statement :: (p: *Parser) -> *Node {
t := peek_token(p); t := peek_token(p);
if t.kind == { if t.kind == {
case .equal; case .equal; #through;
case .plus_equal; #through;
case .minus_equal; #through;
case .star_equal; #through;
case .f_slash_equal; #through;
case .percent_equal;
consume_token(p); consume_token(p);
src := parse_expression(p); src := parse_expression(p);
@ -421,7 +467,29 @@ parse_simple_statement :: (p: *Parser) -> *Node {
node := make_node(p, Node_Assign); node := make_node(p, Node_Assign);
node.op = t; node.op = t;
node.dst = dst; node.dst = dst;
node.src = src;
if t.kind == .equal {
node.src = src;
}
// transform these into 'dst = dst op src'
else {
bin := make_node(p, Node_Binary);
bin.left = dst;
bin.right = src;
bin.op = t;
if t.kind == {
case .plus_equal; bin.op.kind = .plus;
case .minus_equal; bin.op.kind = .minus;
case .star_equal; bin.op.kind = .star;
case .f_slash_equal; bin.op.kind = .f_slash;
case .percent_equal; bin.op.kind = .percent;
}
node.op.kind = .equal;
node.src = bin;
}
return node; return node;
} }
@ -516,13 +584,13 @@ parse_expression :: (p: *Parser, min_precedence := 1) -> *Node {
case .minus; case .minus;
return 3; return 3;
// case .equal_equal; #through; case .equal_equal; #through;
// case .bang_equal; #through; case .bang_equal; #through;
// case .less; #through; case .less; #through;
// case .less_equal; #through; case .less_equal; #through;
// case .more; #through; case .more; #through;
// case .more_equal; case .more_equal;
// return 2; return 2;
} }
return 0; return 0;
@ -802,8 +870,9 @@ consume_token :: (p: *Parser) -> Token {
case "true"; t.kind = .kw_true; case "true"; t.kind = .kw_true;
case "false"; t.kind = .kw_false; case "false"; t.kind = .kw_false;
case "print"; t.kind = .kw_print; case "print"; t.kind = .kw_print;
case; t.kind = .symbol; case "assert"; t.kind = .kw_assert;
case; t.kind = .symbol;
} }
return t; return t;
@ -821,15 +890,36 @@ consume_token :: (p: *Parser) -> Token {
return t; return t;
} }
with_optional_equal :: (current_kind: Token.Kind, equal_kind: Token.Kind) -> Token #expand {
token := Token.{
kind = current_kind,
str = string.{ data = `p.source.data + `p.offset, count = 1 },
};
`p.offset += 1;
if `p.offset < `p.source.count && `p.source[`p.offset] == #char "=" {
`p.offset += 1;
token.kind = equal_kind;
token.str.count = 2;
}
return token;
}
if c == { if c == {
case "+"; #through; case "+"; return with_optional_equal(c.(Token.Kind), .plus_equal);
case "-"; #through; case "-"; return with_optional_equal(c.(Token.Kind), .minus_equal);
case "*"; #through; case "*"; return with_optional_equal(c.(Token.Kind), .star_equal);
case "/"; #through; case "/"; return with_optional_equal(c.(Token.Kind), .f_slash_equal);
case "="; #through; case "="; return with_optional_equal(c.(Token.Kind), .equal_equal);
case "%"; #through; case "%"; return with_optional_equal(c.(Token.Kind), .percent_equal);
case "!"; #through; case "!"; return with_optional_equal(c.(Token.Kind), .bang_equal);
case "&"; #through; case "&"; return with_optional_equal(c.(Token.Kind), .and_equal);
case "|"; return with_optional_equal(c.(Token.Kind), .or_equal);
case "<"; return with_optional_equal(c.(Token.Kind), .less_equal);
case ">"; return with_optional_equal(c.(Token.Kind), .more_equal);
case ","; #through; case ","; #through;
case "."; #through; case "."; #through;
case ":"; #through; case ":"; #through;