fortrangoingonforty/fortbite / bb1c5f4

Browse files

small fixes, tests

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
bb1c5f49d6c5a45e47239a2ccf825ecb121f1e7f
Parents
d6fd7dd
Tree
984c0f5

13 changed files

StatusFile+-
A .tool-versions 1 0
M Makefile 73 8
M src/fortbite_evaluator_m.f90 8 8
M src/fortbite_parser_m.f90 16 16
M src/fortbite_types_m.f90 13 2
A test_results/arithmetic.log 26 0
A tests/fixtures/basic_arithmetic.expected 5 0
A tests/fixtures/basic_arithmetic.fb 6 0
A tests/integration/test_expressions.f90 190 0
A tests/run_tests.sh 131 0
A tests/test_framework.f90 179 0
A tests/unit/test_arithmetic.f90 145 0
A tests/unit/test_functions.f90 199 0
.tool-versionsadded
@@ -0,0 +1,1 @@
1
+nodejs 24.9.0
Makefilemodified
@@ -56,8 +56,8 @@ $(BUILDDIR)/fortbite_arithmetic_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDD
5656
 $(BUILDDIR)/fortbite_ast_m.o: $(BUILDDIR)/fortbite_types_m.o
5757
 $(BUILDDIR)/fortbite_lexer_m.o: $(BUILDDIR)/fortbite_types_m.o
5858
 $(BUILDDIR)/fortbite_parser_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o
59
-$(BUILDDIR)/fortbite_evaluator_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o $(BUILDDIR)/fortbite_arithmetic_m.o $(BUILDDIR)/fortbite_functions_m.o
60
-$(BUILDDIR)/fortbite_io_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o
59
+$(BUILDDIR)/fortbite_evaluator_m.o: $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_ast_m.o $(BUILDDIR)/fortbite_arithmetic_m.o $(BUILDDIR)/fortbite_functions_m.o $(BUILDDIR)/fortbite_matrix_m.o
60
+$(BUILDDIR)/fortbite_io_m.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_lexer_m.o $(BUILDDIR)/fortbite_parser_m.o $(BUILDDIR)/fortbite_evaluator_m.o $(BUILDDIR)/fortbite_ast_m.o
6161
 $(BUILDDIR)/fortbite.o: $(BUILDDIR)/fortbite_precision_m.o $(BUILDDIR)/fortbite_types_m.o $(BUILDDIR)/fortbite_io_m.o
6262
 
6363
 # Clean up
@@ -72,13 +72,78 @@ run: $(TARGET)
7272
 install: $(TARGET)
7373
 	cp $(TARGET) /usr/local/bin/fortbite
7474
 
75
+# Test compilation
76
+TEST_FRAMEWORK = tests/test_framework.f90
77
+TEST_SOURCES = tests/unit/test_arithmetic.f90 \
78
+               tests/unit/test_functions.f90 \
79
+               tests/integration/test_expressions.f90
80
+
81
+TEST_OBJECTS = $(TEST_SOURCES:tests/%.f90=$(BUILDDIR)/tests/%.o) \
82
+               $(BUILDDIR)/tests/test_framework.o
83
+
84
+TEST_EXECUTABLES = $(TEST_SOURCES:tests/%.f90=$(BUILDDIR)/tests/%)
85
+
86
+# Build test framework
87
+$(BUILDDIR)/tests/test_framework.o: $(TEST_FRAMEWORK) directories
88
+	@mkdir -p $(BUILDDIR)/tests/unit $(BUILDDIR)/tests/integration
89
+	$(FC) $(FFLAGS) -J$(MODDIR) -c $< -o $@
90
+
91
+# Library objects (all except main program)
92
+LIB_OBJECTS = $(filter-out $(BUILDDIR)/fortbite.o, $(OBJECTS))
93
+
94
+# Build test executables
95
+$(BUILDDIR)/tests/unit/%.o: tests/unit/%.f90 $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS)
96
+	$(FC) $(FFLAGS) -J$(MODDIR) -I$(MODDIR) -c $< -o $@
97
+
98
+$(BUILDDIR)/tests/integration/%.o: tests/integration/%.f90 $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS)
99
+	$(FC) $(FFLAGS) -J$(MODDIR) -I$(MODDIR) -c $< -o $@
100
+
101
+$(BUILDDIR)/tests/unit/%: $(BUILDDIR)/tests/unit/%.o $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS)
102
+	$(FC) $(LDFLAGS) -J$(MODDIR) -o $@ $^
103
+
104
+$(BUILDDIR)/tests/integration/%: $(BUILDDIR)/tests/integration/%.o $(BUILDDIR)/tests/test_framework.o $(LIB_OBJECTS)
105
+	$(FC) $(LDFLAGS) -J$(MODDIR) -o $@ $^
106
+
107
+# Test targets
108
+test-build: directories $(TARGET) $(TEST_EXECUTABLES)
109
+	@echo "Test build complete!"
110
+
111
+test: test-build
112
+	@./tests/run_tests.sh --no-build
113
+
114
+test-unit: test-build
115
+	@echo "Running unit tests..."
116
+	@$(BUILDDIR)/tests/unit/test_arithmetic || true
117
+	@$(BUILDDIR)/tests/unit/test_functions || true
118
+
119
+test-integration: test-build
120
+	@echo "Running integration tests..."
121
+	@$(BUILDDIR)/tests/integration/test_expressions || true
122
+
123
+test-verbose: test-build
124
+	@for test in $(TEST_EXECUTABLES); do \
125
+		echo "=== Running $$test ==="; \
126
+		$$test || true; \
127
+		echo; \
128
+	done
129
+
130
+# Clean including tests
131
+clean-all: clean
132
+	rm -rf test_results
133
+
75134
 # Help
76135
 help:
77136
 	@echo "FORTBITE Makefile targets:"
78
-	@echo "  all      - Build the executable (default)"
79
-	@echo "  clean    - Remove build files"
80
-	@echo "  run      - Build and run FORTBITE"
81
-	@echo "  install  - Install to /usr/local/bin"
82
-	@echo "  help     - Show this help message"
137
+	@echo "  all           - Build the executable (default)"
138
+	@echo "  clean         - Remove build files"
139
+	@echo "  clean-all     - Remove build files and test results"
140
+	@echo "  run           - Build and run FORTBITE"
141
+	@echo "  install       - Install to /usr/local/bin"
142
+	@echo "  test          - Run all tests"
143
+	@echo "  test-unit     - Run unit tests only"
144
+	@echo "  test-integration - Run integration tests only"
145
+	@echo "  test-verbose  - Run all tests with full output"
146
+	@echo "  test-build    - Build test executables only"
147
+	@echo "  help          - Show this help message"
83148
 
84
-.PHONY: all clean run install help directories
149
+.PHONY: all clean clean-all run install help directories test test-unit test-integration test-verbose test-build
src/fortbite_evaluator_m.f90modified
@@ -654,21 +654,21 @@ contains
654654
         type(evaluation_context_t), intent(inout) :: context
655655
         type(evaluation_error_t), intent(out) :: error
656656
         type(value_t) :: value
657
-        
657
+
658658
         type(evaluation_error_t) :: expr_error
659
-        
659
+
660660
         error%has_error = .false.
661
-        
662
-        ! For now, just evaluate the expression (ignore precision specification)
663
-        ! TODO: Implement actual precision control in Phase 3
661
+
662
+        ! Evaluate the expression
664663
         value = evaluate_expression(node%expression, context, expr_error)
665664
         if (expr_error%has_error) then
666665
             error = expr_error
667666
             return
668667
         end if
669
-        
670
-        ! Could modify precision here based on node%precision_digits
671
-        ! For now, just return the value as-is
668
+
669
+        ! Store the requested precision in the value
670
+        ! We'll use the precision_kind field to store the number of digits
671
+        value%precision_kind = node%precision_digits
672672
     end function evaluate_precision_spec
673673
     
674674
     !> Evaluate a matrix literal
src/fortbite_parser_m.f90modified
@@ -113,43 +113,43 @@ contains
113113
     end function parse_assignment
114114
     
115115
     !> Parse logical OR expressions (placeholder for future boolean logic)
116
-    function parse_logical_or(parser) result(node)
116
+    recursive function parse_logical_or(parser) result(node)
117117
         type(parser_state_t), intent(inout) :: parser
118118
         type(ast_node_t), pointer :: node
119
-        
119
+
120120
         ! For now, just pass through to the next level
121121
         node => parse_logical_and(parser)
122122
     end function parse_logical_or
123
-    
123
+
124124
     !> Parse logical AND expressions (placeholder for future boolean logic)
125
-    function parse_logical_and(parser) result(node)
125
+    recursive function parse_logical_and(parser) result(node)
126126
         type(parser_state_t), intent(inout) :: parser
127127
         type(ast_node_t), pointer :: node
128
-        
128
+
129129
         ! For now, just pass through to the next level
130130
         node => parse_equality(parser)
131131
     end function parse_logical_and
132
-    
132
+
133133
     !> Parse equality expressions (placeholder for future comparisons)
134
-    function parse_equality(parser) result(node)
134
+    recursive function parse_equality(parser) result(node)
135135
         type(parser_state_t), intent(inout) :: parser
136136
         type(ast_node_t), pointer :: node
137
-        
137
+
138138
         ! For now, just pass through to the next level
139139
         node => parse_relational(parser)
140140
     end function parse_equality
141
-    
141
+
142142
     !> Parse relational expressions (placeholder for future comparisons)
143
-    function parse_relational(parser) result(node)
143
+    recursive function parse_relational(parser) result(node)
144144
         type(parser_state_t), intent(inout) :: parser
145145
         type(ast_node_t), pointer :: node
146
-        
146
+
147147
         ! For now, just pass through to the next level
148148
         node => parse_additive(parser)
149149
     end function parse_relational
150
-    
150
+
151151
     !> Parse additive expressions (+ and -)
152
-    function parse_additive(parser) result(node)
152
+    recursive function parse_additive(parser) result(node)
153153
         type(parser_state_t), intent(inout) :: parser
154154
         type(ast_node_t), pointer :: node
155155
         
@@ -178,7 +178,7 @@ contains
178178
     end function parse_additive
179179
     
180180
     !> Parse multiplicative expressions (*, /, mod)
181
-    function parse_multiplicative(parser) result(node)
181
+    recursive function parse_multiplicative(parser) result(node)
182182
         type(parser_state_t), intent(inout) :: parser
183183
         type(ast_node_t), pointer :: node
184184
         
@@ -264,7 +264,7 @@ contains
264264
     end function parse_unary
265265
     
266266
     !> Parse postfix expressions (precision specifiers)
267
-    function parse_postfix(parser) result(node)
267
+    recursive function parse_postfix(parser) result(node)
268268
         type(parser_state_t), intent(inout) :: parser
269269
         type(ast_node_t), pointer :: node
270270
         
@@ -287,7 +287,7 @@ contains
287287
     end function parse_postfix
288288
     
289289
     !> Parse primary expressions (literals, identifiers, parentheses, functions)
290
-    function parse_primary(parser) result(node)
290
+    recursive function parse_primary(parser) result(node)
291291
         type(parser_state_t), intent(inout) :: parser
292292
         type(ast_node_t), pointer :: node
293293
         
src/fortbite_types_m.f90modified
@@ -266,10 +266,21 @@ contains
266266
     !> Print a value to standard output
267267
     subroutine print_value(value)
268268
         type(value_t), intent(in) :: value
269
-        
269
+        character(len=100) :: fmt_str
270
+        integer :: precision_digits
271
+
270272
         select case (value%value_type)
271273
         case (VALUE_SCALAR)
272
-            write(*, '(G0)') value%scalar_val
274
+            ! Use precision_kind to determine output format
275
+            ! If precision_kind < 100, it's the requested precision digits
276
+            ! Otherwise it's a kind parameter (like real64 = 8)
277
+            if (value%precision_kind > 0 .and. value%precision_kind < 100) then
278
+                precision_digits = value%precision_kind
279
+                write(fmt_str, '(A,I0,A,I0,A)') '(F0.', precision_digits, ')'
280
+                write(*, fmt_str) value%scalar_val
281
+            else
282
+                write(*, '(G0)') value%scalar_val
283
+            end if
273284
         case (VALUE_COMPLEX)
274285
             if (aimag(value%complex_val) >= 0.0_real64) then
275286
                 write(*, '(G0,"+",G0,"i")') real(value%complex_val), aimag(value%complex_val)
test_results/arithmetic.logadded
@@ -0,0 +1,26 @@
1
+============================================================
2
+Running Test Suite: Arithmetic Operations
3
+============================================================
4
+✓ Addition: 2 + 3 = 5 ... PASSED
5
+✓ Addition: -5 + 3 = -2 ... PASSED
6
+✓ Addition: 0.1 + 0.2 ... PASSED
7
+✓ Subtraction: 10 - 3 = 7 ... PASSED
8
+✓ Subtraction: -5 - 3 = -8 ... PASSED
9
+✓ Multiplication: 4 * 5 = 20 ... PASSED
10
+✓ Multiplication: -3 * 7 = -21 ... PASSED
11
+✓ Multiplication: -2 * -3 = 6 ... PASSED
12
+✓ Division: 15 / 3 = 5 ... PASSED
13
+✓ Division: 7 / 2 = 3.5 ... PASSED
14
+✓ Division: -12 / 4 = -3 ... PASSED
15
+✓ Power: 2^3 = 8 ... PASSED
16
+✓ Power: 5^2 = 25 ... PASSED
17
+✓ Power: 4^0.5 = 2 ... PASSED
18
+✓ Power: 10^-1 = 0.1 ... PASSED
19
+✓ Negation: -(5) = -5 ... PASSED
20
+✓ Negation: -(-3) = 3 ... PASSED
21
+✓ Addition with zero: 5 + 0 = 5 ... PASSED
22
+✓ Multiplication by zero: 5 * 0 = 0 ... PASSED
23
+✓ Multiplication by one: 7 * 1 = 7 ... PASSED
24
+------------------------------------------------------------
25
+Results: 20 passed, 0 failed, 20 total
26
+============================================================
tests/fixtures/basic_arithmetic.expectedadded
@@ -0,0 +1,5 @@
1
+5.0000000000000000
2
+6.0000000000000000
3
+30.000000000000000
4
+5.0000000000000000
5
+8.0000000000000000
tests/fixtures/basic_arithmetic.fbadded
@@ -0,0 +1,6 @@
1
+2 + 3
2
+10 - 4
3
+5 * 6
4
+20 / 4
5
+2 ^ 3
6
+exit
tests/integration/test_expressions.f90added
@@ -0,0 +1,190 @@
1
+!> Integration tests for complete expression evaluation
2
+program test_expressions
3
+    use test_framework
4
+    use fortbite_types_m, only: value_t, token_t, print_value
5
+    use fortbite_lexer_m, only: tokenize, free_tokens
6
+    use fortbite_parser_m, only: parse_expression, parse_error_t
7
+    use fortbite_evaluator_m, only: evaluate_expression, evaluation_context_t, &
8
+                                   evaluation_error_t, create_context
9
+    use fortbite_ast_m, only: ast_node_t, free_ast
10
+    use iso_fortran_env, only: real64
11
+    implicit none
12
+
13
+    type(test_suite) :: suite
14
+    type(evaluation_context_t) :: context
15
+    type(value_t) :: result
16
+    real(real64) :: expected
17
+
18
+    ! Initialize test suite
19
+    suite = init_test_suite('Expression Evaluation')
20
+    context = create_context()
21
+
22
+    ! Test simple arithmetic expressions
23
+    call test_expression(suite, context, '2 + 3', 5.0_real64, 'Simple addition')
24
+    call test_expression(suite, context, '10 - 4', 6.0_real64, 'Simple subtraction')
25
+    call test_expression(suite, context, '3 * 7', 21.0_real64, 'Simple multiplication')
26
+    call test_expression(suite, context, '15 / 3', 5.0_real64, 'Simple division')
27
+    call test_expression(suite, context, '2 ^ 3', 8.0_real64, 'Power operation')
28
+    call test_expression(suite, context, '2 ** 3', 8.0_real64, 'Power with ** operator')
29
+
30
+    ! Test operator precedence
31
+    call test_expression(suite, context, '2 + 3 * 4', 14.0_real64, 'Precedence: multiplication before addition')
32
+    call test_expression(suite, context, '(2 + 3) * 4', 20.0_real64, 'Parentheses override precedence')
33
+    call test_expression(suite, context, '10 - 2 * 3', 4.0_real64, 'Precedence: multiplication before subtraction')
34
+    call test_expression(suite, context, '2 * 3 + 4 * 5', 26.0_real64, 'Multiple multiplications and addition')
35
+    call test_expression(suite, context, '2 ^ 3 * 4', 32.0_real64, 'Power before multiplication')
36
+    call test_expression(suite, context, '2 * 3 ^ 2', 18.0_real64, 'Power before multiplication (2)')
37
+
38
+    ! Test nested expressions
39
+    call test_expression(suite, context, '((2 + 3) * 4) / 2', 10.0_real64, 'Nested parentheses')
40
+    call test_expression(suite, context, '2 * (3 + (4 * 5))', 46.0_real64, 'Multiple nesting levels')
41
+
42
+    ! Test unary operators
43
+    call test_expression(suite, context, '-5', -5.0_real64, 'Unary minus')
44
+    call test_expression(suite, context, '+5', 5.0_real64, 'Unary plus')
45
+    call test_expression(suite, context, '-(2 + 3)', -5.0_real64, 'Unary minus with expression')
46
+    call test_expression(suite, context, '-2 * 3', -6.0_real64, 'Unary minus with multiplication')
47
+    call test_expression(suite, context, '2 * -3', -6.0_real64, 'Multiplication with unary minus')
48
+
49
+    ! Test floating point expressions
50
+    call test_expression_near(suite, context, '0.1 + 0.2', 0.3_real64, 1.0e-10_real64, 'Floating point addition')
51
+    call test_expression(suite, context, '3.5 * 2', 7.0_real64, 'Floating point multiplication')
52
+    call test_expression(suite, context, '7.5 / 2.5', 3.0_real64, 'Floating point division')
53
+
54
+    ! Test mathematical constants
55
+    call test_expression_near(suite, context, 'pi', 3.141592653589793_real64, 1.0e-10_real64, 'Pi constant')
56
+    call test_expression_near(suite, context, 'e', 2.718281828459045_real64, 1.0e-10_real64, 'E constant')
57
+    call test_expression_near(suite, context, '2 * pi', 6.283185307179586_real64, 1.0e-10_real64, 'Expression with pi')
58
+
59
+    ! Test precision specification (our recently fixed feature!)
60
+    call test_precision(suite, context, 'pi::10', 10, 'Pi with 10 digit precision')
61
+    call test_precision(suite, context, 'e::5', 5, 'E with 5 digit precision')
62
+
63
+    ! Run the test suite
64
+    call run_test_suite(suite)
65
+
66
+contains
67
+
68
+    !> Test an expression evaluation
69
+    subroutine test_expression(suite, context, expr_str, expected, test_name)
70
+        type(test_suite), intent(inout) :: suite
71
+        type(evaluation_context_t), intent(inout) :: context
72
+        character(len=*), intent(in) :: expr_str
73
+        real(real64), intent(in) :: expected
74
+        character(len=*), intent(in) :: test_name
75
+
76
+        type(token_t), allocatable :: tokens(:)
77
+        type(ast_node_t), pointer :: ast
78
+        type(parse_error_t) :: parse_err
79
+        type(evaluation_error_t) :: eval_err
80
+        type(value_t) :: result
81
+
82
+        call add_test_case(suite, test_name)
83
+
84
+        ! Tokenize
85
+        tokens = tokenize(expr_str)
86
+
87
+        ! Parse
88
+        ast => parse_expression(tokens, parse_err)
89
+        if (parse_err%has_error) then
90
+            call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message))
91
+            call free_tokens(tokens)
92
+            return
93
+        end if
94
+
95
+        ! Evaluate
96
+        result = evaluate_expression(ast, context, eval_err)
97
+        if (eval_err%has_error) then
98
+            call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message))
99
+        else
100
+            call assert_equals(suite, result%scalar_val, expected)
101
+        end if
102
+
103
+        ! Clean up
104
+        call free_ast(ast)
105
+        call free_tokens(tokens)
106
+    end subroutine test_expression
107
+
108
+    !> Test an expression evaluation with tolerance
109
+    subroutine test_expression_near(suite, context, expr_str, expected, tolerance, test_name)
110
+        type(test_suite), intent(inout) :: suite
111
+        type(evaluation_context_t), intent(inout) :: context
112
+        character(len=*), intent(in) :: expr_str
113
+        real(real64), intent(in) :: expected, tolerance
114
+        character(len=*), intent(in) :: test_name
115
+
116
+        type(token_t), allocatable :: tokens(:)
117
+        type(ast_node_t), pointer :: ast
118
+        type(parse_error_t) :: parse_err
119
+        type(evaluation_error_t) :: eval_err
120
+        type(value_t) :: result
121
+
122
+        call add_test_case(suite, test_name)
123
+
124
+        ! Tokenize
125
+        tokens = tokenize(expr_str)
126
+
127
+        ! Parse
128
+        ast => parse_expression(tokens, parse_err)
129
+        if (parse_err%has_error) then
130
+            call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message))
131
+            call free_tokens(tokens)
132
+            return
133
+        end if
134
+
135
+        ! Evaluate
136
+        result = evaluate_expression(ast, context, eval_err)
137
+        if (eval_err%has_error) then
138
+            call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message))
139
+        else
140
+            call assert_near(suite, result%scalar_val, expected, tolerance)
141
+        end if
142
+
143
+        ! Clean up
144
+        call free_ast(ast)
145
+        call free_tokens(tokens)
146
+    end subroutine test_expression_near
147
+
148
+    !> Test precision specification
149
+    subroutine test_precision(suite, context, expr_str, expected_precision, test_name)
150
+        type(test_suite), intent(inout) :: suite
151
+        type(evaluation_context_t), intent(inout) :: context
152
+        character(len=*), intent(in) :: expr_str
153
+        integer, intent(in) :: expected_precision
154
+        character(len=*), intent(in) :: test_name
155
+
156
+        type(token_t), allocatable :: tokens(:)
157
+        type(ast_node_t), pointer :: ast
158
+        type(parse_error_t) :: parse_err
159
+        type(evaluation_error_t) :: eval_err
160
+        type(value_t) :: result
161
+
162
+        call add_test_case(suite, test_name)
163
+
164
+        ! Tokenize
165
+        tokens = tokenize(expr_str)
166
+
167
+        ! Parse
168
+        ast => parse_expression(tokens, parse_err)
169
+        if (parse_err%has_error) then
170
+            call assert_true(suite, .false., 'Parse error: ' // trim(parse_err%message))
171
+            call free_tokens(tokens)
172
+            return
173
+        end if
174
+
175
+        ! Evaluate
176
+        result = evaluate_expression(ast, context, eval_err)
177
+        if (eval_err%has_error) then
178
+            call assert_true(suite, .false., 'Evaluation error: ' // trim(eval_err%message))
179
+        else
180
+            ! Check that precision_kind was set correctly
181
+            call assert_true(suite, result%precision_kind == expected_precision, &
182
+                'Precision specification stored correctly')
183
+        end if
184
+
185
+        ! Clean up
186
+        call free_ast(ast)
187
+        call free_tokens(tokens)
188
+    end subroutine test_precision
189
+
190
+end program test_expressions
tests/run_tests.shadded
@@ -0,0 +1,131 @@
1
+#!/bin/bash
2
+
3
+# FORTBITE Test Runner Script
4
+# Runs all unit and integration tests
5
+
6
+set -e  # Exit on error
7
+
8
+# Colors for output
9
+RED='\033[0;31m'
10
+GREEN='\033[0;32m'
11
+YELLOW='\033[1;33m'
12
+NC='\033[0m' # No Color
13
+
14
+echo "================================"
15
+echo "    FORTBITE Test Suite"
16
+echo "================================"
17
+echo
18
+
19
+# Create test results directory
20
+mkdir -p test_results
21
+
22
+# Track overall results
23
+TOTAL_TESTS=0
24
+PASSED_TESTS=0
25
+FAILED_TESTS=0
26
+FAILED_LIST=""
27
+
28
+# Function to run a test
29
+run_test() {
30
+    local test_name=$1
31
+    local test_exec=$2
32
+    local test_type=$3
33
+
34
+    echo -n "Running $test_type test: $test_name ... "
35
+
36
+    if [ -f "$test_exec" ]; then
37
+        if $test_exec > "test_results/${test_name}.log" 2>&1; then
38
+            echo -e "${GREEN}PASSED${NC}"
39
+            ((PASSED_TESTS++))
40
+        else
41
+            echo -e "${RED}FAILED${NC}"
42
+            ((FAILED_TESTS++))
43
+            FAILED_LIST="$FAILED_LIST\n  - $test_name"
44
+            echo "  Error output:"
45
+            tail -n 5 "test_results/${test_name}.log" | sed 's/^/    /'
46
+        fi
47
+    else
48
+        echo -e "${YELLOW}NOT FOUND${NC} (skipping)"
49
+    fi
50
+    ((TOTAL_TESTS++))
51
+}
52
+
53
+# Build tests if needed
54
+if [ "$1" != "--no-build" ]; then
55
+    echo "Building tests..."
56
+    make test-build || exit 1
57
+    echo
58
+fi
59
+
60
+# Run unit tests
61
+echo "=== Unit Tests ==="
62
+run_test "arithmetic" "build/tests/unit/test_arithmetic" "unit"
63
+run_test "functions" "build/tests/unit/test_functions" "unit"
64
+run_test "complex" "build/tests/unit/test_complex" "unit"
65
+run_test "matrix" "build/tests/unit/test_matrix" "unit"
66
+echo
67
+
68
+# Run integration tests
69
+echo "=== Integration Tests ==="
70
+run_test "expressions" "build/tests/integration/test_expressions" "integration"
71
+run_test "variables" "build/tests/integration/test_variables" "integration"
72
+echo
73
+
74
+# Run end-to-end tests using test scripts
75
+echo "=== End-to-End Tests ==="
76
+if [ -d "tests/fixtures" ]; then
77
+    for fixture in tests/fixtures/*.fb; do
78
+        if [ -f "$fixture" ]; then
79
+            test_name=$(basename "$fixture" .fb)
80
+            echo -n "Running E2E test: $test_name ... "
81
+
82
+            expected_output="${fixture%.fb}.expected"
83
+            if [ -f "$expected_output" ]; then
84
+                if ./build/bin/fortbite < "$fixture" > "test_results/${test_name}.out" 2>&1; then
85
+                    # Strip prompts and compare output
86
+                    grep -v "fortbite>" "test_results/${test_name}.out" | \
87
+                    grep -v "======" | \
88
+                    grep -v "Type" | \
89
+                    grep -v "Use" | \
90
+                    grep -v "Goodbye" > "test_results/${test_name}.clean"
91
+
92
+                    if diff -q "$expected_output" "test_results/${test_name}.clean" > /dev/null; then
93
+                        echo -e "${GREEN}PASSED${NC}"
94
+                        ((PASSED_TESTS++))
95
+                    else
96
+                        echo -e "${RED}FAILED${NC} (output mismatch)"
97
+                        ((FAILED_TESTS++))
98
+                        FAILED_LIST="$FAILED_LIST\n  - E2E: $test_name"
99
+                    fi
100
+                else
101
+                    echo -e "${RED}FAILED${NC} (execution error)"
102
+                    ((FAILED_TESTS++))
103
+                    FAILED_LIST="$FAILED_LIST\n  - E2E: $test_name"
104
+                fi
105
+            else
106
+                echo -e "${YELLOW}NO EXPECTED OUTPUT${NC} (skipping)"
107
+            fi
108
+            ((TOTAL_TESTS++))
109
+        fi
110
+    done
111
+fi
112
+echo
113
+
114
+# Summary
115
+echo "================================"
116
+echo "         Test Summary"
117
+echo "================================"
118
+echo -e "Total:  $TOTAL_TESTS tests"
119
+echo -e "Passed: ${GREEN}$PASSED_TESTS${NC} tests"
120
+echo -e "Failed: ${RED}$FAILED_TESTS${NC} tests"
121
+
122
+if [ $FAILED_TESTS -gt 0 ]; then
123
+    echo
124
+    echo -e "${RED}Failed tests:${NC}"
125
+    echo -e "$FAILED_LIST"
126
+    exit 1
127
+else
128
+    echo
129
+    echo -e "${GREEN}All tests passed!${NC}"
130
+    exit 0
131
+fi
tests/test_framework.f90added
@@ -0,0 +1,179 @@
1
+!> Test framework module for FORTBITE
2
+!> Provides utilities for unit testing
3
+module test_framework
4
+    use iso_fortran_env, only: real64, output_unit, error_unit
5
+    implicit none
6
+    private
7
+
8
+    public :: test_suite, test_case, assert_equals, assert_true, assert_false
9
+    public :: assert_near, run_test_suite, init_test_suite, add_test_case
10
+
11
+    !> Test case type
12
+    type :: test_case
13
+        character(len=100) :: name = ''
14
+        logical :: passed = .true.
15
+        character(len=200) :: failure_message = ''
16
+    end type test_case
17
+
18
+    !> Test suite type
19
+    type :: test_suite
20
+        character(len=100) :: name = ''
21
+        type(test_case) :: tests(100)
22
+        integer :: test_count = 0
23
+        integer :: passed_count = 0
24
+        integer :: failed_count = 0
25
+        integer :: current_test_index = 0
26
+    end type test_suite
27
+
28
+contains
29
+
30
+    !> Initialize a test suite
31
+    function init_test_suite(name) result(suite)
32
+        character(len=*), intent(in) :: name
33
+        type(test_suite) :: suite
34
+
35
+        suite%name = trim(name)
36
+        suite%test_count = 0
37
+        suite%passed_count = 0
38
+        suite%failed_count = 0
39
+        suite%current_test_index = 0
40
+    end function init_test_suite
41
+
42
+    !> Add a test case to the suite
43
+    subroutine add_test_case(suite, name)
44
+        type(test_suite), intent(inout) :: suite
45
+        character(len=*), intent(in) :: name
46
+
47
+        suite%test_count = suite%test_count + 1
48
+        if (suite%test_count > size(suite%tests)) then
49
+            write(error_unit, *) 'ERROR: Too many test cases!'
50
+            return
51
+        end if
52
+
53
+        suite%current_test_index = suite%test_count
54
+        suite%tests(suite%test_count)%name = trim(name)
55
+        suite%tests(suite%test_count)%passed = .true.
56
+        suite%tests(suite%test_count)%failure_message = ''
57
+    end subroutine add_test_case
58
+
59
+    !> Run all tests in a suite
60
+    subroutine run_test_suite(suite)
61
+        type(test_suite), intent(inout) :: suite
62
+        integer :: i
63
+
64
+        write(output_unit, '(A)') repeat('=', 60)
65
+        write(output_unit, '(A,A)') 'Running Test Suite: ', trim(suite%name)
66
+        write(output_unit, '(A)') repeat('=', 60)
67
+
68
+        suite%passed_count = 0
69
+        suite%failed_count = 0
70
+
71
+        do i = 1, suite%test_count
72
+            if (suite%tests(i)%passed) then
73
+                suite%passed_count = suite%passed_count + 1
74
+                write(output_unit, '(A,A,A)') '✓ ', trim(suite%tests(i)%name), ' ... PASSED'
75
+            else
76
+                suite%failed_count = suite%failed_count + 1
77
+                write(output_unit, '(A,A,A)') '✗ ', trim(suite%tests(i)%name), ' ... FAILED'
78
+                if (len_trim(suite%tests(i)%failure_message) > 0) then
79
+                    write(output_unit, '(A,A)') '  ', trim(suite%tests(i)%failure_message)
80
+                end if
81
+            end if
82
+        end do
83
+
84
+        write(output_unit, '(A)') repeat('-', 60)
85
+        write(output_unit, '(A,I0,A,I0,A,I0,A)') 'Results: ', &
86
+            suite%passed_count, ' passed, ', &
87
+            suite%failed_count, ' failed, ', &
88
+            suite%test_count, ' total'
89
+        write(output_unit, '(A)') repeat('=', 60)
90
+    end subroutine run_test_suite
91
+
92
+    !> Assert that two real values are equal
93
+    subroutine assert_equals(suite, actual, expected, message)
94
+        type(test_suite), intent(inout) :: suite
95
+        real(real64), intent(in) :: actual, expected
96
+        character(len=*), intent(in), optional :: message
97
+
98
+        character(len=200) :: fail_msg
99
+        integer :: idx
100
+
101
+        idx = suite%current_test_index
102
+        if (idx < 1 .or. idx > suite%test_count) return
103
+
104
+        if (abs(actual - expected) > epsilon(1.0_real64)) then
105
+            write(fail_msg, '(A,G0,A,G0)') 'Expected: ', expected, ', Got: ', actual
106
+            if (present(message)) then
107
+                fail_msg = trim(message) // ' - ' // trim(fail_msg)
108
+            end if
109
+            suite%tests(idx)%passed = .false.
110
+            suite%tests(idx)%failure_message = trim(fail_msg)
111
+        end if
112
+    end subroutine assert_equals
113
+
114
+    !> Assert that two real values are approximately equal
115
+    subroutine assert_near(suite, actual, expected, tolerance, message)
116
+        type(test_suite), intent(inout) :: suite
117
+        real(real64), intent(in) :: actual, expected, tolerance
118
+        character(len=*), intent(in), optional :: message
119
+
120
+        character(len=200) :: fail_msg
121
+        integer :: idx
122
+
123
+        idx = suite%current_test_index
124
+        if (idx < 1 .or. idx > suite%test_count) return
125
+
126
+        if (abs(actual - expected) > tolerance) then
127
+            write(fail_msg, '(A,G0,A,G0,A,G0)') 'Expected: ', expected, &
128
+                ' (±', tolerance, '), Got: ', actual
129
+            if (present(message)) then
130
+                fail_msg = trim(message) // ' - ' // trim(fail_msg)
131
+            end if
132
+            suite%tests(idx)%passed = .false.
133
+            suite%tests(idx)%failure_message = trim(fail_msg)
134
+        end if
135
+    end subroutine assert_near
136
+
137
+    !> Assert that a condition is true
138
+    subroutine assert_true(suite, condition, message)
139
+        type(test_suite), intent(inout) :: suite
140
+        logical, intent(in) :: condition
141
+        character(len=*), intent(in), optional :: message
142
+
143
+        integer :: idx
144
+
145
+        idx = suite%current_test_index
146
+        if (idx < 1 .or. idx > suite%test_count) return
147
+
148
+        if (.not. condition) then
149
+            suite%tests(idx)%passed = .false.
150
+            if (present(message)) then
151
+                suite%tests(idx)%failure_message = trim(message)
152
+            else
153
+                suite%tests(idx)%failure_message = 'Assertion failed: Expected true, got false'
154
+            end if
155
+        end if
156
+    end subroutine assert_true
157
+
158
+    !> Assert that a condition is false
159
+    subroutine assert_false(suite, condition, message)
160
+        type(test_suite), intent(inout) :: suite
161
+        logical, intent(in) :: condition
162
+        character(len=*), intent(in), optional :: message
163
+
164
+        integer :: idx
165
+
166
+        idx = suite%current_test_index
167
+        if (idx < 1 .or. idx > suite%test_count) return
168
+
169
+        if (condition) then
170
+            suite%tests(idx)%passed = .false.
171
+            if (present(message)) then
172
+                suite%tests(idx)%failure_message = trim(message)
173
+            else
174
+                suite%tests(idx)%failure_message = 'Assertion failed: Expected false, got true'
175
+            end if
176
+        end if
177
+    end subroutine assert_false
178
+
179
+end module test_framework
tests/unit/test_arithmetic.f90added
@@ -0,0 +1,145 @@
1
+!> Unit tests for arithmetic operations
2
+program test_arithmetic
3
+    use test_framework
4
+    use fortbite_types_m, only: value_t, create_scalar, VALUE_SCALAR
5
+    use fortbite_arithmetic_m, only: add_values, subtract_values, multiply_values, &
6
+                                    divide_values, power_values, negate_value
7
+    use iso_fortran_env, only: real64
8
+    implicit none
9
+
10
+    type(test_suite) :: suite
11
+    type(value_t) :: a, b, result
12
+    real(real64) :: expected
13
+
14
+    ! Initialize test suite
15
+    suite = init_test_suite('Arithmetic Operations')
16
+
17
+    ! Test addition
18
+    call add_test_case(suite, 'Addition: 2 + 3 = 5')
19
+    a = create_scalar(2.0_real64)
20
+    b = create_scalar(3.0_real64)
21
+    result = add_values(a, b)
22
+    call assert_equals(suite, result%scalar_val, 5.0_real64)
23
+
24
+    call add_test_case(suite, 'Addition: -5 + 3 = -2')
25
+    a = create_scalar(-5.0_real64)
26
+    b = create_scalar(3.0_real64)
27
+    result = add_values(a, b)
28
+    call assert_equals(suite, result%scalar_val, -2.0_real64)
29
+
30
+    call add_test_case(suite, 'Addition: 0.1 + 0.2')
31
+    a = create_scalar(0.1_real64)
32
+    b = create_scalar(0.2_real64)
33
+    result = add_values(a, b)
34
+    call assert_near(suite, result%scalar_val, 0.3_real64, 1.0e-10_real64)
35
+
36
+    ! Test subtraction
37
+    call add_test_case(suite, 'Subtraction: 10 - 3 = 7')
38
+    a = create_scalar(10.0_real64)
39
+    b = create_scalar(3.0_real64)
40
+    result = subtract_values(a, b)
41
+    call assert_equals(suite, result%scalar_val, 7.0_real64)
42
+
43
+    call add_test_case(suite, 'Subtraction: -5 - 3 = -8')
44
+    a = create_scalar(-5.0_real64)
45
+    b = create_scalar(3.0_real64)
46
+    result = subtract_values(a, b)
47
+    call assert_equals(suite, result%scalar_val, -8.0_real64)
48
+
49
+    ! Test multiplication
50
+    call add_test_case(suite, 'Multiplication: 4 * 5 = 20')
51
+    a = create_scalar(4.0_real64)
52
+    b = create_scalar(5.0_real64)
53
+    result = multiply_values(a, b)
54
+    call assert_equals(suite, result%scalar_val, 20.0_real64)
55
+
56
+    call add_test_case(suite, 'Multiplication: -3 * 7 = -21')
57
+    a = create_scalar(-3.0_real64)
58
+    b = create_scalar(7.0_real64)
59
+    result = multiply_values(a, b)
60
+    call assert_equals(suite, result%scalar_val, -21.0_real64)
61
+
62
+    call add_test_case(suite, 'Multiplication: -2 * -3 = 6')
63
+    a = create_scalar(-2.0_real64)
64
+    b = create_scalar(-3.0_real64)
65
+    result = multiply_values(a, b)
66
+    call assert_equals(suite, result%scalar_val, 6.0_real64)
67
+
68
+    ! Test division
69
+    call add_test_case(suite, 'Division: 15 / 3 = 5')
70
+    a = create_scalar(15.0_real64)
71
+    b = create_scalar(3.0_real64)
72
+    result = divide_values(a, b)
73
+    call assert_equals(suite, result%scalar_val, 5.0_real64)
74
+
75
+    call add_test_case(suite, 'Division: 7 / 2 = 3.5')
76
+    a = create_scalar(7.0_real64)
77
+    b = create_scalar(2.0_real64)
78
+    result = divide_values(a, b)
79
+    call assert_equals(suite, result%scalar_val, 3.5_real64)
80
+
81
+    call add_test_case(suite, 'Division: -12 / 4 = -3')
82
+    a = create_scalar(-12.0_real64)
83
+    b = create_scalar(4.0_real64)
84
+    result = divide_values(a, b)
85
+    call assert_equals(suite, result%scalar_val, -3.0_real64)
86
+
87
+    ! Test power operations
88
+    call add_test_case(suite, 'Power: 2^3 = 8')
89
+    a = create_scalar(2.0_real64)
90
+    b = create_scalar(3.0_real64)
91
+    result = power_values(a, b)
92
+    call assert_equals(suite, result%scalar_val, 8.0_real64)
93
+
94
+    call add_test_case(suite, 'Power: 5^2 = 25')
95
+    a = create_scalar(5.0_real64)
96
+    b = create_scalar(2.0_real64)
97
+    result = power_values(a, b)
98
+    call assert_equals(suite, result%scalar_val, 25.0_real64)
99
+
100
+    call add_test_case(suite, 'Power: 4^0.5 = 2')
101
+    a = create_scalar(4.0_real64)
102
+    b = create_scalar(0.5_real64)
103
+    result = power_values(a, b)
104
+    call assert_equals(suite, result%scalar_val, 2.0_real64)
105
+
106
+    call add_test_case(suite, 'Power: 10^-1 = 0.1')
107
+    a = create_scalar(10.0_real64)
108
+    b = create_scalar(-1.0_real64)
109
+    result = power_values(a, b)
110
+    call assert_near(suite, result%scalar_val, 0.1_real64, 1.0e-10_real64)
111
+
112
+    ! Test negation
113
+    call add_test_case(suite, 'Negation: -(5) = -5')
114
+    a = create_scalar(5.0_real64)
115
+    result = negate_value(a)
116
+    call assert_equals(suite, result%scalar_val, -5.0_real64)
117
+
118
+    call add_test_case(suite, 'Negation: -(-3) = 3')
119
+    a = create_scalar(-3.0_real64)
120
+    result = negate_value(a)
121
+    call assert_equals(suite, result%scalar_val, 3.0_real64)
122
+
123
+    ! Test special cases
124
+    call add_test_case(suite, 'Addition with zero: 5 + 0 = 5')
125
+    a = create_scalar(5.0_real64)
126
+    b = create_scalar(0.0_real64)
127
+    result = add_values(a, b)
128
+    call assert_equals(suite, result%scalar_val, 5.0_real64)
129
+
130
+    call add_test_case(suite, 'Multiplication by zero: 5 * 0 = 0')
131
+    a = create_scalar(5.0_real64)
132
+    b = create_scalar(0.0_real64)
133
+    result = multiply_values(a, b)
134
+    call assert_equals(suite, result%scalar_val, 0.0_real64)
135
+
136
+    call add_test_case(suite, 'Multiplication by one: 7 * 1 = 7')
137
+    a = create_scalar(7.0_real64)
138
+    b = create_scalar(1.0_real64)
139
+    result = multiply_values(a, b)
140
+    call assert_equals(suite, result%scalar_val, 7.0_real64)
141
+
142
+    ! Run the test suite
143
+    call run_test_suite(suite)
144
+
145
+end program test_arithmetic
tests/unit/test_functions.f90added
@@ -0,0 +1,199 @@
1
+!> Unit tests for mathematical functions
2
+program test_functions
3
+    use test_framework
4
+    use fortbite_types_m, only: value_t, create_scalar, create_complex, VALUE_SCALAR, VALUE_COMPLEX
5
+    use fortbite_functions_m, only: eval_trigonometric, eval_hyperbolic, eval_logarithmic, &
6
+                                   eval_exponential, eval_statistical, eval_special, &
7
+                                   eval_complex_functions
8
+    use iso_fortran_env, only: real64
9
+    implicit none
10
+
11
+    type(test_suite) :: suite
12
+    type(value_t) :: arg, result
13
+    real(real64) :: pi
14
+
15
+    ! Initialize test suite
16
+    suite = init_test_suite('Mathematical Functions')
17
+    pi = 4.0_real64 * atan(1.0_real64)
18
+
19
+    ! Test trigonometric functions
20
+    call add_test_case(suite, 'sin(0) = 0')
21
+    arg = create_scalar(0.0_real64)
22
+    result = eval_trigonometric('sin', arg)
23
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
24
+
25
+    call add_test_case(suite, 'sin(pi/2) = 1')
26
+    arg = create_scalar(pi/2.0_real64)
27
+    result = eval_trigonometric('sin', arg)
28
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
29
+
30
+    call add_test_case(suite, 'cos(0) = 1')
31
+    arg = create_scalar(0.0_real64)
32
+    result = eval_trigonometric('cos', arg)
33
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
34
+
35
+    call add_test_case(suite, 'cos(pi) = -1')
36
+    arg = create_scalar(pi)
37
+    result = eval_trigonometric('cos', arg)
38
+    call assert_near(suite, result%scalar_val, -1.0_real64, 1.0e-10_real64)
39
+
40
+    call add_test_case(suite, 'tan(0) = 0')
41
+    arg = create_scalar(0.0_real64)
42
+    result = eval_trigonometric('tan', arg)
43
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
44
+
45
+    call add_test_case(suite, 'tan(pi/4) = 1')
46
+    arg = create_scalar(pi/4.0_real64)
47
+    result = eval_trigonometric('tan', arg)
48
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
49
+
50
+    ! Test inverse trigonometric functions
51
+    call add_test_case(suite, 'asin(0) = 0')
52
+    arg = create_scalar(0.0_real64)
53
+    result = eval_trigonometric('asin', arg)
54
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
55
+
56
+    call add_test_case(suite, 'asin(1) = pi/2')
57
+    arg = create_scalar(1.0_real64)
58
+    result = eval_trigonometric('asin', arg)
59
+    call assert_near(suite, result%scalar_val, pi/2.0_real64, 1.0e-10_real64)
60
+
61
+    call add_test_case(suite, 'acos(1) = 0')
62
+    arg = create_scalar(1.0_real64)
63
+    result = eval_trigonometric('acos', arg)
64
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
65
+
66
+    call add_test_case(suite, 'atan(1) = pi/4')
67
+    arg = create_scalar(1.0_real64)
68
+    result = eval_trigonometric('atan', arg)
69
+    call assert_near(suite, result%scalar_val, pi/4.0_real64, 1.0e-10_real64)
70
+
71
+    ! Test hyperbolic functions
72
+    call add_test_case(suite, 'sinh(0) = 0')
73
+    arg = create_scalar(0.0_real64)
74
+    result = eval_hyperbolic('sinh', arg)
75
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
76
+
77
+    call add_test_case(suite, 'cosh(0) = 1')
78
+    arg = create_scalar(0.0_real64)
79
+    result = eval_hyperbolic('cosh', arg)
80
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
81
+
82
+    call add_test_case(suite, 'tanh(0) = 0')
83
+    arg = create_scalar(0.0_real64)
84
+    result = eval_hyperbolic('tanh', arg)
85
+    call assert_near(suite, result%scalar_val, 0.0_real64, 1.0e-10_real64)
86
+
87
+    ! Test logarithmic functions
88
+    call add_test_case(suite, 'log(e) = 1')
89
+    arg = create_scalar(exp(1.0_real64))
90
+    result = eval_logarithmic('log', arg)
91
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
92
+
93
+    call add_test_case(suite, 'ln(e) = 1')
94
+    arg = create_scalar(exp(1.0_real64))
95
+    result = eval_logarithmic('ln', arg)
96
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
97
+
98
+    call add_test_case(suite, 'log10(10) = 1')
99
+    arg = create_scalar(10.0_real64)
100
+    result = eval_logarithmic('log10', arg)
101
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
102
+
103
+    call add_test_case(suite, 'log10(100) = 2')
104
+    arg = create_scalar(100.0_real64)
105
+    result = eval_logarithmic('log10', arg)
106
+    call assert_near(suite, result%scalar_val, 2.0_real64, 1.0e-10_real64)
107
+
108
+    call add_test_case(suite, 'log2(8) = 3')
109
+    arg = create_scalar(8.0_real64)
110
+    result = eval_logarithmic('log2', arg)
111
+    call assert_near(suite, result%scalar_val, 3.0_real64, 1.0e-10_real64)
112
+
113
+    ! Test exponential functions
114
+    call add_test_case(suite, 'exp(0) = 1')
115
+    arg = create_scalar(0.0_real64)
116
+    result = eval_exponential('exp', arg)
117
+    call assert_near(suite, result%scalar_val, 1.0_real64, 1.0e-10_real64)
118
+
119
+    call add_test_case(suite, 'exp(1) = e')
120
+    arg = create_scalar(1.0_real64)
121
+    result = eval_exponential('exp', arg)
122
+    call assert_near(suite, result%scalar_val, exp(1.0_real64), 1.0e-10_real64)
123
+
124
+    call add_test_case(suite, 'exp2(3) = 8')
125
+    arg = create_scalar(3.0_real64)
126
+    result = eval_exponential('exp2', arg)
127
+    call assert_near(suite, result%scalar_val, 8.0_real64, 1.0e-10_real64)
128
+
129
+    call add_test_case(suite, 'exp10(2) = 100')
130
+    arg = create_scalar(2.0_real64)
131
+    result = eval_exponential('exp10', arg)
132
+    call assert_near(suite, result%scalar_val, 100.0_real64, 1.0e-10_real64)
133
+
134
+    ! Test special functions
135
+    call add_test_case(suite, 'floor(3.7) = 3')
136
+    arg = create_scalar(3.7_real64)
137
+    result = eval_special('floor', arg)
138
+    call assert_equals(suite, result%scalar_val, 3.0_real64)
139
+
140
+    call add_test_case(suite, 'floor(-2.3) = -3')
141
+    arg = create_scalar(-2.3_real64)
142
+    result = eval_special('floor', arg)
143
+    call assert_equals(suite, result%scalar_val, -3.0_real64)
144
+
145
+    call add_test_case(suite, 'ceil(3.2) = 4')
146
+    arg = create_scalar(3.2_real64)
147
+    result = eval_special('ceil', arg)
148
+    call assert_equals(suite, result%scalar_val, 4.0_real64)
149
+
150
+    call add_test_case(suite, 'ceil(-2.7) = -2')
151
+    arg = create_scalar(-2.7_real64)
152
+    result = eval_special('ceil', arg)
153
+    call assert_equals(suite, result%scalar_val, -2.0_real64)
154
+
155
+    call add_test_case(suite, 'round(3.5) = 4')
156
+    arg = create_scalar(3.5_real64)
157
+    result = eval_special('round', arg)
158
+    call assert_equals(suite, result%scalar_val, 4.0_real64)
159
+
160
+    call add_test_case(suite, 'round(3.4) = 3')
161
+    arg = create_scalar(3.4_real64)
162
+    result = eval_special('round', arg)
163
+    call assert_equals(suite, result%scalar_val, 3.0_real64)
164
+
165
+    call add_test_case(suite, 'factorial(5) = 120')
166
+    arg = create_scalar(5.0_real64)
167
+    result = eval_special('factorial', arg)
168
+    call assert_equals(suite, result%scalar_val, 120.0_real64)
169
+
170
+    call add_test_case(suite, 'factorial(0) = 1')
171
+    arg = create_scalar(0.0_real64)
172
+    result = eval_special('factorial', arg)
173
+    call assert_equals(suite, result%scalar_val, 1.0_real64)
174
+
175
+    ! Test complex functions
176
+    call add_test_case(suite, 'real(3+4i) = 3')
177
+    arg = create_complex(3.0_real64, 4.0_real64)
178
+    result = eval_complex_functions('real', arg)
179
+    call assert_equals(suite, result%scalar_val, 3.0_real64)
180
+
181
+    call add_test_case(suite, 'imag(3+4i) = 4')
182
+    arg = create_complex(3.0_real64, 4.0_real64)
183
+    result = eval_complex_functions('imag', arg)
184
+    call assert_equals(suite, result%scalar_val, 4.0_real64)
185
+
186
+    call add_test_case(suite, 'abs(3+4i) = 5')
187
+    arg = create_complex(3.0_real64, 4.0_real64)
188
+    result = eval_complex_functions('cabs', arg)
189
+    call assert_equals(suite, result%scalar_val, 5.0_real64)
190
+
191
+    call add_test_case(suite, 'arg(1+i) = pi/4')
192
+    arg = create_complex(1.0_real64, 1.0_real64)
193
+    result = eval_complex_functions('arg', arg)
194
+    call assert_near(suite, result%scalar_val, pi/4.0_real64, 1.0e-10_real64)
195
+
196
+    ! Run the test suite
197
+    call run_test_suite(suite)
198
+
199
+end program test_functions