├── .clangd ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── docs ├── README.md ├── language-examples │ ├── calculator.fm │ ├── fibonnaci.fm │ ├── general-fumo-test.fm │ ├── linked-list.fm │ ├── memory-demo.fm │ ├── minimal-io.fm │ ├── raylib.fm │ ├── sorting-algorithms.fm │ ├── stdio-demo.fm │ ├── string.fm │ └── vector-math.fm └── language_specification │ └── fumo_bnf.md ├── initialize_build.sh ├── install.sh ├── rebuild.sh ├── src ├── base_definitions │ ├── ast_node.cpp │ ├── ast_node.hpp │ ├── symbol_table_stack.hpp │ ├── symbols.hpp │ ├── tokens.hpp │ └── types.hpp ├── codegen │ ├── codegen_utils.cpp │ ├── compile_llvm_module.cpp │ ├── link_executable.cpp │ ├── llvm_codegen.cpp │ └── llvm_codegen.hpp ├── compiler_test.cpp ├── lexer │ ├── lexer.cpp │ ├── lexer.hpp │ └── lexer_utils.cpp ├── main.cpp ├── parser │ ├── declarations.cpp │ ├── expressions.cpp │ └── parser.hpp ├── semantic_analysis │ ├── analyzer.cpp │ ├── analyzer.hpp │ ├── analyzer_utils.cpp │ └── control_flow_analysis.cpp └── utils │ ├── common_utils.hpp │ ├── map-macro.hpp │ ├── match_construct.hpp │ ├── sequence-for.hpp │ ├── vimcommands.txt │ └── zip-macro.hpp ├── test.sh └── tests ├── any-type ├── any-compare.fm ├── any-functions.fm ├── any-structs.fm ├── basic-any.fm └── complex-any.fm ├── char-literals ├── chars.fm └── japanese-chars.fm ├── control-flow ├── accept-returns.fm ├── flow1.fm ├── flow2.fm ├── flow3.fm ├── flow4.fm ├── flow5.fm └── if-else-flow.fm ├── fail-any-type ├── any-arithmetic.fm ├── any-member.fm └── deref.fm ├── fail-char-literals └── fail-chars.fm ├── fail-control-flow ├── fail1.fm ├── fail10.fm ├── fail11.fm ├── fail2.fm ├── fail3.fm ├── fail4.fm ├── fail5.fm ├── fail6.fm ├── fail7.fm ├── fail8.fm ├── fail9.fm └── returnfail.fm ├── fail-init-lists ├── fail1.fm ├── fail10.fm ├── fail2.fm ├── fail3.fm ├── fail4.fm ├── fail5.fm ├── fail6.fm ├── fail7.fm ├── fail8.fm ├── fail9.fm └── toomanyelems.fm ├── fail-static-functions └── failstatic1.fm ├── generics ├── box.fm ├── complete-example.fm ├── complex.fm ├── container.fm ├── fail-self-ref.fm ├── functions.fm ├── multiple-types.fm ├── namespaced.fm ├── pair.fm ├── self-reference.fm └── simple.fm ├── if-statements ├── basic-if-statements │ ├── basic-if-else.fm │ ├── basic-if.fm │ ├── if-else-chain.fm │ ├── many-basic-ifs.fm │ └── nested-if.fm ├── complex-conditions │ ├── comparison-operators.fm │ ├── fumo_if_test.fm │ ├── logical-operators.fm │ └── mixed-types.fm ├── edge-cases │ ├── complex-expression.fm │ └── empty-blocks.fm ├── fail-tests │ ├── missing-braces.fm │ ├── missing-condition.fm │ └── undeclared-var.fm ├── function-calls │ ├── function-call-condition.fm │ └── multiple-func-calls.fm ├── namespaces-and-structs │ ├── namespace-access.fm │ └── struct-member-access.fm ├── variable-scope │ ├── shadowing-in-if.fm │ └── variable-scope.fm └── with-libc │ ├── libc-math.fm │ └── strcmp-libc.fm ├── initializer-lists ├── global-lists.fm └── valid-initializer-lists.fm ├── language-examples ├── not_working_old_tests ├── codegen-tests │ ├── codegen_func_calls.fm │ ├── codegen_structs.fm │ ├── simple_llvm_codegen.fm │ └── string_literals.fm ├── command-line-string-O0.ll ├── command-line-string.fm ├── command-line-string.ll ├── command-line-string.s ├── early-ast-tests │ ├── ast_syntax.fm │ ├── postfix.fm │ ├── scope_basic_checks.fm │ └── scope_name_lookup.fm ├── example.fm └── fumo_module.fm ├── pointer-tests ├── pointer-comparison.fm ├── pointer-deref.fm └── pointer-null-check.fm ├── static-member-functions └── staticfunc1.fm ├── string-literals ├── basic_printf.fm ├── basic_strings.fm ├── empty-string.fm ├── escape_sequence.fm ├── escaped-string-test.fm ├── fumo-strings.fm ├── func_params.fm ├── namespace-strings.fm ├── strlen-math.fm └── struct_member.fm ├── structs-and-postfix ├── calculator.fm ├── container.fm ├── counter.fm ├── example.fm ├── factory.fm ├── graphics.fm ├── math.fm ├── nullptr-dereference.fm ├── recursive-node.fm └── wrapper.fm └── while-tests ├── while1.fm ├── while2.fm ├── while3.fm ├── while4.fm └── while_lots.fm /.clangd: -------------------------------------------------------------------------------- 1 | If: 2 | PathMatch: .*\.fm 3 | Index: 4 | Background: Skip 5 | CompileFlags: 6 | Remove: [-*] 7 | Diagnostics: 8 | Suppress: ["*"] 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | build 3 | CMakeCache.txt 4 | test-fumo 5 | **/*-outputs/ 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6...4.0) 2 | set(TARGET_NAME "fumo") 3 | project(fumo LANGUAGES CXX) 4 | # set(CMAKE_C_COMPILER "/usr/bin/clang") 5 | # set(CMAKE_CXX_COMPILER "/usr/bin/clang++") 6 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 7 | include(FetchContent) 8 | # set(CMAKE_CXX_FLAGS_DEBUG "" CACHE STRING "" FORCE) 9 | # set(CMAKE_CXX_FLAGS_RELEASE "" CACHE STRING "" FORCE) 10 | option(USE_LIBASSERT "Use libassert" ON) 11 | 12 | # enable ccache if available to speed up compilation 13 | find_program(CCACHE_PROGRAM ccache) 14 | if(CCACHE_PROGRAM) 15 | set_property(GLOBAL PROPERTY RULE_LAUNCH_COMPILE "${CCACHE_PROGRAM}") 16 | endif() 17 | 18 | function(add_git_dependency libName gitURL gitTag) 19 | FetchContent_Declare( 20 | ${libName} 21 | GIT_REPOSITORY ${gitURL} 22 | GIT_TAG ${gitTag} 23 | GIT_SHALLOW TRUE 24 | GIT_PROGRESS TRUE 25 | ) 26 | FetchContent_MakeAvailable(${libName}) 27 | endfunction() 28 | 29 | 30 | if(USE_LIBASSERT) 31 | set(dep libassert) 32 | add_git_dependency(${dep} https://github.com/jeremy-rifkin/libassert.git 33 | v2.1.5) 34 | endif() 35 | #------------------------------------------------------------------------ 36 | # llvm stuff 37 | find_package(LLVM REQUIRED CONFIG) 38 | message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}") 39 | message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") 40 | include_directories(${LLVM_INCLUDE_DIRS}) 41 | separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) 42 | add_definitions(${LLVM_DEFINITIONS_LIST}) 43 | #------------------------------------------------------------------------ 44 | 45 | file(GLOB_RECURSE PROJECT_SOURCES CONFIGURE_DEPENDS 46 | "${CMAKE_CURRENT_LIST_DIR}/src/*.cpp") 47 | set(PROJECT_INCLUDE "${CMAKE_CURRENT_LIST_DIR}/src/") 48 | list(FILTER PROJECT_SOURCES EXCLUDE REGEX ".*compiler_test\\.cpp$") 49 | add_executable(${TARGET_NAME}) 50 | target_sources(${TARGET_NAME} PRIVATE ${PROJECT_SOURCES}) 51 | target_include_directories(${TARGET_NAME} PRIVATE ${PROJECT_INCLUDE}) 52 | target_compile_features(${TARGET_NAME} PUBLIC cxx_std_23) 53 | 54 | 55 | if(USE_LIBASSERT) 56 | FetchContent_GetProperties(libassert) 57 | if(libassert_SOURCE_DIR) 58 | target_include_directories(${TARGET_NAME} PRIVATE ${libassert_SOURCE_DIR}/src) 59 | endif() 60 | target_link_libraries(${TARGET_NAME} PRIVATE LLVM libassert::assert) 61 | else() 62 | target_link_libraries(${TARGET_NAME} PRIVATE LLVM) 63 | endif() 64 | 65 | if(CMAKE_BUILD_TYPE STREQUAL "Debug") 66 | set(CMAKE_CXX_FLAGS "-O0 -g -fsanitize=address,leak -fno-omit-frame-pointer -Werror -Wall -Wno-unused-variable") 67 | 68 | endif() 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 egas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/language-examples/calculator.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: const char*, ...) -> i32; 2 | fn scanf(format: const char*, ...) -> i32; 3 | 4 | struct Calculator { 5 | let result: f64; 6 | let history_count: i32; 7 | 8 | fn static new() -> Calculator { 9 | return Calculator {0.0, 0}; 10 | } 11 | 12 | fn add(value: f64) -> f64 { 13 | result = result + value; 14 | history_count = history_count + 1; 15 | return result; 16 | } 17 | 18 | fn subtract(value: f64) -> f64 { 19 | result = result - value; 20 | history_count = history_count + 1; 21 | return result; 22 | } 23 | 24 | fn multiply(value: f64) -> f64 { 25 | result = result * value; 26 | history_count = history_count + 1; 27 | return result; 28 | } 29 | 30 | fn divide(value: f64) -> f64 { 31 | if value == 0.0 { 32 | printf("Error: Division by zero!\n"); 33 | return result; 34 | } 35 | result = result / value; 36 | history_count = history_count + 1; 37 | return result; 38 | } 39 | 40 | fn clear() -> void { 41 | result = 0.0; 42 | history_count = 0; 43 | } 44 | 45 | fn get_result() -> f64 { 46 | return result; 47 | } 48 | 49 | fn get_operation_count() -> i32 { 50 | return history_count; 51 | } 52 | } 53 | 54 | fn print_menu() -> void { 55 | printf("\n=== Simple Calculator ===\n"); 56 | printf("1. Add\n"); 57 | printf("2. Subtract\n"); 58 | printf("3. Multiply\n"); 59 | printf("4. Divide\n"); 60 | printf("5. Clear\n"); 61 | printf("6. Show result\n"); 62 | printf("7. Exit\n"); 63 | printf("Current result: %.2f\n"); 64 | printf("Enter choice (1-7): "); 65 | } 66 | 67 | fn main() -> i32 { 68 | let calc = Calculator::new(); 69 | let choice: i32; 70 | let value: f64; 71 | let running: bool = true; 72 | 73 | printf("Welcome to Fumo Calculator!\n"); 74 | 75 | while running { 76 | print_menu(); 77 | printf("%.2f\n", calc.get_result()); 78 | printf("Enter choice (1-7): "); 79 | scanf("%d", &choice); 80 | 81 | if choice >= 1 && choice <= 4 { 82 | printf("Enter value: "); 83 | scanf("%lf", &value); 84 | 85 | if choice == 1 { 86 | printf("%.2f + %.2f = %.2f\n", calc.get_result(), value, calc.add(value)); 87 | } else if choice == 2 { 88 | printf("%.2f - %.2f = %.2f\n", calc.get_result() + value, value, calc.subtract(value)); 89 | } else if choice == 3 { 90 | let old_result: f64 = calc.get_result(); 91 | printf("%.2f * %.2f = %.2f\n", old_result, value, calc.multiply(value)); 92 | } else if choice == 4 { 93 | let old_result: f64 = calc.get_result(); 94 | printf("%.2f / %.2f = %.2f\n", old_result, value, calc.divide(value)); 95 | } 96 | } else if choice == 5 { 97 | calc.clear(); 98 | printf("Calculator cleared.\n"); 99 | } else if choice == 6 { 100 | printf("Current result: %.2f\n", calc.get_result()); 101 | printf("Operations performed: %d\n", calc.get_operation_count()); 102 | } else if choice == 7 { 103 | running = false; 104 | printf("Thank you for using Fumo Calculator!\n"); 105 | } else { 106 | printf("Invalid choice. Please enter 1-7.\n"); 107 | } 108 | } 109 | 110 | return 0; 111 | } 112 | -------------------------------------------------------------------------------- /docs/language-examples/fibonnaci.fm: -------------------------------------------------------------------------------- 1 | fn scanf (format: char* , ... ) -> i32; 2 | fn printf (format: char* , ... ) -> i32; 3 | 4 | fn fib(n: i64) -> i64 { 5 | if n < 2 { 6 | return n; 7 | } 8 | return fib(n - 1) + fib(n - 2); 9 | } 10 | 11 | fn main() -> void { 12 | let var: i64; 13 | scanf("%ld", &var); 14 | printf("%ld\n", fib(var)); 15 | } 16 | -------------------------------------------------------------------------------- /docs/language-examples/general-fumo-test.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | namespace math { 3 | struct Point { 4 | let x: f64; 5 | let y: f64; 6 | 7 | fn distance_from_origin() -> f64 { 8 | return x * x + y * y; 9 | } 10 | fn static new(x: f64, y: f64) -> Point { 11 | return Point {x, y}; 12 | } 13 | fn static origin() -> Point { 14 | return Point {0.0, 0.0}; 15 | } 16 | } 17 | namespace utils { 18 | struct Stats { 19 | let count: i32; 20 | let sum: f64; 21 | 22 | fn add_value(val: f64) -> void { 23 | count = count + 1; 24 | sum = sum + val; 25 | } 26 | fn average() -> f64 { 27 | if count > 0 { 28 | return sum / count; 29 | } 30 | return 0.0; 31 | } 32 | fn static new() -> Stats { 33 | return Stats {0, 0.0}; 34 | } 35 | } 36 | } 37 | } 38 | let global_counter: i32 = 100; 39 | 40 | fn process_any_data(data: any*, name: char*) -> void { 41 | if data { 42 | printf("Processing %s data at %p\n", name, data); 43 | } else { 44 | printf("No %s data provided\n", name); 45 | } 46 | } 47 | 48 | fn process_point(pt: math::Point*) -> void { 49 | printf("Point: (%.2f, %.2f)\n", pt->x, pt->y); 50 | 51 | if (let dist: f64 = pt->distance_from_origin()) { 52 | if dist > 25.0 { 53 | printf("Point is far from origin: %.2f\n", dist); 54 | } else { 55 | printf("Point is close to origin: %.2f\n", dist); 56 | } 57 | } 58 | } 59 | 60 | fn main() -> i32 { 61 | printf("Fumo language demonstration\n"); 62 | 63 | // static constructors and initializer lists 64 | let point1 = math::Point::new(3.0, 4.0); 65 | let point2 = math::Point {1.5, 2.5}; 66 | let origin = math::Point::origin(); 67 | 68 | let stats = math::utils::Stats::new(); 69 | let preset_stats = math::utils::Stats {2, 10.0}; 70 | 71 | let i: i32 = 0; 72 | while i < 3 { 73 | if i == 0 { 74 | stats.add_value(point1.x); 75 | } else if i == 1 { 76 | stats.add_value(point1.y); 77 | } else { 78 | stats.add_value(point2.y); 79 | } 80 | i = i + 1; 81 | } 82 | 83 | process_point(&point1); 84 | process_point(&origin); 85 | 86 | // Demonstrate any* - generic data handling 87 | process_any_data(&point1, "point"); 88 | process_any_data(&stats, "stats"); 89 | process_any_data(null, "empty"); 90 | 91 | printf("Stats average: %.2f\n", stats.average()); 92 | printf("Preset average: %.2f\n", preset_stats.average()); 93 | 94 | let ptr: i32* = &global_counter; 95 | printf("Global counter: %d\n", *ptr); 96 | 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /docs/language-examples/linked-list.fm: -------------------------------------------------------------------------------- 1 | struct Node; 2 | fn malloc(size: i64) -> any*; 3 | fn free(ptr: any*) -> void; 4 | fn printf(format: const char*, ...) -> i32; 5 | 6 | struct Node { 7 | let value: any*; 8 | let next: Node*; 9 | 10 | fn static new(val: any*) -> Node* { 11 | let node: Node* = malloc(16); // sizeof(any*) + sizeof(Node*) 12 | node->value = val; 13 | node->next = null; 14 | return node; 15 | } 16 | } 17 | 18 | struct List { 19 | let head: Node*; 20 | let size: i32; 21 | 22 | fn static new() -> List { 23 | return List {null, 0}; 24 | } 25 | 26 | fn push_front(value: any*) -> void { 27 | let new_node: Node* = Node::new(value); 28 | new_node->next = head; 29 | head = new_node; 30 | size = size + 1; 31 | } 32 | 33 | fn pop_front() -> any* { 34 | if !head { 35 | return null; // Return null on empty list 36 | } 37 | 38 | let value: any* = head->value; 39 | let old_head: Node* = head; 40 | head = head->next; 41 | free(old_head); 42 | size = size - 1; 43 | return value; 44 | } 45 | 46 | fn print_as_ints() -> void { 47 | // Helper to print assuming all values are i32* 48 | printf("["); 49 | let current: Node* = head; 50 | let first: bool = true; 51 | 52 | while current { 53 | if !first { 54 | printf(", "); 55 | } 56 | if current->value { 57 | let int_ptr: i32* = current->value; 58 | printf("%d", *int_ptr); 59 | } else { 60 | printf("null"); 61 | } 62 | current = current->next; 63 | first = false; 64 | } 65 | printf("]\n"); 66 | } 67 | 68 | fn print_as_strings() -> void { 69 | // Helper to print assuming all values are char* 70 | printf("["); 71 | let current: Node* = head; 72 | let first: bool = true; 73 | 74 | while current { 75 | if !first { 76 | printf(", "); 77 | } 78 | if current->value { 79 | let str_ptr: char* = current->value; 80 | printf("\"%s\"", str_ptr); 81 | } else { 82 | printf("null"); 83 | } 84 | current = current->next; 85 | first = false; 86 | } 87 | printf("]\n"); 88 | } 89 | 90 | fn length() -> i32 { 91 | return size; 92 | } 93 | 94 | fn find(value: any*) -> bool { 95 | // This compares pointers, not values 96 | let current: Node* = head; 97 | while current { 98 | if current->value == value { 99 | return true; 100 | } 101 | current = current->next; 102 | } 103 | return false; 104 | } 105 | 106 | fn clear() -> void { 107 | while head { 108 | let temp: Node* = head; 109 | head = head->next; 110 | free(temp); 111 | } 112 | size = 0; 113 | } 114 | } 115 | 116 | fn main() -> i32 { 117 | let list = List::new(); 118 | 119 | // Create some data to store 120 | let val1: i32 = 10; 121 | let val2: i32 = 20; 122 | let val3: i32 = 30; 123 | let val4: i32 = 40; 124 | 125 | printf("Adding integer pointers to list...\n"); 126 | list.push_front(&val1); 127 | list.push_front(&val2); 128 | list.push_front(&val3); 129 | list.push_front(&val4); 130 | 131 | printf("List contents (as ints): "); 132 | list.print_as_ints(); 133 | printf("List size: %d\n", list.length()); 134 | 135 | // Finding by pointer value 136 | if list.find(&val2) { 137 | printf("Looking for val2 pointer: found\n"); 138 | } else { 139 | printf("Looking for val2 pointer: not found\n"); 140 | } 141 | 142 | printf("Popping elements:\n"); 143 | while list.length() > 0 { 144 | let value: any* = list.pop_front(); 145 | if value { 146 | let int_val: i32* = value; 147 | printf("Popped: %d, remaining: ", *int_val); 148 | } else { 149 | printf("Popped: null, remaining: "); 150 | } 151 | list.print_as_ints(); 152 | } 153 | 154 | // Now try with strings 155 | printf("\nTesting with strings:\n"); 156 | let str1: char* = "hello"; 157 | let str2: char* = "world"; 158 | let str3: char* = "test"; 159 | 160 | list.push_front(str1); 161 | list.push_front(str2); 162 | list.push_front(str3); 163 | 164 | printf("String list contents: "); 165 | list.print_as_strings(); 166 | 167 | list.clear(); 168 | return 0; 169 | } 170 | -------------------------------------------------------------------------------- /docs/language-examples/memory-demo.fm: -------------------------------------------------------------------------------- 1 | fn malloc(size: i64) -> i32*; 2 | fn free(ptr: i32*) -> void; 3 | fn memset(ptr: i32*, value: char, n: i64) -> void; 4 | fn printf(format: const char*, ...) -> i32; 5 | 6 | struct IntBuffer { 7 | let data: i32*; 8 | let size: i32; 9 | let capacity: i32; 10 | 11 | fn static new(initial_capacity: i32) -> IntBuffer { 12 | let data_size: i64 = initial_capacity * 4; // sizeof(i32) = 4 13 | let data: i32* = malloc(data_size); 14 | 15 | // Initialize to zero 16 | memset(data, 0, data_size); 17 | 18 | return IntBuffer {data, 0, initial_capacity}; 19 | } 20 | 21 | fn push(value: i32) -> bool { 22 | if size >= capacity { 23 | return false; // Buffer full 24 | } 25 | 26 | *(data + size) = value; 27 | size = size + 1; 28 | return true; 29 | } 30 | 31 | fn get(index: i32) -> i32 { 32 | if index < 0 || index >= size { 33 | return -1; // Error value 34 | } 35 | return *(data + index); 36 | } 37 | 38 | fn set(index: i32, value: i32) -> bool { 39 | if index < 0 || index >= size { 40 | return false; 41 | } 42 | *(data + index) = value; 43 | return true; 44 | } 45 | 46 | fn find(value: i32) -> i32 { 47 | let i: i32 = 0; 48 | while i < size { 49 | if *(data + i) == value { 50 | return i; 51 | } 52 | i = i + 1; 53 | } 54 | return -1; // Not found 55 | } 56 | 57 | fn print() -> void { 58 | printf("["); 59 | let i: i32 = 0; 60 | while i < size { 61 | if i > 0 { 62 | printf(", "); 63 | } 64 | printf("%d", *(data + i)); 65 | i = i + 1; 66 | } 67 | printf("]\n"); 68 | } 69 | 70 | fn sum() -> i32 { 71 | let total: i32 = 0; 72 | let i: i32 = 0; 73 | while i < size { 74 | total = total + *(data + i); 75 | i = i + 1; 76 | } 77 | return total; 78 | } 79 | 80 | fn reverse() -> void { 81 | let left: i32 = 0; 82 | let right: i32 = size - 1; 83 | 84 | while left < right { 85 | let temp: i32 = *(data + left); 86 | *(data + left) = *(data + right); 87 | *(data + right) = temp; 88 | 89 | left = left + 1; 90 | right = right - 1; 91 | } 92 | } 93 | 94 | fn destroy() -> void { 95 | free(data); 96 | } 97 | } 98 | 99 | fn demonstrate_pointers() -> void { 100 | printf("=== Pointer Demonstration ===\n"); 101 | 102 | let value: i32 = 42; 103 | let ptr: i32* = &value; 104 | let ptr_to_ptr: i32** = &ptr; 105 | 106 | printf("Original value: %d\n", value); 107 | printf("Value through pointer: %d\n", *ptr); 108 | printf("Value through pointer to pointer: %d\n", **ptr_to_ptr); 109 | 110 | // Modify through pointer 111 | *ptr = 100; 112 | printf("After modifying through pointer: %d\n", value); 113 | 114 | // Pointer arithmetic 115 | let numbers: i32 = 10; 116 | let more_numbers: i32 = 20; 117 | let even_more: i32 = 30; 118 | 119 | let p: i32* = &numbers; 120 | printf("First number: %d\n", *p); 121 | 122 | // Note: This is unsafe unless the variables are guaranteed to be adjacent 123 | // which they're not in this case, but demonstrates the syntax 124 | printf("Address of numbers: %p\n", p); 125 | printf("Address + 1: %p\n", p + 1); 126 | 127 | printf("\n"); 128 | } 129 | 130 | fn main() -> i32 { 131 | demonstrate_pointers(); 132 | 133 | printf("=== Dynamic Buffer Example ===\n"); 134 | 135 | let buffer = IntBuffer::new(10); 136 | 137 | printf("Adding numbers to buffer...\n"); 138 | buffer.push(5); 139 | buffer.push(15); 140 | buffer.push(25); 141 | buffer.push(10); 142 | buffer.push(30); 143 | 144 | printf("Buffer contents: "); 145 | buffer.print(); 146 | printf("Buffer size: %d/%d\n", buffer.size, buffer.capacity); 147 | printf("Sum of all elements: %d\n", buffer.sum()); 148 | 149 | printf("\nModifying element at index 2 from %d to 99\n", buffer.get(2)); 150 | buffer.set(2, 99); 151 | printf("Buffer after modification: "); 152 | buffer.print(); 153 | 154 | printf("Finding value 15: index %d\n", buffer.find(15)); 155 | printf("Finding value 99: index %d\n", buffer.find(99)); 156 | printf("Finding value 999: index %d\n", buffer.find(999)); 157 | 158 | printf("\nReversing buffer...\n"); 159 | buffer.reverse(); 160 | printf("Reversed buffer: "); 161 | buffer.print(); 162 | 163 | printf("\nFilling buffer to capacity...\n"); 164 | let i: i32 = buffer.size; 165 | while i < buffer.capacity { 166 | buffer.push(i * 10); 167 | i = i + 1; 168 | } 169 | 170 | printf("Full buffer: "); 171 | buffer.print(); 172 | 173 | printf("Trying to add one more (should fail): "); 174 | if buffer.push(999) { 175 | printf("Success\n"); 176 | } else { 177 | printf("Failed - buffer full\n"); 178 | } 179 | 180 | buffer.destroy(); 181 | 182 | printf("\nMemory demonstration complete!\n"); 183 | return 0; 184 | } 185 | -------------------------------------------------------------------------------- /docs/language-examples/minimal-io.fm: -------------------------------------------------------------------------------- 1 | // Forward declare the opaque FILE type 2 | struct FILE; 3 | 4 | // Declare the global standard streams 5 | let extern stdout: FILE*; 6 | let extern stderr: FILE*; 7 | 8 | fn printf(format: const char*, ...) -> i32; 9 | fn fprintf(stream: FILE*, format: const char*, ...) -> i32; 10 | fn fopen(filename: const char*, mode: const char*) -> FILE*; 11 | fn fclose(stream: FILE*) -> i32; 12 | fn malloc(size: i64) -> char*; 13 | fn free(ptr: char*) -> void; 14 | 15 | fn main() -> i32 { 16 | printf("=== Basic I/O Test ===\n"); 17 | 18 | fprintf(stdout, "Number: %d\n", 42); 19 | fprintf(stderr, "This is stderr\n"); 20 | 21 | let file: FILE* = fopen("test.txt", "w"); 22 | if !file { 23 | fprintf(stderr, "Failed to open file\n"); 24 | return 1; 25 | } 26 | 27 | fprintf(file, "Test file content\n"); 28 | fclose(file); 29 | 30 | printf("File test complete\n"); 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /docs/language-examples/raylib.fm: -------------------------------------------------------------------------------- 1 | // Raylib-compatible structs 2 | struct Color { 3 | let r: i8; 4 | let g: i8; 5 | let b: i8; 6 | let a: i8; 7 | } 8 | 9 | // Forward declare Raylib functions 10 | fn InitWindow(width: i32, height: i32, title: const char*) -> void; 11 | fn CloseWindow() -> void; 12 | fn WindowShouldClose() -> bool; 13 | fn BeginDrawing() -> void; 14 | fn EndDrawing() -> void; 15 | fn ClearBackground(color: Color) -> void; 16 | fn DrawRectangle(posX: i32, posY: i32, width: i32, height: i32, color: *Color) -> void; 17 | fn DrawText(text: const char*, posX: i32, posY: i32, fontSize: i32, color: *Color) -> void; 18 | 19 | fn main() -> i32 { 20 | // Initialize window 21 | InitWindow(800, 450, "Raylib Test - Fumo Language"); 22 | 23 | // Define some colors 24 | let red = Color{255, 0, 0, 255}; 25 | let blue = Color{0, 0, 255, 255}; 26 | let white = Color{255, 255, 255, 255}; 27 | let dark_gray = Color{80, 80, 80, 255}; 28 | 29 | // Main game loop 30 | while !WindowShouldClose() { 31 | BeginDrawing(); 32 | 33 | // Clear background with dark gray 34 | // ClearBackground(dark_gray); 35 | 36 | // Draw some rectangles 37 | DrawRectangle(100, 100, 200, 150, red); 38 | DrawRectangle(350, 200, 150, 100, blue); 39 | 40 | // Draw text 41 | DrawText("Hello from Fumo!", 250, 50, 20, white); 42 | DrawText("Testing Raylib compatibility", 200, 350, 16, white); 43 | 44 | EndDrawing(); 45 | } 46 | 47 | CloseWindow(); 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /docs/language-examples/stdio-demo.fm: -------------------------------------------------------------------------------- 1 | // Forward declare the opaque FILE type 2 | struct FILE; 3 | 4 | // Declare the global standard streams (external linkage from libc) 5 | // NOTE: extern keyword is required. will segfault otherwise on usage 6 | let extern stdin: FILE*; 7 | let extern stdout: FILE*; 8 | let extern stderr: FILE*; 9 | 10 | // File I/O functions 11 | fn fopen(filename: const char*, mode: const char*) -> FILE*; 12 | fn fclose(stream: FILE*) -> i32; 13 | fn fprintf(stream: FILE*, format: const char*, ...) -> i32; 14 | fn fgets(str: char*, n: i32, stream: FILE*) -> char*; 15 | fn fputs(str: const char*, stream: FILE*) -> i32; 16 | fn feof(stream: FILE*) -> i32; 17 | fn fflush(stream: FILE*) -> i32; 18 | 19 | fn printf(format: const char*, ...) -> i32; 20 | fn scanf(format: const char*, ...) -> i32; 21 | 22 | fn malloc(size: i64) -> any*; 23 | fn free(ptr: char*) -> void; 24 | 25 | struct Logger { 26 | let log_file: FILE*; 27 | let enable_console: bool; 28 | 29 | fn static new(filename: const char*, console_output: bool) -> Logger { 30 | let file: FILE* = fopen(filename, "a"); 31 | return Logger {file, console_output}; 32 | } 33 | 34 | fn log_info(message: const char*) -> void { 35 | if log_file { 36 | fprintf(log_file, "[INFO] %s\n", message); 37 | fflush(log_file); 38 | } 39 | 40 | if enable_console { 41 | fprintf(stdout, "[INFO] %s\n", message); 42 | fflush(stdout); 43 | } 44 | } 45 | 46 | fn log_warning(message: const char*) -> void { 47 | if log_file { 48 | fprintf(log_file, "[WARNING] %s\n", message); 49 | fflush(log_file); 50 | } 51 | 52 | if enable_console { 53 | fprintf(stderr, "[WARNING] %s\n", message); 54 | fflush(stderr); 55 | } 56 | } 57 | 58 | fn log_error(message: const char*) -> void { 59 | if log_file { 60 | fprintf(log_file, "[ERROR] %s\n", message); 61 | fflush(log_file); 62 | } 63 | 64 | fprintf(stderr, "[ERROR] %s\n", message); 65 | fflush(stderr); 66 | } 67 | 68 | fn close() -> void { 69 | if log_file { 70 | fclose(log_file); 71 | } 72 | } 73 | } 74 | 75 | fn read_user_input(buffer: char*, size: i32) -> bool { 76 | fprintf(stdout, "Enter some text: "); 77 | fflush(stdout); 78 | 79 | let result: char* = fgets(buffer, size, stdin); 80 | return result != null; 81 | } 82 | 83 | fn demonstrate_file_operations() -> void { 84 | fprintf(stdout, "=== File Operations Demo ===\n"); 85 | 86 | let file: FILE* = fopen("test_output.txt", "w"); 87 | if !file { 88 | fprintf(stderr, "Failed to open file for writing\n"); 89 | return; 90 | } 91 | 92 | fprintf(file, "Hello from Fumo!\n"); 93 | fprintf(file, "This is a test file.\n"); 94 | fprintf(file, "Line number: %d\n", 3); 95 | fclose(file); 96 | 97 | fprintf(stdout, "Successfully wrote to test_output.txt\n"); 98 | 99 | file = fopen("test_output.txt", "r"); 100 | if !file { 101 | fprintf(stderr, "Failed to open file for reading\n"); 102 | return; 103 | } 104 | 105 | fprintf(stdout, "File contents:\n"); 106 | let buffer: char* = malloc(256); 107 | 108 | while !feof(file) { 109 | if fgets(buffer, 256, file) { 110 | fprintf(stdout, " %s", buffer); 111 | } 112 | } 113 | 114 | fclose(file); 115 | free(buffer); 116 | } 117 | 118 | fn demonstrate_stream_redirection() -> void { 119 | fprintf(stdout, "=== Stream Demonstration ===\n"); 120 | 121 | printf("Using printf (stdout): Hello World\n"); 122 | fprintf(stdout, "Using fprintf(stdout): Hello World\n"); 123 | fprintf(stderr, "Using fprintf(stderr): This goes to error stream\n"); 124 | 125 | fprintf(stderr, "Error message 1\n"); 126 | fprintf(stderr, "Error message 2\n"); 127 | fprintf(stdout, "Regular message (this might be buffered)\n"); 128 | 129 | fflush(stdout); 130 | } 131 | 132 | fn interactive_session() -> void { 133 | let buffer: char* = malloc(256); 134 | let continue_session: bool = true; 135 | 136 | fprintf(stdout, "=== Interactive Session ===\n"); 137 | fprintf(stdout, "Type 'quit' to exit\n\n"); 138 | 139 | while continue_session { 140 | if read_user_input(buffer, 256) { 141 | if *(buffer + 0) == 'q' && *(buffer + 1) == 'u' && 142 | *(buffer + 2) == 'i' && *(buffer + 3) == 't' { 143 | continue_session = false; 144 | fprintf(stdout, "Goodbye!\n"); 145 | } else { 146 | fprintf(stdout, "You entered: %s", buffer); 147 | } 148 | } else { 149 | fprintf(stderr, "Error reading input\n"); 150 | continue_session = false; 151 | } 152 | } 153 | 154 | free(buffer); 155 | } 156 | 157 | 158 | fn main() -> i32 { 159 | // Test basic stream operations 160 | demonstrate_stream_redirection(); 161 | fprintf(stdout, "\n"); 162 | 163 | // Test file operations 164 | demonstrate_file_operations(); 165 | fprintf(stdout, "\n"); 166 | 167 | // Test logging system 168 | let logger = Logger::new("application.log", true); 169 | logger.log_info("Application started"); 170 | logger.log_warning("This is a warning message"); 171 | logger.log_error("This is an error message"); 172 | logger.log_info("Logging test complete"); 173 | logger.close(); 174 | 175 | fprintf(stdout, "Check 'application.log' for logged messages\n\n"); 176 | 177 | interactive_session(); 178 | 179 | let bad_file: FILE* = fopen("nonexistent_directory/file.txt", "r"); 180 | if !bad_file { 181 | fprintf(stderr, "Expected error: Could not open nonexistent file\n"); 182 | } else { 183 | fclose(bad_file); 184 | } 185 | 186 | fprintf(stdout, "Standard I/O demonstration complete!\n"); 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /docs/language-examples/string.fm: -------------------------------------------------------------------------------- 1 | fn malloc(size: i64) -> any*; 2 | fn free(ptr: char*) -> void; 3 | fn memcpy(dest: char*, src: char*, n: i64) -> void; 4 | fn memset(ptr: char*, value: char, n: i64) -> void; 5 | fn printf(format: const char*, ...) -> i32; 6 | fn scanf(format: const char*, ...) -> i32; 7 | 8 | namespace fm { 9 | // Helper function 10 | fn cstr_length(cstr: char*) -> i32 { 11 | let len: i32 = 0; 12 | while *(cstr + len) != '\0' { 13 | len = len + 1; 14 | } 15 | return len; 16 | } 17 | struct str { 18 | let data: char*; 19 | let length: i32; 20 | let capacity: i32; 21 | 22 | // Static constructors 23 | fn static new() -> str { 24 | let data: char* = malloc(1); 25 | *data = '\0'; 26 | return str {data, 0, 1}; 27 | } 28 | 29 | fn static from_cstr(cstr: char*) -> str { 30 | let len: i32 = cstr_length(cstr); 31 | let cap: i32 = len + 1; 32 | let data: char* = malloc(cap); 33 | memcpy(data, cstr, len); 34 | *(data + len) = '\0'; 35 | return str {data, len, cap}; 36 | } 37 | // Core methods 38 | fn c_str() -> char* { 39 | return data; 40 | } 41 | fn size() -> i32 { 42 | return length; 43 | } 44 | fn empty() -> bool { 45 | return length == '\0'; 46 | } 47 | fn at(index: i32) -> char { 48 | if index >= 0 && index < length { 49 | return *(data + index); 50 | } 51 | return 0; 52 | } 53 | 54 | fn equals(other: str*) -> bool { 55 | if length != other->length { 56 | return false; 57 | } 58 | let i: i32 = '\0'; 59 | while i < length { 60 | if *(data + i) != *(other->data + i) { 61 | return false; 62 | } 63 | i = i + 1; 64 | } 65 | return true; 66 | } 67 | 68 | fn append(other: str*) -> void { 69 | let new_len: i32 = length + other->length; 70 | 71 | if new_len + 1 > capacity { 72 | let new_cap: i32 = (new_len + 1) * 2; 73 | let new_data: char* = malloc(new_cap); 74 | memcpy(new_data, data, length); 75 | free(data); 76 | data = new_data; 77 | capacity = new_cap; 78 | } 79 | 80 | memcpy(data + length, other->data, other->length); 81 | length = new_len; 82 | *(data + length) = '\0'; 83 | } 84 | 85 | fn append_cstr(cstr: char*) -> void { 86 | let cstr_len: i32 = cstr_length(cstr); 87 | let new_len: i32 = length + cstr_len; 88 | 89 | if new_len + 1 > capacity { 90 | let new_cap: i32 = (new_len + 1) * 2; 91 | let new_data: char* = malloc(new_cap); 92 | memcpy(new_data, data, length); 93 | free(data); 94 | data = new_data; 95 | capacity = new_cap; 96 | } 97 | 98 | memcpy(data + length, cstr, cstr_len); 99 | length = new_len; 100 | *(data + length) = '\0'; 101 | } 102 | 103 | fn clear() -> void { 104 | length = '\0'; 105 | *data = '\0'; 106 | } 107 | 108 | fn destroy() -> void { 109 | free(data); 110 | data = null; 111 | length = '\0'; 112 | capacity = '\0'; 113 | } 114 | 115 | // utility for finding substring 116 | fn find_in(haystack: str*) -> i32 { 117 | if length > haystack->length { 118 | return -1; 119 | } 120 | let i: i32 = '\0'; 121 | while i <= haystack->length - length { 122 | let j: i32 = '\0'; 123 | let found: bool = true; 124 | 125 | while j < length { 126 | if *(haystack->data + i + j) != *(data + j) { 127 | found = false; 128 | j = length; // break out 129 | } else { 130 | j = j + 1; 131 | } 132 | } 133 | 134 | if found { 135 | return i; 136 | } 137 | i = i + 1; 138 | } 139 | return -1; 140 | } 141 | } 142 | } 143 | 144 | fn main() -> void { 145 | let s1 = fm::str::new(); 146 | let s2 = fm::str::from_cstr("Hello"); 147 | let s3 = fm::str::from_cstr(" World"); 148 | 149 | s2.append(&s3); 150 | s1.append_cstr("Test: "); 151 | s1.append(&s2); 152 | 153 | printf("Result: %s\n", s1.c_str()); 154 | printf("Length: %d\n", s1.size()); 155 | 156 | let pos: i32 = s2.find_in(&s1); 157 | printf("Found at position: %d\n", pos); 158 | 159 | s1.destroy(); 160 | s2.destroy(); 161 | s3.destroy(); 162 | 163 | } 164 | -------------------------------------------------------------------------------- /docs/language-examples/vector-math.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: const char*, ...) -> i32; 2 | fn sqrt(x: f64) -> f64; 3 | 4 | struct Vector2D { 5 | let x: f64; 6 | let y: f64; 7 | 8 | fn static new(x: f64, y: f64) -> Vector2D { 9 | return Vector2D {x, y}; 10 | } 11 | 12 | fn static zero() -> Vector2D { 13 | return Vector2D {0.0, 0.0}; 14 | } 15 | 16 | fn magnitude() -> f64 { 17 | return sqrt(x * x + y * y); 18 | } 19 | 20 | fn add(other: Vector2D) -> Vector2D { 21 | return Vector2D {x + other.x, y + other.y}; 22 | } 23 | 24 | fn subtract(other: Vector2D) -> Vector2D { 25 | return Vector2D {x - other.x, y - other.y}; 26 | } 27 | 28 | fn scale(factor: f64) -> Vector2D { 29 | return Vector2D {x * factor, y * factor}; 30 | } 31 | 32 | fn dot(other: Vector2D) -> f64 { 33 | return x * other.x + y * other.y; 34 | } 35 | 36 | fn normalize() -> Vector2D { 37 | let mag: f64 = magnitude(); 38 | if mag > 0.0 { 39 | return Vector2D {x / mag, y / mag}; 40 | } 41 | return Vector2D {0.0, 0.0}; 42 | } 43 | 44 | fn print() -> void { 45 | printf("(%.2f, %.2f)", x, y); 46 | } 47 | } 48 | 49 | fn main() -> i32 { 50 | let v1 = Vector2D::new(3.0, 4.0); 51 | let v2 = Vector2D {6.0, 8.0}; 52 | let v3 = v1.add(v2); 53 | let v4 = v2.subtract(v1); 54 | 55 | printf("v1 = "); 56 | v1.print(); 57 | printf(" (magnitude: %.2f)\n", v1.magnitude()); 58 | 59 | printf("v2 = "); 60 | v2.print(); 61 | printf(" (magnitude: %.2f)\n", v2.magnitude()); 62 | 63 | printf("v1 + v2 = "); 64 | v3.print(); 65 | printf("\n"); 66 | 67 | printf("v2 - v1 = "); 68 | v4.print(); 69 | printf("\n"); 70 | 71 | printf("v1 · v2 = %.2f\n", v1.dot(v2)); 72 | 73 | let v1_norm = v1.normalize(); 74 | printf("v1 normalized = "); 75 | v1_norm.print(); 76 | printf(" (magnitude: %.2f)\n", v1_norm.magnitude()); 77 | 78 | let scaled = v1.scale(2.5); 79 | printf("v1 * 2.5 = "); 80 | scaled.print(); 81 | printf("\n"); 82 | 83 | return 0; 84 | } 85 | -------------------------------------------------------------------------------- /docs/language_specification/fumo_bnf.md: -------------------------------------------------------------------------------- 1 | # Complete Fumo Language BNF Grammar 2 | 3 | ## Top Level 4 | ```bnf 5 | ::= * 6 | 7 | ::= 8 | | 9 | | 10 | | 11 | ``` 12 | 13 | ## Statements 14 | ```bnf 15 | ::= 16 | | 17 | | 18 | | 19 | | 20 | | 21 | 22 | ::= "{" * "}" 23 | 24 | ::= "if" "("? ")"? 25 | ( "else" ( | ) )? 26 | 27 | ::= "while" 28 | 29 | ::= "return" ? ";" 30 | | "continue" ";" 31 | | "break" ";" 32 | 33 | ::= ";" 34 | ``` 35 | 36 | ## Declarations 37 | ```bnf 38 | ::= "let" ("extern")? ( ":" )? ( "=" )? ";" 39 | 40 | ::= "fn" ("static")? "(" ? ")" "->" ? 41 | 42 | ::= "struct" "{" * "}" 43 | 44 | ::= "namespace" "{" * "}" 45 | 46 | ::= 47 | | 48 | 49 | ::= ( "," )* 50 | 51 | ::= ":" 52 | ``` 53 | 54 | ## Types 55 | ```bnf 56 | ::= 57 | | 58 | | 59 | 60 | ::= "void" | "i8" | "i16" | "i32" | "i64" | "u8" | "u16" | "u32" | "u64" 61 | | "f32" | "f64" | "bool" | "char" 62 | 63 | ::= "*" 64 | 65 | ::= 66 | 67 | ::= "const" 68 | ``` 69 | 70 | ## Expressions 71 | ```bnf 72 | ::= 73 | 74 | ::= 75 | | "=" 76 | | "+=" 77 | | "-=" 78 | | "*=" 79 | | "/=" 80 | 81 | ::= 82 | | "||" 83 | 84 | ::= 85 | | "&&" 86 | 87 | ::= 88 | | "==" 89 | | "!=" 90 | 91 | ::= 92 | | "<" 93 | | ">" 94 | | "<=" 95 | | ">=" 96 | 97 | ::= 98 | | "+" 99 | | "-" 100 | 101 | ::= 102 | | "*" 103 | | "/" 104 | | "%" 105 | 106 | ::= 107 | | 108 | 109 | ::= "&" | "*" | "+" | "-" | "!" | "~" 110 | 111 | ::= 112 | | "(" ? ")" 113 | | "." 114 | | "->" 115 | 116 | ::= 117 | | 118 | | 119 | | "(" ")" 120 | | 121 | 122 | ::= ( "," )* 123 | ``` 124 | 125 | ## Initializers 126 | ```bnf 127 | ::= 128 | | 129 | 130 | ::= "{" ? "}" 131 | 132 | ::= ( "," )* ","? 133 | ``` 134 | 135 | ## Identifiers and Literals 136 | ```bnf 137 | ::= ( "::" )* 138 | 139 | ::= [a-zA-Z_][a-zA-Z0-9_]* 140 | 141 | ::= 142 | | 143 | | 144 | | 145 | | 146 | | 147 | 148 | ::= [0-9]+ 149 | 150 | ::= [0-9]+ "." [0-9]+ 151 | 152 | ::= "'" "'" 153 | 154 | ::= "\"" * "\"" 155 | 156 | ::= "true" | "false" 157 | 158 | ::= "null" 159 | ``` 160 | -------------------------------------------------------------------------------- /initialize_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PROJECT_NAME="${PWD##*/}" 5 | BUILD_DIR="build" 6 | 7 | echo "Initializing build for $PROJECT_NAME..." 8 | 9 | if [ -d "$BUILD_DIR" ]; then 10 | echo "Build directory '$BUILD_DIR' already exists. Removing it..." 11 | rm -rf "$BUILD_DIR" 12 | fi 13 | 14 | mkdir "$BUILD_DIR" 15 | cd "$BUILD_DIR" 16 | cmake -S .. -B . -G Ninja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ 17 | ninja 18 | cd .. 19 | 20 | echo "Build initialization complete!" 21 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # install.sh - Install fumo compiler system-wide 4 | 5 | set -e # Exit on any error 6 | 7 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 8 | COMPILER_NAME="fumo" 9 | BUILD_DIR="$SCRIPT_DIR/build" 10 | BINARY_PATH="$BUILD_DIR/$COMPILER_NAME" 11 | 12 | # Colors for output 13 | GREEN='\033[0;32m' 14 | YELLOW='\033[1;33m' 15 | RED='\033[0;31m' 16 | NC='\033[0m' # No Color 17 | 18 | print_status() { 19 | echo -e "${GREEN}[INFO]${NC} $1" 20 | } 21 | 22 | print_warning() { 23 | echo -e "${YELLOW}[WARNING]${NC} $1" 24 | } 25 | 26 | print_error() { 27 | echo -e "${RED}[ERROR]${NC} $1" 28 | } 29 | 30 | # Function to check if running as root 31 | check_root() { 32 | if [[ $EUID -eq 0 ]]; then 33 | print_error "This script should not be run as root!" 34 | print_error "Run as regular user - it will ask for sudo when needed." 35 | exit 1 36 | fi 37 | } 38 | 39 | # Function to build the compiler if needed 40 | build_compiler() { 41 | print_status "Checking if compiler binary exists..." 42 | 43 | if [[ ! -f "$BINARY_PATH" ]]; then 44 | print_warning "Compiler binary not found. Building..." 45 | 46 | if [[ -f "$SCRIPT_DIR/rebuild.sh" ]]; then 47 | cd "$SCRIPT_DIR" 48 | bash rebuild.sh 49 | elif [[ -f "$SCRIPT_DIR/Makefile" ]]; then 50 | cd "$SCRIPT_DIR" 51 | make 52 | elif [[ -f "$SCRIPT_DIR/CMakeLists.txt" ]]; then 53 | mkdir -p "$BUILD_DIR" 54 | cd "$BUILD_DIR" 55 | cmake .. 56 | make 57 | else 58 | print_error "No build system found (rebuild.sh, Makefile, or CMakeLists.txt)" 59 | exit 1 60 | fi 61 | 62 | if [[ ! -f "$BINARY_PATH" ]]; then 63 | print_error "Build failed - binary not created" 64 | exit 1 65 | fi 66 | fi 67 | 68 | print_status "Compiler binary found at: $BINARY_PATH" 69 | } 70 | 71 | # Function to install to /usr/local/bin (preferred) 72 | install_system_wide() { 73 | local target_dir="/usr/local/bin" 74 | local target_path="$target_dir/$COMPILER_NAME" 75 | 76 | print_status "Installing $COMPILER_NAME to $target_dir..." 77 | 78 | # Check if /usr/local/bin exists and is in PATH 79 | if [[ ! -d "$target_dir" ]]; then 80 | print_error "$target_dir does not exist" 81 | return 1 82 | fi 83 | 84 | # Copy the binary 85 | sudo cp "$BINARY_PATH" "$target_path" 86 | sudo chmod +x "$target_path" 87 | 88 | print_status "Successfully installed to $target_path" 89 | 90 | # Verify installation 91 | if command -v "$COMPILER_NAME" >/dev/null 2>&1; then 92 | print_status "✓ '$COMPILER_NAME' command is now available globally" 93 | print_status "Version check:" 94 | "$COMPILER_NAME" --version 2>/dev/null || "$COMPILER_NAME" --help | head -1 || echo " (no version info available)" 95 | else 96 | print_warning "$target_dir might not be in your PATH" 97 | print_warning "You may need to restart your terminal or run: export PATH=\"$target_dir:\$PATH\"" 98 | fi 99 | 100 | return 0 101 | } 102 | 103 | # Function to install to ~/.local/bin (user-local) 104 | install_user_local() { 105 | local target_dir="$HOME/.local/bin" 106 | local target_path="$target_dir/$COMPILER_NAME" 107 | 108 | print_status "Installing $COMPILER_NAME to $target_dir (user-local)..." 109 | 110 | # Create directory if it doesn't exist 111 | mkdir -p "$target_dir" 112 | 113 | # Copy the binary 114 | cp "$BINARY_PATH" "$target_path" 115 | chmod +x "$target_path" 116 | 117 | print_status "Successfully installed to $target_path" 118 | 119 | # Check if ~/.local/bin is in PATH 120 | if [[ ":$PATH:" == *":$target_dir:"* ]]; then 121 | print_status "✓ '$COMPILER_NAME' command is now available globally" 122 | else 123 | print_warning "$target_dir is not in your PATH" 124 | print_warning "Add this line to your ~/.bashrc or ~/.zshrc:" 125 | echo "export PATH=\"\$HOME/.local/bin:\$PATH\"" 126 | print_warning "Then restart your terminal or run: source ~/.bashrc" 127 | fi 128 | } 129 | 130 | # Function to create a symbolic link (development mode) 131 | install_symlink() { 132 | local target_dir="/usr/local/bin" 133 | local target_path="$target_dir/$COMPILER_NAME" 134 | 135 | print_status "Creating symbolic link in $target_dir (development mode)..." 136 | 137 | # Remove existing link/binary if it exists 138 | if [[ -L "$target_path" ]]; then 139 | sudo rm "$target_path" 140 | print_status "Removed existing symbolic link" 141 | elif [[ -f "$target_path" ]]; then 142 | print_warning "Existing binary found at $target_path" 143 | read -p "Replace it with symbolic link? (y/N): " -n 1 -r 144 | echo 145 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then 146 | print_status "Installation cancelled" 147 | return 1 148 | fi 149 | sudo rm "$target_path" 150 | fi 151 | 152 | # Create symbolic link 153 | sudo ln -s "$BINARY_PATH" "$target_path" 154 | 155 | print_status "✓ Symbolic link created: $target_path -> $BINARY_PATH" 156 | print_status "✓ '$COMPILER_NAME' command is now available globally" 157 | print_warning "Note: This links to your development build. Rebuild to update the installed version." 158 | } 159 | 160 | # Function to uninstall 161 | uninstall() { 162 | local locations=("/usr/local/bin/$COMPILER_NAME" "$HOME/.local/bin/$COMPILER_NAME") 163 | local found=false 164 | 165 | print_status "Uninstalling $COMPILER_NAME..." 166 | 167 | for location in "${locations[@]}"; do 168 | if [[ -f "$location" ]] || [[ -L "$location" ]]; then 169 | found=true 170 | if [[ "$location" == "/usr/local/bin/$COMPILER_NAME" ]]; then 171 | sudo rm "$location" 172 | else 173 | rm "$location" 174 | fi 175 | print_status "Removed: $location" 176 | fi 177 | done 178 | 179 | if [[ "$found" == false ]]; then 180 | print_warning "No installation found" 181 | else 182 | print_status "✓ Uninstall complete" 183 | fi 184 | } 185 | 186 | # Main installation menu 187 | main() { 188 | check_root 189 | 190 | echo "Fumo Compiler Installation Script" 191 | echo "=================================" 192 | 193 | if [[ "$1" == "--uninstall" ]]; then 194 | uninstall 195 | return 0 196 | fi 197 | 198 | build_compiler 199 | 200 | echo "" 201 | echo "Choose installation method:" 202 | echo "1) System-wide (/usr/local/bin) - requires sudo" 203 | echo "2) User-local (~/.local/bin) - no sudo required" 204 | echo "3) Development symlink (/usr/local/bin -> build/) - requires sudo" 205 | echo "4) Uninstall" 206 | echo "5) Cancel" 207 | echo "" 208 | 209 | read -p "Enter choice (1-5): " choice 210 | 211 | case $choice in 212 | 1) 213 | install_system_wide 214 | ;; 215 | 2) 216 | install_user_local 217 | ;; 218 | 3) 219 | install_symlink 220 | ;; 221 | 4) 222 | uninstall 223 | ;; 224 | 5) 225 | print_status "Installation cancelled" 226 | ;; 227 | *) 228 | print_error "Invalid choice" 229 | exit 1 230 | ;; 231 | esac 232 | } 233 | 234 | # Run main function 235 | main "$@" 236 | -------------------------------------------------------------------------------- /rebuild.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | PROJECT_NAME="${PWD##*/}" 5 | BUILD_DIR="build" 6 | BINARY="$BUILD_DIR/$PROJECT_NAME" 7 | #!/bin/bash 8 | set -e 9 | 10 | PROJECT_NAME="${PWD##*/}" 11 | BUILD_DIR="build" 12 | 13 | echo "Building $PROJECT_NAME..." 14 | 15 | if [ ! -d "$BUILD_DIR" ]; then 16 | echo "Error: $BUILD_DIR directory not found" 17 | echo "Run: cmake -B build -S . -GNinja" 18 | exit 1 19 | fi 20 | 21 | cd "$BUILD_DIR" 22 | ninja 23 | cd .. 24 | 25 | echo "Build complete!" 26 | -------------------------------------------------------------------------------- /src/base_definitions/symbol_table_stack.hpp: -------------------------------------------------------------------------------- 1 | // works just like a stack of scopes 2 | // we push a std::map when entering a new scope 3 | // we pop the current std::map when leaving a scope 4 | 5 | // all declarations are flattened and identifiers are changed to match them 6 | // they are "global" but renamed internally 7 | // for example: 8 | // 9 | // struct foo { struct bar {}; } => struct "foo::bar" {}; 10 | // struct foo { fn func() -> void; } => fn "foo::func"(this: foo*) -> void; 11 | // 12 | // namespace foo { struct bar {}; } => struct "foo::bar" {}; 13 | // namespace foo { fn func() -> void; } => fn "foo::func"() -> void; 14 | // 15 | // fn f() -> void { struct bar {}; } => struct "foo()::bar" {}; 16 | // fn f() -> void { let bar: i32; } => let "foo()::bar": i32; 17 | // 18 | // for generics: 19 | // struct foo::bar[T, U, V]() { ... } 20 | // struct "foo::bar[{}]()" { ... } 21 | // 22 | // fn foo::bar[T]::thing[U]() { ... } 23 | // fn "foo::bar[{}]::thing[{}]()" { ... } 24 | // 25 | #pragma once 26 | 27 | #include "base_definitions/ast_node.hpp" 28 | #include "utils/common_utils.hpp" 29 | 30 | #define find_value(key, map) (const auto& iter = map.find(key); iter != map.end()) 31 | #define each_case_label(the_case) case the_case: 32 | #undef cases 33 | #define case(...) map_macro(each_case_label, __VA_ARGS__) 34 | 35 | #include 36 | #include 37 | 38 | struct SymbolTableStack { 39 | // -------------------------------------------- 40 | // NOTE: these are not used in codegen 41 | std::map local_variable_decls {}; 42 | std::map namespace_decls {}; 43 | // -------------------------------------------- 44 | // for separating lookups (could be just a flag on each member but this is fine) 45 | // the flag is elsewhere and then we just pick a map to search. same result 46 | std::map global_variable_decls {}; 47 | std::map type_decls {}; 48 | std::map function_decls {}; 49 | std::map member_function_decls {}; 50 | std::map member_variable_decls {}; 51 | // -------------------------------------------- 52 | std::map curr_generic_context {}; // holds all the generic type identifiers we can use 53 | std::map all_declarations {}; 54 | // change this to only include the top level declarations so we can recursively codegen them. 55 | // NOTE: doesn't include namespaces or local variables or member variables 56 | // -------------------------------------------- 57 | vec scope_stack {}; 58 | 59 | str curr_scope_name = ""; 60 | ScopeKind curr_scope_kind; 61 | 62 | void push_scope(str name, ScopeKind kind) { 63 | // std::cerr << name << '\n'; 64 | switch (kind) { 65 | case ScopeKind::Namespace: name += "::"; break; 66 | case ScopeKind::TypeBody: name += "::"; break; 67 | case ScopeKind::MemberFuncBody: 68 | case ScopeKind::FunctionBody: name += "()::"; break; 69 | case ScopeKind::CompoundStatement: // += "0::" 70 | case ScopeKind::MemberCompoundStatement: 71 | if (!scope_stack.empty()) { 72 | int& prev_scope_count = scope_stack.back().inner_scope_count; 73 | name = std::to_string(prev_scope_count) + "::"; 74 | prev_scope_count++; 75 | } 76 | break; 77 | } 78 | curr_scope_kind = kind; 79 | curr_scope_name += name; 80 | scope_stack.push_back({curr_scope_name, kind, name}); 81 | } 82 | void pop_scope() { 83 | curr_scope_name.resize(curr_scope_name.size() - scope_stack.back().isolated_name.size()); 84 | scope_stack.pop_back(); 85 | if (!scope_stack.empty()) [[likely]] curr_scope_kind = scope_stack.back().kind; 86 | } 87 | 88 | auto push_variable_decl(Identifier& identifier, ASTNode& node) { 89 | identifier.mangled_name = curr_scope_name + identifier.name; 90 | switch (curr_scope_kind) { 91 | case ScopeKind::Namespace: 92 | all_declarations.insert({identifier.mangled_name, &node}); 93 | return global_variable_decls.insert({identifier.mangled_name, &node}); 94 | case ScopeKind::TypeBody: 95 | return member_variable_decls.insert({identifier.mangled_name, &node}); 96 | case ScopeKind::MemberFuncBody: 97 | case ScopeKind::FunctionBody: 98 | case ScopeKind::CompoundStatement: 99 | case ScopeKind::MemberCompoundStatement: 100 | return local_variable_decls.insert({identifier.mangled_name, &node}); 101 | } 102 | std::unreachable(); 103 | } 104 | auto push_type_decl(Identifier& identifier, ASTNode& node) { 105 | identifier.mangled_name = curr_scope_name + identifier.name; 106 | all_declarations.insert({identifier.mangled_name, &node}); 107 | return type_decls.insert({identifier.mangled_name, &node}); 108 | } 109 | auto push_namespace_decl(Identifier& identifier, ASTNode& node) { 110 | identifier.mangled_name = curr_scope_name + identifier.name; 111 | return namespace_decls.insert({identifier.mangled_name, &node}); 112 | } 113 | auto push_function_decl(Identifier& identifier, ASTNode& node) { 114 | // NOTE: this function might need some changes if you get issues with identifiers 115 | 116 | identifier.mangled_name = curr_scope_name + identifier.name; 117 | all_declarations.insert({identifier.mangled_name, &node}); 118 | switch (curr_scope_kind) { 119 | case ScopeKind::TypeBody: 120 | if (node.type.qualifiers.contains(Type::static_)) { 121 | return function_decls.insert({identifier.mangled_name, &node}); 122 | } 123 | return member_function_decls.insert({identifier.mangled_name, &node}); 124 | case ScopeKind::Namespace: 125 | case ScopeKind::FunctionBody: 126 | case ScopeKind::MemberFuncBody: 127 | case ScopeKind::CompoundStatement: 128 | case ScopeKind::MemberCompoundStatement: 129 | if (get(&node).kind == FunctionDecl::member_func_declaration) { 130 | if (node.type.qualifiers.contains(Type::static_)) { 131 | return function_decls.insert({identifier.mangled_name, &node}); 132 | } 133 | return member_function_decls.insert({identifier.mangled_name, &node}); 134 | } 135 | return function_decls.insert({identifier.mangled_name, &node}); 136 | } 137 | std::unreachable(); 138 | } 139 | 140 | [[nodiscard]] Opt find_declaration(Identifier& id); 141 | [[nodiscard]] ScopeKind find_scope_kind(const str& name); 142 | }; 143 | -------------------------------------------------------------------------------- /src/base_definitions/types.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "base_definitions/symbols.hpp" 5 | #include "utils/common_utils.hpp" 6 | struct ASTNode; 7 | #define make_enum_member(_v) _v##_, 8 | 9 | 10 | 11 | struct Type { 12 | ASTNode* identifier; // will get mapped to the declaration 13 | enum Kind { 14 | Undetermined, // should be converted in the type checker or give error 15 | Generic, // generic Any type (unused atm) 16 | // Function, // add this later if you want function objects/pointers 17 | struct_, 18 | enum_, 19 | namespace_, 20 | map_macro(make_enum_member, builtin_types) 21 | } kind = Undetermined; 22 | enum TypeQualifier { const_, volatile_, extern_, static_ }; 23 | std::unordered_set qualifiers; 24 | int ptr_count = 0; 25 | bool is_const = false; 26 | // Opt declaration {}; NOTE: is present in identifier node 27 | }; 28 | 29 | #define each_builtin_type(builtin_name) if (type_name == #builtin_name) return Type::builtin_name##_; 30 | [[nodiscard]] constexpr Type::Kind builtin_type_kind(std::string_view type_name) { 31 | map_macro(each_builtin_type, builtin_types); 32 | internal_panic("internal compiler error: provided unknown type name: {}", type_name); 33 | } 34 | 35 | #undef make_enum_member 36 | #undef each_builtin_type 37 | 38 | -------------------------------------------------------------------------------- /src/codegen/link_executable.cpp: -------------------------------------------------------------------------------- 1 | #include "codegen/llvm_codegen.hpp" 2 | 3 | Result link_executable(const LinkOptions& opts) { 4 | 5 | vec cmd; 6 | LinkerType linker = opts.linker; 7 | 8 | if (linker == LinkerType::AUTO) { 9 | if (system("which gcc >/dev/null 2>&1") == 0) linker = LinkerType::GCC; 10 | else if (system("which clang >/dev/null 2>&1") == 0) linker = LinkerType::CLANG; 11 | else 12 | Err("No suitable linker found (install gcc or clang)"); 13 | } 14 | 15 | switch (linker) { 16 | case LinkerType::AUTO: 17 | case LinkerType::GCC: cmd.push_back("gcc"); break; 18 | case LinkerType::CLANG: cmd.push_back("clang"); break; 19 | } 20 | 21 | cmd.push_back("-o"); 22 | cmd.push_back(opts.output_name); 23 | 24 | for (const auto& obj : opts.object_files) cmd.push_back(obj); 25 | for (const auto& path : opts.library_paths) cmd.push_back("-L" + path); 26 | for (const auto& lib : opts.libraries) cmd.push_back("-l" + lib); 27 | 28 | if (static_link) cmd.push_back("-static"); 29 | if (strip_syms) cmd.push_back("-s"); 30 | 31 | str command; 32 | for (std::size_t i = 0; i < cmd.size(); ++i) { 33 | if (i > 0) command += " "; 34 | 35 | if (cmd[i].find(' ') != str::npos) 36 | command += "\"" + cmd[i] + "\""; 37 | else 38 | command += cmd[i]; 39 | } 40 | 41 | if (verbose) std::cerr << "Linking: " << command << std::endl; 42 | 43 | int result = system(command.c_str()); 44 | if (result != 0) return Err("Linker failed with exit code: " + std::to_string(result)); 45 | 46 | return {}; 47 | } 48 | 49 | 50 | LinkOptions build_link_options(const str& output_name, const vec& object_files) { 51 | 52 | LinkOptions opts { 53 | .output_name = output_name, 54 | .object_files = object_files, 55 | .static_link = static_link.getValue(), 56 | .strip_symbols = strip_syms.getValue(), 57 | .verbose = verbose.getValue(), 58 | }; 59 | 60 | if (linker_name.getValue() == "auto") opts.linker = LinkerType::AUTO; 61 | else if (linker_name.getValue() == "gcc") opts.linker = LinkerType::GCC; 62 | else if (linker_name.getValue() == "clang") opts.linker = LinkerType::CLANG; 63 | else 64 | std::cerr << "Unknown linker: " << linker_name.getValue() << ", using auto-detection\n"; 65 | 66 | 67 | for (const auto& lib : libraries) opts.libraries.push_back(lib); 68 | for (const auto& path : lib_paths) opts.library_paths.push_back(path); 69 | 70 | // Always link with libc 71 | if (std::find(opts.libraries.begin(), opts.libraries.end(), "c") == opts.libraries.end()) { 72 | opts.libraries.push_back("c"); 73 | } 74 | 75 | return opts; 76 | } 77 | -------------------------------------------------------------------------------- /src/lexer/lexer.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | #include 4 | 5 | #define add_token(tok) tokens.push_back(Token {.type = tkn(tok), add_token_info}) 6 | #define add_and_consume_token(tok) do {tokens.push_back(Token {.type = tkn(tok), add_token_info}); get_curr();} while(0) 7 | #define next_char_is(tok) (file_stream.peek() == 0[#tok]) 8 | #define make_token(tok) Token {.type = tkn(tok), add_token_info} 9 | #define make_and_consume_token(tok) ({get_curr(); Token {.type = tkn(tok), add_token_info};}) 10 | 11 | [[nodiscard]] std::pair, File> Lexer::tokenize_file(const fs::path& _file_name) { 12 | __FUMO_FILE__ = _file_name; 13 | file_stream << std::ifstream(_file_name).rdbuf(); 14 | return {tokenize(), File{_file_name, file_stream.str()}}; 15 | } 16 | [[nodiscard]] std::pair, File> Lexer::tokenize_string(const std::string& testname, const std::string& test_string) { 17 | __FUMO_FILE__ = testname; 18 | file_stream << test_string; 19 | return {tokenize(), File{testname, file_stream.str()}}; 20 | } 21 | 22 | [[nodiscard]] vec Lexer::tokenize() { 23 | vec tokens; 24 | __FUMO_LINE__ = peek_line(); 25 | 26 | while ((curr = get_curr()) != EOF) { 27 | 28 | if (curr == '\'') { 29 | tokens.push_back(parse_character_literal()); 30 | continue; 31 | } 32 | if (curr == '"') { 33 | tokens.push_back(parse_string_literal()); 34 | continue; 35 | } 36 | if (std::isdigit(curr)) { 37 | tokens.push_back(parse_numeric_literal()); 38 | continue; 39 | } 40 | if (std::isalpha(curr) || curr == '_') { 41 | tokens.push_back(parse_identifier()); 42 | continue; 43 | } 44 | switch (curr) { 45 | // 46 | // ----------------------------------------------------------- 47 | // handle each symbol based on its possible compound symbols 48 | cases(singular); 49 | cases(has_equals); 50 | cases(has_double_and_equals); 51 | cases(ignore); 52 | // ----------------------------------------------------------- 53 | // triple cases are handled individually 54 | case ':': 55 | tokens.push_back(next_char_is(:) ? make_and_consume_token(::) 56 | : make_token(:)); 57 | break; 58 | case '.': 59 | if ((next_char_is(.))) { 60 | if (get_curr(); (next_char_is(.))) add_and_consume_token(...); 61 | else 62 | lexer_error("Expected complete '...' ellipsis."); 63 | } else 64 | add_token(.); 65 | break; 66 | case '<': 67 | if (next_char_is(<)) { 68 | if (get_curr(); next_char_is(=)) 69 | add_and_consume_token(<<=); 70 | else 71 | add_and_consume_token(<<); 72 | } else if (next_char_is(=)) 73 | add_and_consume_token(<=); 74 | else 75 | add_token(<); 76 | break; 77 | 78 | case '>': 79 | if (next_char_is(>)) { 80 | if (get_curr(); next_char_is(=)) 81 | add_and_consume_token(>>=); 82 | else 83 | add_and_consume_token(>>); 84 | } else if (next_char_is(=)) 85 | add_and_consume_token(>=); 86 | else 87 | add_token(>); 88 | break; 89 | // ----------------------------------------------------------- 90 | // special cases 91 | case '-': 92 | if(next_char_is(-)) 93 | add_and_consume_token(--); 94 | else if (next_char_is(=)) 95 | add_and_consume_token(-=); 96 | else if (next_char_is(>)) 97 | add_and_consume_token(->); 98 | else 99 | add_token(-); 100 | break; 101 | 102 | case '+': 103 | if(next_char_is(+)) 104 | add_and_consume_token(++); 105 | else if (next_char_is(=)) 106 | add_and_consume_token(+=); 107 | else 108 | add_token(+); 109 | break; 110 | 111 | case '/': 112 | if (next_char_is(/)) 113 | while (file_stream.peek() != EOF && !next_char_is(\n)) curr = get_curr(); 114 | else 115 | tokens.push_back(next_char_is(=) ? make_and_consume_token(/=) 116 | : make_token(/)); 117 | break; 118 | 119 | case '#': 120 | tokens.push_back(next_char_is(#) ? make_and_consume_token(##) 121 | : make_token(#)); 122 | break; 123 | 124 | case '\n': __FUMO_LINE_NUM__++; __FUMO_LINE_OFFSET__ = 0; __FUMO_LINE__ = peek_line(); break; 125 | 126 | default: lexer_error("Source file is not valid ASCII."); 127 | } 128 | } 129 | add_token(is_EOF); 130 | return tokens; 131 | } 132 | 133 | #undef cases 134 | -------------------------------------------------------------------------------- /src/lexer/lexer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base_definitions/tokens.hpp" 3 | 4 | struct Lexer { 5 | i64 __FUMO_LINE_NUM__ = 1; 6 | i64 __FUMO_LINE_OFFSET__ = 0; 7 | std::string __FUMO_LINE__; 8 | fs::path __FUMO_FILE__; 9 | std::stringstream file_stream; 10 | int curr = 0; 11 | 12 | [[nodiscard]] std::pair, File> tokenize_file(const fs::path& _file_name); 13 | [[nodiscard]] std::pair, File> tokenize_string(const std::string& testname, 14 | const std::string& test_string); 15 | 16 | private: 17 | [[nodiscard]] vec tokenize(); 18 | [[nodiscard]] Token parse_string_literal(); 19 | [[nodiscard]] Token parse_character_literal(); 20 | [[nodiscard]] Token parse_numeric_literal(); 21 | [[nodiscard]] Token parse_identifier(); 22 | [[nodiscard]] str peek_line(); 23 | [[nodiscard]] bool identifier_ended(); 24 | int get_curr(); 25 | }; 26 | 27 | // clang-format off 28 | 29 | #define cases(symbols) map_macro(symbols##_case, symbols) 30 | 31 | #define singular_case(tkn) \ 32 | case (int)(Symbol::tkn): { \ 33 | tokens.push_back({.type = TokenType::tkn, add_token_info}); \ 34 | break; \ 35 | } 36 | #define has_equals_case(tkn) \ 37 | case (int)Symbol::tkn: { \ 38 | tokens.push_back( \ 39 | file_stream.peek() == '=' \ 40 | ? ({get_curr(); \ 41 | Token {.type = TokenType::tkn##_equals,add_token_info};}) \ 42 | : Token {.type = TokenType::tkn, add_token_info}); \ 43 | break; \ 44 | } 45 | #define has_double_and_equals_case(tkn) \ 46 | case (int)Symbol::tkn: { \ 47 | tokens.push_back( \ 48 | file_stream.peek() == (int)Symbol::tkn \ 49 | ? ({get_curr(); \ 50 | Token {.type =TokenType::tkn##_##tkn,add_token_info};}) \ 51 | : file_stream.peek() == '=' ? \ 52 | ({get_curr(); \ 53 | Token {.type =TokenType::tkn##_equals,add_token_info};}) \ 54 | : Token {.type =TokenType::tkn, add_token_info}); \ 55 | break; \ 56 | } 57 | #define ignore_case(tkn) \ 58 | case static_cast(Symbol::tkn): { \ 59 | break; \ 60 | } 61 | 62 | #define add_token_info \ 63 | .literal = std::nullopt, \ 64 | .line_number = __FUMO_LINE_NUM__, \ 65 | .line_offset = __FUMO_LINE_OFFSET__, \ 66 | .file_offset = file_stream.tellg(), \ 67 | .file_name = __FUMO_FILE__.string() 68 | 69 | #define lexer_error(...) \ 70 | { \ 71 | std::cerr << _lexer_error(__VA_ARGS__) << std::endl; \ 72 | std::exit(1); \ 73 | } 74 | 75 | #define _lexer_error(...) \ 76 | std::format("\n | error in file '{}' at line {}:\n | {}\n |{}{}", \ 77 | __FUMO_FILE__.string(), \ 78 | __FUMO_LINE_NUM__, \ 79 | __FUMO_LINE__, \ 80 | std::string(__FUMO_LINE_OFFSET__, ' ') + "^ ", \ 81 | std::format(__VA_ARGS__) \ 82 | ) 83 | -------------------------------------------------------------------------------- /src/lexer/lexer_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer.hpp" 2 | #include 3 | 4 | #define symbol_case(v_) case int(Symbol::v_): return true; 5 | 6 | [[nodiscard]] bool Lexer::identifier_ended() { 7 | switch (file_stream.peek()) { 8 | map_macro(symbol_case, all_symbols); 9 | default: 10 | return false; 11 | } 12 | } 13 | 14 | 15 | [[nodiscard]] str Lexer::peek_line() { 16 | std::string line; 17 | int len = file_stream.tellg(); 18 | std::getline(file_stream, line); 19 | file_stream.seekg(len, std::ios_base::beg); 20 | return line; 21 | } 22 | 23 | int Lexer::get_curr() { 24 | __FUMO_LINE_OFFSET__++; 25 | curr = file_stream.get(); 26 | return curr; 27 | } 28 | 29 | [[nodiscard]] Token Lexer::parse_identifier() { 30 | str value = std::format("{}", char(curr)); 31 | 32 | while (!identifier_ended() && file_stream.peek() != EOF) { 33 | if (char next = get_curr(); std::isalnum(next) || next == '_') value += next; 34 | else 35 | lexer_error("Source file is not valid ASCII, or used unsupported character in identifier."); 36 | } 37 | 38 | return Token {.type = is_keyword(value) ? is_builtin_type(value) ? TokenType::builtin_type : TokenType::keyword 39 | : TokenType::identifier, 40 | .literal = std::move(value), 41 | .line_number = __FUMO_LINE_NUM__, 42 | .line_offset = __FUMO_LINE_OFFSET__, 43 | .file_offset = file_stream.tellg(), 44 | .file_name = __FUMO_FILE__.string()}; 45 | } 46 | 47 | [[nodiscard]] Token Lexer::parse_character_literal() { 48 | std::string str_inner{}; 49 | 50 | get_curr(); 51 | if (curr == '\'') lexer_error("empty character constant."); 52 | 53 | while (file_stream.peek() != EOF) { 54 | if (curr == '\\') { 55 | get_curr(); 56 | switch (curr) { 57 | case '0': str_inner += '\0'; break; 58 | case 'a': str_inner += '\a'; break; 59 | case 'r': str_inner += '\r'; break; 60 | case 'n': str_inner += '\n'; break; 61 | case 't': str_inner += '\t'; break; 62 | case 'f': str_inner += '\f'; break; 63 | case 'v': str_inner += '\v'; break; 64 | case 'e': str_inner += '\e'; break; 65 | case '"': str_inner += '"'; break; 66 | case '\\': str_inner += '\\'; break; 67 | case '\'': str_inner += '\''; break; 68 | default: lexer_error("Invalid escape code."); 69 | } 70 | } else { 71 | str_inner += curr; 72 | } 73 | 74 | get_curr(); 75 | 76 | if (str_inner.size() > 1) { 77 | lexer_error("Invalid number of characters in character literal."); 78 | } 79 | 80 | if (curr == '\'') break; 81 | } 82 | 83 | if (curr != '\'') { 84 | lexer_error("Unmatched quote in character literal."); 85 | } 86 | int64_t char_literal = str_inner[0]; 87 | return Token { .type = TokenType::char_literal, 88 | .literal = char_literal, 89 | .line_number = __FUMO_LINE_NUM__, 90 | .line_offset = __FUMO_LINE_OFFSET__, 91 | .file_offset = file_stream.tellg(), 92 | .file_name = __FUMO_FILE__.string()}; 93 | } 94 | 95 | [[nodiscard]] Token Lexer::parse_string_literal() { 96 | std::string str_inner{}; 97 | get_curr(); 98 | while (file_stream.peek() != EOF) { 99 | if (curr == '"') break; 100 | 101 | if (curr != '\\') { 102 | str_inner += curr; 103 | } else { 104 | if (file_stream.peek() != EOF) { 105 | get_curr(); 106 | } else { 107 | lexer_error("Dangling backslash escape character."); 108 | } 109 | switch (curr) { 110 | case '0': str_inner += '\0'; break; 111 | case 'a': str_inner += '\a'; break; 112 | case 'r': str_inner += '\r'; break; 113 | case 'n': str_inner += '\n'; break; 114 | case 't': str_inner += '\t'; break; 115 | case 'f': str_inner += '\f'; break; 116 | case 'v': str_inner += '\v'; break; 117 | case 'e': str_inner += '\e'; break; 118 | case '"': str_inner += '"'; break; 119 | case '\\': str_inner += '\\'; str_inner += '\\'; break; 120 | case '\'': str_inner += '\\'; str_inner += '\''; break; 121 | default: lexer_error("Invalid escape code."); 122 | } 123 | } 124 | 125 | get_curr(); 126 | } 127 | if (curr != '"') { 128 | lexer_error("Unmatched quote in string literal."); 129 | } 130 | return Token { .type = TokenType::string, .literal = std::move(str_inner), 131 | .line_number = __FUMO_LINE_NUM__, .line_offset = __FUMO_LINE_OFFSET__, .file_offset = file_stream.tellg(), 132 | .file_name = __FUMO_FILE__.string()}; 133 | } 134 | 135 | [[nodiscard]] Token Lexer::parse_numeric_literal() { 136 | str value = std::format("{}", char(curr)); 137 | Token token {.type = TokenType::integer}; 138 | 139 | // FIXME: fix the syntax for floating point numbers to be compliant 140 | while ((!identifier_ended() || file_stream.peek() == '.') 141 | && file_stream.peek() != EOF) { 142 | 143 | get_curr(); 144 | 145 | if (curr == '.' && std::isdigit(file_stream.peek())) { 146 | value += curr; 147 | token.type = TokenType::floating_point; 148 | } 149 | else if (std::isdigit(curr)) 150 | value += curr; 151 | else if (std::isalpha(curr)) { 152 | if (curr != 'f' || token.type != TokenType::floating_point) 153 | lexer_error("invalid digit '{}' in decimal constant. NOTE: (only 'f' is allowed in floats).", 154 | char(curr)); 155 | } else 156 | lexer_error("Source file is not valid ASCII."); 157 | } 158 | 159 | if (token.type == TokenType::integer) token.literal = std::stoi(value); 160 | else if (token.type == TokenType::floating_point) 161 | token.literal = std::stof(value); 162 | else 163 | token.literal = std::move(value); 164 | 165 | token.line_number = __FUMO_LINE_NUM__; 166 | token.line_offset = __FUMO_LINE_OFFSET__; 167 | token.file_offset = file_stream.tellg(); 168 | token.file_name = __FUMO_FILE__.string(); 169 | 170 | return token; 171 | } 172 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "lexer/lexer.hpp" 2 | #include "parser/parser.hpp" 3 | #include "semantic_analysis/analyzer.hpp" 4 | #include "codegen/llvm_codegen.hpp" 5 | #include "utils/common_utils.hpp" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "llvm/Support/Signals.h" 13 | 14 | extern "C" const char* __asan_default_options() { return "detect_leaks=0"; } 15 | 16 | llvm::OptimizationLevel opt_level = llvm::OptimizationLevel::O2; 17 | #define was_opt_level(level) if (O##level.getNumOccurrences()) opt_level = llvm::OptimizationLevel::O##level; else 18 | 19 | using namespace llvm::cl; 20 | OptionCategory fumo_category("Fumo Compiler Options", 21 | " Options for controlling the compilation process, such as opt levels"); 22 | 23 | opt O0 {"O0", desc("No optimizations"), cat(fumo_category)}; 24 | opt O1 {"O1", desc("Few optimizations"), cat(fumo_category)}; 25 | opt O2 {"O2", desc("Default optimizations"), cat(fumo_category)}; 26 | opt O3 {"O3", desc("Aggressive optimizations"), cat(fumo_category)}; 27 | 28 | opt output_IR_O0{"emit-ir-O0", desc("Outputs a .ll file with the generated llvm IR"), cat(fumo_category)}; 29 | opt output_IR {"emit-ir", desc("Outputs a .ll file with the generated llvm IR"), cat(fumo_category)}; 30 | opt output_AST {"emit-ast", desc("Outputs a .ast file with the generated debug AST"), cat(fumo_category)}; 31 | opt output_ASM {"emit-asm", desc("Outputs a .asm file with the generated assembly"), cat(fumo_category)}; 32 | opt output_OBJ {"emit-obj", desc("Outputs a .o object file"), cat(fumo_category)}; 33 | 34 | opt print_file {"print-file", desc("Print the inputed file contents to the terminal"), cat(fumo_category)}; 35 | opt print_IR {"print-ir", desc("Print llvm-IR to the terminal"), cat(fumo_category)}; 36 | opt print_AST {"print-ast", desc("Prints the debug AST representation to the terminal"), cat(fumo_category)}; 37 | opt print_ASM {"print-asm", desc("Prints the output ASM to the terminal"), cat(fumo_category)}; 38 | 39 | opt verbose {"v", desc("Show commands executed during compilation and linking"), cat(fumo_category)}; 40 | 41 | opt out_file {"o", desc("Output filename"), value_desc("filename"), cat(fumo_category)}; 42 | list input_files {Positional, desc("Input source files"), value_desc("filenames"), OneOrMore,cat(fumo_category)}; 43 | 44 | // Linking control 45 | opt no_link {"c", desc("Compile only, do not link (produces .o files)"), cat(fumo_category)}; 46 | opt static_link {"static", desc("Create a statically linked executable"), cat(fumo_category)}; 47 | opt strip_syms {"s", desc("Strip symbol table from executable"), cat(fumo_category)}; 48 | // Linker selection 49 | opt linker_name {"linker", desc("Specify which linker to use (auto/gcc/clang)"), 50 | value_desc("linker"), init("auto"), cat(fumo_category)}; 51 | list libraries {"l", desc("Link with library"), 52 | value_desc("library"), ZeroOrMore, cat(fumo_category)}; 53 | list lib_paths {"L", desc("Add directory to library search path"), 54 | value_desc("directory"), ZeroOrMore, cat(fumo_category)}; 55 | 56 | void custom_handler(int sig) { 57 | // print stack trace without bug report message 58 | // this removes the "PLEASE submit a bug report" message specifically 59 | llvm::sys::PrintStackTrace(llvm::errs()); 60 | std::exit(1); 61 | } 62 | 63 | auto main(int argc, char** argv) -> int { 64 | std::map barrr{}; 65 | auto e = barrr["foo"]; 66 | 67 | llvm::InitLLVM init(argc, argv); 68 | signal(SIGSEGV, custom_handler); signal(SIGABRT, custom_handler); 69 | signal(SIGFPE, custom_handler); signal(SIGILL, custom_handler); 70 | 71 | //-------------------------------------------------------------------------- 72 | // Lexer and Command-line arguments parsing 73 | vec obj_files = {}; 74 | 75 | llvm::cl::HideUnrelatedOptions(fumo_category); 76 | llvm::cl::ParseCommandLineOptions(argc, argv, str(str("ᗜ") + gray("‿") + str("ᗜ Fumo Compiler\n"))); 77 | // 78 | 79 | for (const auto& file_name : input_files) { 80 | if (!std::filesystem::exists(file_name)) std::print("Error: file '{}' not found\n", file_name), std::exit(1); 81 | 82 | { map_macro(was_opt_level, 0, 1, 2, 3); } 83 | 84 | Lexer lexer {}; 85 | auto [tokens, file] = lexer.tokenize_file(file_name); 86 | //-------------------------------------------------------------------------- 87 | // Parser 88 | Parser parser {file}; 89 | auto file_root_node = parser.parse_tokens(tokens); 90 | //-------------------------------------------------------------------------- 91 | // Semantic Analysis 92 | // for (const auto& node : get(file_root_node).nodes) { 93 | // std::cerr << "node found:\n " + node->to_str() + "\n"; 94 | // } 95 | Analyzer analyzer {file}; 96 | analyzer.semantic_analysis(file_root_node); 97 | // //-------------------------------------------------------------------------- 98 | // // recursive structs will crash the AST printing until after semantic analysis 99 | Codegen codegen {file, analyzer.symbol_tree}; 100 | codegen.codegen_file(file_root_node); 101 | obj_files.push_back(codegen.compile_file(opt_level)); 102 | } 103 | link_object_files(obj_files, out_file.getNumOccurrences()? out_file.getValue() : "fumo.out"); 104 | } 105 | // // Codegen 106 | -------------------------------------------------------------------------------- /src/parser/parser.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "base_definitions/ast_node.hpp" 3 | #include 4 | 5 | 6 | 7 | struct Parser { 8 | Parser(const File & file) { file_stream << file.contents; } 9 | [[nodiscard]] ASTNode* parse_tokens(vec& tokens); 10 | 11 | private: 12 | vec> all_nodes {}; 13 | vec tokens; 14 | std::vector::iterator curr_tkn; 15 | std::vector::iterator prev_tkn; 16 | std::stringstream file_stream; 17 | vec AST; 18 | 19 | ASTNode* push(ASTNode&& node) { 20 | if (!node.type.identifier) { 21 | ASTNode temp {*prev_tkn, Identifier {Identifier::type_name, "Undetermined Type"}}; 22 | all_nodes.push_back(std::make_unique(std::move(temp))); 23 | node.type.identifier = all_nodes.back().get(); 24 | } 25 | all_nodes.push_back(std::make_unique(node)); 26 | return all_nodes.back().get(); 27 | } 28 | Type make_type(Type::Kind kind, str name, int ptr_count) { 29 | return Type {.identifier = push(ASTNode {*prev_tkn, Identifier {Identifier::type_name, name}}), 30 | .kind = kind, 31 | .ptr_count = ptr_count}; 32 | } 33 | 34 | // implemention of the FumoLang BNF (language_specification/fumo_bnf.md) 35 | // -------------------------------------------------------------- 36 | // expressions 37 | [[nodiscard]] ASTNode* statement(); 38 | [[nodiscard]] ASTNode* expression_statement(); 39 | [[nodiscard]] ASTNode* if_statement(IfStmt::Kind kind); 40 | [[nodiscard]] ASTNode* while_loop(); 41 | [[nodiscard]] Opt else_statement(); 42 | [[nodiscard]] ASTNode* expression(); 43 | [[nodiscard]] ASTNode* assignment(); 44 | [[nodiscard]] ASTNode* initializer(); 45 | [[nodiscard]] ASTNode* initializer_list(); 46 | [[nodiscard]] Opt equality(); 47 | [[nodiscard]] Opt logical(); 48 | [[nodiscard]] Opt relational(); 49 | [[nodiscard]] Opt add(); 50 | [[nodiscard]] Opt multiply(); 51 | [[nodiscard]] Opt unary(); 52 | [[nodiscard]] Opt postfix(); 53 | [[nodiscard]] vec argument_list(); 54 | [[nodiscard]] Opt primary(); 55 | [[nodiscard]] ASTNode* identifier(Identifier::Kind id_kind); 56 | [[nodiscard]] ASTNode* declaration_identifier(); 57 | [[nodiscard]] ASTNode* qualified_identifier(); 58 | [[nodiscard]] ASTNode* unqualified_identifier(Identifier::Kind id_kind); 59 | void parse_generic_parameters(Identifier& id) ; 60 | 61 | // -------------------------------------------------------------- 62 | // declarations 63 | [[nodiscard]] Type declaration_specifier(); 64 | 65 | [[nodiscard]] ASTNode* namespace_declaration(); 66 | [[nodiscard]] ASTNode* struct_declaration(); 67 | [[nodiscard]] ASTNode* enum_declaration(); 68 | [[nodiscard]] vec named_scope_definition(); 69 | // variable 70 | [[nodiscard]] ASTNode* variable_declaration(bool optional_comma = false); 71 | // function 72 | [[nodiscard]] ASTNode* function_declaration(); 73 | [[nodiscard]] ASTNode* compound_statement(); 74 | [[nodiscard]] std::pair, bool> parameter_list(); 75 | // -------------------------------------------------------------- 76 | 77 | // #define token_is(tok) (std::print("is_tkn '{}' == '{}' ?\n", curr_tkn->to_str(), #tok), is_tkn(tkn(tok))) 78 | #define token_is_str(tok) is_tkn(str_to_tkn_type(tok)) 79 | #define token_is(tok) is_tkn(tkn(tok)) 80 | constexpr bool is_tkn(const TokenType& type) { 81 | return curr_tkn != tokens.end() && (curr_tkn->type == type) ? prev_tkn = curr_tkn++, true : false; 82 | } 83 | 84 | #define peek_token_str(tok) peek_is_tkn(str_to_tkn_type(tok)) 85 | #define peek_token(tok) peek_is_tkn(tkn(tok)) 86 | 87 | constexpr bool peek_is_tkn(const TokenType& type) { 88 | return curr_tkn != tokens.end() && (curr_tkn->type == type); 89 | } 90 | constexpr bool peek_token_amount(const TokenType& type, int increase) { 91 | auto next = curr_tkn + increase; 92 | return next != tokens.end() && (next->type == type); 93 | } 94 | constexpr bool peek_keyword_amount(str_view keyword, int increase) { 95 | auto next = curr_tkn + increase; 96 | return next != tokens.end() 97 | && (next->type == TokenType::keyword && std::get(next->literal.value()) == keyword); 98 | } 99 | 100 | #define peek_keyword(keyword) peek_tkn_keyword(#keyword) 101 | constexpr bool peek_tkn_keyword(str_view keyword) { 102 | return curr_tkn != tokens.end() 103 | && (curr_tkn->type == TokenType::keyword && std::get(curr_tkn->literal.value()) == keyword); 104 | } 105 | #define token_is_keyword(keyword) is_tkn_keyword(#keyword) 106 | constexpr bool is_tkn_keyword(std::string_view keyword) { 107 | if (curr_tkn != tokens.end() && curr_tkn->type == TokenType::keyword 108 | && std::get(curr_tkn->literal.value()) == keyword) { 109 | prev_tkn = curr_tkn++; 110 | return true; 111 | } 112 | return false; 113 | } 114 | 115 | #define expect_token(tok) consume_tkn_or_error(tkn(tok), #tok) 116 | #define expect_token_str(tok) consume_tkn_or_error(str_to_tkn_type(tok), tok) 117 | void consume_tkn_or_error(const TokenType& type, std::string_view repr) { 118 | if (!is_tkn(type)) report_error((*prev_tkn), "expected '{}'.", repr); 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /src/semantic_analysis/analyzer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "base_definitions/ast_node.hpp" 5 | #include "base_definitions/symbol_table_stack.hpp" 6 | #include "utils/common_utils.hpp" 7 | 8 | enum struct ControlFlowResult; 9 | 10 | struct Analyzer { 11 | Analyzer(const File& file) { file_stream << file.contents; } 12 | void semantic_analysis(ASTNode* file_scope); 13 | 14 | SymbolTableStack symbol_tree {}; 15 | vec> extra_nodes {}; // used for member functions to allocate a parameter 16 | Opt curr_postfix_id {}; 17 | ASTNode* root_node; 18 | 19 | private: 20 | std::stringstream file_stream; 21 | 22 | void analyze(ASTNode& node); 23 | void report_binary_error(const ASTNode& node, const BinaryExpr& bin); 24 | void check_initializer_lists(const ASTNode& node, BinaryExpr& bin); 25 | 26 | void analyze_function_control_flow(ASTNode& node); 27 | ControlFlowResult control_flow_analysis(ASTNode& node); 28 | 29 | ASTNode* create_main_node(Token& token) { 30 | // NOTE: not being used anymore 31 | auto id = push(ASTNode {token, Identifier {Identifier::declaration_name, "main"}}); 32 | FunctionDecl function {FunctionDecl::function_declaration, id}; 33 | 34 | auto* main_node = push(ASTNode {token, function}); 35 | main_node->type = Type {push(ASTNode {token, Identifier {Identifier::type_name, "i32"}}), Type::i32_}; 36 | 37 | return main_node; 38 | } 39 | void add_declaration(ASTNode& node); 40 | vec iterate_qualified_names(FunctionDecl& func, ASTNode& node); 41 | 42 | void instantiate_or_replace_generic(ASTNode& node); 43 | bool is_instantiable_generic_id(Identifier& id); 44 | 45 | [[nodiscard]] ASTNode* copy_ast(ASTNode* node); 46 | 47 | ASTNode* push(const ASTNode& node) { 48 | // NOTE: not creating the default type struct values 49 | extra_nodes.push_back(std::make_unique(node)); 50 | return extra_nodes.back().get(); 51 | } 52 | 53 | ASTNode* allocate_node(const ASTNode& node) { 54 | // NOTE: not creating the default type struct values 55 | extra_nodes.push_back(std::make_unique(node)); 56 | return extra_nodes.back().get(); 57 | } 58 | // let var: U = T{}; // should error 59 | // let var: Vec[U] = Vec[T]{}; // should error 60 | [[nodiscard]] bool is_compatible_or_generic_t(const Type& a, const Type& b) { 61 | auto& a_id = get_id(a); 62 | auto& b_id = get_id(b); 63 | if (!(a_id.kind == Identifier::generic_type_name || b_id.kind == Identifier::generic_type_name) 64 | && !(a_id.kind == Identifier::generic_wrapper_type_name || b_id.kind == Identifier::generic_wrapper_type_name)) { 65 | // we dont want to allow: 66 | // let a: any* = {}; let var: T* = a; let var: Vec[T]* = a; 67 | // cant assign an any* to a generic. 68 | // only allow compatibility with 'any*' if it wasnt a generic type 69 | if ((a.kind == Type::any_ || b.kind == Type::any_) && a.ptr_count == b.ptr_count) return true; 70 | } 71 | 72 | if (is_arithmetic_t(a) && is_arithmetic_t(b) && a.ptr_count == b.ptr_count) return true; 73 | 74 | if (a.kind == b.kind) { 75 | if (type_name(a) == type_name(b)) { 76 | return true; 77 | } 78 | } else { 79 | if ((type_name(a) == "null*" && b.ptr_count) || (type_name(b) == "null*" && a.ptr_count)) { 80 | // allow compatibility between any pointer to null 81 | return true; 82 | } 83 | } 84 | return false; 85 | } 86 | 87 | void push_generic_context(vec generic_identifiers) { 88 | for (auto* node : generic_identifiers) { 89 | str name = get(node).mangled_name; // T, U, etc. from struct Foo[T, U], and so on 90 | const auto& [node_iterator, was_inserted] = symbol_tree.curr_generic_context.insert({name, node}); 91 | if (!was_inserted) { // dont allow reusing generic type name identifiers 92 | report_error(node->source_token, "declaration of '{}' shadows previous generic parameter.", name); 93 | } 94 | } 95 | } 96 | 97 | void pop_generic_context(vec generic_identifiers) { 98 | for (auto* node : generic_identifiers) { 99 | str name = get(node).mangled_name; // T, U, etc. from struct Foo[T, U], and so on 100 | if find_value(name, symbol_tree.curr_generic_context) { 101 | symbol_tree.curr_generic_context.erase(iter); 102 | } else { 103 | report_error(node->source_token, "attempting to pop non-existent generic parameter '{}'.", name); 104 | } 105 | } 106 | } 107 | }; 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/semantic_analysis/control_flow_analysis.cpp: -------------------------------------------------------------------------------- 1 | #include "base_definitions/ast_node.hpp" 2 | #include "semantic_analysis/analyzer.hpp" 3 | #include "utils/common_utils.hpp" 4 | 5 | enum struct ControlFlowResult { NoReturn, Returns, MayReturn}; 6 | using enum ControlFlowResult; 7 | ControlFlowResult combine_branch_flow(ControlFlowResult one, ControlFlowResult two) { 8 | if (one == Returns && two == Returns) return Returns; 9 | if (one == Returns || two == Returns) return MayReturn; 10 | return NoReturn; 11 | } 12 | ControlFlowResult combine_sequential_flow(ControlFlowResult one, ControlFlowResult two) { 13 | if (one == Returns || two == Returns) return Returns; 14 | if (one == MayReturn || two == MayReturn) return MayReturn; 15 | return NoReturn; 16 | } 17 | 18 | void Analyzer::analyze_function_control_flow(ASTNode& node) { 19 | auto& func_decl = get(&node); 20 | // we need to iterate through all the paths of this function and find out if we always have valid paths 21 | // we still want to remove dead code for void functions 22 | 23 | auto* body = func_decl.body.value_or(nullptr); 24 | if (!body) return; // was a forward declaration of a function in another file (or libc function) 25 | 26 | ControlFlowResult curr_result = NoReturn; 27 | 28 | auto &nodes = get(body).nodes; 29 | for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it) { 30 | auto next_result = control_flow_analysis(**node_it); 31 | curr_result = combine_sequential_flow(curr_result, next_result); 32 | 33 | if (curr_result == Returns) { 34 | // remove dead code (for valid LLVM IR) 35 | nodes.erase(std::next(node_it), nodes.end()); 36 | return; // this function will always return (OK) 37 | } 38 | } 39 | if (get_id(func_decl).mangled_name != "main" && node.type.kind != Type::void_) { 40 | // void functions dont have to return in all control paths 41 | report_error(node.source_token, "non-void function does not return a value in all control paths."); 42 | // if we get here, function MayReturn or is NoReturn. therefore, its invalid 43 | } 44 | } 45 | 46 | ControlFlowResult Analyzer::control_flow_analysis(ASTNode& node) { 47 | match(node) { 48 | holds(UnaryExpr, &un) { 49 | if (un.kind == UnaryExpr::return_statement) return ControlFlowResult::Returns; 50 | } 51 | holds(IfStmt, &if_stmt) { 52 | switch (if_stmt.kind) { 53 | case IfStmt::if_statement: 54 | case IfStmt::else_if_statement: { 55 | ControlFlowResult if_result = NoReturn; 56 | auto &nodes = get(if_stmt.body).nodes; 57 | for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it) { 58 | auto next_result = control_flow_analysis(**node_it); 59 | 60 | if_result = combine_sequential_flow(if_result, next_result); 61 | 62 | if (if_result == Returns) { 63 | nodes.erase(std::next(node_it), nodes.end()); 64 | break; 65 | } 66 | } 67 | ControlFlowResult else_result = NoReturn; 68 | if (auto* else_node = if_stmt.else_stmt.value_or(nullptr)) { 69 | else_result = control_flow_analysis(*else_node); 70 | } 71 | return combine_branch_flow(if_result, else_result); 72 | } 73 | case IfStmt::else_statement: { 74 | ControlFlowResult else_result = NoReturn; 75 | auto &nodes = get(if_stmt.body).nodes; 76 | for (auto node_it = nodes.begin(); node_it != nodes.end(); ++node_it) { 77 | auto next_result = control_flow_analysis(**node_it); 78 | 79 | else_result = combine_sequential_flow(else_result, next_result); 80 | 81 | if (else_result == Returns) { 82 | nodes.erase(std::next(node_it), nodes.end()); 83 | return Returns; 84 | } 85 | } 86 | return else_result; 87 | } 88 | } 89 | } 90 | _default {} // do nothing, this function wont return in this current statement 91 | } 92 | return ControlFlowResult::NoReturn; 93 | } 94 | -------------------------------------------------------------------------------- /src/utils/map-macro.hpp: -------------------------------------------------------------------------------- 1 | // clang-format off 2 | 3 | // map-macro implementation is from: 4 | // https://github.com/swansontec/map-macro 5 | 6 | /* 7 | * map-macro by William R Swanson is marked with CC0 1.0 Universal. 8 | * 9 | * To view a copy of this license, 10 | * visit https://creativecommons.org/publicdomain/zero/1.0/ 11 | */ 12 | 13 | #pragma once 14 | 15 | #define EVAL0(...) __VA_ARGS__ 16 | #define EVAL1(...) EVAL0(EVAL0(EVAL0(__VA_ARGS__))) 17 | #define EVAL2(...) EVAL1(EVAL1(EVAL1(__VA_ARGS__))) 18 | #define EVAL3(...) EVAL2(EVAL2(EVAL2(__VA_ARGS__))) 19 | #define EVAL4(...) EVAL3(EVAL3(EVAL3(__VA_ARGS__))) 20 | #define EVAL5(...) EVAL4(EVAL4(EVAL4(__VA_ARGS__))) 21 | 22 | #ifdef _MSC_VER 23 | // MSVC needs more evaluations 24 | #define EVAL6(...) EVAL5(EVAL5(EVAL5(__VA_ARGS__))) 25 | #define EVAL(...) EVAL6(EVAL6(__VA_ARGS__)) 26 | #else 27 | #define EVAL(...) EVAL5(__VA_ARGS__) 28 | #endif 29 | 30 | #define MAP_END(...) 31 | #define MAP_OUT 32 | 33 | #define EMPTY() 34 | #define DEFER(id) id EMPTY() 35 | 36 | #define MAP_GET_END2() 0, MAP_END 37 | #define MAP_GET_END1(...) MAP_GET_END2 38 | #define MAP_GET_END(...) MAP_GET_END1 39 | #define MAP_NEXT0(test, next, ...) next MAP_OUT 40 | #define MAP_NEXT1(test, next) DEFER ( MAP_NEXT0 ) ( test, next, 0) 41 | #define MAP_NEXT(test, next) MAP_NEXT1(MAP_GET_END test, next) 42 | #define MAP_INC(X) MAP_INC_ ## X 43 | 44 | #define MAP0(f, x, peek, ...) f(x) DEFER ( MAP_NEXT(peek, MAP1) ) ( f, peek, __VA_ARGS__ ) 45 | #define MAP1(f, x, peek, ...) f(x) DEFER ( MAP_NEXT(peek, MAP0) ) ( f, peek, __VA_ARGS__ ) 46 | 47 | // Applies the function macro `f` to each of the remaining parameters. 48 | #define map_macro(f, ...) EVAL(MAP1(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) 49 | 50 | -------------------------------------------------------------------------------- /src/utils/match_construct.hpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | constexpr int type_index(int index = 0) { 6 | if constexpr (std::is_same_v) return index; 7 | else 8 | return type_index(index + 1); 9 | } 10 | template 11 | struct type_sequence { 12 | constexpr type_sequence(const std::variant&) {} 13 | template 14 | constexpr static int idx = type_index(); 15 | }; 16 | 17 | #define match(v) \ 18 | { \ 19 | auto& v___ = v; \ 20 | bool was_default = false; \ 21 | switch (auto t_seq___ = wrapped_type_seq(v); index_of(v)) { 22 | 23 | #define holds(T, ...) \ 24 | break; \ 25 | } \ 26 | case t_seq___.idx::type>::type>: { \ 27 | __VA_OPT__(T __VA_ARGS__ = \ 28 | get_elem::type>::type>(v___);) 29 | 30 | #define _default \ 31 | break; \ 32 | } \ 33 | default: \ 34 | was_default = true; \ 35 | } \ 36 | if (was_default) 37 | 38 | template constexpr std::size_t index_of(std::variant& node) { return node.index(); } 39 | template auto& get_elem(std::variant& node) { return std::get(node); } 40 | -------------------------------------------------------------------------------- /src/utils/vimcommands.txt: -------------------------------------------------------------------------------- 1 | arg **/*.cpp 2 | argadd **/*.hpp 3 | argdo %s///ge | update 4 | argd * 5 | 6 | %bd!|e# 7 | -------------------------------------------------------------------------------- /src/utils/zip-macro.hpp: -------------------------------------------------------------------------------- 1 | #include "sequence-for.hpp" 2 | 3 | #define zip_to_macro(step, a, b) SF_FOR_EACH(DETAIL_ZIP_FOREACH_BODY, DETAIL_ZIP_FOREACH_STEP, SF_NULL, (step, b), a) 4 | 5 | #define DETAIL_ZIP_FOREACH_BODY(n, d, ...) DETAIL_ZIP_FOREACH_BODY_1(n, (__VA_ARGS__), IDENTITY d) 6 | #define DETAIL_ZIP_FOREACH_BODY_1(...) DETAIL_ZIP_FOREACH_BODY_2(__VA_ARGS__) 7 | #define DETAIL_ZIP_FOREACH_BODY_2(n, a_elem, step, b) step(a_elem, (DETAIL_ZIP_FOREACH_SEQ_FIRST(b))) 8 | 9 | #define DETAIL_ZIP_FOREACH_STEP(n, d, ...) DETAIL_ZIP_FOREACH_STEP_1(n, IDENTITY d) 10 | #define DETAIL_ZIP_FOREACH_STEP_1(...) DETAIL_ZIP_FOREACH_STEP_2(__VA_ARGS__) 11 | #define DETAIL_ZIP_FOREACH_STEP_2(n, step, b) (step, DETAIL_ZIP_FOREACH_EMPTY b) 12 | 13 | #define DETAIL_ZIP_FOREACH_EMPTY(...) 14 | #define IDENTITY(...) __VA_ARGS__ 15 | 16 | #define DETAIL_ZIP_FOREACH_SEQ_FIRST(...) DETAIL_ZIP_FOREACH_SEQ_FIRST_ __VA_ARGS__ ) 17 | #define DETAIL_ZIP_FOREACH_SEQ_FIRST_(...) __VA_ARGS__ DETAIL_ZIP_FOREACH_EMPTY( 18 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Smart test runner that only recompiles when necessary 4 | 5 | BINARY="test-fumo" 6 | SOURCE="src/compiler_test.cpp" 7 | 8 | # Function to check if recompilation is needed 9 | needs_rebuild() { 10 | # If binary doesn't exist, rebuild 11 | if [ ! -f "$BINARY" ]; then 12 | return 0 13 | fi 14 | 15 | # If source is newer than binary, rebuild 16 | if [ "$SOURCE" -nt "$BINARY" ]; then 17 | return 0 18 | fi 19 | 20 | # Check if any header files in src/ are newer than binary 21 | if find src/ -name "*.h" -o -name "*.hpp" 2>/dev/null | while read header; do 22 | if [ "$header" -nt "$BINARY" ]; then 23 | exit 0 # Found a newer header 24 | fi 25 | done | grep -q .; then 26 | return 0 27 | fi 28 | 29 | # No rebuild needed 30 | return 1 31 | } 32 | 33 | # Check for --force-rebuild flag 34 | force_rebuild=false 35 | args=() 36 | for arg in "$@"; do 37 | if [ "$arg" = "--force-rebuild" ]; then 38 | force_rebuild=true 39 | else 40 | args+=("$arg") 41 | fi 42 | done 43 | 44 | # Rebuild if necessary or forced 45 | if $force_rebuild || needs_rebuild; then 46 | echo "Compiling test runner..." 47 | if ! clang++ -std=c++23 -Wno-ignored-attributes "$SOURCE" -o "$BINARY"; then 48 | echo "Compilation failed!" 49 | exit 1 50 | fi 51 | echo "Compilation successful." 52 | fi 53 | 54 | # Run the test with remaining arguments 55 | exec "./$BINARY" "${args[@]}" 56 | -------------------------------------------------------------------------------- /tests/any-type/any-compare.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | fn main() -> i32 { 4 | let x: i32 = 100; 5 | let y: i32 = 200; 6 | 7 | let any_ptr1: any* = &x; 8 | let any_ptr2: any* = &x; // Same address 9 | let any_ptr3: any* = &y; // Different address 10 | 11 | let int_ptr: i32* = &x; 12 | 13 | // any* == any* 14 | if any_ptr1 == any_ptr2 { 15 | printf("any_ptr1 == any_ptr2: true (same address)\n"); 16 | } 17 | 18 | if any_ptr1 != any_ptr3 { 19 | printf("any_ptr1 != any_ptr3: true (different addresses)\n"); 20 | } 21 | 22 | // any* == typed pointer 23 | if any_ptr1 == int_ptr { 24 | printf("any_ptr1 == int_ptr: true (same address)\n"); 25 | } 26 | 27 | // any* != typed pointer (different addresses) 28 | let int_ptr2: i32* = &y; 29 | if any_ptr1 != int_ptr2 { 30 | printf("any_ptr1 != int_ptr2: true (different addresses)\n"); 31 | } 32 | 33 | // Null comparisons 34 | let null_any: any* = null; 35 | let null_any2: any*; // Initialized to null by default 36 | 37 | if null_any == null { 38 | printf("null any* == null: true\n"); 39 | } 40 | 41 | if null_any == null_any2 { 42 | printf("null any* == null any*: true\n"); 43 | } 44 | 45 | if null_any != any_ptr1 { 46 | printf("null any* != valid any*: true\n"); 47 | } 48 | 49 | // Null check with ! 50 | if !null_any { 51 | printf("!null_any: true (null pointer)\n"); 52 | } 53 | 54 | if !any_ptr1 { 55 | printf("This should not print (any_ptr1 is not null)\n"); 56 | } else { 57 | printf("any_ptr1 is not null\n"); 58 | } 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /tests/any-type/any-functions.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | fn process_any_data(data: any*) -> void { 4 | printf("Processing any* data at address\n"); 5 | // Cannot dereference, but can pass around and compare 6 | } 7 | 8 | fn return_any_ptr(choice: i32) -> any* { 9 | let x: i32 = 42; 10 | let y: f64 = 3.14; 11 | 12 | if choice == 1 { 13 | return &x; // Return i32* as any* 14 | } else if choice == 2 { 15 | return &y; // Return f64* as any* 16 | } else { 17 | return null; // Return null any* 18 | } 19 | } 20 | 21 | fn main() -> i32 { 22 | let num: i32 = 123; 23 | let fnum: f64 = 45.67; 24 | 25 | // Pass different pointer types to any* parameter 26 | process_any_data(&num); // i32* -> any* 27 | process_any_data(&fnum); // f64* -> any* 28 | 29 | // Get any* return value and assign to typed pointers 30 | let any_result: any* = return_any_ptr(1); 31 | let int_result: i32* = return_any_ptr(1); // any* -> i32* 32 | let float_result: f64* = return_any_ptr(2); // any* -> f64* 33 | let null_result: any* = return_any_ptr(99); // Should return null 34 | 35 | if !null_result { 36 | printf("Returned null any* detected\n"); 37 | } 38 | 39 | if any_result { 40 | printf("Non-null any* result confirmed\n"); 41 | } 42 | 43 | printf("Function parameter and return tests completed\n"); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /tests/any-type/any-structs.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | struct Point { 4 | let x: f64; 5 | let y: f64; 6 | } 7 | 8 | struct Person { 9 | let age: i32; 10 | let height: f64; 11 | } 12 | 13 | fn store_any_struct(storage: any*) -> void { 14 | if storage { 15 | printf("Stored non-null struct as any*\n"); 16 | } else { 17 | printf("Received null any* parameter\n"); 18 | } 19 | } 20 | 21 | fn main() -> i32 { 22 | let pt: Point = Point {3.0, 4.0}; 23 | let person: Person = Person {25, 175.5}; 24 | 25 | // Store different struct pointers as any* 26 | let any_storage: any*; 27 | 28 | any_storage = &pt; // Point* -> any* 29 | printf("Point stored as any*\n"); 30 | 31 | any_storage = &person; // Person* -> any* 32 | printf("Person stored as any*\n"); 33 | 34 | // Pass struct pointers to any* parameter 35 | store_any_struct(&pt); 36 | store_any_struct(&person); 37 | store_any_struct(null); // Test null parameter 38 | 39 | // Get back as typed pointers 40 | let point_ptr: Point* = any_storage; // any* -> Point* (wrong, but should compile) 41 | let person_ptr: Person* = any_storage; // any* -> Person* (correct) 42 | 43 | // Test null assignment and checks 44 | any_storage = null; 45 | if !any_storage { 46 | printf("any_storage is now null\n"); 47 | } 48 | 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /tests/any-type/basic-any.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | fn main() -> i32 { 4 | let x: i32 = 42; 5 | let y: f64 = 3.14; 6 | let c: char = 'A'; 7 | 8 | // Basic assignment from typed pointers to any* 9 | let any_ptr: any*; 10 | any_ptr = &x; // i32* -> any* 11 | printf("any_ptr assigned from i32*\n"); 12 | 13 | any_ptr = &y; // f64* -> any* 14 | printf("any_ptr assigned from f64*\n"); 15 | 16 | any_ptr = &c; // char* -> any* 17 | printf("any_ptr assigned from char*\n"); 18 | 19 | // Assignment back to typed pointers 20 | let int_ptr: i32* = any_ptr; // any* -> i32* (should work but be wrong type) 21 | let float_ptr: f64* = any_ptr; // any* -> f64* 22 | let char_ptr: char* = any_ptr; // any* -> char* 23 | 24 | printf("Implicit conversions completed\n"); 25 | 26 | return 0; 27 | } 28 | -------------------------------------------------------------------------------- /tests/any-type/complex-any.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | fn main() -> i32 { 4 | let a: i32 = 10; 5 | let b: f64 = 20.5; 6 | let c: char = 'X'; 7 | 8 | let any1: any*; 9 | let any2: any*; 10 | let any3: any*; 11 | 12 | // Chain assignments through any* 13 | any1 = &a; // i32* -> any* 14 | any2 = any1; // any* -> any* 15 | any3 = any2; // any* -> any* 16 | 17 | // Test null in chains 18 | let null_any: any* = null; 19 | any1 = null_any; // null any* -> any* 20 | any2 = any1; // null any* -> any* 21 | 22 | if !any2 { 23 | printf("Null propagated through assignment chain\n"); 24 | } 25 | 26 | // Reset to valid pointers 27 | any1 = &a; 28 | any2 = any1; 29 | any3 = any2; 30 | 31 | // Convert back through chain 32 | let int_ptr: i32* = any3; // any* -> i32* 33 | let float_ptr: f64* = any2; // any* -> f64* (type mismatch but should compile) 34 | let char_ptr: char* = any1; // any* -> char* (type mismatch but should compile) 35 | 36 | if int_ptr && float_ptr && char_ptr { 37 | printf("All converted pointers are non-null\n"); 38 | } 39 | 40 | printf("Assignment chain completed\n"); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /tests/char-literals/chars.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | fn sscanf(string: char*, format: char*, ...) -> i32; 3 | 4 | fn test_basic_character_literals() -> void { 5 | printf("=== Testing Basic Character Literals ===\n"); 6 | 7 | let letter: char = 'A'; 8 | let digit: char = '4'; 9 | let lowercase: char = 'z'; 10 | let space_char: char = ' '; 11 | 12 | printf("Digit '4' = %d (ASCII value)\n", digit); 13 | printf("Letter 'A' = %d (ASCII value)\n", letter); 14 | printf("Letter 'z' = %d (ASCII value)\n", lowercase); 15 | printf("Space ' ' = %d (ASCII value)\n", space_char); 16 | 17 | // Test character output 18 | printf("Characters: %c %c %c '%c'\n", digit, letter, lowercase, space_char); 19 | } 20 | 21 | fn test_special_character_literals() -> void { 22 | printf("\n=== Testing Special Character Literals ===\n"); 23 | 24 | let newline: char = '\n'; 25 | let tab: char = '\t'; 26 | let carriage_return: char = '\r'; 27 | let backslash: char = '\\'; 28 | let single_quote: char = '\''; 29 | let null_char: char = '\0'; 30 | 31 | printf("Newline ASCII: %d\n", newline); 32 | printf("Tab ASCII: %d\n", tab); 33 | printf("Carriage return ASCII: %d\n", carriage_return); 34 | printf("Backslash ASCII: %d\n", backslash); 35 | printf("Single quote ASCII: %d\n", single_quote); 36 | printf("Null character ASCII: %d\n", null_char); 37 | 38 | printf("Tab test:\tAfter tab\n"); 39 | printf("Backslash test: %c\n", backslash); 40 | printf("Quote test: %c\n", single_quote); 41 | } 42 | 43 | fn test_punctuation_characters() -> void { 44 | printf("\n=== Testing Punctuation Character Literals ===\n"); 45 | 46 | let exclamation: char = '!'; 47 | let question: char = '?'; 48 | let semicolon: char = ';'; 49 | let comma: char = ','; 50 | let period: char = '.'; 51 | let colon: char = ':'; 52 | 53 | printf("Punctuation: %c%c%c %c%c%c\n", 54 | exclamation, question, period, comma, semicolon, colon); 55 | printf("ASCII values: %d %d %d %d %d %d\n", 56 | exclamation, question, period, comma, semicolon, colon); 57 | } 58 | 59 | fn test_bracket_characters() -> void { 60 | printf("\n=== Testing Bracket Character Literals ===\n"); 61 | 62 | let open_paren: char = '('; 63 | let close_paren: char = ')'; 64 | let open_bracket: char = '['; 65 | let close_bracket: char = ']'; 66 | let open_brace: char = '{'; 67 | let close_brace: char = '}'; 68 | 69 | printf("Brackets: %c%c %c%c %c%c\n", 70 | open_paren, close_paren, 71 | open_bracket, close_bracket, 72 | open_brace, close_brace); 73 | 74 | printf("ASCII values: %d %d %d %d %d %d\n", 75 | open_paren, close_paren, 76 | open_bracket, close_bracket, 77 | open_brace, close_brace); 78 | } 79 | 80 | fn test_operator_characters() -> void { 81 | printf("\n=== Testing Operator Character Literals ===\n"); 82 | 83 | let plus: char = '+'; 84 | let minus: char = '-'; 85 | let multiply: char = '*'; 86 | let divide: char = '/'; 87 | let modulo: char = '%'; 88 | let equals: char = '='; 89 | let less_than: char = '<'; 90 | let greater_than: char = '>'; 91 | 92 | printf("Operators: %c%c%c%c%c%c%c%c\n", 93 | plus, minus, multiply, divide, modulo, equals, less_than, greater_than); 94 | 95 | printf("ASCII values: %d %d %d %d %d %d %d %d\n", 96 | plus, minus, multiply, divide, modulo, equals, less_than, greater_than); 97 | } 98 | 99 | fn test_character_arithmetic() -> void { 100 | printf("\n=== Testing Character Arithmetic ===\n"); 101 | 102 | let char_a: char = 'A'; 103 | let char_z: char = 'Z'; 104 | let digit_0: char = '0'; 105 | let digit_9: char = '9'; 106 | 107 | printf("'A' to 'Z' range: %d to %d (difference: %d)\n", 108 | char_a, char_z, char_z - char_a); 109 | printf("'0' to '9' range: %d to %d (difference: %d)\n", 110 | digit_0, digit_9, digit_9 - digit_0); 111 | 112 | // Convert uppercase to lowercase 113 | let lowercase_a: char = char_a + 32; 114 | printf("'A' + 32 = %c (ASCII %d)\n", lowercase_a, lowercase_a); 115 | 116 | // Test digit to number conversion 117 | let digit_value: i32 = digit_9 - digit_0; 118 | printf("'9' - '0' = %d\n", digit_value); 119 | } 120 | 121 | fn test_character_comparison() -> void { 122 | printf("\n=== Testing Character Comparison ===\n"); 123 | 124 | let char1: char = 'A'; 125 | let char2: char = 'B'; 126 | let char3: char = 'A'; 127 | 128 | if char1 == char3 { 129 | printf("'%c' equals '%c' ✓\n", char1, char3); 130 | } 131 | 132 | if char1 < char2 { 133 | printf("'%c' is less than '%c' ✓\n", char1, char2); 134 | } 135 | 136 | if char2 > char1 { 137 | printf("'%c' is greater than '%c' ✓\n", char2, char1); 138 | } 139 | 140 | let digit1: char = '5'; 141 | let digit2: char = '7'; 142 | 143 | if digit1 < digit2 { 144 | printf("'%c' is less than '%c' ✓\n", digit1, digit2); 145 | } 146 | } 147 | 148 | 149 | fn test_sscanf_with_characters() -> void { 150 | printf("\n=== Testing sscanf with Characters ===\n"); 151 | 152 | let input: char* = "X 5 @"; 153 | let char1: char; 154 | let char2: char; 155 | let char3: char; 156 | 157 | let result: i32 = sscanf(input, "%c %c %c", &char1, &char2, &char3); 158 | printf("Parsed %d characters from '%s'\n", result, input); 159 | printf("Characters: '%c' '%c' '%c'\n", char1, char2, char3); 160 | printf("ASCII values: %d %d %d\n", char1, char2, char3); 161 | } 162 | 163 | fn test_edge_cases() -> void { 164 | printf("\n=== Testing Edge Cases ===\n"); 165 | 166 | // Test minimum and maximum char values 167 | let min_char: char = 0; 168 | let max_printable: char = 126; // '~' 169 | 170 | printf("Minimum char value: %d\n", min_char); 171 | printf("Maximum printable ASCII: %d ('%c')\n", max_printable, max_printable); 172 | 173 | // Test some non-printable characters 174 | let bell: char = '\a'; // Alert/bell 175 | let form_feed: char = '\f'; 176 | let vertical_tab: char = '\v'; 177 | 178 | printf("Bell character ASCII: %d\n", bell); 179 | printf("Form feed ASCII: %d\n", form_feed); 180 | printf("Vertical tab ASCII: %d\n", vertical_tab); 181 | } 182 | 183 | fn main() -> i32 { 184 | printf("Character Literal Tests for Fumo Language\n"); 185 | printf("=========================================\n"); 186 | 187 | test_basic_character_literals(); 188 | test_special_character_literals(); 189 | test_punctuation_characters(); 190 | test_bracket_characters(); 191 | test_operator_characters(); 192 | test_character_arithmetic(); 193 | test_character_comparison(); 194 | test_sscanf_with_characters(); 195 | test_edge_cases(); 196 | 197 | printf("\n=== All Character Literal Tests Complete ===\n"); 198 | return 0; 199 | } 200 | 201 | -------------------------------------------------------------------------------- /tests/control-flow/accept-returns.fm: -------------------------------------------------------------------------------- 1 | fn foo() -> void { 2 | return; 3 | return; 4 | let x: i32; 5 | } 6 | 7 | fn main() -> void { } 8 | -------------------------------------------------------------------------------- /tests/control-flow/flow1.fm: -------------------------------------------------------------------------------- 1 | // If statement tests 2 | fn test_if_else_both_return(x: i32) -> i32 { 3 | if x > 0 { 4 | return 1; 5 | } 6 | else { 7 | return 0; 8 | } 9 | } 10 | 11 | fn test_if_no_else_return_after(x: i32) -> i32 { 12 | if x > 0 { 13 | let y = 1; 14 | } 15 | return 42; 16 | } 17 | -------------------------------------------------------------------------------- /tests/control-flow/flow2.fm: -------------------------------------------------------------------------------- 1 | // Else-if chain tests 2 | fn test_else_if_all_return(x: i32) -> i32 { 3 | if x > 0 { 4 | return 1; 5 | } 6 | else if x < 0 { 7 | return -1; 8 | } 9 | else { 10 | return 0; 11 | } 12 | } 13 | 14 | fn test_else_if_return_after(x: i32) -> i32 { 15 | if x > 100 { 16 | return 100; 17 | } 18 | else if x > 50 { 19 | let y = x; 20 | } 21 | return x; 22 | } 23 | -------------------------------------------------------------------------------- /tests/control-flow/flow3.fm: -------------------------------------------------------------------------------- 1 | // // Sequential tests 2 | // fn test_sequential_return_after(x: i32) -> i32 { 3 | // if x > 0 { 4 | // let y = x * 2; 5 | // } 6 | // 7 | // let z = 42; 8 | // return z; 9 | // } 10 | // Nested if tests 11 | fn test_nested_if_all_return(x: i32, y: i32) -> i32 { 12 | if x > 0 { 13 | if y > 0 { 14 | return 1; 15 | } else { 16 | return 2; 17 | } 18 | } else { 19 | return 0; 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/control-flow/flow4.fm: -------------------------------------------------------------------------------- 1 | fn test_early_return_unreachable(x: i32) -> i32 { 2 | return 42; 3 | let y = 100; // Unreachable 4 | if x > 0 { 5 | return 1; 6 | } 7 | } 8 | 9 | // Void function tests 10 | fn test_void_conditional_return(x: i32) -> void { 11 | return; 12 | } 13 | -------------------------------------------------------------------------------- /tests/control-flow/flow5.fm: -------------------------------------------------------------------------------- 1 | fn test_void_early_return(x: i32) -> void { 2 | if x < 0 { 3 | return; 4 | } 5 | 6 | let y = x * 2; 7 | } 8 | 9 | // Edge cases 10 | fn test_multiple_returns() -> i32 { 11 | return 1; 12 | return 2; // Unreachable 13 | return 3; // Unreachable 14 | } 15 | 16 | fn test_deep_nesting(a: i32, b: i32, c: i32) -> i32 { 17 | if a > 0 { 18 | if b > 0 { 19 | if c > 0 { 20 | return 1; 21 | } 22 | else { 23 | return 2; 24 | } 25 | } 26 | else { 27 | return 3; 28 | } 29 | } 30 | else { 31 | return 4; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/control-flow/if-else-flow.fm: -------------------------------------------------------------------------------- 1 | // Control Flow Analysis Tests - These should all PASS 2 | 3 | // Basic return tests 4 | fn test_simple_return() -> i32 { 5 | return 42; 6 | } 7 | 8 | fn test_void_no_return() -> void { 9 | let x = 42; 10 | } 11 | 12 | fn test_void_with_return() -> void { 13 | return; 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /tests/fail-any-type/any-arithmetic.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: i8*, ...) -> i32; 2 | 3 | fn main() -> i32 { 4 | let any_ptr: any*; 5 | 6 | // This should FAIL - cannot do arithmetic on any* 7 | any_ptr = any_ptr + 1; // ERROR: can't do arithmetic on any* 8 | 9 | return 0; 10 | } 11 | -------------------------------------------------------------------------------- /tests/fail-any-type/any-member.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: i8*, ...) -> i32; 2 | 3 | struct Point { 4 | let x: f64; 5 | let y: f64; 6 | } 7 | 8 | fn main() -> i32 { 9 | let pt: Point = Point {3.0, 4.0}; 10 | let any_ptr: any* = &pt; 11 | 12 | // This should FAIL - cannot access members through any* 13 | let x_val: f64 = any_ptr->x; // ERROR: can't access members of any* 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /tests/fail-any-type/deref.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | fn main() -> i32 { 4 | let x: i32 = 42; 5 | let any_ptr: any* = &x; 6 | 7 | // This should FAIL - cannot dereference any* 8 | let value: i32 = *any_ptr; // ERROR: can't dereference any* 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail1.fm: -------------------------------------------------------------------------------- 1 | // Basic missing return 2 | fn test_missing_return() -> i32 { 3 | let x = 42; 4 | } 5 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail10.fm: -------------------------------------------------------------------------------- 1 | fn test_deep_nesting_incomplete(a: i32, b: i32, c: i32) -> i32 { 2 | if a > 0 { 3 | if b > 0 { 4 | if c > 0 { 5 | return 1; 6 | } 7 | // Missing else for innermost if 8 | } 9 | else { 10 | return 3; 11 | } 12 | } 13 | else { 14 | return 4; 15 | } 16 | // Path: a > 0 && b > 0 && c <= 0 has no return 17 | } 18 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail11.fm: -------------------------------------------------------------------------------- 1 | // Sequential failures 2 | fn test_sequential_conditional_no_final(x: i32) -> i32 { 3 | if x > 0 { 4 | return x; 5 | } 6 | 7 | let y = 42; // No return after this 8 | } 9 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail2.fm: -------------------------------------------------------------------------------- 1 | // If statement failures 2 | fn test_if_only_returns(x: i32) -> i32 { 3 | if x > 0 { 4 | return 1; 5 | } 6 | // Missing return here 7 | } 8 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail3.fm: -------------------------------------------------------------------------------- 1 | fn test_else_only_returns(x: i32) -> i32 { 2 | if x > 0 { 3 | let y = 1; 4 | } 5 | else { 6 | return 0; 7 | } 8 | // Missing return here 9 | } 10 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail4.fm: -------------------------------------------------------------------------------- 1 | fn test_if_no_else_no_return(x: i32) -> i32 { 2 | if x > 0 { 3 | return 1; 4 | } 5 | // Missing return - condition might be false 6 | } 7 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail5.fm: -------------------------------------------------------------------------------- 1 | // Else-if chain failures 2 | fn test_else_if_middle_missing(x: i32) -> i32 { 3 | if x > 10 { 4 | return 1; 5 | } 6 | else if x > 0 { 7 | let y = x; // No return! 8 | } 9 | else { 10 | return 0; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail6.fm: -------------------------------------------------------------------------------- 1 | fn test_else_if_no_final_else(x: i32) -> i32 { 2 | if x > 0 { 3 | return 1; 4 | } 5 | else if x < 0 { 6 | return -1; 7 | } 8 | // Missing final else or return 9 | } 10 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail7.fm: -------------------------------------------------------------------------------- 1 | fn test_incomplete_else_if_chain(x: i32) -> i32 { 2 | if x == 1 { 3 | return 1; 4 | } 5 | else if x == 2 { 6 | return 2; 7 | } 8 | else if x == 3 { 9 | return 3; 10 | } 11 | // What if x is 4? No return path 12 | } 13 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail8.fm: -------------------------------------------------------------------------------- 1 | // Nested if failures 2 | fn test_nested_if_inner_incomplete(x: i32, y: i32) -> i32 { 3 | if x > 0 { 4 | if y > 0 { 5 | return 1; 6 | } 7 | // Missing else for inner if 8 | } 9 | else { 10 | return 0; 11 | } 12 | // Path where x > 0 && y <= 0 doesn't return 13 | } 14 | -------------------------------------------------------------------------------- /tests/fail-control-flow/fail9.fm: -------------------------------------------------------------------------------- 1 | fn test_nested_if_outer_incomplete(x: i32, y: i32) -> i32 { 2 | if x > 0 { 3 | if y > 0 { 4 | return 1; 5 | } 6 | else { 7 | return 2; 8 | } 9 | } 10 | // Missing else for outer if 11 | } 12 | -------------------------------------------------------------------------------- /tests/fail-control-flow/returnfail.fm: -------------------------------------------------------------------------------- 1 | fn foo() -> i32 { 2 | return; 3 | } 4 | 5 | fn main() -> void { } 6 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail1.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 1: Assignment to initializer list (your main concern) 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | struct OtherStruct { 8 | let x: i32; 9 | let y: i32; 10 | } 11 | 12 | struct ThirdStruct { 13 | let p: i32; 14 | let q: i32; 15 | } 16 | 17 | fn func() -> i32 { return 0; } 18 | 19 | fn main() -> i32 { 20 | let foo: SomeStruct; 21 | let y: SomeStruct; 22 | let x: i32; 23 | let a: i32; 24 | let obj: SomeStruct; 25 | let arr: SomeStruct; 26 | let bar: i32; 27 | 28 | // This should fail - assignment to initializer list 29 | foo {213, 123} = {213, 21321}; 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail10.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 10: Initializer with missing closing brace 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | fn main() -> i32 { 8 | // This should fail - missing closing brace 9 | let x = SomeStruct {1, 2; 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail2.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 2: Initializer list without type name 2 | fn main() -> i32 { 3 | let x = {1, 2, 3}; 4 | return 0; 5 | } 6 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail3.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 3: Assignment chain with initializer 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | fn main() -> i32 { 8 | let x: SomeStruct; 9 | let y: SomeStruct; 10 | 11 | // This should fail - chained assignment with initializer 12 | let x = y = SomeStruct {1, 2}; 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail4.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 4: Initializer in binary expression 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | fn main() -> i32 { 8 | // This should fail - initializers in binary expression 9 | let result = SomeStruct {1, 2} + SomeStruct {3, 4}; 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail5.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 5: Function call followed by initializer (ambiguous) 2 | fn func() -> i32 { return 0; } 3 | 4 | fn main() -> i32 { 5 | // This should fail - function call followed by braces 6 | func() {1, 2, 3}; 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail6.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 6: Member access followed by initializer 2 | struct SomeStruct { 3 | let member: i32; 4 | } 5 | 6 | fn main() -> i32 { 7 | let obj: SomeStruct; 8 | 9 | // This should fail - member access followed by braces 10 | obj.member {1, 2, 3}; 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail7.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 7: Nested assignment with initializers 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | struct OtherStruct { 8 | let x: i32; 9 | let y: i32; 10 | } 11 | 12 | fn main() -> i32 { 13 | let x: SomeStruct; 14 | let y: SomeStruct; 15 | 16 | // This should fail - complex nested assignment with initializers 17 | let x = (y = SomeStruct {1, 2}) = OtherStruct {3, 4}; 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail8.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 8: Initializer as function argument in assignment 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | fn func(s: SomeStruct) -> i32 { return 0; } 8 | 9 | fn main() -> i32 { 10 | // This should fail - function call with initializer assigned to 11 | func(SomeStruct {1, 2}) = 5; 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/fail-init-lists/fail9.fm: -------------------------------------------------------------------------------- 1 | // FAIL TEST 9: Chained initializer assignments 2 | struct SomeStruct { 3 | let a: i32; 4 | let b: i32; 5 | } 6 | 7 | struct OtherStruct { 8 | let x: i32; 9 | let y: i32; 10 | } 11 | 12 | struct ThirdStruct { 13 | let p: i32; 14 | let q: i32; 15 | } 16 | 17 | fn main() -> i32 { 18 | // This should fail - chained assignments with initializers 19 | SomeStruct {1, 2} = OtherStruct {3, 4} = ThirdStruct {5, 6}; 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/fail-init-lists/toomanyelems.fm: -------------------------------------------------------------------------------- 1 | struct Foo { 2 | let a: i32; 3 | } 4 | 5 | fn main() -> void { 6 | let bar = Foo { 0, 0 }; 7 | } 8 | -------------------------------------------------------------------------------- /tests/fail-static-functions/failstatic1.fm: -------------------------------------------------------------------------------- 1 | namespace test { 2 | struct Stateful { 3 | let state: i32; 4 | 5 | fn static bad_modify() -> void { 6 | state = 100; // ERROR: Cannot modify instance member from static function 7 | } 8 | } 9 | } 10 | 11 | fn main() -> i32 { 12 | return 0; 13 | } 14 | namespace test { 15 | struct Problematic { 16 | let member: f64; 17 | 18 | fn static problematic_static() -> f64 { 19 | return member; // ERROR: Implicit 'this' access in static function 20 | } 21 | } 22 | } 23 | 24 | fn main() -> i32 { 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/generics/box.fm: -------------------------------------------------------------------------------- 1 | namespace Foo[T] { 2 | struct Box { 3 | let value: T; 4 | let x: i32; 5 | 6 | fn get() -> T { 7 | return value; 8 | } 9 | 10 | fn set(new_value: T) -> void { 11 | value = new_value; 12 | } 13 | 14 | fn static make(val: T) -> Box[T] { 15 | return Box[T]{val}; 16 | } 17 | } 18 | } 19 | 20 | fn main() -> i32 { 21 | let int_box = Box[i32]::make(42); 22 | let float_box = Box[f64]::make(3.14); 23 | 24 | int_box.set(100); 25 | let result = int_box.get(); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/generics/complete-example.fm: -------------------------------------------------------------------------------- 1 | 2 | struct Foo { 3 | let data: i32; 4 | } 5 | 6 | struct Outer[T] { 7 | let data: T; 8 | struct Inner[U] { 9 | let other: U; 10 | fn static func[V](param: i32) -> V; 11 | } 12 | } 13 | fn static Outer[T]::Inner[U]::func[V](param: i32) -> V { 14 | let var = Outer[T]{}; 15 | return V{param}; 16 | } 17 | fn main() -> void { 18 | // let var: Outer[i32]; 19 | // let hm = Outer[f64]{123.0}; 20 | let ehh = Outer[i32]::Inner[f64]::func[Foo](69); 21 | } 22 | -------------------------------------------------------------------------------- /tests/generics/complex.fm: -------------------------------------------------------------------------------- 1 | 2 | struct Container[T] { 3 | let value: T; 4 | fn get() -> T { return value; } 5 | fn static make(v: T) -> Container[T]; 6 | } 7 | 8 | fn static Container[T]::make(v: T) -> Container[T] { return Container[T]{v}; } 9 | 10 | struct Pair[T, U] { 11 | let first: T; 12 | let second: U; 13 | 14 | fn get_first() -> T { return first; } 15 | fn get_second() -> U { return second; } 16 | fn static make(a: T, b: U) -> Pair[T, U]; 17 | } 18 | 19 | fn static Pair[T, U]::make(a: T, b: U) -> Pair[T, U] { return Pair[T, U]{a, b}; } 20 | 21 | fn process_nested[T](data: T) -> Container[Pair[T, Container[i32]]] { 22 | let inner_container = Container[i32]::make(42); 23 | let pair = Pair[T, Container[i32]]::make(data, inner_container); 24 | return Container[Pair[T, Container[i32]]]::make(pair); 25 | } 26 | 27 | fn combine[T, U](a: T, b: U) -> Pair[Container[T], Container[U]] { 28 | // Both containers use generic types 29 | let wrapped_a = Container[T]::make(a); 30 | let wrapped_b = Container[U]::make(b); 31 | let var:i32 = T::make(a); 32 | // Concrete instantiation inside generic function 33 | let debug_container = Container[i32]::make(999); // Should instantiate eagerly 34 | return Pair[Container[T], Container[U]]::make[T](wrapped_a, wrapped_b); 35 | } 36 | 37 | fn main() -> void { 38 | let var: Container[f64]; 39 | let foo = Container[f64]{1312.213}; 40 | 41 | let simple = Container[i32]::make(10); 42 | let complex = process_nested[f64](3.14); 43 | let combined = combine[i32, f64](42, 2.71); 44 | let inner_pair = complex.get(); 45 | let original_float = inner_pair.get_first(); 46 | let nested_container = inner_pair.get_second(); 47 | let inner_int = nested_container.get(); 48 | } 49 | -------------------------------------------------------------------------------- /tests/generics/container.fm: -------------------------------------------------------------------------------- 1 | struct Foo {}; 2 | 3 | struct Container[T] { 4 | let data: T*; 5 | let var: Vec[Foo]; // is this an instantion or a declaration 6 | 7 | fn get() -> T { return *data; } 8 | fn set(value: T) -> void { *data = value; } 9 | fn static new(value: T) -> Container[T] { 10 | let ptr: T* = malloc(sizeof(T)); 11 | *ptr = value; 12 | return Container[T]{ptr}; 13 | } 14 | } 15 | 16 | fn main() -> i32 { 17 | let simple_container = Container[i32]::new(42); 18 | let nested_container = Container[Container[i32]]::new(simple_container); 19 | 20 | let inner_value = nested_container.get().get(); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /tests/generics/fail-self-ref.fm: -------------------------------------------------------------------------------- 1 | struct Node[T] { 2 | let data: T; 3 | let next: Node[Node[T]]*; // is not generic type name, "Node[{}]" is solvable 4 | 5 | fn get_data() -> T { 6 | return data; 7 | } 8 | 9 | // push generic context(); becomes T and U 10 | fn set_next[U](next_node: Node[U]*) -> void { 11 | next = next_node; // NOTE: should error but isnt because its not finding it as Node[U] 12 | // should error 13 | let var: Node[Node[T]]; 14 | let b: Node[Node[U]] = var; // FIXME: this should error but doesn't 15 | } 16 | 17 | fn static make(value: T) -> Node[T] { 18 | return Node[T]{value, null}; 19 | } 20 | } 21 | 22 | // solve every identifier, unless they are a generic_type_name 23 | fn create_chain[Node[T]](first: T, second: T) -> T { 24 | // only T in generic context 25 | let head = Node[T]::make(first); 26 | let tail = Node[T]::make(second); // no typechecking 27 | 28 | let int_node = Node[i32]::make(42); // no typechecking? 29 | 30 | let e: T; e = 123; // what to do here? 31 | int_node = head; // what now? 32 | head.set_next(&int_node); // and now? 33 | head.set_next(&tail); 34 | return head; 35 | } 36 | 37 | fn main() -> i32 { 38 | // no generic context 39 | let int_node = Node[i32]::make(42); 40 | let float_node = Node[f64]::make(3.14); 41 | 42 | let chain = create_chain[i32](1, 2); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /tests/generics/functions.fm: -------------------------------------------------------------------------------- 1 | struct Point[T] { 2 | let x: T; 3 | let y: T; 4 | 5 | fn static make(x_val: T, y_val: T) -> Point[T] { 6 | return Point[T]{x_val, y_val}; 7 | } 8 | } 9 | 10 | fn distance_squared[T](p1: Point[T], p2: Point[T]) -> T { 11 | let dx: T = p1.x - p2.x; 12 | let dy: T = p1.y - p2.y; 13 | return dx * dx + dy * dy; 14 | } 15 | 16 | fn create_point[T](value: T) -> Point[T] { 17 | return Point[T]::make(value, value); 18 | } 19 | 20 | fn main() -> i32 { 21 | let p1 = Point[i32]::make(10, 20); 22 | let p2 = Point[i32]::make(5, 15); 23 | let dist = distance_squared[i32](p1, p2); 24 | 25 | let origin = create_point[f64](0.0); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /tests/generics/multiple-types.fm: -------------------------------------------------------------------------------- 1 | struct Pair[T, U] { 2 | let first: T; 3 | let second: U; 4 | 5 | fn static make(a: T, b: U) -> Pair[T, U] { 6 | return Pair[T, U]{a, b}; 7 | } 8 | } 9 | 10 | struct Triple[T, U, V] { 11 | let first: T; 12 | let second: U; 13 | let third: V; 14 | 15 | fn rotate_left() -> Triple[U, V, T]; 16 | fn to_pair_first() -> Pair[T, U]; 17 | fn static make(a: T, b: U, c: V) -> Triple[T, U, V]; 18 | } 19 | 20 | fn Triple[T, U, V]::rotate_left() -> Triple[U, V, T] { 21 | return Triple[U, V, T]{second, third, first}; 22 | } 23 | 24 | fn Triple[T, U, V]::to_pair_first() -> Pair[T, U] { 25 | return Pair[T, U]::make(first, second); 26 | } 27 | 28 | fn Triple[T, U, V]::make(a: T, b: U, c: V) -> Triple[T, U, V] { 29 | return Triple[T, U, V]{a, b, c}; 30 | } 31 | 32 | fn main() -> i32 { 33 | let triple = Triple[i32, f64, char]::make(42, 3.14, 'A'); 34 | let rotated = triple.rotate_left(); 35 | let pair = triple.to_pair_first(); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /tests/generics/namespaced.fm: -------------------------------------------------------------------------------- 1 | namespace math { 2 | struct Vector[T] { 3 | let x: T; 4 | let y: T; 5 | 6 | fn magnitude_squared() -> T { 7 | return x * x + y * y; 8 | } 9 | 10 | fn static make(x_val: T, y_val: T) -> Vector[T] { 11 | return Vector[T]{x_val, y_val}; 12 | } 13 | } 14 | 15 | fn add[T](a: Vector[T], b: Vector[T]) -> Vector[T] { 16 | return Vector[T]::make(a.x + b.x, a.y + b.y); 17 | } 18 | } 19 | 20 | fn main() -> i32 { 21 | let v1 = math::Vector[f64]::make(1.0, 2.0); 22 | let v2 = math::Vector[f64]::make(3.0, 4.0); 23 | let sum = math::add[f64](v1, v2); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/generics/pair.fm: -------------------------------------------------------------------------------- 1 | struct Container[T] { 2 | let value: T; 3 | fn get() -> T { return value; } 4 | fn static make(v: T) -> Container[T]; 5 | } 6 | fn static Container[T]::make(v: T) -> Container[T] { return Container[T]{v}; } 7 | 8 | struct Pair[T, U] { 9 | let first: T; 10 | let second: U; 11 | 12 | fn static make(a: T, b: U) -> Pair[T, U]; 13 | } 14 | fn Pair[T, U]::make(a: T, b: U) -> Pair[T, U] { 15 | return Pair[T, U]{a, b}; 16 | } 17 | 18 | fn main() -> void { 19 | let nested = 20 | Container[Pair[i32, Container[f64]]]::make(Pair[i32, 21 | Container[f64]]::make(42, Container[f64]::make(3.14))); 22 | let inner_pair = nested.get(); 23 | let wrapped_float = inner_pair.second; 24 | let actual_float = wrapped_float.get(); 25 | } 26 | -------------------------------------------------------------------------------- /tests/generics/self-reference.fm: -------------------------------------------------------------------------------- 1 | struct Node[T] { 2 | let data: T; 3 | let next: Node[T]*; // is not generic type name, "Node[{}]" is solvable 4 | 5 | fn get_data() -> T { 6 | return data; 7 | } 8 | 9 | fn set_next(next_node: Node[T]*) -> void { 10 | next = next_node; 11 | } 12 | 13 | fn static make(value: T) -> Node[T] { 14 | return Node[T]{value, null}; 15 | } 16 | } 17 | 18 | // solve every identifier, unless they are a generic_type_name 19 | fn create_chain[T](first: T, second: T) -> T { 20 | let head = Node[T]::make(first); // no typechecking 21 | let tail = Node[Foo]::make(second); // no typechecking 22 | 23 | let int_no = Node[i32]{23}; // no typechecking? 24 | let int_no = Foo[i32, T]{23}; // this is partially generic,what to do? 25 | 26 | let int_node = Node[i32]::make(42); // no typechecking? 27 | 28 | int_node = head; // what now? 29 | head.set_next(&int_node); // and now? 30 | head.set_next(&tail); 31 | return head; 32 | } 33 | 34 | fn main() -> i32 { 35 | let int_node = Node[i32]::make(42); 36 | let float_node = Node[f64]::make(3.14); 37 | 38 | let chain = create_chain[i32](1, 2); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /tests/generics/simple.fm: -------------------------------------------------------------------------------- 1 | fn identity[T](x: T) -> T { 2 | return x; 3 | } 4 | 5 | fn swap[T](a: T*, b: T*) -> void { 6 | let temp: T = *a; 7 | *a = *b; 8 | *b = temp; 9 | } 10 | 11 | fn max[T](a: T, b: T) -> T { 12 | if a < b { 13 | return a; 14 | } 15 | return b; 16 | } 17 | 18 | fn main() -> i32 { 19 | let x: i32 = identity[i32](42); 20 | let y: f64 = identity[f64](3.14); 21 | 22 | let a: i32 = 10; 23 | let b: i32 = 20; 24 | swap[i32](&a, &b); 25 | 26 | let bigger = max[i32](a, b); 27 | let bigger_float = max[f64](1.5, 2.7); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /tests/if-statements/basic-if-statements/basic-if-else.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = -5; 3 | if (x > 0) { 4 | return 1; 5 | } else { 6 | return 2; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/if-statements/basic-if-statements/basic-if.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = 42; 3 | if (x > 0) { 4 | x = 23; 5 | return 3; 6 | } 7 | return 69; 8 | } 9 | -------------------------------------------------------------------------------- /tests/if-statements/basic-if-statements/if-else-chain.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let grade = 85; 3 | if (grade >= 90) { 4 | return 4; // A 5 | } else if (grade >= 80) { 6 | return 3; // B 7 | } else if (grade >= 70) { 8 | return 2; // C 9 | } else { 10 | return 1; // F 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/if-statements/basic-if-statements/many-basic-ifs.fm: -------------------------------------------------------------------------------- 1 | // Basic if statement parsing tests 2 | // All tests in main() with proper variable declarations 3 | 4 | let x: i32; let y: i32; let z: i32; let result: i32; let a: i32; let b: i32; 5 | let flag: bool = true; let condition: bool = true; let outer_condition: bool = true; let inner_condition: bool = false; 6 | 7 | fn main() -> void { 8 | 9 | // Test 13: Logical AND condition 10 | 11 | if (x > 0) && (y > 0) { 12 | result = 1; 13 | } 14 | // Test 14: Logical OR condition 15 | if x < 0 || y < 0 { 16 | result = -1; 17 | } 18 | // Test 15: Logical NOT condition 19 | if !flag { 20 | result = 0; 21 | } 22 | // Test 16: Parenthesized complex condition 23 | if ((x > 5 && y < 10) || flag) { 24 | result = 42; 25 | } 26 | // Test 17: Multiple statements in if block 27 | if (true) { 28 | x = 1; 29 | y = 2; 30 | z = x + y; 31 | } 32 | // Test 18: Multiple statements in both branches 33 | if (condition) { 34 | a = 1; 35 | b = 2; 36 | } else { 37 | a = 3; 38 | b = 4; 39 | } 40 | // Test 19: Longer else-if chain 41 | if (x == 1) { 42 | result = 1; 43 | } else if (x == 2) { 44 | result = 2; 45 | } else if (x == 3) { 46 | result = 3; 47 | } else { 48 | result = 99; 49 | } 50 | // Test 20: Dangling else case (else should bind to inner if) 51 | if (outer_condition) { 52 | if (inner_condition) { 53 | x = 1; 54 | } else { 55 | x = 2; 56 | } 57 | 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /tests/if-statements/basic-if-statements/nested-if.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = 10; 3 | let y = 20; 4 | 5 | if (x > 0) { 6 | x = 31231; 7 | } else { 8 | if (y > 15) { 9 | return 42; 10 | } else if y > 3 { 11 | if y < 3 { 12 | x = 123; 13 | } 14 | else if x < 3 { 15 | y = 3; 16 | } 17 | } 18 | y = 3; 19 | } 20 | x = 43; 21 | } 22 | -------------------------------------------------------------------------------- /tests/if-statements/complex-conditions/comparison-operators.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = 42; 3 | let y = 42; 4 | let z = 100; 5 | 6 | if (x == y) { 7 | if (x != z) { 8 | if (z > x) { 9 | if (x >= 42) { 10 | if (y <= 50) { 11 | return 1; 12 | } 13 | } 14 | } 15 | } 16 | } 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /tests/if-statements/complex-conditions/fumo_if_test.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | namespace fm { 4 | struct Fumo { 5 | let name: char*; 6 | let squished: i32; 7 | 8 | fn squish(times: i32) -> void { 9 | squished = squished + times; 10 | printf("%sちゃんは%d回squished!\n", name, squished); 11 | } 12 | } 13 | fn pet_fumo(fumo: Fumo*, times: i32) -> void; 14 | } 15 | 16 | fn fm::pet_fumo(fumo: Fumo*, times: i32) -> void { 17 | printf("petting %s %d times...\n", fumo->name, times); 18 | fumo->squish(times); 19 | 20 | if fumo->squished > 100 { 21 | printf("%s is getting very squished!\n", fumo->name); 22 | } 23 | } 24 | 25 | let reimu = fm::Fumo {"Reimu", 66}; 26 | let cirno = fm::Fumo {"Cirno", 418}; 27 | 28 | fn main() -> i32 { 29 | printf("fumo lang example.\n"); 30 | 31 | fm::pet_fumo(&reimu, 3); 32 | fm::pet_fumo(&cirno, 2); 33 | 34 | if (let total_pats: i32 = reimu.squished + cirno.squished) { 35 | printf("\ntotal pats across all fumos: %d\n", total_pats); 36 | if total_pats > 500 { 37 | printf("that's a lot of pats!\n"); 38 | } else { 39 | printf("need more pats\n"); 40 | } 41 | } 42 | printf("\nfinal pat count:\n"); 43 | printf("Reimu: %d pats\n", reimu.squished); 44 | printf("Cirno: %d pats\n", cirno.squished); 45 | } 46 | -------------------------------------------------------------------------------- /tests/if-statements/complex-conditions/logical-operators.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let a = 5; 3 | let b = 10; 4 | let c = 15; 5 | 6 | if (a < b && b < c) { 7 | return 1; // true 8 | } 9 | if (a > b || c > b) { 10 | return 2; // true 11 | } 12 | if (!(a > b)) { 13 | return 3; // true 14 | } 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /tests/if-statements/complex-conditions/mixed-types.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let int_val = 42; 3 | let float_val = 3.14; 4 | let bool_val = true; 5 | 6 | if (int_val > 40) { 7 | if (float_val > 3.0) { 8 | if (bool_val) { 9 | return 1; 10 | } 11 | } 12 | } 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/if-statements/edge-cases/complex-expression.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let a = 5; 3 | let b = 10; 4 | let c = 15; 5 | let d = 2; 6 | 7 | if (a + b) * c / d - 3 > 100 && !(a == b) || c / 5 == 0 { 8 | return 1; 9 | } 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/if-statements/edge-cases/empty-blocks.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = 42; 3 | 4 | if (x > 0) { 5 | // empty if block 6 | } else { 7 | return 1; 8 | } 9 | 10 | if (x < 0) { 11 | return 2; 12 | } else { 13 | // empty else block 14 | } 15 | 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /tests/if-statements/fail-tests/missing-braces.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | if (true) // missing opening brace 3 | let x = 5; 4 | return x; 5 | } // unmatched closing brace 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/if-statements/fail-tests/missing-condition.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | if () { // missing condition 3 | return 1; 4 | } 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/if-statements/fail-tests/undeclared-var.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | if (undefined_var > 0) { // undefined variable 3 | return 1; 4 | } 5 | return 0; 6 | } 7 | -------------------------------------------------------------------------------- /tests/if-statements/function-calls/function-call-condition.fm: -------------------------------------------------------------------------------- 1 | fn is_positive(x: i32) -> bool { 2 | return x > 0; 3 | } 4 | 5 | fn get_value() -> i64 { 6 | return 42; 7 | } 8 | 9 | fn main() -> i32 { 10 | if (is_positive(get_value())) { 11 | return 1; 12 | } else { 13 | return 0; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/if-statements/function-calls/multiple-func-calls.fm: -------------------------------------------------------------------------------- 1 | fn add(a: i32, b: i32) -> i32 { 2 | return a + b; 3 | } 4 | 5 | fn multiply(a: i32, b: i32) -> i32 { 6 | return a * b; 7 | } 8 | 9 | fn main() -> i32 { 10 | let x = 5; 11 | let y = 3; 12 | 13 | if (add(x, y) > multiply(x, 2)) { 14 | return 1; 15 | } else { 16 | return 0; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/if-statements/namespaces-and-structs/namespace-access.fm: -------------------------------------------------------------------------------- 1 | namespace math { 2 | let pi = 3.14159; 3 | 4 | fn abs(x: i32) -> i32 { 5 | if x < 0 { 6 | return -x; 7 | } 8 | return x; 9 | } 10 | } 11 | 12 | fn main() -> i32 { 13 | let value = -42; 14 | 15 | if math::abs(value) > 40 { 16 | if math::pi > 3.0 { 17 | return 1; 18 | } 19 | } 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /tests/if-statements/namespaces-and-structs/struct-member-access.fm: -------------------------------------------------------------------------------- 1 | struct Point { 2 | let x: i32; 3 | let y: i32; 4 | 5 | fn is_origin() -> bool { 6 | return x == 0 && y == 0; 7 | } 8 | 9 | fn distance_from_origin() -> i32 { 10 | // simplified distance (no sqrt) 11 | return x * x + y * y; 12 | } 13 | } 14 | 15 | fn main() -> i32 { 16 | let p = Point{3, 4}; 17 | 18 | if (!p.is_origin()) { 19 | if (p.distance_from_origin() > 20) { 20 | return 1; 21 | } else { 22 | return 2; 23 | } 24 | } 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /tests/if-statements/variable-scope/shadowing-in-if.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let x = 10; 3 | if (let x = 20) { 4 | if (x > 15) { 5 | let x = 30; // shadows previous x 6 | return x; // should return 30 7 | } 8 | return x; // should return 20 9 | } 10 | return x; // should return 10 11 | } 12 | -------------------------------------------------------------------------------- /tests/if-statements/variable-scope/variable-scope.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let outer = 10; 3 | 4 | if outer > 5 { 5 | let inner = 20; 6 | if inner > outer { 7 | let deeply_nested = 30; 8 | return deeply_nested; 9 | } 10 | return inner; 11 | } 12 | return outer; 13 | } 14 | -------------------------------------------------------------------------------- /tests/if-statements/with-libc/libc-math.fm: -------------------------------------------------------------------------------- 1 | // Forward declare libc functions 2 | fn abs(x: i32) -> i32; 3 | fn fabs(x: f64) -> f64; 4 | 5 | fn main() -> i32 { 6 | let negative = -42; 7 | let float_val = -3.14; 8 | 9 | if (abs(negative) > 40) { 10 | if (fabs(float_val) > 3.0) { 11 | return 1; 12 | } 13 | } 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /tests/if-statements/with-libc/strcmp-libc.fm: -------------------------------------------------------------------------------- 1 | // Forward declare libc functions 2 | fn strcmp(s1: char*, s2: char*) -> i32; 3 | fn strlen(s: char*) -> i64; 4 | 5 | fn main() -> i32 { 6 | let str1 = "hello"; 7 | let str2 = "world"; 8 | let str3 = "hello"; 9 | 10 | let var = strlen(str1); 11 | if (strcmp(str1, str3) == 0) { 12 | if ( var > 3) { 13 | return 1; 14 | } 15 | } 16 | 17 | if (strcmp(str1, str2) != 0) { 18 | return 2; 19 | } 20 | 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/initializer-lists/global-lists.fm: -------------------------------------------------------------------------------- 1 | 2 | struct foo { 3 | let x: i32; 4 | let y: i32; 5 | } 6 | 7 | let x = 123; 8 | let var = foo {2, 3}; 9 | 10 | fn main() -> void { 11 | } 12 | -------------------------------------------------------------------------------- /tests/initializer-lists/valid-initializer-lists.fm: -------------------------------------------------------------------------------- 1 | fn get_value() -> i32 { return 42; } 2 | fn another_func() -> i32 { return 24; } 3 | 4 | struct SomeStruct { 5 | let a: i32; 6 | let b: i32; 7 | } 8 | struct InnerStruct { 9 | let x: i32; 10 | let y: i32; 11 | } 12 | 13 | struct OuterStruct { 14 | let inner: InnerStruct; 15 | let value: i32; 16 | } 17 | 18 | 19 | struct EmptyStruct {} 20 | 21 | struct SingleStruct { 22 | let value: i32; 23 | } 24 | 25 | struct StringStruct { 26 | let s1: char*; 27 | let s2: char*; 28 | } 29 | 30 | struct ExprStruct { 31 | let a: i32; 32 | let b: i32; 33 | } 34 | 35 | struct VarStruct { 36 | let x: i32; 37 | let y: i32; 38 | } 39 | 40 | struct TrailingStruct { 41 | let a: i32; 42 | let b: i32; 43 | let c: i32; 44 | } 45 | 46 | struct NestedStruct { 47 | let inner: InnerStruct; 48 | let value: i32; 49 | } 50 | struct SimpleStruct; 51 | struct MiddleStruct; 52 | 53 | struct ComplexStruct { 54 | let simple: SimpleStruct; 55 | let middle: MiddleStruct; 56 | let value: i32; 57 | } 58 | 59 | struct SimpleStruct { 60 | let a: i32; 61 | let b: i32; 62 | } 63 | 64 | struct MiddleStruct { 65 | let inner: InnerStruct; 66 | let value: i32; 67 | } 68 | 69 | struct FuncStruct { 70 | let a: i32; 71 | let b: i32; 72 | } 73 | 74 | namespace foo { 75 | struct BarStruct { 76 | let x: i32; 77 | let y: i32; 78 | } 79 | 80 | namespace bar { 81 | struct InnerStruct { 82 | let a: i32; 83 | let b: i32; 84 | } 85 | struct DeepStruct { 86 | let inner: InnerStruct; 87 | let value: i32; 88 | } 89 | 90 | } 91 | } 92 | // Valid initializer list tests 93 | fn main() -> i32 { 94 | // Test 1: Basic struct initialization 95 | let s1 = SomeStruct {123, 456}; 96 | 97 | // Test 2: Nested struct initialization 98 | let s2 = OuterStruct {InnerStruct {1, 2}, 3}; 99 | 100 | // Test 3: Empty initializer list 101 | let s3 = EmptyStruct {}; 102 | 103 | // Test 4: Single element initializer 104 | let s4 = SingleStruct {42}; 105 | 106 | // Test 5: Initializer with string literals 107 | let s5 = StringStruct {"hello", "world"}; 108 | 109 | // Test 6: Initializer with expressions 110 | let s6 = ExprStruct {1 + 2, 3 * 4}; 111 | 112 | // Test 7: Initializer with variables 113 | let x = 10; 114 | let y = 20; 115 | let s7 = VarStruct {x, y}; 116 | 117 | // Test 8: Trailing comma (should be allowed) 118 | let s8 = TrailingStruct {1, 2, 3,}; 119 | 120 | // Test 9: Nested initializers with trailing commas 121 | let s9 = NestedStruct { 122 | InnerStruct {1, 2,}, 123 | 3, 124 | }; 125 | 126 | // Test 10: Complex nested structure 127 | let s10 = ComplexStruct { 128 | SimpleStruct {1, 2}, 129 | MiddleStruct { 130 | InnerStruct {3, 4}, 131 | 5 132 | }, 133 | 6 134 | }; 135 | 136 | // Test 11: Function call in initializer 137 | let s11 = FuncStruct {get_value(), another_func()}; 138 | 139 | // Test 12: Qualified type name 140 | let s12 = foo::BarStruct {1, 2}; 141 | 142 | // Test 13: Deeply nested qualified name 143 | let s13 = foo::bar::DeepStruct { 144 | foo::bar::InnerStruct {1, 2}, 145 | 3 146 | }; 147 | 148 | return 0; 149 | } 150 | 151 | -------------------------------------------------------------------------------- /tests/language-examples: -------------------------------------------------------------------------------- 1 | ../docs/language-examples -------------------------------------------------------------------------------- /tests/not_working_old_tests/codegen-tests/codegen_func_calls.fm: -------------------------------------------------------------------------------- 1 | //t( 2 | //"fn func() -> void {}\n" 3 | //"fn main() -> i32 {func();}\n" 4 | //,pass), 5 | //t( 6 | //"fn func() -> i32 {return 213;}\n" 7 | //"fn main() -> i32 {\n" 8 | //" let x = func();\n" 9 | //" return x;\n" 10 | //"}\n" 11 | //,pass), 12 | 13 | //t( 14 | //"fn func(a: i32) -> i32 { return a; }\n" 15 | //"fn main() -> i32 {\n" 16 | //" let x = func(123);\n" 17 | //" return x;\n" 18 | //"}\n" 19 | //,pass), 20 | // 21 | //t( 22 | //"fn main() -> i32 {\n" 23 | //" let x = 69;\n" 24 | //" let y = &x;\n" 25 | //" &x = y;\n" 26 | //" return y;\n" 27 | //"}\n" 28 | //,fail), 29 | // 30 | //t( 31 | //"fn func(a: i32*) -> i32 { \n" 32 | //" return *a; \n" 33 | //"}\n" 34 | //"fn main() -> i32 {\n" 35 | //" let x = 69;\n" 36 | //" let y = func(&x);\n" 37 | //" return y;\n" 38 | //"}\n" 39 | //,pass), 40 | 41 | t( 42 | 43 | "fn main() -> i32 {\n" 44 | " let x = 69;\n" 45 | " let z = &x;\n" 46 | " let y = &z;\n" 47 | " **y = 140;\n" 48 | " let b = **y;\n" 49 | " return b;\n" 50 | "}\n" 51 | 52 | 53 | 54 | 55 | ,pass), 56 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/codegen-tests/simple_llvm_codegen.fm: -------------------------------------------------------------------------------- 1 | t( 2 | "fn func() -> void ;\n" 3 | "let x: i32;\n" 4 | "let y = 123123;\n" 5 | "fn func() -> void {\n" 6 | "}\n" 7 | "fn main() -> i32 { let x = 213;}" 8 | ,pass), 9 | 10 | t( 11 | "let x: i32;\n" 12 | "let y = 123123;\n" 13 | "fn func() -> void;\n" 14 | "fn func() -> void;\n" 15 | "fn func() -> void;\n" 16 | "fn func() -> void {\n" 17 | " let x = 69420;\n" 18 | "}\n" 19 | "fn func() -> void;\n" 20 | "fn func() -> void;\n" 21 | "fn main() -> i32 {return 123;}" 22 | ,pass), 23 | 24 | 25 | 26 | t( 27 | "let y = 6;\n" 28 | "let x: i32;\n" 29 | "fn main() -> i32 {\n" 30 | " let x = 30;\n" 31 | " y = x;\n" 32 | " x = x + 312;\n" 33 | " return 69;\n" 34 | "}\n" 35 | ,pass), 36 | 37 | t( 38 | "let x = 23;\n" 39 | "fn main() -> i32 {\n" 40 | " x = x * 69;\n" 41 | " let y = 150;\n" 42 | " let ptr: i32* = &y;\n" 43 | " return y;\n" 44 | "}\n" 45 | ,pass), 46 | 47 | t( 48 | "let x = 69;\n" 49 | "fn main() -> i32 {\n" 50 | " let ptr: i32* = &x;\n" 51 | " let ptr2: i32** = &ptr;\n" 52 | " let x = **ptr2;\n" 53 | " return x;\n" 54 | "}\n" 55 | ,pass), 56 | 57 | 58 | t( 59 | "let x = 32;\n" 60 | "fn main() -> i32 {\n" 61 | " let y = **(&(&x + 1));\n" 62 | " return y;\n" 63 | "}\n" 64 | ,fail), 65 | 66 | t( 67 | "let x = 32;\n" 68 | "fn main() -> i32 {\n" 69 | " let z = &(x + 1);\n" 70 | " return z;\n" 71 | "}\n" 72 | ,fail), 73 | 74 | t( 75 | "let x = 23;\n" 76 | "let y: i32* = &x;\n" 77 | "fn main() -> i32 {return 123;}" 78 | ,pass), 79 | 80 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/codegen-tests/string_literals.fm: -------------------------------------------------------------------------------- 1 | // Basic string literal assignment and usage 2 | t( 3 | "fn puts(s: char*) -> i32;\n" 4 | "fn main() -> i32 {\n" 5 | " let message: char* = \"Hello, World!\";\n" 6 | " puts(message);\n" 7 | " return 0;\n" 8 | "}\n" 9 | ,pass), 10 | 11 | // String literal with printf 12 | t( 13 | "fn main() -> i32 {\n" 14 | " let name: char* = \"Alice\";\n" 15 | " let age: i32 = 25;\n" 16 | " printf(\"Name: %s, Age: %d\\n\", name, age);\n" 17 | " return 0;\n" 18 | "}\n" 19 | ,pass), 20 | 21 | // String literal with escape sequences 22 | t( 23 | "fn main() -> i32 {\n" 24 | " let text: char* = \"Line 1\\nLine 2\\tTabbed\";\n" 25 | " printf(\"%s\\n\", text);\n" 26 | " return 0;\n" 27 | "}\n" 28 | ,pass), 29 | 30 | // String literal as function parameter 31 | t( 32 | "fn print_greeting(msg: char*) -> void {\n" 33 | " printf(\"Greeting: %s\\n\", msg);\n" 34 | "}\n" 35 | "fn main() -> i32 {\n" 36 | " print_greeting(\"Welcome!\");\n" 37 | " return 0;\n" 38 | "}\n" 39 | ,pass), 40 | 41 | // String literal in struct 42 | t( 43 | "struct person {\n" 44 | " let name: char* = \"Unknown\";\n" 45 | " let age: i32 = 0;\n" 46 | " fn introduce() -> void;\n" 47 | "};\n" 48 | "fn person::introduce() -> void {\n" 49 | " printf(\"Hi, I'm %s, age %d\\n\", name, age);\n" 50 | "}\n" 51 | "fn main() -> i32 {\n" 52 | " let p: person = {\"Bob\", 30};\n" 53 | " p.introduce();\n" 54 | " return 0;\n" 55 | "}\n" 56 | ,pass), 57 | 58 | // String comparison using strcmp 59 | t( 60 | "fn main() -> i32 {\n" 61 | " let str1: char* = \"hello\";\n" 62 | " let str2: char* = \"hello\";\n" 63 | " let str3: char* = \"world\";\n" 64 | " if (strcmp(str1, str2) == 0) {\n" 65 | " puts(\"Strings match!\");\n" 66 | " }\n" 67 | " if (strcmp(str1, str3) != 0) {\n" 68 | " puts(\"Strings differ!\");\n" 69 | " }\n" 70 | " return 0;\n" 71 | "}\n" 72 | ,pass), 73 | 74 | // String length calculation 75 | t( 76 | "fn main() -> i32 {\n" 77 | " let message: char* = \"Programming\";\n" 78 | " let len: i32 = strlen(message);\n" 79 | " printf(\"Length of '%s' is %d\\n\", message, len);\n" 80 | " return len;\n" 81 | "}\n" 82 | ,pass), 83 | 84 | // Empty string literal 85 | t( 86 | "fn main() -> i32 {\n" 87 | " let empty: char* = \"\";\n" 88 | " printf(\"Empty string length: %d\\n\", strlen(empty));\n" 89 | " return 0;\n" 90 | "}\n" 91 | ,pass), 92 | 93 | // String literal with quotes escaped 94 | t( 95 | "fn main() -> i32 {\n" 96 | " let quoted: char* = \"She said \\\"Hello\\\" to me.\";\n" 97 | " puts(quoted);\n" 98 | " return 0;\n" 99 | "}\n" 100 | ,pass), 101 | 102 | // Multiple string literals in namespace 103 | t( 104 | "namespace messages {\n" 105 | " struct config {\n" 106 | " let error_msg: char* = \"Error occurred!\";\n" 107 | " let success_msg: char* = \"Operation successful!\";\n" 108 | " fn print_error() -> void;\n" 109 | " fn print_success() -> void;\n" 110 | " };\n" 111 | "}\n" 112 | "fn messages::config::print_error() -> void {\n" 113 | " puts(error_msg);\n" 114 | "}\n" 115 | "fn messages::config::print_success() -> void {\n" 116 | " puts(success_msg);\n" 117 | "}\n" 118 | "fn main() -> i32 {\n" 119 | " let cfg: messages::config = {};\n" 120 | " cfg.print_success();\n" 121 | " cfg.print_error();\n" 122 | " return 0;\n" 123 | "}\n" 124 | ,pass), 125 | // Invalid: trying to assign string to non-pointer type (should fail) 126 | t( 127 | "fn main() -> i32 {\n" 128 | " let invalid: i32 = \"This should fail\";\n" 129 | " return 0;\n" 130 | "}\n" 131 | ,fail) 132 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/command-line-string-O0.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'tests/command-line-string.out' 2 | source_filename = "command-line-string.fm" 3 | 4 | @.str = private unnamed_addr constant [14 x i8] c"Hello, World!\00", align 1 5 | 6 | declare i32 @printf(ptr, ...) 7 | 8 | declare i32 @puts(ptr) 9 | 10 | declare i64 @strlen(ptr) 11 | 12 | declare i32 @strcmp(ptr, ptr) 13 | 14 | declare ptr @strcpy(ptr, ptr) 15 | 16 | declare ptr @strcat(ptr, ptr) 17 | 18 | declare ptr @malloc(i64) 19 | 20 | declare void @free(ptr) 21 | 22 | declare void @exit(i32) 23 | 24 | declare void @abort() 25 | 26 | declare ptr @memcpy(ptr, ptr, i64) 27 | 28 | declare ptr @memset(ptr, i32, i64) 29 | 30 | define void @fumo.init() #0 { 31 | ret void 32 | } 33 | 34 | declare i32 @puts.1(ptr) 35 | 36 | define internal i32 @fumo.user_main() { 37 | %"main()::message" = alloca ptr, align 8 38 | store ptr null, ptr %"main()::message", align 8 39 | store ptr @.str, ptr %"main()::message", align 8 40 | %1 = load ptr, ptr %"main()::message", align 8 41 | %2 = call i32 @puts(ptr %1) 42 | ret i32 0 43 | } 44 | 45 | define i32 @main(i32 %argc, ptr %argv) #0 { 46 | call void @fumo.init() 47 | %1 = call i32 @fumo.user_main() 48 | ret i32 %1 49 | } 50 | 51 | attributes #0 = { "used" } 52 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/command-line-string.fm: -------------------------------------------------------------------------------- 1 | 2 | let x = 69; 3 | fn main() -> i32 { 4 | let ptr: i32* = &x; 5 | let ptr2: i32** = &ptr; 6 | let x = **ptr2; 7 | return x; 8 | } 9 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/command-line-string.ll: -------------------------------------------------------------------------------- 1 | ; ModuleID = 'tests/command-line-string.out' 2 | source_filename = "command-line-string.fm" 3 | target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" 4 | target triple = "x86_64-pc-linux-gnu" 5 | 6 | @.str = private unnamed_addr constant [14 x i8] c"Hello, World!\00", align 1 7 | 8 | ; Function Attrs: nofree nounwind 9 | declare noundef i32 @puts(ptr nocapture noundef readonly) local_unnamed_addr #0 10 | 11 | ; Function Attrs: mustprogress nofree norecurse nosync nounwind willreturn memory(none) 12 | define void @fumo.init() local_unnamed_addr #1 { 13 | ret void 14 | } 15 | 16 | ; Function Attrs: nofree nounwind 17 | define noundef i32 @main(i32 %argc, ptr nocapture readnone %argv) local_unnamed_addr #2 { 18 | %1 = tail call i32 @puts(ptr nonnull dereferenceable(1) @.str) 19 | ret i32 0 20 | } 21 | 22 | attributes #0 = { nofree nounwind } 23 | attributes #1 = { mustprogress nofree norecurse nosync nounwind willreturn memory(none) "used" } 24 | attributes #2 = { nofree nounwind "used" } 25 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/command-line-string.s: -------------------------------------------------------------------------------- 1 | .file "command-line-string.fm" 2 | .text 3 | .globl fumo.init # -- Begin function fumo.init 4 | .p2align 4 5 | .type fumo.init,@function 6 | fumo.init: # @fumo.init 7 | # %bb.0: 8 | retq 9 | .Lfunc_end0: 10 | .size fumo.init, .Lfunc_end0-fumo.init 11 | # -- End function 12 | .globl main # -- Begin function main 13 | .p2align 4 14 | .type main,@function 15 | main: # @main 16 | # %bb.0: 17 | pushq %rax 18 | leaq .L.str(%rip), %rdi 19 | callq puts@PLT 20 | xorl %eax, %eax 21 | popq %rcx 22 | retq 23 | .Lfunc_end1: 24 | .size main, .Lfunc_end1-main 25 | # -- End function 26 | .type .L.str,@object # @.str 27 | .section .rodata.str1.1,"aMS",@progbits,1 28 | .L.str: 29 | .asciz "Hello, World!" 30 | .size .L.str, 14 31 | 32 | .section ".note.GNU-stack","",@progbits 33 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/early-ast-tests/ast_syntax.fm: -------------------------------------------------------------------------------- 1 | t("let var = 69 + 21 + (3 + 3 = 2) * (-2 - 3 / ~3) + 3 + 30;\nlet epic = 213;", fail), 2 | t("123123 + ~213213* 3123;", pass), 3 | t("let gamer = ~1231 + 21312 * 3213 / (1231230 + 2130 + 2 * 3 - 45 + 3123 + 10);",pass), 4 | t("(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-(-1))))))))))))))))))))))))))))))))))));",pass), 5 | t("let var: i64 = {69213 * 2 , {2, {323213, 123123}}};\n", pass), 6 | t("fn func_name(a: i32, b: struct gaming{}) -> const i32* {}", fail), 7 | t("fn another_f() -> const i32****;\n" 8 | "fn func_name(a: i32, b: f64) -> const i32* {\n" 9 | " let var: i32 = 213123;\n" 10 | " {\n" 11 | " let x: i32;\n" 12 | " }\n" 13 | " var = 213;\n" 14 | "}\n" 15 | ,pass), 16 | t("let var;", fail), 17 | t("~1 + !0.0f;~-0; 1 + !(~3 - 4 * 3 + 9);",pass), 18 | t( 19 | "let var: some::thing = {};\n" 20 | "let var2: some::thing = {12312, 123213};\n" 21 | , fail), 22 | t( 23 | "fn func() -> struct gaming {} {\n" 24 | "}\n" 25 | , fail), 26 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/early-ast-tests/postfix.fm: -------------------------------------------------------------------------------- 1 | t( 2 | "struct foo {\n" 3 | " fn func(x: i32) -> i32;\n" 4 | " fn bar(x: i32) -> i32;\n" 5 | "};\n" 6 | "fn foo::func(x: i32) -> i32 {\n" 7 | " foo::bar();\n" 8 | " return x;\n" 9 | "}\n" 10 | ,fail), 11 | t( 12 | "struct foo {\n" 13 | " fn func(x: i32) -> i32;\n" 14 | "};\n" 15 | "fn somefunc() -> void {\n" 16 | " let x:i32 = foo::func();\n" 17 | "}\n" 18 | ,fail), 19 | t( 20 | "struct foo {\n" 21 | " fn func(x: i32) -> i32{return 213;}\n" 22 | " let some: i32 = 124;\n" 23 | "};\n" 24 | "fn somefunc() -> void {\n" 25 | " let var: foo = {};\n" 26 | " var.some = 3;\n" 27 | " let x: i32 = var.func();\n" 28 | "}\n" 29 | ,fail), 30 | t( 31 | "struct foo {\n" 32 | " fn foo::func(x: i32) -> gaming;\n" 33 | "};\n" 34 | ,fail), 35 | t( 36 | "namespace foo {\n" 37 | " struct foo {\n" 38 | " struct gaming {\n" 39 | " let xee: i32 = 213; \n" 40 | " fn ahhh() -> i32 {return xee;} \n" 41 | " fn some() -> i32 {return ahhh();} \n" 42 | " };\n" 43 | " struct bar {\n" 44 | " fn func(x: i32) -> gaming; \n" 45 | " };\n" 46 | " fn another_func() -> void {let x: i32 = 213;}\n" 47 | " fn some_func() -> void;\n" 48 | " };\n" 49 | "}\n" 50 | "fn foo::foo::bar::func(x: i32) -> gaming {let var = gaming {x};}\n" 51 | "fn foo::foo::some_func() -> void {\n" 52 | " {\n" 53 | " another_func();\n" 54 | " }\n" 55 | " let var: bar = {};\n" 56 | " let eee: gaming = {212322};\n" 57 | " let e_ptr: gaming* = eee;\n" 58 | " let y = var.func(e_ptr->xee).some();\n" 59 | "}\n" 60 | ,pass), 61 | t( 62 | "namespace gaming {\n" 63 | "struct foo {};\n" 64 | "}\n" 65 | "fn huh(e: i32) -> void {}\n" 66 | "fn gaming::fff() -> void {\n" 67 | " let var: foo = {};\n" 68 | " huh(var);\n" 69 | "}\n" 70 | ,fail), 71 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/early-ast-tests/scope_basic_checks.fm: -------------------------------------------------------------------------------- 1 | t( 2 | "namespace foo {}\n" 3 | "struct foo {};\n" 4 | ,fail), 5 | t( 6 | "namespace foo {\n" 7 | " namespace foo {}\n" 8 | " struct bar {};\n" 9 | "}\n" 10 | "struct bar;\n" 11 | "namespace foo {\n" 12 | " fn func(x: i32) -> void;\n" 13 | "}\n" 14 | "struct bar {};\n" 15 | "struct bar;\n" 16 | "struct bar;\n" 17 | ,pass), 18 | t( 19 | "fn foo() -> void;\n" 20 | "struct foo {};\n" 21 | ,pass), 22 | t( 23 | "namespace foo {\n" 24 | " struct bar { let x: i32; };\n" 25 | " fn bar() -> void;\n" 26 | " fn bar() -> void { let x: i32 = 213123; }\n" 27 | "}\n" 28 | ,pass), 29 | t( 30 | "let x = 69;\n" 31 | "namespace foo {\n" 32 | " let x: i32;\n" 33 | " struct bar { let x: i32; };\n" 34 | " fn bar() -> void { let x: i32 = 213123; }\n" 35 | "}\n" 36 | ,pass), 37 | t( 38 | "let x = 69;\n" 39 | "fn func(x: i32) -> void {\n" 40 | " {\n" 41 | " let x = 420;\n" 42 | " {\n" 43 | " let x = 33333;\n" 44 | " }\n" 45 | " }\n" 46 | "}\n" 47 | ,pass), 48 | t( 49 | "let x = 69;\n" 50 | "fn func() -> void {\n" 51 | " let x = 123;\n" 52 | " {\n" 53 | " let x = 420;\n" 54 | " {\n" 55 | " let x = 33333;\n" 56 | " }\n" 57 | " }\n" 58 | " let x: i32;\n" 59 | "}\n" 60 | ,fail), 61 | t( 62 | "let x = 69;\n" 63 | "fn func(x: i32) -> void { let x: i32 = 123; }\n" 64 | ,fail), 65 | 66 | t( 67 | "let x: i32 = 213123;\n" 68 | "1\n" 69 | "let y = 213123;\n" 70 | ,fail), 71 | 72 | t( 73 | "struct foo{};\n" 74 | "fn func(x: foo) -> void;\n" 75 | "\n" 76 | ,pass), 77 | 78 | 79 | t( 80 | 81 | "let var = 213123;\n" 82 | "fn main() -> i32 {}\n" 83 | 84 | 85 | ,pass), 86 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/early-ast-tests/scope_name_lookup.fm: -------------------------------------------------------------------------------- 1 | t( 2 | "namespace x {let x: i32;}\n" 3 | "let x = 69;\n" 4 | "fn bar() -> void { \n" 5 | " struct x {};\n" 6 | " x = 420;\n" 7 | " let x: i32 = 213123; \n" 8 | " {\n" 9 | " x = 696969;\n" 10 | " {\n" 11 | " x = 11111;\n" 12 | " }\n" 13 | " }\n" 14 | "}\n" 15 | "fn func() -> void { x = 44444444; }\n" 16 | ,pass), 17 | t( 18 | "let x = 69;\n" 19 | "namespace foo {\n" 20 | " let x: i32 = 69420;\n" 21 | " namespace gaming {\n" 22 | " let x: i32;\n" 23 | " fn func() -> void {\n" 24 | " x = 222222;\n" 25 | " }\n" 26 | " }\n" 27 | " fn func() -> void {\n" 28 | " x = 111111;\n" 29 | " gaming::x = 333333;\n" 30 | " }\n" 31 | "}\n" 32 | ,pass), 33 | t( 34 | "let x = 69;\n" 35 | "namespace foo {\n" 36 | " let x: i32 = 69420;\n" 37 | " namespace gaming {\n" 38 | " let x: i32;\n" 39 | " }\n" 40 | "}\n" 41 | "fn func() -> void {\n" 42 | " x = 111111;\n" 43 | " foo::x = 222222; \n" 44 | " foo::gaming::x = 333333;\n" 45 | "}\n" 46 | ,pass), 47 | 48 | t( 49 | "let x: i32 = 123123; \n" 50 | "let a: i32 = 123123; \n" 51 | "let z: i32 = 123123; \n" 52 | "fn func_name() -> const i32* { \n" 53 | " x = 69420; \n" 54 | " a = 69420; \n" 55 | " let x = 1111111; \n" 56 | " { \n" 57 | " z = 69; \n" 58 | " let x: f64; \n" 59 | " x = 12.0f; \n" 60 | " } \n" 61 | " x = 213; \n" 62 | "} \n" 63 | , pass), 64 | t( 65 | "struct gaming {let ahhh: i32;};\n" 66 | "let x: gaming;\n" 67 | "fn func() -> void {\n" 68 | " struct gaming {let bbbb: f32;};\n" 69 | " let x: gaming = {123123};\n" 70 | " x.bbbb = 23.0f;\n" 71 | " {\n" 72 | " struct gaming {let vvvvv = 321;};\n" 73 | " let x: gaming;\n" 74 | " }\n" 75 | "}\n" 76 | , pass), 77 | t( 78 | "let x: i32;\n" 79 | "namespace foo {\n" 80 | " struct bar {\n" 81 | " fn func() -> void {\n" 82 | " {\n" 83 | " x = 21313;\n" 84 | " }\n" 85 | " {\n" 86 | " let x = 21313;\n" 87 | " }\n" 88 | " {\n" 89 | " let x = 21313;\n" 90 | " }\n" 91 | " }\n" 92 | " };\n" 93 | "}\n" 94 | ,pass), 95 | t( 96 | "fn func() -> void {\n" 97 | " //fnc()::x\n" 98 | " let x = 123;\n" 99 | " { //func()::0::\n" 100 | " struct bar{};\n" 101 | " let x = 21; //func()::0::x\n" 102 | " { //func()::0::0\n" 103 | " let x = 6969;\n" 104 | " }\n" 105 | " }\n" 106 | " { //func()::1::\n" 107 | " struct bar{};\n" 108 | " let x = 2132; //func()::1::x\n" 109 | " { //func()::1::0\n" 110 | " let x = 1; //func()::1::0::x\n" 111 | " }\n" 112 | " { //func()::1::1\n" 113 | " let x = 2; //func()::1::1::x\n" 114 | " }\n" 115 | " }\n" 116 | "}\n" 117 | ,pass), 118 | 119 | t( 120 | "struct foo {\n" 121 | " let x: i32 = 123213;\n" 122 | "};\n" 123 | "fn func() -> void {\n" 124 | " let y = foo::x;\n" 125 | "}\n" 126 | ,fail), 127 | 128 | t( 129 | " let var = {};\n" 130 | ,fail), 131 | t( 132 | "struct foo {\n" 133 | " struct bar {\n" 134 | " let x = 213;\n" 135 | " };\n" 136 | "};\n" 137 | ,pass), 138 | //t( 139 | //"let x: i32 = {213} + {123};\n" 140 | //,fail), 141 | 142 | t( 143 | "namespace huh {\n" 144 | " struct foo {\n" 145 | " fn mefunc() -> void {}\n" 146 | " fn somefunc() -> void {}\n" 147 | " fn another() -> void {\n" 148 | " struct bar {};\n" 149 | " {{\n" 150 | " somefunc();\n" 151 | " }}\n" 152 | " }\n" 153 | " };\n" 154 | "}\n" 155 | , fail), 156 | t( 157 | "let var = 123123213;\n" 158 | "fn foo() -> i32 {\n" 159 | " let x: i32 = 3124124;\n" 160 | " struct gaming {let x: i32;};\n" 161 | " {\n" 162 | " struct gaming {let x: i32;};\n" 163 | " let var1 = gaming{2313};\n" 164 | " let var2 = gaming{2313};\n" 165 | " var1.x = 69420;\n" 166 | " var2.x = 69420;\n" 167 | " }\n" 168 | "}\n" 169 | , pass), 170 | 171 | //t( 172 | // 173 | //"struct foo {\n" 174 | //" struct bar {let y = 23;};\n" 175 | //"\n" 176 | //" let x: i32 = 21313;\n" 177 | //" let var = bar{1212};\n" 178 | //"\n" 179 | //" fn func(param: i32) -> void {\n" 180 | //" x = 6969;\n" 181 | //" var.y = 343434;\n" 182 | //" }\n" 183 | //"};\n" 184 | //"fn some() -> void {\n" 185 | //" let local = foo{12, foo::bar{3}};\n" 186 | //" local.var.y = 10;\n" 187 | //"}\n" 188 | // 189 | //,pass), 190 | // 191 | //t( 192 | // 193 | //"fn func() -> void;\n" 194 | //"fn func() -> void {}\n" 195 | //"fn func() -> void;\n" 196 | //"struct gaming;\n" 197 | //"struct gaming{};\n" 198 | //"struct gaming;\n" 199 | //"struct gaming;\n" 200 | // 201 | //, pass), 202 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/example.fm: -------------------------------------------------------------------------------- 1 | let x = 69; 2 | namespace foo { 3 | let x: i32 = 69420; 4 | namespace inner { 5 | let x: i32; 6 | fn func() -> void { x = 222222; } 7 | } 8 | fn func(a: i32, b: f64) -> void; 9 | struct SomeStruct { 10 | struct InnerStruct { 11 | let x = 123; 12 | let y = "string example"; 13 | }; 14 | let var = InnerStruct {69420, "passing a string"}; 15 | let some_number: i32; 16 | }; 17 | } 18 | 19 | fn foo::func(a: i32, b: f64) -> void { 20 | x = 111111 + a * b; 21 | inner::x = 333333; 22 | } 23 | 24 | let global_example: foo::SomeStruct = {{11111, "example"}, 6969}; 25 | fn some_func() -> i32* { 26 | return &global_example.some_number; 27 | } 28 | -------------------------------------------------------------------------------- /tests/not_working_old_tests/fumo_module.fm: -------------------------------------------------------------------------------- 1 | let x = 23; 2 | fn main() -> i32 { 3 | x = x * 69; 4 | let y = (x + 43) / 23 * x; 5 | return y; 6 | } 7 | -------------------------------------------------------------------------------- /tests/pointer-tests/pointer-comparison.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let a = 10; 3 | let b = 20; 4 | let ptr1: i32* = &a; 5 | let ptr2: i32* = &b; 6 | let ptr3: i32* = &a; 7 | 8 | if (ptr1 == ptr3) { 9 | if (ptr1 != ptr2) { 10 | if (*ptr1 < *ptr2) { 11 | return 1; 12 | } 13 | } 14 | } 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /tests/pointer-tests/pointer-deref.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let value = 42; 3 | let ptr: i32* = &value; 4 | 5 | if (ptr) { 6 | if (*ptr > 40) { 7 | *ptr = 100; 8 | if (*ptr == 100) { 9 | return 1; 10 | } 11 | } 12 | } 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/pointer-tests/pointer-null-check.fm: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | let ptr: i32*; 3 | let value = 42; 4 | let valid_ptr: i32* = &value; 5 | 6 | if (ptr) { 7 | if (valid_ptr) { 8 | return 1; 9 | } 10 | } 11 | return 0; 12 | } 13 | -------------------------------------------------------------------------------- /tests/static-member-functions/staticfunc1.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | namespace math { 4 | struct Point { 5 | let x: f64; 6 | let y: f64; 7 | 8 | // Instance method 9 | fn distance_from_origin() -> f64 { 10 | return x * x + y * y; 11 | } 12 | 13 | // Static constructor 14 | fn static new(x: f64, y: f64) -> Point { 15 | return Point {x, y}; 16 | } 17 | 18 | // Static utility function 19 | fn static origin() -> Point { 20 | return Point {0.0, 0.0}; 21 | } 22 | 23 | // Static function with parameters 24 | fn static distance_between(p1: Point*, p2: Point*) -> f64 { 25 | let dx: f64 = p2->x - p1->x; 26 | let dy: f64 = p2->y - p1->y; 27 | return dx * dx + dy * dy; 28 | } 29 | 30 | // Static function returning primitive type 31 | fn static is_valid_coordinate(x: f64, y: f64) -> bool { 32 | if x == 0.0 && y == 0.0 { 33 | return false; 34 | } else { 35 | return true; 36 | } 37 | } 38 | } 39 | } 40 | 41 | // Test 1: Basic static function call via type namespace 42 | fn test_static_namespace_access() -> void { 43 | printf("=== Testing static namespace access ===\n"); 44 | 45 | // Call static function using type namespace 46 | let origin: math::Point = math::Point::origin(); 47 | printf("Origin point: (%.1f, %.1f)\n", origin.x, origin.y); 48 | 49 | // Call static factory method 50 | let point: math::Point = math::Point::new(5.0, 12.0); 51 | printf("Created point: (%.1f, %.1f)\n", point.x, point.y); 52 | 53 | // Call static function with parameters 54 | let dist: f64 = math::Point::distance_between(&origin, &point); 55 | printf("Distance from origin: %.2f\n", dist); 56 | 57 | // Call static function returning primitive 58 | let valid: bool = math::Point::is_valid_coordinate(3.0, 4.0); 59 | if valid { 60 | printf("Coordinates valid: true\n"); 61 | } else { 62 | printf("Coordinates valid: false\n"); 63 | } 64 | } 65 | 66 | // Test 2: Static function call from instance (should work) 67 | fn test_static_instance_access() -> void { 68 | printf("\n=== Testing static access from instance ===\n"); 69 | 70 | let point: math::Point = math::Point {3.0, 4.0}; 71 | 72 | // Call static function through instance (should work) 73 | let origin: math::Point = point.origin(); 74 | printf("Origin via instance: (%.1f, %.1f)\n", origin.x, origin.y); 75 | 76 | // Call another static function through instance 77 | let new_point: math::Point = point.new(7.0, 24.0); 78 | printf("New point via instance: (%.1f, %.1f)\n", new_point.x, new_point.y); 79 | } 80 | 81 | namespace utils { 82 | struct Counter { 83 | let count: i32; 84 | 85 | // Static factory method 86 | fn static create(initial: i32) -> Counter { 87 | return Counter {initial}; 88 | } 89 | 90 | // Static constant 91 | fn static max_value() -> i32 { 92 | return 1000; 93 | } 94 | 95 | // Static function calling other static function 96 | fn static is_valid_count(value: i32) -> bool { 97 | if value >= 0 && value <= Counter::max_value() { 98 | return true; 99 | } else { 100 | return false; 101 | } 102 | } 103 | } 104 | } 105 | 106 | 107 | // Test 3: Nested static function calls 108 | fn test_nested_static_calls() -> void { 109 | printf("\n=== Testing nested static calls ===\n"); 110 | 111 | // Static function calling other static function 112 | let valid: bool = utils::Counter::is_valid_count(500); 113 | if valid { 114 | printf("Count 500 is valid: true\n"); 115 | } else { 116 | printf("Count 500 is valid: false\n"); 117 | } 118 | 119 | let invalid: bool = utils::Counter::is_valid_count(1500); 120 | if invalid { 121 | printf("Count 1500 is valid: true\n"); 122 | } else { 123 | printf("Count 1500 is valid: false\n"); 124 | } 125 | } 126 | 127 | // Test 4: Static functions in expressions 128 | fn test_static_in_expressions() -> void { 129 | printf("\n=== Testing static functions in expressions ===\n"); 130 | 131 | // Use static function in conditional 132 | if math::Point::is_valid_coordinate(1.0, 2.0) { 133 | printf("Coordinates are valid\n"); 134 | } 135 | 136 | // Use static function in assignment 137 | let max_allowed: i32 = utils::Counter::max_value(); 138 | printf("Max allowed count: %d\n", max_allowed); 139 | 140 | // Chain static calls 141 | let counter: utils::Counter = utils::Counter::create(utils::Counter::max_value() / 2); 142 | printf("Created counter with half max value: %d\n", counter.count); 143 | } 144 | 145 | // Test 6: Complex usage with multiple types 146 | fn test_complex_usage() -> void { 147 | printf("\n=== Testing complex static usage ===\n"); 148 | 149 | // Create individual points using static factory 150 | let point1: math::Point = math::Point::new(0.0, 0.0); 151 | let point2: math::Point = math::Point::new(3.0, 4.0); 152 | let point3: math::Point = math::Point::new(6.0, 8.0); 153 | 154 | // Use static function to validate each point 155 | let valid1: bool = math::Point::is_valid_coordinate(point1.x, point1.y); 156 | let valid2: bool = math::Point::is_valid_coordinate(point2.x, point2.y); 157 | let valid3: bool = math::Point::is_valid_coordinate(point3.x, point3.y); 158 | 159 | if valid1 { 160 | printf("Point 1 valid: true\n"); 161 | } else { 162 | printf("Point 1 valid: false\n"); 163 | } 164 | 165 | if valid2 { 166 | printf("Point 2 valid: true\n"); 167 | } else { 168 | printf("Point 2 valid: false\n"); 169 | } 170 | 171 | if valid3 { 172 | printf("Point 3 valid: true\n"); 173 | } else { 174 | printf("Point 3 valid: false\n"); 175 | } 176 | 177 | // Calculate distances using static function 178 | let dist1: f64 = math::Point::distance_between(&point1, &point2); 179 | let dist2: f64 = math::Point::distance_between(&point2, &point3); 180 | printf("Distance 1->2: %.2f, 2->3: %.2f\n", dist1, dist2); 181 | } 182 | 183 | // Test 7: Static functions with different return types 184 | fn test_return_types() -> void { 185 | printf("\n=== Testing different return types ===\n"); 186 | 187 | // Returning struct 188 | let origin: math::Point = math::Point::origin(); 189 | 190 | // Returning primitive 191 | let max: i32 = utils::Counter::max_value(); 192 | 193 | // Returning bool 194 | let valid: bool = math::Point::is_valid_coordinate(1.0, 1.0); 195 | 196 | printf("Origin: (%.1f, %.1f), Max: %d, ", origin.x, origin.y, max); 197 | if valid { 198 | printf("Valid: true\n"); 199 | } else { 200 | printf("Valid: false\n"); 201 | } 202 | } 203 | 204 | fn main() -> i32 { 205 | printf("Starting static function tests...\n"); 206 | 207 | test_static_namespace_access(); 208 | test_static_instance_access(); 209 | test_nested_static_calls(); 210 | test_static_in_expressions(); 211 | test_complex_usage(); 212 | test_return_types(); 213 | 214 | printf("\nAll static function tests completed!\n"); 215 | return 0; 216 | } 217 | -------------------------------------------------------------------------------- /tests/string-literals/basic_printf.fm: -------------------------------------------------------------------------------- 1 | // String literal with printf 2 | fn printf(string: char*, ...) -> i32; 3 | fn main() -> i32 { 4 | let fumofumo: char* = "fumos"; 5 | let amount: i32 = 69; 6 | printf("i have %d %s\n", amount, fumofumo); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/string-literals/basic_strings.fm: -------------------------------------------------------------------------------- 1 | // Basic string literal assignment and usage 2 | fn puts(s: char*) -> i32; 3 | fn main() -> i32 { 4 | let message: char* = "Hello, World!"; 5 | puts(message); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/string-literals/empty-string.fm: -------------------------------------------------------------------------------- 1 | // Empty string literal 2 | fn printf(format: char*, ...) -> i32; 3 | fn strlen(s: char*) -> i64; // size_t is typically 64-bit 4 | fn main() -> i32 { 5 | let empty: char* = ""; 6 | printf("Empty string length: %d\n", strlen(empty)); 7 | return 0; 8 | } 9 | -------------------------------------------------------------------------------- /tests/string-literals/escape_sequence.fm: -------------------------------------------------------------------------------- 1 | // String literal with escape sequences 2 | fn printf(string: char*, ...) -> i32; 3 | fn main() -> i32 { 4 | let text: char* = "Line 1\nLine 2\tTabbed"; 5 | printf("%s\n", text); 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /tests/string-literals/escaped-string-test.fm: -------------------------------------------------------------------------------- 1 | // String literal with quotes escaped 2 | fn puts(s: char*) -> i32; 3 | fn main() -> i32 { 4 | let quoted: char* = "She said \"Hello\" to me."; 5 | puts(quoted); 6 | return 0; 7 | } 8 | -------------------------------------------------------------------------------- /tests/string-literals/fumo-strings.fm: -------------------------------------------------------------------------------- 1 | // linking with libc 2 | fn printf(format: char*, ...) -> i32; 3 | 4 | namespace fm { 5 | struct Fumo { 6 | let name: char*; 7 | let squished: i32; 8 | 9 | fn squish(times: i32) -> void { 10 | squished = squished + times; 11 | printf("%sちゃんは%d回squished!\n", name, squished); 12 | } 13 | } 14 | fn pet_fumo(fumo: Fumo*, times: i32) -> void; 15 | } 16 | 17 | fn fm::pet_fumo(fumo: Fumo*, times: i32) -> void { 18 | printf("petting %s %d times...\n", fumo->name, times); 19 | fumo->squish(times); 20 | } 21 | 22 | let reimu = fm::Fumo {"Reimu", 66}; 23 | let cirno = fm::Fumo {"Cirno", 418}; 24 | 25 | fn main() -> i32 { 26 | printf("fumo lang example.\n"); 27 | 28 | fm::pet_fumo(&reimu, 3); 29 | fm::pet_fumo(&cirno, 2); 30 | 31 | printf("\nfinal pat count:\n"); 32 | printf("Reimu: %d pats\n", reimu.squished); 33 | printf("Cirno: %d pats\n", cirno.squished); 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /tests/string-literals/func_params.fm: -------------------------------------------------------------------------------- 1 | // String literal as function parameter 2 | fn printf(string: char*, ...) -> i32; 3 | fn print_greeting(msg: char*) -> void { 4 | printf("Greeting: %s\n", msg); 5 | } 6 | fn main() -> i32 { 7 | print_greeting("Welcome!"); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /tests/string-literals/namespace-strings.fm: -------------------------------------------------------------------------------- 1 | // Multiple string literals in namespace 2 | fn puts(s: char*) -> i32; 3 | namespace messages { 4 | struct config { 5 | let error_msg: char* = "Error occurred!"; 6 | let success_msg: char* = "Operation successful!"; 7 | fn print_error() -> void; 8 | fn print_success() -> void; 9 | }; 10 | } 11 | fn messages::config::print_error() -> void { 12 | puts(error_msg); 13 | } 14 | fn messages::config::print_success() -> void { 15 | puts(success_msg); 16 | } 17 | fn main() -> i32 { 18 | let cfg: messages::config = {}; 19 | cfg.print_success(); 20 | cfg.print_error(); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /tests/string-literals/strlen-math.fm: -------------------------------------------------------------------------------- 1 | // String length calculation 2 | fn printf(format: char*, ...) -> i32; 3 | fn strlen(s: char*) -> i32; // size_t is typically 64-bit 4 | fn main() -> i32 { 5 | let message: char* = "Programming"; 6 | let len: i32 = strlen(message); 7 | printf("Length of '%s' is %d\n", message, len); 8 | return len; // NOTE: add casting operator 9 | } 10 | -------------------------------------------------------------------------------- /tests/string-literals/struct_member.fm: -------------------------------------------------------------------------------- 1 | // String literal in struct 2 | fn printf(string: char*, ...) -> i32; 3 | struct person { 4 | let name: char* = "Unknown"; 5 | let age: i32 = 0; 6 | fn introduce() -> void; 7 | }; 8 | fn person::introduce() -> void { 9 | printf("Hi, I'm %s, age %d\n", name, age); 10 | } 11 | fn main() -> i32 { 12 | let p: person = {"Bob", 30}; 13 | p.introduce(); 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/calculator.fm: -------------------------------------------------------------------------------- 1 | // Basic calculator struct with external definition - PASS 2 | // TODO: fix this tests 3 | 4 | struct calculator { 5 | let value: i32 = 0; 6 | fn get_value() -> i32 { return value; } 7 | fn double() -> i32; 8 | fn triple() -> i32 { return get_value() * 3; } 9 | }; 10 | fn calculator::double() -> i32 { return get_value() * 2; } 11 | fn main() -> i32 { 12 | let calc: calculator = {34}; 13 | let calc_ptr: calculator* = &calc; 14 | let result: i32 = calc_ptr->double(); 15 | return result + 1; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/container.fm: -------------------------------------------------------------------------------- 1 | // Complex nested struct with pointers and postfix - PASS 2 | namespace container { 3 | struct inner { 4 | let data: i32 = 0; 5 | fn get_data() -> i32 { return data; } 6 | fn modify_data(delta: i32) -> void; 7 | }; 8 | struct outer { 9 | let inner_obj: inner = {}; 10 | let inner_ptr: inner* = {}; 11 | fn init() -> void { inner_ptr = &inner_obj; } 12 | fn get_inner_via_ptr() -> inner*; 13 | fn cascade_modify(delta: i32) -> i32; 14 | }; 15 | } 16 | fn container::inner::modify_data(delta: i32) -> void { data = data + delta; } 17 | fn container::outer::get_inner_via_ptr() -> inner* { return inner_ptr; } 18 | fn container::outer::cascade_modify(delta: i32) -> i32 { 19 | inner_ptr->modify_data(delta); 20 | return inner_ptr->get_data(); 21 | } 22 | fn main() -> i32 { 23 | let outer_obj: container::outer = {container::inner{42}}; 24 | let outer_ptr: container::outer* = &outer_obj; 25 | outer_ptr->init(); 26 | return outer_ptr->get_inner_via_ptr()->get_data(); 27 | } 28 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/counter.fm: -------------------------------------------------------------------------------- 1 | // Member function modifying member variable - with pointer access and external definition - PASS 2 | struct counter { 3 | let count: i32 = 0; 4 | fn increment() -> void; 5 | fn get_count() -> i32 { return count; } 6 | fn get_count_ptr() -> i32*; 7 | }; 8 | fn counter::increment() -> void { 9 | count = count + 1; 10 | } 11 | fn counter::get_count_ptr() -> i32* { return &count; } 12 | fn main() -> i32 { 13 | let c: counter = {68}; 14 | let c_ptr: counter* = &c; 15 | c_ptr->increment(); 16 | let count_ptr: i32* = c_ptr->get_count_ptr(); 17 | return *count_ptr; 18 | } 19 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/example.fm: -------------------------------------------------------------------------------- 1 | let x = 69; 2 | namespace foo { 3 | let x: i32 = 69420; 4 | namespace inner { 5 | let x: i32; 6 | fn func() -> void { x = 222222; } 7 | } 8 | fn func(a: i32, b: f64) -> void; 9 | struct SomeStruct { 10 | struct InnerStruct { 11 | let x = 123; 12 | let y = "string example"; 13 | fn gaming_func() -> i32 { return x; } 14 | } 15 | let var = InnerStruct {69420, "passing a string"}; 16 | let some_number: i32; 17 | }; 18 | } 19 | 20 | fn foo::func(a: i32, b: f64) -> void { 21 | x = 111111 + a * a; 22 | inner::x = 333333; 23 | } 24 | let global_example = foo::SomeStruct {foo::SomeStruct::InnerStruct {11111, "example"}, 66}; 25 | fn main() -> i32 { 26 | return global_example.some_number; 27 | } 28 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/factory.fm: -------------------------------------------------------------------------------- 1 | // Testing member function on temporary - FAIL 2 | namespace factory { 3 | struct product { 4 | let id: i32 = 0; 5 | fn get_id() -> i32 { return id; } 6 | } 7 | struct factory { 8 | fn create(id: i32) -> product { return product{id}; } 9 | fn process(p: product*) -> i32; 10 | } 11 | } 12 | struct product { 13 | } 14 | fn factory::factory::process(p: product*) -> i32 { return p->get_id(); } 15 | fn main() -> i32 { 16 | let f: factory::factory = {}; 17 | return f.create(42).get_id(); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/graphics.fm: -------------------------------------------------------------------------------- 1 | // Nested namespaces with postfix chaining - PASS 2 | namespace graphics { 3 | namespace shapes { 4 | struct circle { 5 | let radius: i32 = 0; 6 | fn get_radius() -> i32 { return radius; } 7 | fn get_area_approx() -> i32; 8 | fn get_radius_ptr() -> i32* { return &radius; } 9 | }; 10 | }; 11 | } 12 | fn graphics::shapes::circle::get_area_approx() -> i32 { 13 | return get_radius() * get_radius() * 3; 14 | } 15 | fn main() -> i32 { 16 | let c: graphics::shapes::circle = {5}; 17 | let c_ptr: graphics::shapes::circle* = &c; 18 | let radius_ptr: i32* = c_ptr->get_radius_ptr(); 19 | *radius_ptr = 10; 20 | return c_ptr->get_area_approx(); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/math.fm: -------------------------------------------------------------------------------- 1 | // Namespaced structs with mixed inline/external definitions - PASS 2 | namespace math { 3 | struct vector { 4 | let x: i32 = 0; 5 | let y: i32 = 0; 6 | fn magnitude_squared() -> i32 { return x*x + y*y; } 7 | fn set_x(new_x: i32) -> void; 8 | fn get_x_ptr() -> i32* { return &x; } 9 | }; 10 | } 11 | fn math::vector::set_x(new_x: i32) -> void { x = new_x; } 12 | fn main() -> i32 { 13 | let v: math::vector = {3, 4}; 14 | let v_ptr: math::vector* = &v; 15 | v_ptr->set_x(5); 16 | let x_ptr: i32* = v_ptr->get_x_ptr(); 17 | *x_ptr = 6; 18 | return v_ptr->magnitude_squared(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/nullptr-dereference.fm: -------------------------------------------------------------------------------- 1 | // Struct with null pointer member access - PASS (will cause runtime error) 2 | struct node { 3 | let value: i32 = 0; 4 | let next: node* = {}; 5 | fn get_next() -> node* { return next; } 6 | } 7 | fn main() -> i32 { 8 | let node1 = node {5}; 9 | let node2 = node {69}; 10 | node1.next->value = node2.value; 11 | return node1.get_next()->value;// will cause runtime error 12 | } 13 | 14 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/recursive-node.fm: -------------------------------------------------------------------------------- 1 | // Struct with pointer members and complex postfix expressions - PASS 2 | struct node { 3 | let value: i32 = 0; 4 | let next: node* = {}; 5 | fn get_next() -> node* { return next; } 6 | fn set_next(n: node*) -> void { next = n; } 7 | }; 8 | fn main() -> i32 { 9 | let node1 = node {10}; 10 | let node2 = node {20}; 11 | let node3 = node {30}; 12 | let node4 = node {40}; 13 | node1.set_next(&node2); 14 | node2.set_next(&node3); 15 | node3.set_next(&node4); 16 | node3.set_next(&node4); 17 | return node1.get_next()->get_next()->get_next()->value; 18 | } 19 | -------------------------------------------------------------------------------- /tests/structs-and-postfix/wrapper.fm: -------------------------------------------------------------------------------- 1 | // Pointer-to-pointer with member functions - PASS 2 | struct wrapper { 3 | let ptr: i32* = {}; 4 | fn set_ptr(p: i32*) -> void { ptr = p; } 5 | fn get_ptr_ptr() -> i32**; 6 | fn deref_twice() -> i32 { return **get_ptr_ptr(); } 7 | }; 8 | fn wrapper::get_ptr_ptr() -> i32** { return &ptr; } 9 | fn main() -> i32 { 10 | let x: i32 = 100; 11 | let w: wrapper = {}; 12 | w.set_ptr(&x); 13 | let ptr_ptr: i32** = w.get_ptr_ptr(); 14 | **ptr_ptr = 200; 15 | return w.deref_twice(); 16 | } 17 | -------------------------------------------------------------------------------- /tests/while-tests/while1.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | 3 | // Test 1: Basic while loop with simple condition 4 | fn test_basic_while() -> void { 5 | let i: i32 = 0; 6 | while i < 3 { 7 | printf("i = %d\n", i); 8 | i = i + 1; 9 | } 10 | printf("Final i = %d\n", i); // Should be 3 11 | } 12 | 13 | // Test 2: While loop that never executes 14 | fn test_false_condition() -> void { 15 | let executed: bool = false; 16 | while false { 17 | executed = true; 18 | printf("This should never print\n"); 19 | } 20 | if executed { 21 | printf("Never executed: %s\n", "true"); 22 | } else { 23 | printf("Never executed: %s\n", "false"); 24 | } 25 | } 26 | 27 | 28 | fn main() -> i32 { 29 | printf("=== While Loop Tests ===\n"); 30 | 31 | printf("\n--- Test 1: Basic While ---\n"); 32 | test_basic_while(); 33 | return 0; 34 | 35 | // printf("\n--- Test 2: False Condition ---\n"); 36 | // test_false_condition(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /tests/while-tests/while2.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | fn malloc(size: i64) -> i32*; 3 | fn free(ptr: i32*) -> void; 4 | 5 | 6 | // Test 3: While loop with complex condition 7 | fn test_complex_condition() -> void { 8 | let x: i32 = 10; 9 | let y: i32 = 5; 10 | while x > y && y < 8 { 11 | printf("x = %d, y = %d\n", x, y); 12 | x = x - 1; 13 | y = y + 1; 14 | } 15 | printf("Final: x = %d, y = %d\n", x, y); 16 | } 17 | 18 | // Test 4: Nested while loops 19 | fn test_nested_while() -> void { 20 | let outer: i32 = 0; 21 | while outer < 2 { 22 | let inner: i32 = 0; 23 | while inner < 2 { 24 | printf("outer = %d, inner = %d\n", outer, inner); 25 | inner = inner + 1; 26 | } 27 | outer = outer + 1; 28 | } 29 | } 30 | fn main() -> i32 { 31 | printf("\n--- Test 3: Complex Condition ---\n"); 32 | test_complex_condition(); 33 | 34 | printf("\n--- Test 4: Nested While ---\n"); 35 | test_nested_while(); 36 | } 37 | -------------------------------------------------------------------------------- /tests/while-tests/while3.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | fn malloc(size: i64) -> i32*; 3 | fn free(ptr: i32*) -> void; 4 | 5 | // Test 5: While loop with early return 6 | fn test_early_return() -> i32 { 7 | let count: i32 = 0; 8 | while count < 100 { 9 | if count == 5 { 10 | return count; 11 | } 12 | count = count + 1; 13 | } 14 | return -1; // Should never reach here 15 | } 16 | 17 | // Test 6: Variable scoping in while loop 18 | fn test_scoping() -> void { 19 | let x: i32 = 42; 20 | printf("Before loop: x = %d\n", x); 21 | 22 | while x > 40 { 23 | let y: i32 = x * 2; // y only exists in loop 24 | printf("Inside loop: x = %d, y = %d\n", x, y); 25 | x = x - 1; 26 | } 27 | 28 | printf("After loop: x = %d\n", x); 29 | // y is not accessible here - should not compile if referenced 30 | } 31 | 32 | 33 | fn main() -> i32 { 34 | printf("\n--- Test 5: Early Return ---\n"); 35 | let result: i32 = test_early_return(); 36 | printf("Early return result: %d\n", result); 37 | 38 | printf("\n--- Test 6: Scoping ---\n"); 39 | test_scoping(); 40 | } 41 | -------------------------------------------------------------------------------- /tests/while-tests/while4.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | fn malloc(size: i64) -> i32*; 3 | fn free(ptr: i32*) -> void; 4 | 5 | // Test 8: While loop modifying condition variable inside body 6 | fn test_modify_condition_var() -> void { 7 | let n: i32 = 10; 8 | while n > 0 { 9 | printf("n = %d\n", n); 10 | n = n - 2; // Modify by 2 each time 11 | } 12 | printf("Final n = %d\n", n); // Should be 0 or -1 13 | } 14 | 15 | // Test 9: Empty while loop body (should compile but do nothing) 16 | fn test_empty_body() -> void { 17 | let count: i32 = 0; 18 | while count < 3 { 19 | count = count + 1; 20 | } 21 | printf("Count after empty body loop: %d\n", count); 22 | } 23 | 24 | fn main() -> i32 { 25 | 26 | printf("\n--- Test 8: Modify Condition Var ---\n"); 27 | test_modify_condition_var(); 28 | 29 | printf("\n--- Test 9: Empty Body ---\n"); 30 | test_empty_body(); 31 | } 32 | -------------------------------------------------------------------------------- /tests/while-tests/while_lots.fm: -------------------------------------------------------------------------------- 1 | fn printf(format: char*, ...) -> i32; 2 | fn malloc(size: i64) -> i32*; 3 | fn free(ptr: i32*) -> void; 4 | 5 | // Test 1: Basic counting loop 6 | fn test_basic_count() -> void { 7 | printf("Test 1: Basic counting\n"); 8 | let i: i32 = 0; 9 | while i < 5 { 10 | printf("Count: %d\n", i); 11 | i = i + 1; 12 | } 13 | printf("Final i: %d\n", i); 14 | } 15 | 16 | // Test 2: Countdown loop 17 | fn test_countdown() -> void { 18 | printf("Test 2: Countdown\n"); 19 | let i: i32 = 5; 20 | while i > 0 { 21 | printf("Countdown: %d\n", i); 22 | i = i - 1; 23 | } 24 | printf("Liftoff!\n"); 25 | } 26 | // Test 3: Zero iterations (condition false from start) 27 | fn test_zero_iterations() -> void { 28 | printf("Test 3: Zero iterations\n"); 29 | let i: i32 = 10; 30 | while i < 5 { 31 | printf("This should not print\n"); 32 | i = i + 1; 33 | } 34 | printf("Loop never executed, i is still: %d\n", i); 35 | } 36 | 37 | // Test 4: Variable scope - variables from outside accessible inside 38 | fn test_variable_scope() -> void { 39 | printf("Test 4: Variable scope\n"); 40 | let outer: i32 = 42; 41 | let counter: i32 = 0; 42 | while counter < 3 { 43 | printf("Outer variable: %d, Counter: %d\n", outer, counter); 44 | counter = counter + 1; 45 | } 46 | printf("After loop - Outer: %d, Counter: %d\n", outer, counter); 47 | } 48 | 49 | // Test 5: Nested while loops 50 | fn test_nested_loops() -> void { 51 | printf("Test 5: Nested loops\n"); 52 | let i: i32 = 0; 53 | while i < 3 { 54 | printf("Outer loop i: %d\n", i); 55 | let j: i32 = 0; 56 | while j < 2 { 57 | printf(" Inner loop j: %d\n", j); 58 | j = j + 1; 59 | } 60 | i = i + 1; 61 | } 62 | } 63 | 64 | // Test 6: Complex condition with multiple variables 65 | fn test_complex_condition() -> void { 66 | printf("Test 6: Complex condition\n"); 67 | let a: i32 = 1; 68 | let b: i32 = 10; 69 | while a < 5 && b > 5 { 70 | printf("a: %d, b: %d\n", a, b); 71 | a = a + 1; 72 | b = b - 1; 73 | } 74 | printf("Final: a: %d, b: %d\n", a, b); 75 | } 76 | 77 | // Test 7: While loop with function calls 78 | fn increment_and_print(val: i32*) -> bool { 79 | *val = *val + 1; 80 | printf("Function called, value is now: %d\n", *val); 81 | return *val < 4; 82 | } 83 | 84 | fn test_function_calls() -> void { 85 | printf("Test 7: Function calls in condition\n"); 86 | let x: i32 = 0; 87 | while increment_and_print(&x) { 88 | printf("Inside loop body, x: %d\n", x); 89 | } 90 | printf("Final x: %d\n", x); 91 | } 92 | 93 | // Test 8: Early termination with return 94 | fn test_early_return() -> i32 { 95 | printf("Test 8: Early return from loop\n"); 96 | let i: i32 = 0; 97 | while i < 10 { 98 | printf("i: %d\n", i); 99 | if i == 3 { 100 | printf("Returning early!\n"); 101 | return i; 102 | } 103 | i = i + 1; 104 | } 105 | return -1; // Should never reach here 106 | } 107 | 108 | // Test 9: Pointer arithmetic in loop 109 | fn test_pointer_arithmetic() -> void { 110 | printf("Test 9: Pointer arithmetic\n"); 111 | let arr: i32* = malloc(5 * 4); // 5 integers 112 | let i: i32 = 0; 113 | 114 | // Initialize array 115 | while i < 5 { 116 | *(arr + i) = i * 2; 117 | i = i + 1; 118 | } 119 | 120 | // Print array 121 | i = 0; 122 | while i < 5 { 123 | printf("arr[%d] = %d\n", i, *(arr + i)); 124 | i = i + 1; 125 | } 126 | 127 | free(arr); 128 | } 129 | 130 | 131 | fn main() -> i32 { 132 | printf("=== While Loop Tests ===\n"); 133 | 134 | test_basic_count(); 135 | printf("\n"); 136 | 137 | // test_countdown(); 138 | // printf("\n"); 139 | // 140 | // test_zero_iterations(); 141 | // printf("\n"); 142 | // 143 | // test_variable_scope(); 144 | // printf("\n"); 145 | // 146 | // test_nested_loops(); 147 | // printf("\n"); 148 | // 149 | // test_complex_condition(); 150 | // printf("\n"); 151 | // 152 | // test_function_calls(); 153 | // printf("\n"); 154 | // 155 | // let early_result: i32 = test_early_return(); 156 | // printf("Early return result: %d\n", early_result); 157 | // printf("\n"); 158 | // 159 | // test_pointer_arithmetic(); 160 | 161 | printf("=== All tests complete ===\n"); 162 | return 0; 163 | } 164 | --------------------------------------------------------------------------------