diff --git a/CMakeLists.txt b/CMakeLists.txt index cb5af47..506310c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.37.42 LANGUAGES C) +project(fun VERSION 0.37.43 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/examples/math_cos.fun b/examples/math_cos.fun new file mode 100644 index 0000000..f498126 --- /dev/null +++ b/examples/math_cos.fun @@ -0,0 +1,30 @@ +#!/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 cos(x) + +print("=== math cos demo start ===") + +print("cos(0) -> " + to_string(cos(0))) +print("cos(3.14159265359) ~ pi -> " + to_string(cos(3.14159265359))) +print("cos(1.57079632679) ~ pi/2 -> " + to_string(cos(1.57079632679))) + +print("=== math cos demo end ===") + +/* Expected output (approximate): +=== math cos demo start === +cos(0) -> 1 +cos(3.14159265359) ~ pi -> -1 +cos(1.57079632679) ~ pi/2 -> 0 +=== math cos demo end === +*/ diff --git a/examples/math_exp_log.fun b/examples/math_exp_log.fun new file mode 100644 index 0000000..5be06d2 --- /dev/null +++ b/examples/math_exp_log.fun @@ -0,0 +1,34 @@ +#!/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 exp(x), log(x), log10(x) + +print("=== math exp/log demo start ===") + +print("exp(0) -> " + to_string(exp(0))) +print("log(1) -> " + to_string(log(1))) +print("log10(1) -> " + to_string(log10(1))) +print("exp(1) -> " + to_string(exp(1))) +print("log(2.71828182846) ~ ln(e) -> " + to_string(log(2.71828182846))) + +print("=== math exp/log demo end ===") + +/* Expected output (approximate): +=== math exp/log demo start === +exp(0) -> 1 +log(1) -> 0 +log10(1) -> 0 +exp(1) -> 2.718281828... +log(2.71828182846) ~ ln(e) -> 1 +=== math exp/log demo end === +*/ diff --git a/examples/math_sin.fun b/examples/math_sin.fun new file mode 100644 index 0000000..193881b --- /dev/null +++ b/examples/math_sin.fun @@ -0,0 +1,30 @@ +#!/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 sin(x) + +print("=== math sin demo start ===") + +print("sin(0) -> " + to_string(sin(0))) +print("sin(1.57079632679) ~ pi/2 -> " + to_string(sin(1.57079632679))) +print("sin(-1.57079632679) -> " + to_string(sin(-1.57079632679))) + +print("=== math sin demo end ===") + +/* Expected output (approximate): +=== math sin demo start === +sin(0) -> 0 +sin(1.57079632679) ~ pi/2 -> 1 +sin(-1.57079632679) -> -1 +=== math sin demo end === +*/ diff --git a/examples/math_sqrt.fun b/examples/math_sqrt.fun new file mode 100644 index 0000000..6052981 --- /dev/null +++ b/examples/math_sqrt.fun @@ -0,0 +1,32 @@ +#!/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 sqrt(x) + +print("=== math sqrt demo start ===") + +print("sqrt(0) -> " + to_string(sqrt(0))) +print("sqrt(9) -> " + to_string(sqrt(9))) +print("sqrt(2) -> " + to_string(sqrt(2))) +print("sqrt(-1) -> " + to_string(sqrt(-1))) // NaN expected + +print("=== math sqrt demo end ===") + +/* Expected output (approximate): +=== math sqrt demo start === +sqrt(0) -> 0 +sqrt(9) -> 3 +sqrt(2) -> 1.41421356... +sqrt(-1) -> nan +=== math sqrt demo end === +*/ diff --git a/examples/math_tan.fun b/examples/math_tan.fun new file mode 100644 index 0000000..fd63efa --- /dev/null +++ b/examples/math_tan.fun @@ -0,0 +1,28 @@ +#!/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 tan(x) + +print("=== math tan demo start ===") + +print("tan(0) -> " + to_string(tan(0))) +print("tan(0.78539816339) ~ pi/4 -> " + to_string(tan(0.78539816339))) + +print("=== math tan demo end ===") + +/* Expected output (approximate): +=== math tan demo start === +tan(0) -> 0 +tan(0.78539816339) ~ pi/4 -> 1 +=== math tan demo end === +*/ diff --git a/src/bytecode.c b/src/bytecode.c index 05bdcec..04eabb6 100644 --- a/src/bytecode.c +++ b/src/bytecode.c @@ -193,6 +193,13 @@ static const char *opcode_name(OpCode op) { case OP_CEIL: return "CEIL"; case OP_TRUNC: return "TRUNC"; case OP_ROUND: return "ROUND"; + case OP_SIN: return "SIN"; + case OP_COS: return "COS"; + case OP_TAN: return "TAN"; + case OP_EXP: return "EXP"; + case OP_LOG: return "LOG"; + case OP_LOG10: return "LOG10"; + case OP_SQRT: return "SQRT"; default: return "???"; } } diff --git a/src/bytecode.h b/src/bytecode.h index 2d29b40..fb2bfd8 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -242,7 +242,16 @@ typedef enum { 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) + OP_ROUND, // pops x (int/float); pushes round(x) (half away from zero) + + // C99 math.h transcendentals (float-aware) + OP_SIN, // pops x (int/float); pushes sin(x) + OP_COS, // pops x (int/float); pushes cos(x) + OP_TAN, // pops x (int/float); pushes tan(x) + OP_EXP, // pops x (int/float); pushes exp(x) + OP_LOG, // pops x (int/float); pushes natural log ln(x) + OP_LOG10, // pops x (int/float); pushes log10(x) + OP_SQRT // pops x (int/float); pushes sqrt(x) } OpCode; typedef struct { diff --git a/src/fun_test.c b/src/fun_test.c index f71f8a1..5642d0e 100644 --- a/src/fun_test.c +++ b/src/fun_test.c @@ -39,6 +39,9 @@ int main(void) { 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)); + int cf0 = bytecode_add_constant(bc, make_float(0.0)); + int cf1 = bytecode_add_constant(bc, make_float(1.0)); + int c9 = bytecode_add_constant(bc, make_int(9)); // ---------- Arithmetic ---------- bytecode_add_instruction(bc, OP_LOAD_CONST, c42); @@ -168,6 +171,36 @@ int main(void) { bytecode_add_instruction(bc, OP_FLOOR, 0); bytecode_add_instruction(bc, OP_PRINT, 0); + // ---------- Transcendentals demo ---------- + // sin(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_SIN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // cos(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_COS, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // tan(0)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_TAN, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // exp(0)=1 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf0); + bytecode_add_instruction(bc, OP_EXP, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // log10(1)=0 + bytecode_add_instruction(bc, OP_LOAD_CONST, cf1); + bytecode_add_instruction(bc, OP_LOG10, 0); + bytecode_add_instruction(bc, OP_PRINT, 0); + // sqrt(9)=3 + bytecode_add_instruction(bc, OP_LOAD_CONST, c9); + bytecode_add_instruction(bc, OP_SQRT, 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 fb243c5..38e2a26 100644 --- a/src/parser.c +++ b/src/parser.c @@ -1786,6 +1786,55 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos) free(name); return 1; } + if (strcmp(name, "sin") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sin expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SIN, 0); + free(name); + return 1; + } + if (strcmp(name, "cos") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "cos expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_COS, 0); + free(name); + return 1; + } + if (strcmp(name, "tan") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "tan expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_TAN, 0); + free(name); + return 1; + } + if (strcmp(name, "exp") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "exp expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_EXP, 0); + free(name); + return 1; + } + if (strcmp(name, "log") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LOG, 0); + free(name); + return 1; + } + if (strcmp(name, "log10") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "log10 expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_LOG10, 0); + free(name); + return 1; + } + if (strcmp(name, "sqrt") == 0) { + (*pos)++; /* '(' */ + if (!emit_expression(bc, src, len, pos) || !consume_char(src, len, pos, ')')) { parser_fail(*pos, "sqrt expects 1 arg"); free(name); return 0; } + bytecode_add_instruction(bc, OP_SQRT, 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 951ab9d..2e553e3 100644 --- a/src/vm.c +++ b/src/vm.c @@ -678,6 +678,13 @@ void vm_run(VM *vm, Bytecode *entry) { #include "vm/math/ceil.c" #include "vm/math/trunc.c" #include "vm/math/round.c" + #include "vm/math/sin.c" + #include "vm/math/cos.c" + #include "vm/math/tan.c" + #include "vm/math/exp.c" + #include "vm/math/log.c" + #include "vm/math/log10.c" + #include "vm/math/sqrt.c" #include "vm/math/random_int.c" #include "vm/math/random_seed.c" diff --git a/src/vm/math/cos.c b/src/vm/math/cos.c new file mode 100644 index 0000000..b5651f0 --- /dev/null +++ b/src/vm/math/cos.c @@ -0,0 +1,31 @@ +/** + * 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 cos.c + * @brief Implements the OP_COS opcode using C99 math.h cos(). + */ + +#include + +case OP_COS: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = cos(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: COS expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/exp.c b/src/vm/math/exp.c new file mode 100644 index 0000000..8ad99c2 --- /dev/null +++ b/src/vm/math/exp.c @@ -0,0 +1,31 @@ +/** + * 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 exp.c + * @brief Implements the OP_EXP opcode using C99 math.h exp(). + */ + +#include + +case OP_EXP: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = exp(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: EXP expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/log.c b/src/vm/math/log.c new file mode 100644 index 0000000..e8bd665 --- /dev/null +++ b/src/vm/math/log.c @@ -0,0 +1,36 @@ +/** + * 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 log.c + * @brief Implements the OP_LOG opcode using C99 math.h log() (natural logarithm). + */ + +#include + +case OP_LOG: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + /* Domain error: return NaN to signal invalid input */ + push_value(vm, make_float(NAN)); + } else { + double r = log(x); + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/log10.c b/src/vm/math/log10.c new file mode 100644 index 0000000..0da07e1 --- /dev/null +++ b/src/vm/math/log10.c @@ -0,0 +1,35 @@ +/** + * 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 log10.c + * @brief Implements the OP_LOG10 opcode using C99 math.h log10(). + */ + +#include + +case OP_LOG10: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x <= 0.0) { + push_value(vm, make_float(NAN)); + } else { + double r = log10(x); + push_value(vm, make_float(r)); + } + free_value(v); + } else { + fprintf(stderr, "Runtime type error: LOG10 expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/sin.c b/src/vm/math/sin.c new file mode 100644 index 0000000..aa989b2 --- /dev/null +++ b/src/vm/math/sin.c @@ -0,0 +1,31 @@ +/** + * 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 sin.c + * @brief Implements the OP_SIN opcode using C99 math.h sin(). + */ + +#include + +case OP_SIN: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = sin(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: SIN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/sqrt.c b/src/vm/math/sqrt.c new file mode 100644 index 0000000..aaffee2 --- /dev/null +++ b/src/vm/math/sqrt.c @@ -0,0 +1,45 @@ +/** + * 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 sqrt.c + * @brief Implements the OP_SQRT opcode using C99 math.h sqrt(). + */ + +#include + +case OP_SQRT: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + if (x < 0.0) { + push_value(vm, make_float(NAN)); + } else { + double r = sqrt(x); + /* preserve int if exactly integral */ + 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: SQRT expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +} diff --git a/src/vm/math/tan.c b/src/vm/math/tan.c new file mode 100644 index 0000000..dd7ede3 --- /dev/null +++ b/src/vm/math/tan.c @@ -0,0 +1,31 @@ +/** + * 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 tan.c + * @brief Implements the OP_TAN opcode using C99 math.h tan(). + */ + +#include + +case OP_TAN: { + Value v = pop_value(vm); + if (v.type == VAL_INT || v.type == VAL_FLOAT) { + double x = (v.type == VAL_FLOAT) ? v.d : (double)v.i; + double r = tan(x); + push_value(vm, make_float(r)); + free_value(v); + } else { + fprintf(stderr, "Runtime type error: TAN expects number, got %s\n", value_type_name(v.type)); + exit(1); + } + break; +}