diff --git a/CMakeLists.txt b/CMakeLists.txt index b80abb1..38894bf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/examples/boolean_decl.fun b/examples/boolean_decl.fun new file mode 100755 index 0000000..9d5e8c3 --- /dev/null +++ b/examples/boolean_decl.fun @@ -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)) diff --git a/examples/booleans.fun b/examples/booleans.fun new file mode 100755 index 0000000..df53a0d --- /dev/null +++ b/examples/booleans.fun @@ -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 + * 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") diff --git a/src/parser.c b/src/parser.c index d4ff507..f5a49a8 100644 --- a/src/parser.c +++ b/src/parser.c @@ -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) { diff --git a/src/value.c b/src/value.c index baa800c..f864f42 100644 --- a/src/value.c +++ b/src/value.c @@ -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("", (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), "", (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 : ""; diff --git a/src/value.h b/src/value.h index fcb7552..b1dcaac 100644 --- a/src/value.h +++ b/src/value.h @@ -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); diff --git a/src/vm/logic/and.c b/src/vm/logic/and.c index d2cd11c..d0d61b9 100644 --- a/src/vm/logic/and.c +++ b/src/vm/logic/and.c @@ -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; } diff --git a/src/vm/logic/eq.c b/src/vm/logic/eq.c index ad1578b..2a5d7ab 100644 --- a/src/vm/logic/eq.c +++ b/src/vm/logic/eq.c @@ -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; } diff --git a/src/vm/logic/neq.c b/src/vm/logic/neq.c index 6bbd565..78f4b19 100644 --- a/src/vm/logic/neq.c +++ b/src/vm/logic/neq.c @@ -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; } diff --git a/src/vm/logic/not.c b/src/vm/logic/not.c index 774c87c..0ed2233 100644 --- a/src/vm/logic/not.c +++ b/src/vm/logic/not.c @@ -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; } diff --git a/src/vm/logic/or.c b/src/vm/logic/or.c index a109305..a5eda06 100644 --- a/src/vm/logic/or.c +++ b/src/vm/logic/or.c @@ -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; } diff --git a/src/vm/typeof.c b/src/vm/typeof.c index ad96e27..44117dc 100644 --- a/src/vm/typeof.c +++ b/src/vm/typeof.c @@ -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;