Made Booleans a first-class type. (0.23.0)
This commit is contained in:
parent
bd51aea318
commit
95eba1eb12
12 changed files with 131 additions and 18 deletions
|
|
@ -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
6
examples/boolean_decl.fun
Executable 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
52
examples/booleans.fun
Executable 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")
|
||||
36
src/parser.c
36
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) {
|
||||
|
|
|
|||
20
src/value.c
20
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("<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 : "";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue