From 59faaf9d8d03b95d0f906cf3e3e3790510a98d6a Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Mon, 7 Sep 2026 23:04:22 +0300 Subject: [PATCH 01/17] add simple std, update cmake to build it --- .gitignore | 2 ++ CMakeLists.txt | 1 + files/std/reclib.asm | 19 ------------------- std/CMakeLists.txt | 32 ++++++++++++++++++++++++++++++++ std/include/io.tea | 3 +++ std/src/io.cpp | 5 +++++ 6 files changed, 43 insertions(+), 19 deletions(-) delete mode 100644 files/std/reclib.asm create mode 100644 std/CMakeLists.txt create mode 100644 std/include/io.tea create mode 100644 std/src/io.cpp diff --git a/.gitignore b/.gitignore index 25417fb..0655561 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,8 @@ bin files/grammar/grammar.lr files/lexis/lexis.lx src/syntax/BuildersRegistry.h +files/std/lib +files/std/include examples/build examples/exe diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d0acc9..82828a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,7 @@ add_subdirectory(src/lexis) add_subdirectory(src/syntax) add_subdirectory(src) +add_subdirectory(std) # add executables from cli directory file(GLOB_RECURSE CLI_SOURCES diff --git a/files/std/reclib.asm b/files/std/reclib.asm deleted file mode 100644 index e31d788..0000000 --- a/files/std/reclib.asm +++ /dev/null @@ -1,19 +0,0 @@ -.p2align 4 -.global _print -_print: -stp x29, x30, [sp, #-16]! -mov x29, sp - -sub sp, sp, #16 -str x0, [sp] -adrp x0, format@PAGE -add x0, x0, format@PAGEOFF -mov x1, sp -bl _printf -add sp, sp, #16 - -ldp x29, x30, [sp], #16 -ret - -.data -format: .asciz "%i\n" diff --git a/std/CMakeLists.txt b/std/CMakeLists.txt new file mode 100644 index 0000000..3f6452e --- /dev/null +++ b/std/CMakeLists.txt @@ -0,0 +1,32 @@ +# Targets for standard library building and deployment + +file(GLOB_RECURSE STD_SOURCES CONFIGURE_DEPENDS "src/*.cpp" "src/*.h") + +add_library(std STATIC ${STD_SOURCES}) +set_target_properties(std PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${TEALANG_FILES_DIRECTORY}/std/lib +) + +file(GLOB_RECURSE STD_HEADERS CONFIGURE_DEPENDS + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}/include + "include/*.tea" "include/*.team" +) + +set(STD_COPIED_HEADERS "") +foreach (header ${STD_HEADERS}) + set(copied_header ${TEALANG_FILES_DIRECTORY}/std/include/${header}) + + add_custom_command( + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_SOURCE_DIR}/include/${header} ${copied_header} + + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/include/${header} + + OUTPUT ${copied_header} + COMMENT "Copying standard library header ${header}" + ) + + list(APPEND STD_COPIED_HEADERS ${copied_header}) +endforeach () + +add_custom_target(std_headers ALL DEPENDS ${STD_COPIED_HEADERS}) diff --git a/std/include/io.tea b/std/include/io.tea new file mode 100644 index 0000000..f6b0e9a --- /dev/null +++ b/std/include/io.tea @@ -0,0 +1,3 @@ +export extern print(x: i64) -> (); + +export extern println(x: i64) -> (); \ No newline at end of file diff --git a/std/src/io.cpp b/std/src/io.cpp new file mode 100644 index 0000000..5b463d2 --- /dev/null +++ b/std/src/io.cpp @@ -0,0 +1,5 @@ +#include + +void print(long long value) { std::cout << value; } + +void println(long long value) { std::cout << value << std::endl; } From e3990f0685258f81873689816694bc9d6eeb71f2 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Mon, 7 Sep 2026 23:06:41 +0300 Subject: [PATCH 02/17] disable libraries installation --- cmake/load_libraries.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/load_libraries.cmake b/cmake/load_libraries.cmake index 23ba1eb..6a39f34 100644 --- a/cmake/load_libraries.cmake +++ b/cmake/load_libraries.cmake @@ -22,6 +22,7 @@ llvm_map_components_to_libnames(llvm_libs support core irreader linker # -- fmt -- set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) +set(FMT_INSTALL OFF) add_subdirectory(lib/fmt EXCLUDE_FROM_ALL) # -- fmt end -- @@ -30,5 +31,6 @@ add_subdirectory(lib/argparse EXCLUDE_FROM_ALL) # -- argparse end -- # -- google test -- +set(INSTALL_GTEST OFF) add_subdirectory(lib/googletest EXCLUDE_FROM_ALL) # -- google test end -- \ No newline at end of file From 3f15906021be565d8ae2dd43eb6fd18b9a4ad513 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 10:07:57 +0300 Subject: [PATCH 03/17] include std modules in tlang front --- examples/sources/main.tea | 22 ++++++------ src/CMakeLists.txt | 3 ++ src/Constants.h | 2 ++ src/cli/ArgumentsReader.cpp | 33 +++++++----------- src/cli/ArgumentsReader.h | 6 ++-- src/cli/main.cpp | 41 ++++++++++++++++++----- src/compilation/FrontendConfiguration.cpp | 17 ++++++++++ src/compilation/FrontendConfiguration.h | 4 ++- src/sources/SourceManager.cpp | 4 ++- std/include/io.tea | 4 +-- 10 files changed, 91 insertions(+), 45 deletions(-) create mode 100644 src/compilation/FrontendConfiguration.cpp diff --git a/examples/sources/main.tea b/examples/sources/main.tea index 8dd1d51..0720717 100644 --- a/examples/sources/main.tea +++ b/examples/sources/main.tea @@ -1,15 +1,17 @@ -import "rectangle" import "io" -import "math" main: () -> i64 = { - print(math::factorial(6)); +// print(math::factorial(6)); +// +// rect: geometry::Rectangle = geometry::make_rectangle(1 as! u64, 2 as! u64, 3, 4); +// +// rect +// .geometry::move(1, 2) +// .geometry::print_rect(); +// +// return 0; - rect: geometry::Rectangle = geometry::make_rectangle(1 as! u64, 2 as! u64, 3, 4); - - rect - .geometry::move(1, 2) - .geometry::print_rect(); - - return 0; + println(1); + println(2); + println(3); } \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cf86c59..c6f84f8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,3 +35,6 @@ target_include_directories(TeaLang PUBLIC ${LLVM_INCLUDE_DIRS}) # TeaLang requires grammar and lexis tables to work add_dependencies(TeaLang run_grammar_tablegen run_lexis_tablegen) + +# TeaLang comes with a standard library +add_dependencies(TeaLang std std_headers) diff --git a/src/Constants.h b/src/Constants.h index 998183e..e7cc615 100644 --- a/src/Constants.h +++ b/src/Constants.h @@ -16,6 +16,8 @@ extern const bool is_installed_build; constexpr inline auto lexis_relative_filepath = "lexis/lexis.lx"; constexpr inline auto grammar_relative_filepath = "grammar/grammar.lr"; +constexpr inline auto std_library_relative_filepath = "std/lib/libstd.a"; +constexpr inline auto std_include_relative_path = "std/include"; inline std::filesystem::path GetRuntimeFilePath( std::filesystem::path relative_path) { diff --git a/src/cli/ArgumentsReader.cpp b/src/cli/ArgumentsReader.cpp index f608473..9327121 100644 --- a/src/cli/ArgumentsReader.cpp +++ b/src/cli/ArgumentsReader.cpp @@ -12,17 +12,9 @@ namespace fs = std::filesystem; namespace Cli { -SourcesList ArgumentsReader::parse_source_paths( - std::vector sources) { - SourcesList result; - auto add_source = [&result](const std::string& name, const fs::path& path) { - auto [_, was_emplaced] = result.emplace(name, path); - if (!was_emplaced) { - throw std::runtime_error(fmt::format( - "File {} was already included with different name.", path.c_str())); - } - }; - +void ArgumentsReader::parse_source_paths( + const std::vector& sources, + Front::TeaFrontendConfiguration& config) { std::string separator{fs::path::preferred_separator}; for (auto& include : sources) { @@ -42,7 +34,7 @@ SourcesList ArgumentsReader::parse_source_paths( "Named include \"{}\" must refer to regular file.", name)); } - add_source(name, path); + config.add_source(name, path); } else { // unnamed include // for this type of include name is a stem part of path @@ -60,7 +52,7 @@ SourcesList ArgumentsReader::parse_source_paths( auto include_name = std::regex_replace(std::string{path_copy}, std::regex(separator), "."); - add_source(include_name, path); + config.add_source(include_name, path); continue; } @@ -85,12 +77,10 @@ SourcesList ArgumentsReader::parse_source_paths( auto include_name = std::regex_replace(relative_path, std::regex(separator), "."); - add_source(include_name, subfile.path()); + config.add_source(include_name, subfile.path()); } } } - - return result; } std::filesystem::path ArgumentsReader::parse_output(const std::string& output) { @@ -111,8 +101,11 @@ Front::EmitType ArgumentsReader::get_emit_type(std::string_view name) { if (name == "ast") { return Front::EmitType::AST; } - if (name == "binary") { - return Front::EmitType::BINARY; + if (name == "obj") { + return Front::EmitType::OBJECT; + } + if (name == "exe") { + return Front::EmitType::EXECUTABLE; } throw std::runtime_error("unknown compiler emit type."); } @@ -145,8 +138,8 @@ Front::TeaFrontendConfiguration ArgumentsReader::read(int argc, char* argv[]) { } Front::TeaFrontendConfiguration result; - result.sources = - parse_source_paths(parser.get>("sources")); + + parse_source_paths(parser.get>("sources"), result); result.emit_type = get_emit_type(parser.get("emit")); result.output_file = parse_output(parser.get("output")); diff --git a/src/cli/ArgumentsReader.h b/src/cli/ArgumentsReader.h index db4cbe3..2b8548d 100644 --- a/src/cli/ArgumentsReader.h +++ b/src/cli/ArgumentsReader.h @@ -9,14 +9,13 @@ #include "compilation/FrontendConfiguration.h" namespace Cli { -// import name / filepath -using SourcesList = std::unordered_map; class ArgumentsReader { constexpr static auto kDefaultOutputName = "out"; constexpr static auto kSourceNamePathDelimiter = ":"; - static SourcesList parse_source_paths(std::vector sources); + static void parse_source_paths(const std::vector& sources, + Front::TeaFrontendConfiguration& config); static std::filesystem::path parse_output(const std::string& output); @@ -25,4 +24,5 @@ class ArgumentsReader { public: static Front::TeaFrontendConfiguration read(int argc, char* argv[]); }; + } // namespace Cli diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 21771d9..d87bc47 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -22,6 +22,22 @@ namespace Cli { namespace fs = std::filesystem; class Main { + static void add_std_includes(Front::TeaFrontendConfiguration& config) { + const char* std_filenames[] = {"io"}; + + for (const char* name : std_filenames) { + auto path = Constants::GetRuntimeFilePath( + fs::path(Constants::std_include_relative_path) / name); + path.replace_extension(".tea"); + + auto [_, inserted] = config.sources.emplace(name, path); + + if (!inserted) { + throw std::runtime_error("Duplicate module name."); + } + } + } + static void emit_ir(std::unique_ptr module, const Front::TeaFrontendConfiguration& config) { std::ofstream ofs; @@ -39,7 +55,7 @@ class Main { module->print(llvm_out, nullptr); } - static void emit_binary(std::unique_ptr module, + static void emit_object(std::unique_ptr module, const Front::TeaFrontendConfiguration& config) { llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmParser(); @@ -81,11 +97,11 @@ class Main { } llvm::legacy::PassManager pass; - auto file_type = llvm::CodeGenFileType::ObjectFile; - if (target_machine->addPassesToEmitFile(pass, dest, nullptr, file_type)) { + if (target_machine->addPassesToEmitFile( + pass, dest, nullptr, llvm::CodeGenFileType::ObjectFile)) { throw std::runtime_error( - "Target machine doesn't support binary output format."); + "Target machine doesn't support object output format."); } pass.run(*module); @@ -97,7 +113,10 @@ class Main { return ExceptionsHandler::execute([argc, argv] { auto config = ArgumentsReader::read(argc, argv); + add_std_includes(config); + auto front = Front::TeaFrontend(config); + auto llvm_module = front.compile(); if (config.emit_type == Front::EmitType::AST) { @@ -106,10 +125,16 @@ class Main { assert(llvm_module != nullptr); - if (config.emit_type == Front::EmitType::IR) { - emit_ir(std::move(llvm_module), config); - } else if (config.emit_type == Front::EmitType::BINARY) { - emit_binary(std::move(llvm_module), config); + switch (config.emit_type) { + case Front::EmitType::IR: + emit_ir(std::move(llvm_module), config); + break; + case Front::EmitType::OBJECT: + emit_object(std::move(llvm_module), config); + break; + case Front::EmitType::EXECUTABLE: + // TODO: + break; } }); } diff --git a/src/compilation/FrontendConfiguration.cpp b/src/compilation/FrontendConfiguration.cpp new file mode 100644 index 0000000..b3262aa --- /dev/null +++ b/src/compilation/FrontendConfiguration.cpp @@ -0,0 +1,17 @@ +#include "FrontendConfiguration.h" + +#include + +namespace Front { + +void TeaFrontendConfiguration::add_source(std::string name, + std::filesystem::path path) { + auto [_, inserted] = sources.emplace(name, path); + + if (!inserted) { + throw std::runtime_error(fmt::format( + "File {} was already included with different name.", path.c_str())); + } +} + +} // namespace Front diff --git a/src/compilation/FrontendConfiguration.h b/src/compilation/FrontendConfiguration.h index f4e083d..416763a 100644 --- a/src/compilation/FrontendConfiguration.h +++ b/src/compilation/FrontendConfiguration.h @@ -6,12 +6,14 @@ namespace Front { -enum class EmitType { AST, IR, BINARY }; +enum class EmitType { AST, IR, OBJECT, EXECUTABLE }; struct TeaFrontendConfiguration { std::unordered_map sources; std::filesystem::path output_file; EmitType emit_type; + + void add_source(std::string name, std::filesystem::path path); }; } // namespace Front diff --git a/src/sources/SourceManager.cpp b/src/sources/SourceManager.cpp index e27cdec..4f292b1 100644 --- a/src/sources/SourceManager.cpp +++ b/src/sources/SourceManager.cpp @@ -1,6 +1,7 @@ #include "SourceManager.h" #include +#include #include #include #include @@ -15,7 +16,8 @@ SourceView SourceManager::load(const std::filesystem::path& path) { int fd = open(path.c_str(), O_RDWR); if (fd == -1) { - throw std::runtime_error("Failed to open source file."); + throw std::runtime_error( + fmt::format("Failed to open source file at {:?}.", path.string())); } struct stat statbuf; diff --git a/std/include/io.tea b/std/include/io.tea index f6b0e9a..0e026c9 100644 --- a/std/include/io.tea +++ b/std/include/io.tea @@ -1,3 +1,3 @@ -export extern print(x: i64) -> (); +export extern print: (x: i64) -> () -export extern println(x: i64) -> (); \ No newline at end of file +export extern println: (x: i64) -> () \ No newline at end of file From 4141299e5a44f845b3b2af51b91bf6e753e209ba Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 10:27:47 +0300 Subject: [PATCH 04/17] open sources as read-only, fix descriptors leakage, add defer helper --- src/sources/SourceManager.cpp | 7 +++- src/utils/Defer.h | 13 +++++++ tests/unit/sources/SourceManagerTestCase.h | 36 ++++++++++++++++++ tests/unit/sources/SourceManagerTests.cpp | 43 ++++++++++++++++++++++ 4 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 src/utils/Defer.h create mode 100644 tests/unit/sources/SourceManagerTestCase.h create mode 100644 tests/unit/sources/SourceManagerTests.cpp diff --git a/src/sources/SourceManager.cpp b/src/sources/SourceManager.cpp index 4f292b1..9593972 100644 --- a/src/sources/SourceManager.cpp +++ b/src/sources/SourceManager.cpp @@ -7,19 +7,24 @@ #include #include #include +#include #include #include #include +#include "utils/Defer.h" + SourceView SourceManager::load(const std::filesystem::path& path) { - int fd = open(path.c_str(), O_RDWR); + const int fd = open(path.c_str(), O_RDONLY); if (fd == -1) { throw std::runtime_error( fmt::format("Failed to open source file at {:?}.", path.string())); } + const auto defer = Defer([&fd] { close(fd); }); + struct stat statbuf; if (fstat(fd, &statbuf) == -1) { throw std::runtime_error("Failed to read file stat."); diff --git a/src/utils/Defer.h b/src/utils/Defer.h new file mode 100644 index 0000000..5cfc6b0 --- /dev/null +++ b/src/utils/Defer.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +template +class Defer { + F callback_; + + public: + explicit Defer(F callback) : callback_(std::move(callback)) {} + + ~Defer() { std::invoke(callback_); } +}; diff --git a/tests/unit/sources/SourceManagerTestCase.h b/tests/unit/sources/SourceManagerTestCase.h new file mode 100644 index 0000000..d751e35 --- /dev/null +++ b/tests/unit/sources/SourceManagerTestCase.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include "sources/SourceManager.h" + +class SourceManagerTestCase : public ::testing::Test { + private: + std::vector created_; + + protected: + std::filesystem::path create_source(std::string_view content, + std::filesystem::perms permissions) { + auto name = ::testing::UnitTest::GetInstance()->current_test_info()->name(); + std::filesystem::path path = + std::filesystem::temp_directory_path() / + fmt::format("tlang_{}_{}.tea", name, created_.size()); + + std::ofstream(path) << content; + std::filesystem::permissions(path, permissions); + created_.push_back(path); + + return path; + } + + void TearDown() override { + for (const std::filesystem::path& path : created_) { + std::filesystem::remove(path); + } + } +}; diff --git a/tests/unit/sources/SourceManagerTests.cpp b/tests/unit/sources/SourceManagerTests.cpp new file mode 100644 index 0000000..fece05d --- /dev/null +++ b/tests/unit/sources/SourceManagerTests.cpp @@ -0,0 +1,43 @@ +#include +#include + +#include "SourceManagerTestCase.h" + +namespace { + +constexpr std::string_view kProgram = "main: () -> i64 = { return 0; }"; + +// open returns the lowest unused descriptor, so this number grows whenever a +// descriptor is leaked. +int lowest_unused_descriptor() { + int fd = open("/dev/null", O_RDONLY); + close(fd); + return fd; +} + +} // namespace + +TEST_F(SourceManagerTestCase, test_it_loads_read_only_file) { + if (geteuid() == 0) { + GTEST_SKIP() << "root opens files regardless of their permissions"; + } + + auto path = create_source(kProgram, std::filesystem::perms::owner_read); + + SourceManager source_manager; + SourceView view = source_manager.load(path); + + ASSERT_EQ(view.string_view(), kProgram); +} + +TEST_F(SourceManagerTestCase, test_it_closes_descriptor_after_load) { + auto path = create_source(kProgram, std::filesystem::perms::owner_all); + + SourceManager source_manager; + + int before = lowest_unused_descriptor(); + source_manager.load(path); + int after = lowest_unused_descriptor(); + + ASSERT_EQ(before, after); +} From 57a838b27d88487f259d1bed9b9e1a96b9c90aec Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 12:11:34 +0300 Subject: [PATCH 05/17] add executable emitting, add FileDescriptor class for RAII --- examples/sources/main.tea | 2 + src/cli/ArgumentsReader.cpp | 11 ++- src/cli/main.cpp | 96 ++++++++++++++++++------- src/compilation/FrontendConfiguration.h | 17 +++++ src/utils/FileDescriptor.h | 44 ++++++++++++ 5 files changed, 140 insertions(+), 30 deletions(-) create mode 100644 src/utils/FileDescriptor.h diff --git a/examples/sources/main.tea b/examples/sources/main.tea index 0720717..13d0201 100644 --- a/examples/sources/main.tea +++ b/examples/sources/main.tea @@ -14,4 +14,6 @@ main: () -> i64 = { println(1); println(2); println(3); + + return 0; } \ No newline at end of file diff --git a/src/cli/ArgumentsReader.cpp b/src/cli/ArgumentsReader.cpp index 9327121..17fae6e 100644 --- a/src/cli/ArgumentsReader.cpp +++ b/src/cli/ArgumentsReader.cpp @@ -84,6 +84,11 @@ void ArgumentsReader::parse_source_paths( } std::filesystem::path ArgumentsReader::parse_output(const std::string& output) { + // empty output means that result is written to stdout + if (output.empty()) { + return {}; + } + fs::path output_path = output; // output must be directory or path @@ -91,7 +96,7 @@ std::filesystem::path ArgumentsReader::parse_output(const std::string& output) { output_path /= kDefaultOutputName; } - return output_path; + return fs::absolute(output_path).lexically_normal(); } Front::EmitType ArgumentsReader::get_emit_type(std::string_view name) { @@ -127,9 +132,9 @@ Front::TeaFrontendConfiguration ArgumentsReader::read(int argc, char* argv[]) { .help("output file (stdout by default)"); parser.add_argument("--emit") - .choices("ir", "ast", "binary") + .choices("ir", "ast", "obj", "exe") .default_value("ir") - .help("compiler output type: `ir` or `ast`"); + .help("compiler output type: ir, ast, obj, exe"); try { parser.parse_args(argc, argv); diff --git a/src/cli/main.cpp b/src/cli/main.cpp index d87bc47..6f51aaf 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -15,6 +17,8 @@ #include "errors/ExceptionsHandler.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/Support/FileSystem.h" +#include "utils/Defer.h" +#include "utils/FileDescriptor.h" const bool Constants::is_installed_build = BUILD_FOR_INSTALLATION; @@ -22,6 +26,42 @@ namespace Cli { namespace fs = std::filesystem; class Main { + static FileDescriptor get_output_fd(const std::filesystem::path& output_path, + Front::EmitType emit_type) { + const bool prohibit_stdout = + emit_type != Front::EmitType::AST && emit_type != Front::EmitType::IR; + const bool is_executable = emit_type == Front::EmitType::EXECUTABLE; + + if (output_path.empty()) { + if (prohibit_stdout) { + throw std::runtime_error( + fmt::format("Can't emit {} to stdout. Specify output file path.", + to_string(emit_type))); + } + + int stdout_fd = fileno(stdout); + if (stdout_fd == -1) { + throw std::runtime_error("Failed to open stdout."); + } + + int fd = dup(stdout_fd); + if (fd == -1) { + throw std::runtime_error("Failed to duplicate stdout fd."); + } + + return FileDescriptor(fd); + } + + mode_t mode = S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR; + if (is_executable) { + mode |= S_IXGRP | S_IXUSR; + } + + int fd = open(output_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); + + return FileDescriptor(fd); + } + static void add_std_includes(Front::TeaFrontendConfiguration& config) { const char* std_filenames[] = {"io"}; @@ -39,24 +79,13 @@ class Main { } static void emit_ir(std::unique_ptr module, - const Front::TeaFrontendConfiguration& config) { - std::ofstream ofs; - - std::ostream& out = [&]() -> std::ostream& { - if (config.output_file.empty()) { - return std::cout; - } - - ofs.open(config.output_file); - return ofs; - }(); - - llvm::raw_os_ostream llvm_out(out); + const FileDescriptor& fd) { + llvm::raw_fd_ostream llvm_out(fd.get(), false); module->print(llvm_out, nullptr); } static void emit_object(std::unique_ptr module, - const Front::TeaFrontendConfiguration& config) { + const FileDescriptor& fd) { llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmParser(); llvm::InitializeNativeTargetAsmPrinter(); @@ -86,16 +115,7 @@ class Main { module->setDataLayout(target_machine->createDataLayout()); - std::error_code error_code; - llvm::raw_fd_ostream dest( - config.output_file.empty() ? "-" : config.output_file.c_str(), - error_code, llvm::sys::fs::OF_None); - - if (error_code) { - throw std::runtime_error( - fmt::format("Could not open file: {}.", error_code.message())); - } - + llvm::raw_fd_ostream dest(fd.get(), false); llvm::legacy::PassManager pass; if (target_machine->addPassesToEmitFile( @@ -108,6 +128,26 @@ class Main { dest.flush(); } + static void emit_executable(std::unique_ptr module, + const FileDescriptor& fd) { + const auto tmp_fd = FileDescriptor::make_temp(); + + // write object file + emit_object(std::move(module), tmp_fd); + + // link with std + const auto std_path = + Constants::GetRuntimeFilePath(Constants::std_library_relative_filepath); + const auto link_command = + fmt::format("clang++ {} /dev/fd/{} -o /dev/fd/{}", std_path.string(), + tmp_fd.get(), fd.get()); + + int link_status = system(link_command.c_str()); + if (link_status == -1) { + throw std::runtime_error("Error during linking."); + } + } + public: static int main(int argc, char* argv[]) { return ExceptionsHandler::execute([argc, argv] { @@ -125,15 +165,17 @@ class Main { assert(llvm_module != nullptr); + const auto fd = get_output_fd(config.output_file, config.emit_type); + switch (config.emit_type) { case Front::EmitType::IR: - emit_ir(std::move(llvm_module), config); + emit_ir(std::move(llvm_module), fd); break; case Front::EmitType::OBJECT: - emit_object(std::move(llvm_module), config); + emit_object(std::move(llvm_module), fd); break; case Front::EmitType::EXECUTABLE: - // TODO: + emit_executable(std::move(llvm_module), fd); break; } }); diff --git a/src/compilation/FrontendConfiguration.h b/src/compilation/FrontendConfiguration.h index 416763a..9263a09 100644 --- a/src/compilation/FrontendConfiguration.h +++ b/src/compilation/FrontendConfiguration.h @@ -4,6 +4,8 @@ #include #include +#include "errors/Helpers.h" + namespace Front { enum class EmitType { AST, IR, OBJECT, EXECUTABLE }; @@ -16,4 +18,19 @@ struct TeaFrontendConfiguration { void add_source(std::string name, std::filesystem::path path); }; +inline std::string to_string(EmitType emit_type) { + switch (emit_type) { + case EmitType::AST: + return "AST"; + case EmitType::IR: + return "IR"; + case EmitType::OBJECT: + return "OBJ"; + case EmitType::EXECUTABLE: + return "EXE"; + } + + unreachable("All emit types should be enumerated above."); +} + } // namespace Front diff --git a/src/utils/FileDescriptor.h b/src/utils/FileDescriptor.h new file mode 100644 index 0000000..360391b --- /dev/null +++ b/src/utils/FileDescriptor.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +class FileDescriptor { + int fd_; + + public: + explicit FileDescriptor(int fd) : fd_(fd) {} + + FileDescriptor(const FileDescriptor&) = delete; + FileDescriptor& operator=(const FileDescriptor&) = delete; + + FileDescriptor(FileDescriptor&& other) noexcept : fd_(other.fd_) { + other.fd_ = -1; + } + + static FileDescriptor make_temp() { + char filename[] = "/tmp/tlang.XXXXXX"; + int tmp_fd = mkstemp(filename); + + if (tmp_fd == -1) { + throw std::runtime_error( + fmt::format("Could not open file: {}.", strerror(errno))); + } + + return FileDescriptor(tmp_fd); + } + + FileDescriptor& operator=(FileDescriptor&& other) noexcept { + fd_ = other.fd_; + other.fd_ = -1; + + return *this; + } + + int get() const { return fd_; } + + ~FileDescriptor() { + if (fd_ != -1) { + close(fd_); + } + } +}; From e4e1d1e5d3bb7d382b21d6893802bdd1da6980c3 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 14:32:05 +0300 Subject: [PATCH 06/17] change output name --- src/cli/ArgumentsReader.cpp | 40 ++++++++++++++++++++++++++++--------- src/cli/ArgumentsReader.h | 6 ++++-- src/cli/main.cpp | 2 ++ 3 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/cli/ArgumentsReader.cpp b/src/cli/ArgumentsReader.cpp index 17fae6e..57bdf95 100644 --- a/src/cli/ArgumentsReader.cpp +++ b/src/cli/ArgumentsReader.cpp @@ -12,6 +12,20 @@ namespace fs = std::filesystem; namespace Cli { +std::string ArgumentsReader::get_default_output_name(Front::EmitType type) { + switch (type) { + case Front::EmitType::AST: + case Front::EmitType::IR: + return "out.txt"; + case Front::EmitType::OBJECT: + return "out.o"; + case Front::EmitType::EXECUTABLE: + return "out"; + } + + unreachable("All output types should be enumerated above."); +} + void ArgumentsReader::parse_source_paths( const std::vector& sources, Front::TeaFrontendConfiguration& config) { @@ -83,17 +97,27 @@ void ArgumentsReader::parse_source_paths( } } -std::filesystem::path ArgumentsReader::parse_output(const std::string& output) { - // empty output means that result is written to stdout +std::filesystem::path ArgumentsReader::parse_output(std::string output, + Front::EmitType emit_type) { + // empty output means that default value is used if (output.empty()) { - return {}; + switch (emit_type) { + case Front::EmitType::AST: + case Front::EmitType::IR: + // write to stdout + return {}; + case Front::EmitType::OBJECT: + case Front::EmitType::EXECUTABLE: + output = get_default_output_name(emit_type); + break; + } } fs::path output_path = output; // output must be directory or path if (fs::is_directory(output)) { - output_path /= kDefaultOutputName; + output_path /= get_default_output_name(emit_type); } return fs::absolute(output_path).lexically_normal(); @@ -127,13 +151,11 @@ Front::TeaFrontendConfiguration ArgumentsReader::read(int argc, char* argv[]) { "automatically or to include all files in " "directory recursively."); - parser.add_argument("-o", "--output") - .default_value("") - .help("output file (stdout by default)"); + parser.add_argument("-o", "--output").default_value("").help("output file"); parser.add_argument("--emit") .choices("ir", "ast", "obj", "exe") - .default_value("ir") + .default_value("exe") .help("compiler output type: ir, ast, obj, exe"); try { @@ -146,7 +168,7 @@ Front::TeaFrontendConfiguration ArgumentsReader::read(int argc, char* argv[]) { parse_source_paths(parser.get>("sources"), result); result.emit_type = get_emit_type(parser.get("emit")); - result.output_file = parse_output(parser.get("output")); + result.output_file = parse_output(parser.get("output"), result.emit_type); return result; } diff --git a/src/cli/ArgumentsReader.h b/src/cli/ArgumentsReader.h index 2b8548d..282372a 100644 --- a/src/cli/ArgumentsReader.h +++ b/src/cli/ArgumentsReader.h @@ -11,13 +11,15 @@ namespace Cli { class ArgumentsReader { - constexpr static auto kDefaultOutputName = "out"; constexpr static auto kSourceNamePathDelimiter = ":"; + static std::string get_default_output_name(Front::EmitType type); + static void parse_source_paths(const std::vector& sources, Front::TeaFrontendConfiguration& config); - static std::filesystem::path parse_output(const std::string& output); + static std::filesystem::path parse_output(std::string output, + Front::EmitType emit_type); static Front::EmitType get_emit_type(std::string_view name); diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 6f51aaf..cda13bd 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -168,6 +168,8 @@ class Main { const auto fd = get_output_fd(config.output_file, config.emit_type); switch (config.emit_type) { + case Front::EmitType::AST: + assert(false && "Should've been handled above"); case Front::EmitType::IR: emit_ir(std::move(llvm_module), fd); break; From 2993d7434949c970abdc7c9b376b4bc5e4099878 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 14:36:31 +0300 Subject: [PATCH 07/17] add working example --- examples/Makefile | 18 ++++-------------- examples/sources/io.team | 1 - examples/sources/main.tea | 19 ++++++++++--------- examples/sources/print.cpp | 23 ----------------------- 4 files changed, 14 insertions(+), 47 deletions(-) delete mode 100644 examples/sources/io.team delete mode 100644 examples/sources/print.cpp diff --git a/examples/Makefile b/examples/Makefile index 095c37c..040268f 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,19 +1,9 @@ -build/print.s : sources/print.cpp +build/main : sources/main.tea sources/rectangle.team sources/math.team mkdir -p build - clang++ sources/print.cpp -S -o build/print.s + tlang main:sources/main.tea rectangle:sources/rectangle.team math:sources/math.team -o build/main -build/main.s : sources/main.tea sources/io.team sources/rectangle.team sources/math.team - mkdir -p build - tlang main:sources/main.tea io:sources/io.team rectangle:sources/rectangle.team math:sources/math.team -o build/main.ll - /opt/homebrew/Cellar/llvm/22.1.7_1/bin/llc build/main.ll -o build/main.s - -exe/main: build/main.s build/print.s - mkdir -p exe - clang++ build/main.s build/print.s -o exe/main - -run_code: exe/main - ./exe/main +run_code: build/main + ./build/main clean: - rm -rf exe rm -rf build \ No newline at end of file diff --git a/examples/sources/io.team b/examples/sources/io.team deleted file mode 100644 index 4a287f7..0000000 --- a/examples/sources/io.team +++ /dev/null @@ -1 +0,0 @@ -export extern print: (value: i64) -> () \ No newline at end of file diff --git a/examples/sources/main.tea b/examples/sources/main.tea index 13d0201..e131813 100644 --- a/examples/sources/main.tea +++ b/examples/sources/main.tea @@ -1,15 +1,16 @@ import "io" +import "math" +import "rectangle" + main: () -> i64 = { -// print(math::factorial(6)); -// -// rect: geometry::Rectangle = geometry::make_rectangle(1 as! u64, 2 as! u64, 3, 4); -// -// rect -// .geometry::move(1, 2) -// .geometry::print_rect(); -// -// return 0; + println(math::factorial(6)); + + rect: geometry::Rectangle = geometry::make_rectangle(1 as! u64, 2 as! u64, 3, 4); + + rect + .geometry::move(1, 2) + .geometry::print_rect(); println(1); println(2); diff --git a/examples/sources/print.cpp b/examples/sources/print.cpp deleted file mode 100644 index d509484..0000000 --- a/examples/sources/print.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -struct Rectangle { - int64_t x; - int64_t y; - int64_t width; - int64_t height; - - Rectangle(int64_t x, int64_t y, int64_t width, int64_t height): - x(x), - y(y), - width(width), - height(height) {} -}; - -void print(long long value) { - std::cout << value << std::endl; -} - -Rectangle get_rectangle() { - return Rectangle(1, 2, 3, 4); -} - From 9b96fce05db4037fc1af5db18d36580f460f3a67 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 14:46:09 +0300 Subject: [PATCH 08/17] check open errors in main.cpp --- src/cli/main.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index cda13bd..34d67fe 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -59,6 +59,11 @@ class Main { int fd = open(output_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); + if (fd == -1) { + throw std::runtime_error( + fmt::format("Failed to open output file: {}.", strerror(errno))); + } + return FileDescriptor(fd); } From 4dd8b38693bde1588c190df81014c7a7f9360f90 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 14:47:11 +0300 Subject: [PATCH 09/17] check for link status in main.cpp --- src/cli/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 34d67fe..ef2f3f7 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -148,8 +148,8 @@ class Main { tmp_fd.get(), fd.get()); int link_status = system(link_command.c_str()); - if (link_status == -1) { - throw std::runtime_error("Error during linking."); + if (link_status != 0) { + throw std::runtime_error("Failed to link with clang++."); } } From d5767d2aedfc45a3ad6812729427bf38465850ce Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 14:51:13 +0300 Subject: [PATCH 10/17] unlink temp file in make_temp --- src/utils/FileDescriptor.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/utils/FileDescriptor.h b/src/utils/FileDescriptor.h index 360391b..b17c006 100644 --- a/src/utils/FileDescriptor.h +++ b/src/utils/FileDescriptor.h @@ -24,6 +24,8 @@ class FileDescriptor { fmt::format("Could not open file: {}.", strerror(errno))); } + unlink(filename); + return FileDescriptor(tmp_fd); } From 2817286e38e0b873d683fd26daf60fbe1ae1341e Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 15:16:38 +0300 Subject: [PATCH 11/17] report link errors --- src/compilation/TeaFrontend.cpp | 10 +++++++++- tests/lit/errors/duplicate_main/main.tea | 7 +++++++ tests/lit/errors/duplicate_main/second.team | 3 +++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/lit/errors/duplicate_main/main.tea create mode 100644 tests/lit/errors/duplicate_main/second.team diff --git a/src/compilation/TeaFrontend.cpp b/src/compilation/TeaFrontend.cpp index 9e4edd9..f698472 100644 --- a/src/compilation/TeaFrontend.cpp +++ b/src/compilation/TeaFrontend.cpp @@ -246,7 +246,15 @@ std::unique_ptr TeaFrontend::compile() { llvm::Linker linker(*main_module); for (auto& module : llvm_modules_) { - linker.linkInModule(std::move(module)); + // remember module's name for error message + const std::string name = module->getName().str(); + + const bool has_error = linker.linkInModule(std::move(module)); + + if (has_error) { + throw std::runtime_error( + fmt::format("Error during linking of module {:?}.", name)); + } } llvm_modules_.clear(); diff --git a/tests/lit/errors/duplicate_main/main.tea b/tests/lit/errors/duplicate_main/main.tea new file mode 100644 index 0000000..66f7e18 --- /dev/null +++ b/tests/lit/errors/duplicate_main/main.tea @@ -0,0 +1,7 @@ +// RUN: not %tlang main:%S/main.tea second:%S/second.team --emit ir 2>&1 | %FileCheck %s + +// CHECK-NOT: ModuleID + +main: () -> i64 = { + return 0; +} diff --git a/tests/lit/errors/duplicate_main/second.team b/tests/lit/errors/duplicate_main/second.team new file mode 100644 index 0000000..9c0e27b --- /dev/null +++ b/tests/lit/errors/duplicate_main/second.team @@ -0,0 +1,3 @@ +main: () -> i64 = { + return 1; +} From 0d2273b439768ca8c112fcfa3cc118f6e78fe7d8 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 15:29:13 +0300 Subject: [PATCH 12/17] small tweaks --- src/cli/main.cpp | 4 +--- src/utils/Defer.h | 16 ++++++++++++++-- src/utils/FileDescriptor.h | 6 ++++++ tests/unit/utils/DeferTests.cpp | 15 +++++++++++++++ 4 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 tests/unit/utils/DeferTests.cpp diff --git a/src/cli/main.cpp b/src/cli/main.cpp index ef2f3f7..cbaf90f 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -9,7 +8,6 @@ #include #include -#include #include "ArgumentsReader.h" #include "Constants.h" @@ -17,7 +15,6 @@ #include "errors/ExceptionsHandler.h" #include "llvm/IR/LegacyPassManager.h" #include "llvm/Support/FileSystem.h" -#include "utils/Defer.h" #include "utils/FileDescriptor.h" const bool Constants::is_installed_build = BUILD_FOR_INSTALLATION; @@ -175,6 +172,7 @@ class Main { switch (config.emit_type) { case Front::EmitType::AST: assert(false && "Should've been handled above"); + break; case Front::EmitType::IR: emit_ir(std::move(llvm_module), fd); break; diff --git a/src/utils/Defer.h b/src/utils/Defer.h index 5cfc6b0..0305331 100644 --- a/src/utils/Defer.h +++ b/src/utils/Defer.h @@ -4,10 +4,22 @@ template class Defer { - F callback_; + std::optional callback_; public: explicit Defer(F callback) : callback_(std::move(callback)) {} - ~Defer() { std::invoke(callback_); } + Defer(const Defer&) = delete; + Defer& operator=(const Defer&) = delete; + + Defer(Defer&& other) noexcept + : callback_(std::exchange(other.callback_, std::nullopt)) {} + + Defer& operator=(Defer&&) = delete; + + ~Defer() { + if (callback_) { + std::invoke(*callback_); + } + } }; diff --git a/src/utils/FileDescriptor.h b/src/utils/FileDescriptor.h index b17c006..3d90252 100644 --- a/src/utils/FileDescriptor.h +++ b/src/utils/FileDescriptor.h @@ -1,7 +1,13 @@ #pragma once +#include #include +#include +#include +#include +#include + class FileDescriptor { int fd_; diff --git a/tests/unit/utils/DeferTests.cpp b/tests/unit/utils/DeferTests.cpp new file mode 100644 index 0000000..c8bbee5 --- /dev/null +++ b/tests/unit/utils/DeferTests.cpp @@ -0,0 +1,15 @@ +#include + +#include "utils/Defer.h" + +TEST(DeferTests, move_construct) { + int counter = 0; + + { + auto a = Defer([&counter] { ++counter; }); + + auto b = std::move(a); + } + + ASSERT_EQ(counter, 1); +} From 054f0eabcfb87b5fd2bef89ad35c3c87addaeff3 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 15:42:25 +0300 Subject: [PATCH 13/17] use std in lit testing --- src/cli/main.cpp | 2 +- testing.sh | 2 +- tests/lit/CMakeLists.txt | 4 +--- tests/lit/execution/CMakeLists.txt | 6 ------ tests/lit/execution/executor.py | 18 +++++------------- tests/lit/execution/library.cpp | 9 --------- tests/lit/lit.cfg.py | 2 +- tests/lit/lit.site.cfg.py.in | 3 --- 8 files changed, 9 insertions(+), 37 deletions(-) delete mode 100644 tests/lit/execution/CMakeLists.txt delete mode 100644 tests/lit/execution/library.cpp diff --git a/src/cli/main.cpp b/src/cli/main.cpp index cbaf90f..f734f49 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -141,7 +141,7 @@ class Main { const auto std_path = Constants::GetRuntimeFilePath(Constants::std_library_relative_filepath); const auto link_command = - fmt::format("clang++ {} /dev/fd/{} -o /dev/fd/{}", std_path.string(), + fmt::format("clang++ /dev/fd/{} {} -o /dev/fd/{}", std_path.string(), tmp_fd.get(), fd.get()); int link_status = system(link_command.c_str()); diff --git a/testing.sh b/testing.sh index 4ed565c..28c50c5 100755 --- a/testing.sh +++ b/testing.sh @@ -4,7 +4,7 @@ mkdir build cd build || exit 1 cmake -DCMAKE_BUILD_TYPE=Debug .. || exit 1 -cmake --build . -t tests.unit cli tests.lit.execution.library || exit 1 +cmake --build . -t tests.unit cli || exit 1 ./tests/unit/tests.unit --gtest_output="xml:unit-report.xml" unit_status=$? diff --git a/tests/lit/CMakeLists.txt b/tests/lit/CMakeLists.txt index cf49797..6025e78 100644 --- a/tests/lit/CMakeLists.txt +++ b/tests/lit/CMakeLists.txt @@ -4,8 +4,6 @@ find_program(LLVM_FILE_CHECK NAMES "FileCheck" HINTS "${LLVM_TOOLS_BINARY_DIR}" find_program(LLVM_LLC NAMES "llc" HINTS "${LLVM_TOOLS_BINARY_DIR}" REQUIRED) find_program(LLVM_CLANG NAMES "clang++" REQUIRED) -add_subdirectory(execution) - # this handles variables substitution # like `@CMAKE_SOURCE_DIR@` configure_file( @@ -25,5 +23,5 @@ file(GENERATE add_custom_target( tests.lit COMMAND ${LLVM_LIT} "${CMAKE_CURRENT_BINARY_DIR}" -v --timeout=10 - DEPENDS cli tests.lit.execution.library + DEPENDS cli ) \ No newline at end of file diff --git a/tests/lit/execution/CMakeLists.txt b/tests/lit/execution/CMakeLists.txt deleted file mode 100644 index b0f6579..0000000 --- a/tests/lit/execution/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -add_library(tests.lit.execution.library STATIC library.cpp) -set_target_properties( - tests.lit.execution.library PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - LIBRARY_OUTPUT_NAME "library" -) \ No newline at end of file diff --git a/tests/lit/execution/executor.py b/tests/lit/execution/executor.py index 028e383..e59813f 100755 --- a/tests/lit/execution/executor.py +++ b/tests/lit/execution/executor.py @@ -7,23 +7,15 @@ def main(): - [_, tea_compiler, library, llc, clang, program] = sys.argv + [_, tea_compiler, _, _, program] = sys.argv with tempfile.TemporaryDirectory() as tempdir: - # compile tea file - ir = os.path.join(tempdir, "out.ll") - res = subprocess.run([tea_compiler, program, "--emit", "ir", "-o", ir]) - assert res.returncode == 0 - - # compile llvm ir into assembly - asm = os.path.join(tempdir, "out.s") - subprocess.run([llc, ir, "-o", asm]) - - # link our program and library + # compile exe = os.path.join(tempdir, "exe") - subprocess.run([clang, asm, library, "-o", exe]) + res = subprocess.run([tea_compiler, program, "--emit", "exe", "-o", exe]) + assert res.returncode == 0 - # run resulting program + # run subprocess.run([exe]) diff --git a/tests/lit/execution/library.cpp b/tests/lit/execution/library.cpp deleted file mode 100644 index 0be4a51..0000000 --- a/tests/lit/execution/library.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include - -void print(long long value) { - std::cout << value; -} - -void println(long long value) { - std::cout << value << "\n"; -} \ No newline at end of file diff --git a/tests/lit/lit.cfg.py b/tests/lit/lit.cfg.py index dc792b3..09a89b5 100644 --- a/tests/lit/lit.cfg.py +++ b/tests/lit/lit.cfg.py @@ -17,7 +17,7 @@ config.substitutions.append(("%clang", config.llvm_clang)) executor = os.path.join(config.src_root, 'tests/lit/execution/executor.py') -execute_order = f"{executor} {config.tea_path} {config.library} {config.llvm_llc} {config.llvm_clang}" +execute_order = f"{executor} {config.tea_path} {config.llvm_llc} {config.llvm_clang}" config.substitutions.append(("%execute", execute_order)) config.test_format = lit.formats.ShTest() diff --git a/tests/lit/lit.site.cfg.py.in b/tests/lit/lit.site.cfg.py.in index b0994e8..a761076 100644 --- a/tests/lit/lit.site.cfg.py.in +++ b/tests/lit/lit.site.cfg.py.in @@ -6,9 +6,6 @@ config.obj_root = r'@CMAKE_BINARY_DIR@' # tea compiler config.tea_path = r'$' -# library for execution tests -config.library = r'$' - # llvm tools config.llvm_filecheck = r'@LLVM_FILE_CHECK@' config.llvm_llc = r'@LLVM_LLC@' From a78cbe7984c8e7661dbc8e2596e962486fe31b00 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 15:48:59 +0300 Subject: [PATCH 14/17] fix ordering in format in main.cpp, unlink output file before writing, change output file mode --- src/cli/main.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index f734f49..5ef14e9 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -49,11 +49,13 @@ class Main { return FileDescriptor(fd); } - mode_t mode = S_IRGRP | S_IWGRP | S_IRUSR | S_IWUSR; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; if (is_executable) { mode |= S_IXGRP | S_IXUSR; } + unlink(output_path.c_str()); + int fd = open(output_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); if (fd == -1) { @@ -141,8 +143,8 @@ class Main { const auto std_path = Constants::GetRuntimeFilePath(Constants::std_library_relative_filepath); const auto link_command = - fmt::format("clang++ /dev/fd/{} {} -o /dev/fd/{}", std_path.string(), - tmp_fd.get(), fd.get()); + fmt::format("clang++ /dev/fd/{} {} -o /dev/fd/{}", tmp_fd.get(), + std_path.string(), fd.get()); int link_status = system(link_command.c_str()); if (link_status != 0) { From 2589fd9c198ff24bec6a508b9c7995daaaf22214 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 15:55:09 +0300 Subject: [PATCH 15/17] better error message for duplicate modules and test for it, add missing include in Defer --- src/cli/main.cpp | 5 ++++- src/utils/Defer.h | 1 + tests/lit/errors/module_conflict_with_std/culprit.team | 0 tests/lit/errors/module_conflict_with_std/main.tea | 9 +++++++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 tests/lit/errors/module_conflict_with_std/culprit.team create mode 100644 tests/lit/errors/module_conflict_with_std/main.tea diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 5ef14e9..b556cb6 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -77,7 +77,10 @@ class Main { auto [_, inserted] = config.sources.emplace(name, path); if (!inserted) { - throw std::runtime_error("Duplicate module name."); + throw std::runtime_error( + fmt::format("Your module {:?} can't use the same name as a " + "standard library module.", + name)); } } } diff --git a/src/utils/Defer.h b/src/utils/Defer.h index 0305331..f7b56a0 100644 --- a/src/utils/Defer.h +++ b/src/utils/Defer.h @@ -1,6 +1,7 @@ #pragma once #include +#include template class Defer { diff --git a/tests/lit/errors/module_conflict_with_std/culprit.team b/tests/lit/errors/module_conflict_with_std/culprit.team new file mode 100644 index 0000000..e69de29 diff --git a/tests/lit/errors/module_conflict_with_std/main.tea b/tests/lit/errors/module_conflict_with_std/main.tea new file mode 100644 index 0000000..57e6501 --- /dev/null +++ b/tests/lit/errors/module_conflict_with_std/main.tea @@ -0,0 +1,9 @@ +// RUN: not %tlang main:%S/main.tea io:%S/culprit.team 2>&1 | %FileCheck %s + +// CHECK: standard library + +import "io" + +main: () -> i64 = { + return 0; +} From ed6ddaaa87d17816f17d4946b3a49436c82d55b6 Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 16:08:25 +0300 Subject: [PATCH 16/17] quote paths in shell command --- src/cli/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/main.cpp b/src/cli/main.cpp index b556cb6..c3d8a58 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -146,7 +146,7 @@ class Main { const auto std_path = Constants::GetRuntimeFilePath(Constants::std_library_relative_filepath); const auto link_command = - fmt::format("clang++ /dev/fd/{} {} -o /dev/fd/{}", tmp_fd.get(), + fmt::format(R"(clang++ "/dev/fd/{}" "{}" -o "/dev/fd/{}")", tmp_fd.get(), std_path.string(), fd.get()); int link_status = system(link_command.c_str()); From 7889d0ccdb41b3e2341c7941096c59c7af88b2ab Mon Sep 17 00:00:00 2001 From: MishkaSimakov Date: Tue, 8 Sep 2026 16:26:02 +0300 Subject: [PATCH 17/17] run tests as non-root user --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 66f8c2a..3f2accc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,8 @@ jobs: - name: Build docker image run: docker build -t tlang-dev . - name: Run tests inside container - run: docker run -v $(pwd):/tlang -w /tlang --entrypoint /bin/sh tlang-dev -c ./testing.sh + # tests must not run as root, look at tests/unit/sources/SourceManagerTests.cpp to learn more + run: docker run --user $(id -u):$(id -g) -e HOME=/tmp -v $(pwd):/tlang -w /tlang --entrypoint /bin/sh tlang-dev -c ./testing.sh - name: Unit Test Report uses: dorny/test-reporter@v3 if: always()