diff --git a/CMakeLists.txt b/CMakeLists.txt index d5f3dea..cb5af47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.41 LANGUAGES C) +project(fun VERSION 0.37.42 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) @@ -468,6 +468,12 @@ if(Threads_FOUND) target_link_libraries(fun_core PUBLIC Threads::Threads) endif() +# Link libm for C99 math functions if available +find_library(M_LIB m) +if(M_LIB) + target_link_libraries(fun_core PUBLIC ${M_LIB}) +endif() + # Interpreter /usr/bin/fun option(FUN_WITH_REPL "Enable interactive REPL in the fun CLI" OFF) add_executable(fun diff --git a/examples/math_ceil.fun b/examples/math_ceil.fun new file mode 100644 index 0000000..5c2ef16 --- /dev/null +++ b/examples/math_ceil.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like ceil(x) + +print("=== math ceil demo start ===") + +print("ceil(3.2) -> " + to_string(ceil(3.2))) // 4 +print("ceil(3.0) -> " + to_string(ceil(3.0))) // 3 +print("ceil(-3.2) -> " + to_string(ceil(-3.2))) // -3 +print("ceil(-3.0) -> " + to_string(ceil(-3.0))) // -3 +print("ceil(0.5) -> " + to_string(ceil(0.5))) // 1 +print("ceil(-0.5) -> " + to_string(ceil(-0.5))) // 0 + +print("=== math ceil demo end ===") + +/* Expected output (values): +=== math ceil demo start === +ceil(3.2) -> 4 +ceil(3.0) -> 3 +ceil(-3.2) -> -3 +ceil(-3.0) -> -3 +ceil(0.5) -> 1 +ceil(-0.5) -> 0 +=== math ceil demo end === +*/ diff --git a/examples/math_floor.fun b/examples/math_floor.fun new file mode 100644 index 0000000..94783c7 --- /dev/null +++ b/examples/math_floor.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like floor(x) + +print("=== math floor demo start ===") + +print("floor(3.7) -> " + to_string(floor(3.7))) // 3 +print("floor(3.0) -> " + to_string(floor(3.0))) // 3 +print("floor(-3.2) -> " + to_string(floor(-3.2))) // -4 +print("floor(-3.0) -> " + to_string(floor(-3.0))) // -3 +print("floor(0.5) -> " + to_string(floor(0.5))) // 0 +print("floor(-0.5) -> " + to_string(floor(-0.5))) // -1 + +print("=== math floor demo end ===") + +/* Expected output (values): +=== math floor demo start === +floor(3.7) -> 3 +floor(3.0) -> 3 +floor(-3.2) -> -4 +floor(-3.0) -> -3 +floor(0.5) -> 0 +floor(-0.5) -> -1 +=== math floor demo end === +*/ diff --git a/examples/math_round.fun b/examples/math_round.fun new file mode 100644 index 0000000..0c2117c --- /dev/null +++ b/examples/math_round.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like round(x) — half away from zero + +print("=== math round demo start ===") + +print("round(3.2) -> " + to_string(round(3.2))) // 3 +print("round(3.5) -> " + to_string(round(3.5))) // 4 +print("round(3.8) -> " + to_string(round(3.8))) // 4 +print("round(-3.2) -> " + to_string(round(-3.2))) // -3 +print("round(-3.5) -> " + to_string(round(-3.5))) // -4 +print("round(-3.8) -> " + to_string(round(-3.8))) // -4 + +print("=== math round demo end ===") + +/* Expected output (values): +=== math round demo start === +round(3.2) -> 3 +round(3.5) -> 4 +round(3.8) -> 4 +round(-3.2) -> -3 +round(-3.5) -> -4 +round(-3.8) -> -4 +=== math round demo end === +*/ diff --git a/examples/math_trunc.fun b/examples/math_trunc.fun new file mode 100644 index 0000000..e8f395b --- /dev/null +++ b/examples/math_trunc.fun @@ -0,0 +1,36 @@ +#!/usr/bin/env fun + +/* + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +// Demo of C99-like trunc(x) — rounds toward zero + +print("=== math trunc demo start ===") + +print("trunc(3.7) -> " + to_string(trunc(3.7))) // 3 +print("trunc(3.0) -> " + to_string(trunc(3.0))) // 3 +print("trunc(-3.7) -> " + to_string(trunc(-3.7))) // -3 +print("trunc(-3.0) -> " + to_string(trunc(-3.0))) // -3 +print("trunc(0.5) -> " + to_string(trunc(0.5))) // 0 +print("trunc(-0.5) -> " + to_string(trunc(-0.5))) // 0 + +print("=== math trunc demo end ===") + +/* Expected output (values): +=== math trunc demo start === +trunc(3.7) -> 3 +trunc(3.0) -> 3 +trunc(-3.7) -> -3 +trunc(-3.0) -> -3 +trunc(0.5) -> 0 +trunc(-0.5) -> 0 +=== math trunc demo end === +*/ diff --git a/src/bytecode.c b/src/bytecode.c index f1af0e2..05bdcec 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -189,6 +189,10 @@ static const char *opcode_name(OpCode op) { case OP_TK_LABEL: return "TK_LABEL"; case OP_TK_BUTTON: return "TK_BUTTON"; case OP_TK_PACK: return "TK_PACK"; + case OP_FLOOR: return "FLOOR"; + case OP_CEIL: return "CEIL"; + case OP_TRUNC: return "TRUNC"; + case OP_ROUND: return "ROUND"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 0c0e971..2d29b40 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -236,7 +236,13 @@ typedef enum { // exceptions (minimal) OP_TRY_PUSH, // operand = handler ip; push handler onto try-stack OP_TRY_POP, // pop current handler - OP_THROW // pops error value; if handler -> jump to it (push err), else print and terminate + OP_THROW, // pops error value; if handler -> jump to it (push err), else print and terminate + + // C99 math.h rounding family (float-aware) + OP_FLOOR, // pops x (int/float); pushes floor(x) (int if integral else float) + OP_CEIL, // pops x (int/float); pushes ceil(x) (int if integral else float) + OP_TRUNC, // pops x (int/float); pushes trunc(x) (int if integral else float) + OP_ROUND // pops x (int/float); pushes round(x) (half away from zero) } OpCode; typedef struct { diff --git a/src/fun_test.c b/src/fun_test.c index 64928f8..f71f8a1 100644 --- a/src/fun_test.c +++ b/src/fun_test.c @@ -33,6 +33,12 @@ int main(void) { int c3 = bytecode_add_constant(bc, make_int(3)); int c10 = bytecode_add_constant(bc, make_int(10)); int c42 = bytecode_add_constant(bc, make_int(42)); + int cf3_2 = bytecode_add_constant(bc, make_float(3.2)); + int cf3_5 = bytecode_add_constant(bc, make_float(3.5)); + int cf3_8 = bytecode_add_constant(bc, make_float(3.8)); + int cfn3_2 = bytecode_add_constant(bc, make_float(-3.2)); + int cfn3_5 = bytecode_add_constant(bc, make_float(-3.5)); + int cfn3_8 = bytecode_add_constant(bc, make_float(-3.8)); // ---------- Arithmetic ---------- bytecode_add_instruction(bc, OP_LOAD_CONST, c42); @@ -120,6 +126,48 @@ int main(void) { bytecode_add_instruction(bc, OP_LOAD_CONST, c1); bytecode_add_instruction(bc, OP_POP, 0); // dApache-2.0ard 1 (stack now empty) + // ---------- Rounding (math.h) demo ---------- + // floor/ceil/trunc/round on representative values + int start_round_demo = bc->instr_count; + (void)start_round_demo; + + // +3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_TRUNC, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_2); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // +3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // -3.5 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_5); + bytecode_add_instruction(bc, OP_ROUND, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // -3.2 + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + bytecode_add_instruction(bc, OP_LOAD_CONST, cfn3_2); + bytecode_add_instruction(bc, OP_CEIL, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + + // integers should be unchanged + bytecode_add_instruction(bc, OP_LOAD_CONST, c10); + bytecode_add_instruction(bc, OP_FLOOR, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- HALT ---------- bytecode_add_instruction(bc, OP_HALT, 0); diff --git a/src/parser.c b/src/parser.c index 67a4599..fb243c5 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1758,6 +1758,34 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "floor") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "floor expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_FLOOR, 0); + free(name); + return 1; + } + if (strcmp(name, "ceil") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "ceil expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_CEIL, 0); + free(name); + return 1; + } + if (strcmp(name, "trunc") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "trunc expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TRUNC, 0); + free(name); + return 1; + } + if (strcmp(name, "round") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "round expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_ROUND, 0); + free(name); + return 1; + } if (strcmp(name, "pow") == 0) { (*pos)++; /* '(' */ if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ',')) { parser_fail(*pos, "pow expects 2 args"); free(name); return 0; } diff --git a/src/vm.c b/src/vm.c index e67aef5..951ab9d 100644 --- a/src/vm.c +++ b/src/vm.c @@ -674,6 +674,10 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/math/min.c" #include "vm/math/mod.c" #include "vm/math/pow.c" + #include "vm/math/floor.c" + #include "vm/math/ceil.c" + #include "vm/math/trunc.c" + #include "vm/math/round.c" #include "vm/math/random_int.c" #include "vm/math/random_seed.c" diff --git a/src/vm/math/ceil.c b/src/vm/math/ceil.c new file mode 100644 index 0000000..bda8d29 --- /dev/null +++ b/src/vm/math/ceil.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file ceil.c + * @brief Implements the OP_CEIL opcode using C99 math.h ceil(). + */ + +#include + +case OP_CEIL: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* ceil(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = ceil(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: CEIL expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/floor.c b/src/vm/math/floor.c new file mode 100644 index 0000000..72e2440 --- /dev/null +++ b/src/vm/math/floor.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file floor.c + * @brief Implements the OP_FLOOR opcode using C99 math.h floor(). + */ + +#include + +case OP_FLOOR: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + /* floor(n) == n for integers */ + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = floor(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: FLOOR expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/round.c b/src/vm/math/round.c new file mode 100644 index 0000000..3f830ff --- /dev/null +++ b/src/vm/math/round.c @@ -0,0 +1,43 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file round.c + * @brief Implements the OP_ROUND opcode using C99 math.h round(). + * C99 round() rounds half away from zero. + */ + +#include + +case OP_ROUND: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = round(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: ROUND expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/trunc.c b/src/vm/math/trunc.c new file mode 100644 index 0000000..7442074 --- /dev/null +++ b/src/vm/math/trunc.c @@ -0,0 +1,42 @@ +/** + * This file is part of the Fun programming language. + * https://fun-lang.xyz/ + * + * Copyright 2026 Johannes Findeisen + * Licensed under the terms of the Apache-2.0 license. + * https://opensource.org/license/apache-2-0 + * + * Added: 2026-01-03 + */ + +/** +* @file trunc.c + * @brief Implements the OP_TRUNC opcode using C99 math.h trunc(). + */ + +#include + +case OP_TRUNC: { + Value v = pop_value(vm); + if (v.type == VAL_INT) { + push_value(vm, make_int(v.i)); + free_value(v); + } else if (v.type == VAL_FLOAT) { + double r = trunc(v.d); + if (r >= (double)INT64_MIN && r <= (double)INT64_MAX) { + int64_t ii = (int64_t)r; + if ((double)ii == r) { + push_value(vm, make_int(ii)); + } else { + push_value(vm, make_float(r)); + } + } else { + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TRUNC expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +}