1
0
Fork 0
forked from fun/fun

Made Booleans a first-class type. (0.23.0)

This commit is contained in:
Johannes Findeisen 2025-10-04 17:34:23 +02:00
commit 95eba1eb12
12 changed files with 131 additions and 18 deletions

View file

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.16)
project(fun VERSION 0.22.0 LANGUAGES C)
project(fun VERSION 0.23.0 LANGUAGES C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)

6
examples/boolean_decl.fun Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env fun
boolean b = false
print("b => " + to_string(b))
/* Try typeof via built-in function if available */
print("typeof(b) => " + typeof(b))

52
examples/booleans.fun Executable file
View file

@ -0,0 +1,52 @@
#!/usr/bin/env fun
/*
* This file is part of the Fun programming language.
* https://hanez.org/project/fun/
*
* Copyright 2025 Johannes Findeisen <you@hanez.org>
* Licensed under the terms of the Apache-2.0 license.
* https://opensource.org/license/apache-2-0
*/
// Boolean datatype usage examples (first-class booleans)
// Run like:
// FUN_LIB_DIR="$(pwd)/lib" ./build/fun examples/booleans.fun
// Literals
boolean t = true
print("typeof(t) => " + typeof(t))
boolean f = false
print("typeof(f) => " + typeof(f))
print("t literal => " + to_string(t)) // "true"
print("f literal => " + to_string(f)) // "false"
// Logical operators (short-circuit semantics)
print("true && true => " + to_string(true && true))
print("true && false => " + to_string(true && false))
print("false || true => " + to_string(false || true))
print("false || false => " + to_string(false || false))
print("!true => " + to_string(!true))
print("!false => " + to_string(!false))
// Equality and inequality
print("true == true => " + to_string(true == true))
print("true != false => " + to_string(true != false))
// Interoperability with 0/1 (still supported)
print("true == 1 => " + to_string(true == 1))
print("false == 0 => " + to_string(false == 0))
print("1 == true => " + to_string(1 == true))
print("0 != true => " + to_string(0 != true))
// Use in conditionals
if t
print("if t: branch taken")
else
print("if t: branch NOT taken")
if f
print("if f: branch taken (unexpected)")
else
print("if f: else branch taken")

View file

@ -432,7 +432,7 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
char *name = NULL;
if (read_identifier_into(src, len, pos, &name)) {
if (strcmp(name, "true") == 0 || strcmp(name, "false") == 0) {
int ci = bytecode_add_constant(bc, make_int(strcmp(name, "true") == 0 ? 1 : 0));
int ci = bytecode_add_constant(bc, make_bool(strcmp(name, "true") == 0 ? 1 : 0));
free(name);
bytecode_add_instruction(bc, OP_LOAD_CONST, ci);
return 1;
@ -1775,7 +1775,7 @@ static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
}
/* all were truthy -> result true */
int c1 = bytecode_add_constant(bc, make_int(1));
int c1 = bytecode_add_constant(bc, make_bool(1));
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
int j_end = bytecode_add_instruction(bc, OP_JUMP, 0);
@ -1784,7 +1784,7 @@ static int emit_and_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
for (int i = 0; i < jf_count; ++i) {
bytecode_set_operand(bc, jf_idxs[i], l_false);
}
int c0 = bytecode_add_constant(bc, make_int(0));
int c0 = bytecode_add_constant(bc, make_bool(0));
bytecode_add_instruction(bc, OP_LOAD_CONST, c0);
/* end */
@ -1813,8 +1813,8 @@ static int emit_or_expr(Bytecode *bc, const char *src, size_t len, size_t *pos)
/* if current value is false -> proceed to next; else -> result true */
int jf_proceed = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
/* true path: push 1 and jump to end */
int c1 = bytecode_add_constant(bc, make_int(1));
/* true path: push true and jump to end */
int c1 = bytecode_add_constant(bc, make_bool(1));
bytecode_add_instruction(bc, OP_LOAD_CONST, c1);
if (tj_count < (int)(sizeof(true_jumps) / sizeof(true_jumps[0]))) {
true_jumps[tj_count++] = bytecode_add_instruction(bc, OP_JUMP, 0);
@ -2256,7 +2256,18 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
/* Continue after checks */
bytecode_set_operand(bc, j_ok, bc->instr_count);
} else if (decl_meta == TYPE_META_BOOLEAN) {
/* expect Number then clamp to 1 bit (unsigned) */
/* accept Boolean literal or Number; if Number, clamp to 0/1 */
/* check if value is Boolean */
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_TYPEOF, 0);
int ciBool = bytecode_add_constant(bc, make_string("Boolean"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciBool);
bytecode_add_instruction(bc, OP_EQ, 0);
int j_not_bool = bytecode_add_instruction(bc, OP_JUMP_IF_FALSE, 0);
/* it is Boolean -> OK, skip number check */
int j_done = bytecode_add_instruction(bc, OP_JUMP, 0);
/* not Boolean: check Number */
bytecode_set_operand(bc, j_not_bool, bc->instr_count);
bytecode_add_instruction(bc, OP_DUP, 0);
bytecode_add_instruction(bc, OP_TYPEOF, 0);
int ciNum = bytecode_add_constant(bc, make_string("Number"));
@ -2266,14 +2277,16 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
int j_skip_err = bytecode_add_instruction(bc, OP_JUMP, 0);
bytecode_set_operand(bc, j_to_error, bc->instr_count);
{
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Number for boolean"));
int ciMsg = bytecode_add_constant(bc, make_string("TypeError: expected Boolean or Number for boolean"));
bytecode_add_instruction(bc, OP_LOAD_CONST, ciMsg);
bytecode_add_instruction(bc, OP_PRINT, 0);
bytecode_add_instruction(bc, OP_HALT, 0);
}
bytecode_set_operand(bc, j_skip_err, bc->instr_count);
/* clamp to 0/1 */
/* if Number, clamp to 0/1 */
bytecode_add_instruction(bc, OP_UCLAMP, 1);
/* common continuation */
bytecode_set_operand(bc, j_done, bc->instr_count);
} else if (decl_meta == TYPE_META_NIL) {
/* expect Nil */
bytecode_add_instruction(bc, OP_DUP, 0);
@ -2331,8 +2344,11 @@ static void parse_simple_statement(Bytecode *bc, const char *src, size_t len, si
} else if (is_class_tkn) {
/* Class-typed variable defaults to Nil until assigned an instance */
ci = bytecode_add_constant(bc, make_nil());
} else if (is_number || is_boolean || (decl_bits != 0)) {
/* integers/booleans default to 0 */
} else if (is_boolean) {
/* booleans default to false */
ci = bytecode_add_constant(bc, make_bool(0));
} else if (is_number || (decl_bits != 0)) {
/* integers default to 0 */
ci = bytecode_add_constant(bc, make_int(0));
}
if (ci >= 0) {

View file

@ -37,6 +37,13 @@ Value make_int(int64_t v) {
return val;
}
Value make_bool(int v) {
Value val;
val.type = VAL_BOOL;
val.i = v ? 1 : 0;
return val;
}
Value make_string(const char *s) {
Value val;
val.type = VAL_STRING;
@ -222,6 +229,9 @@ Value copy_value(const Value *v) {
case VAL_INT:
out.i = v->i;
break;
case VAL_BOOL:
out.i = v->i ? 1 : 0;
break;
case VAL_STRING:
out.s = v->s ? strdup(v->s) : strdup("");
break;
@ -252,6 +262,8 @@ Value deep_copy_value(const Value *v) {
switch (v->type) {
case VAL_INT:
return make_int(v->i);
case VAL_BOOL:
return make_bool(v->i);
case VAL_STRING:
return make_string(v->s ? v->s : "");
case VAL_FUNCTION:
@ -325,6 +337,9 @@ void print_value(const Value *v) {
case VAL_STRING:
printf("%s", v->s ? v->s : "");
break;
case VAL_BOOL:
printf("%s", v->i ? "true" : "false");
break;
case VAL_FUNCTION:
printf("<function@%p>", (void*)v->fn);
break;
@ -364,6 +379,8 @@ int value_is_truthy(const Value *v) {
switch (v->type) {
case VAL_INT:
return v->i != 0;
case VAL_BOOL:
return v->i != 0;
case VAL_STRING:
return v->s && v->s[0] != '\0';
case VAL_FUNCTION:
@ -390,6 +407,8 @@ char *value_to_string_alloc(const Value *v) {
}
case VAL_STRING:
return strdup(v->s ? v->s : "");
case VAL_BOOL:
return strdup(v->i ? "true" : "false");
case VAL_FUNCTION: {
snprintf(buf, sizeof(buf), "<function@%p>", (void*)v->fn);
return strdup(buf);
@ -410,6 +429,7 @@ int value_equals(const Value *a, const Value *b) {
if (a->type != b->type) return 0;
switch (a->type) {
case VAL_INT: return a->i == b->i;
case VAL_BOOL: return (a->i != 0) == (b->i != 0);
case VAL_STRING: {
const char *sa = a->s ? a->s : "";
const char *sb = b->s ? b->s : "";

View file

@ -46,6 +46,7 @@ struct Map; /* forward */
typedef enum {
VAL_INT,
VAL_BOOL,
VAL_STRING,
VAL_FUNCTION,
VAL_ARRAY,
@ -66,6 +67,7 @@ typedef struct {
/* constructors / helpers */
Value make_int(int64_t v);
Value make_bool(int v);
Value make_string(const char *s);
Value make_function(struct Bytecode *fn);
Value make_nil(void);

View file

@ -37,6 +37,6 @@ case OP_AND: {
int res = value_is_truthy(&a) && value_is_truthy(&b);
free_value(a);
free_value(b);
push_value(vm, make_int(res));
push_value(vm, make_bool(res));
break;
}

View file

@ -38,15 +38,23 @@ case OP_EQ: {
if (a.type == b.type) {
switch (a.type) {
case VAL_INT: eq = (a.i == b.i); break;
case VAL_BOOL: eq = ((a.i != 0) == (b.i != 0)); break;
case VAL_STRING: eq = (a.s && b.s) ? (strcmp(a.s, b.s) == 0) : (a.s == b.s); break;
case VAL_FUNCTION: eq = (a.fn == b.fn); break;
case VAL_NIL: eq = 1; break;
default: eq = 0; break;
}
} else {
eq = 0;
/* interop: bool vs int (0/1) */
if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) {
int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0);
int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0);
eq = (ai == bi);
} else {
eq = 0;
}
}
push_value(vm, make_int(eq ? 1 : 0));
push_value(vm, make_bool(eq));
free_value(a); free_value(b);
break;
}

View file

@ -38,15 +38,23 @@ case OP_NEQ: {
if (a.type == b.type) {
switch (a.type) {
case VAL_INT: neq = (a.i != b.i); break;
case VAL_BOOL: neq = ((a.i != 0) != (b.i != 0)); break;
case VAL_STRING: neq = (a.s && b.s) ? (strcmp(a.s, b.s) != 0) : (a.s != b.s); break;
case VAL_FUNCTION: neq = (a.fn != b.fn); break;
case VAL_NIL: neq = 0; break;
default: neq = 1; break;
}
} else {
neq = 1;
/* interop: bool vs int (0/1) */
if ((a.type == VAL_BOOL && b.type == VAL_INT) || (a.type == VAL_INT && b.type == VAL_BOOL)) {
int ai = (a.type == VAL_BOOL) ? (a.i != 0) : (a.i != 0);
int bi = (b.type == VAL_BOOL) ? (b.i != 0) : (b.i != 0);
neq = (ai != bi);
} else {
neq = 1;
}
}
push_value(vm, make_int(neq ? 1 : 0));
push_value(vm, make_bool(neq));
free_value(a); free_value(b);
break;
}

View file

@ -35,6 +35,6 @@ case OP_NOT: {
Value v = pop_value(vm);
int res = !value_is_truthy(&v);
free_value(v);
push_value(vm, make_int(res));
push_value(vm, make_bool(res));
break;
}

View file

@ -37,6 +37,6 @@ case OP_OR: {
int res = value_is_truthy(&a) || value_is_truthy(&b);
free_value(a);
free_value(b);
push_value(vm, make_int(res));
push_value(vm, make_bool(res));
break;
}

View file

@ -12,6 +12,7 @@ case OP_TYPEOF: {
const char *tname = "Unknown";
switch (v.type) {
case VAL_INT: tname = "Number"; break;
case VAL_BOOL: tname = "Boolean"; break;
case VAL_STRING: tname = "String"; break;
case VAL_FUNCTION: tname = "Function"; break;
case VAL_ARRAY: tname = "Array"; break;