From 5ccc812744d68bfe0df0f7ce1b48344e51e97368 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sat, 14 Feb 2026 23:44:59 +0300 Subject: [PATCH 1/8] src: added cast functionality using 'as' keyword --- src/parser.lv | 3 +++ stages/stage-latest.cpp | 8 +++++++- tests/test_cast.lv | 45 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 tests/test_cast.lv diff --git a/src/parser.lv b/src/parser.lv index d4e16ff..6e5b215 100644 --- a/src/parser.lv +++ b/src/parser.lv @@ -438,6 +438,9 @@ class Parser: elif this.match_any([TK_DOUBLE_COLON]): auto name = this.consume(TK_IDENTIFIER, "Expect member name after '::'.") expr = Expr::StaticGet(expr, name) + elif this.match_any([TK_AS]): + TypeNode target = this.parse_type() + expr = Expr::Cast(expr, target) else: more = false return expr diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index 63d87d8..59c571a 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -5569,7 +5569,13 @@ struct Parser { expr = Expr::make_StaticGet(expr, name); } else { - more = false; + if ((*this).match_any(std::vector{TK_AS})) { + TypeNode target = (*this).parse_type(); + expr = Expr::make_Cast(expr, target); + } + else { + more = false; + } } } } diff --git a/tests/test_cast.lv b/tests/test_cast.lv new file mode 100644 index 0000000..6ce6384 --- /dev/null +++ b/tests/test_cast.lv @@ -0,0 +1,45 @@ +// Test 'as' keyword for type casting + +void fn test_int_to_float(): + int x = 42 + float y = x as float + print("int->float: " + y) + +void fn test_float_to_int(): + float x = 3.14 + int y = x as int + print("float->int: " + y) + +void fn test_int_sizes(): + int x = 100 + int32 y = x as int32 + int64 z = y as int64 + print("int->int32: " + y) + print("int32->int64: " + z) + +void fn test_float_sizes(): + float x = 2.718 + float32 y = x as float32 + float64 z = y as float64 + print("float->float32: " + y) + print("float32->float64: " + z) + +void fn test_chained(): + int x = 42 + int32 y = x as float as int32 + print("chained: " + y) + +void fn test_expr_cast(): + int a = 10 + int b = 20 + float c = (a + b) as float + print("expr cast: " + c) + +void fn main(): + test_int_to_float() + test_float_to_int() + test_int_sizes() + test_float_sizes() + test_chained() + test_expr_cast() + print("All cast tests passed!") From 606556d6855817133bffb0fbcebf79a4742dc493 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 00:02:56 +0300 Subject: [PATCH 2/8] runtime: enriched io.h, added fs.lv wrapper --- runtime/liblavina/io.h | 29 +++++++++++++ runtime/std/fs.lv | 46 +++++++++++++++++++++ src/checker.lv | 2 +- src/main.lv | 2 + stages/stage-latest.cpp | 5 ++- tests/test_std_fs.lv | 91 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 runtime/std/fs.lv create mode 100644 tests/test_std_fs.lv diff --git a/runtime/liblavina/io.h b/runtime/liblavina/io.h index 0718270..6cd81d1 100644 --- a/runtime/liblavina/io.h +++ b/runtime/liblavina/io.h @@ -58,3 +58,32 @@ inline std::vector fs_listdir(const std::string& path) { std::sort(entries.begin(), entries.end()); return entries; } + +inline bool fs_mkdir(const std::string& path) { + return std::filesystem::create_directories(path); +} + +inline bool fs_copy(const std::string& src, const std::string& dst) { + std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing); + return true; +} + +inline void fs_rename(const std::string& from, const std::string& to) { + std::filesystem::rename(from, to); +} + +inline std::string fs_absolute(const std::string& path) { + return std::filesystem::absolute(path).string(); +} + +inline std::string fs_basename(const std::string& path) { + return std::filesystem::path(path).filename().string(); +} + +inline std::string fs_dirname(const std::string& path) { + return std::filesystem::path(path).parent_path().string(); +} + +inline int64_t fs_size(const std::string& path) { + return static_cast(std::filesystem::file_size(path)); +} diff --git a/runtime/std/fs.lv b/runtime/std/fs.lv new file mode 100644 index 0000000..a3465ef --- /dev/null +++ b/runtime/std/fs.lv @@ -0,0 +1,46 @@ +// std::fs — filesystem operations + +public string fn read(string path): + return fs_read(path) + +public void fn write(string path, string content): + fs_write(path, content) + +public void fn append(string path, string content): + fs_append(path, content) + +public bool fn exists(string path): + return fs_exists(path) + +public bool fn remove(string path): + return fs_remove(path) + +public bool fn is_dir(string path): + return fs_is_dir(path) + +public vector[string] fn list_dir(string path): + return fs_listdir(path) + +public vector[string] fn read_lines(string path): + return fs_read_lines(path) + +public bool fn mkdir(string path): + return fs_mkdir(path) + +public bool fn copy(string src, string dst): + return fs_copy(src, dst) + +public void fn rename(string from, string to): + fs_rename(from, to) + +public string fn absolute(string path): + return fs_absolute(path) + +public string fn basename(string path): + return fs_basename(path) + +public string fn dirname(string path): + return fs_dirname(path) + +public int fn size(string path): + return fs_size(path) diff --git a/src/checker.lv b/src/checker.lv index 74cf653..6ac7446 100644 --- a/src/checker.lv +++ b/src/checker.lv @@ -425,7 +425,7 @@ class Checker: void fn register_builtins(): // Runtime functions that are always available - vector[string] builtins = ["print", "println", "lv_assert", "fs_read", "fs_write", "fs_exists", "fs_append", "fs_read_lines", "fs_remove", "fs_is_dir", "fs_listdir", "os_exec", "os_args", "os_env", "os_clock", "os_sleep", "os_cwd", "to_string", "to_int", "to_float", "input", "typeof", "len", "exit", "abs", "str_to_int", "str_to_float", "int_to_str", "float_to_str", "lv_abs", "lv_fabs", "lv_min", "lv_max", "lv_fmin", "lv_fmax", "lv_clamp", "lv_fclamp", "lv_floor", "lv_ceil", "lv_round", "lv_sqrt", "lv_pow", "lv_log", "lv_log2", "lv_sin", "lv_cos", "lv_random", "lv_random_float", "lv_count"] + vector[string] builtins = ["print", "println", "lv_assert", "fs_read", "fs_write", "fs_exists", "fs_append", "fs_read_lines", "fs_remove", "fs_is_dir", "fs_listdir", "fs_mkdir", "fs_copy", "fs_rename", "fs_absolute", "fs_basename", "fs_dirname", "fs_size", "os_exec", "os_args", "os_env", "os_clock", "os_sleep", "os_cwd", "to_string", "to_int", "to_float", "input", "typeof", "len", "exit", "abs", "str_to_int", "str_to_float", "int_to_str", "float_to_str", "lv_abs", "lv_fabs", "lv_min", "lv_max", "lv_fmin", "lv_fmax", "lv_clamp", "lv_fclamp", "lv_floor", "lv_ceil", "lv_round", "lv_sqrt", "lv_pow", "lv_log", "lv_log2", "lv_sin", "lv_cos", "lv_random", "lv_random_float", "lv_count"] vector[Param] empty_params = [] for ref name in builtins: this.known_funcs[name] = ExternFn(name, name, TypeNode::Auto(), empty_params) diff --git a/src/main.lv b/src/main.lv index 923dc23..1846ab4 100644 --- a/src/main.lv +++ b/src/main.lv @@ -60,6 +60,8 @@ class ImportResolver: string file_rel = mod_path.replace("::", "/") vector[string] segments = file_rel.split("/") string module_file = "${dir}${file_rel}.lv" + if segments[0] == "std": + module_file = "runtime/${file_rel}.lv" if segments.len() > 1: // Multi-segment: module import with namespace string short_name = segments[segments.len() - 1] diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index 59c571a..5fef7be 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -4285,7 +4285,7 @@ struct Checker { } void register_builtins() { - std::vector builtins = std::vector{std::string("print"), std::string("println"), std::string("lv_assert"), std::string("fs_read"), std::string("fs_write"), std::string("fs_exists"), std::string("fs_append"), std::string("fs_read_lines"), std::string("fs_remove"), std::string("fs_is_dir"), std::string("fs_listdir"), std::string("os_exec"), std::string("os_args"), std::string("os_env"), std::string("os_clock"), std::string("os_sleep"), std::string("os_cwd"), std::string("to_string"), std::string("to_int"), std::string("to_float"), std::string("input"), std::string("typeof"), std::string("len"), std::string("exit"), std::string("abs"), std::string("str_to_int"), std::string("str_to_float"), std::string("int_to_str"), std::string("float_to_str"), std::string("lv_abs"), std::string("lv_fabs"), std::string("lv_min"), std::string("lv_max"), std::string("lv_fmin"), std::string("lv_fmax"), std::string("lv_clamp"), std::string("lv_fclamp"), std::string("lv_floor"), std::string("lv_ceil"), std::string("lv_round"), std::string("lv_sqrt"), std::string("lv_pow"), std::string("lv_log"), std::string("lv_log2"), std::string("lv_sin"), std::string("lv_cos"), std::string("lv_random"), std::string("lv_random_float"), std::string("lv_count")}; + std::vector builtins = std::vector{std::string("print"), std::string("println"), std::string("lv_assert"), std::string("fs_read"), std::string("fs_write"), std::string("fs_exists"), std::string("fs_append"), std::string("fs_read_lines"), std::string("fs_remove"), std::string("fs_is_dir"), std::string("fs_listdir"), std::string("fs_mkdir"), std::string("fs_copy"), std::string("fs_rename"), std::string("fs_absolute"), std::string("fs_basename"), std::string("fs_dirname"), std::string("fs_size"), std::string("os_exec"), std::string("os_args"), std::string("os_env"), std::string("os_clock"), std::string("os_sleep"), std::string("os_cwd"), std::string("to_string"), std::string("to_int"), std::string("to_float"), std::string("input"), std::string("typeof"), std::string("len"), std::string("exit"), std::string("abs"), std::string("str_to_int"), std::string("str_to_float"), std::string("int_to_str"), std::string("float_to_str"), std::string("lv_abs"), std::string("lv_fabs"), std::string("lv_min"), std::string("lv_max"), std::string("lv_fmin"), std::string("lv_fmax"), std::string("lv_clamp"), std::string("lv_fclamp"), std::string("lv_floor"), std::string("lv_ceil"), std::string("lv_round"), std::string("lv_sqrt"), std::string("lv_pow"), std::string("lv_log"), std::string("lv_log2"), std::string("lv_sin"), std::string("lv_cos"), std::string("lv_random"), std::string("lv_random_float"), std::string("lv_count")}; std::vector empty_params = {}; for (const auto& name : builtins) { this->known_funcs[name] = ExternFn(name, name, TypeNode::make_Auto(), empty_params); @@ -6424,6 +6424,9 @@ struct ImportResolver { std::string file_rel = lv_replace(mod_path, std::string("::"), std::string("/")); std::vector segments = lv_split(file_rel, std::string("/")); std::string module_file = ((((std::string("") + (dir)) + std::string("")) + (file_rel)) + std::string(".lv")); + if ((segments[INT64_C(0)] == std::string("std"))) { + module_file = ((std::string("runtime/") + (file_rel)) + std::string(".lv")); + } if ((static_cast(segments.size()) > INT64_C(1))) { std::string short_name = segments[(static_cast(segments.size()) - INT64_C(1))]; std::string full_name = lv_join(segments, std::string("_")); diff --git a/tests/test_std_fs.lv b/tests/test_std_fs.lv new file mode 100644 index 0000000..b95764b --- /dev/null +++ b/tests/test_std_fs.lv @@ -0,0 +1,91 @@ +import std::fs + +void fn test_write_and_read(): + fs::write("/tmp/lv_test_fs.txt", "hello lavina") + string content = fs::read("/tmp/lv_test_fs.txt") + lv_assert(content == "hello lavina", "write/read failed") + print("PASS: write and read") + +void fn test_exists(): + lv_assert(fs::exists("/tmp/lv_test_fs.txt"), "exists failed") + lv_assert(not fs::exists("/tmp/lv_nonexistent_xyz.txt"), "not exists failed") + print("PASS: exists") + +void fn test_append(): + fs::append("/tmp/lv_test_fs.txt", "\nline2") + string content = fs::read("/tmp/lv_test_fs.txt") + lv_assert(content == "hello lavina\nline2", "append failed") + print("PASS: append") + +void fn test_read_lines(): + vector[string] lines = fs::read_lines("/tmp/lv_test_fs.txt") + lv_assert(lines.len() == 2, "read_lines count failed") + lv_assert(lines[0] == "hello lavina", "read_lines[0] failed") + lv_assert(lines[1] == "line2", "read_lines[1] failed") + print("PASS: read_lines") + +void fn test_size(): + int s = fs::size("/tmp/lv_test_fs.txt") + lv_assert(s > 0, "size failed") + print("PASS: size = " + s) + +void fn test_copy(): + fs::copy("/tmp/lv_test_fs.txt", "/tmp/lv_test_fs_copy.txt") + string content = fs::read("/tmp/lv_test_fs_copy.txt") + lv_assert(content == "hello lavina\nline2", "copy failed") + print("PASS: copy") + +void fn test_rename(): + fs::rename("/tmp/lv_test_fs_copy.txt", "/tmp/lv_test_fs_moved.txt") + lv_assert(fs::exists("/tmp/lv_test_fs_moved.txt"), "rename dest failed") + lv_assert(not fs::exists("/tmp/lv_test_fs_copy.txt"), "rename src still exists") + print("PASS: rename") + +void fn test_mkdir_and_is_dir(): + fs::mkdir("/tmp/lv_test_dir") + lv_assert(fs::exists("/tmp/lv_test_dir"), "mkdir failed") + lv_assert(fs::is_dir("/tmp/lv_test_dir"), "is_dir failed") + print("PASS: mkdir and is_dir") + +void fn test_list_dir(): + fs::write("/tmp/lv_test_dir/a.txt", "a") + fs::write("/tmp/lv_test_dir/b.txt", "b") + vector[string] entries = fs::list_dir("/tmp/lv_test_dir") + lv_assert(entries.len() == 2, "list_dir count failed") + lv_assert(entries[0] == "a.txt", "list_dir[0] failed") + lv_assert(entries[1] == "b.txt", "list_dir[1] failed") + print("PASS: list_dir") + +void fn test_basename_dirname(): + string b = fs::basename("/tmp/lv_test_fs.txt") + string d = fs::dirname("/tmp/lv_test_fs.txt") + lv_assert(b == "lv_test_fs.txt", "basename failed: " + b) + lv_assert(d == "/tmp", "dirname failed: " + d) + print("PASS: basename = " + b + ", dirname = " + d) + +void fn test_absolute(): + string abs = fs::absolute("/tmp/lv_test_fs.txt") + lv_assert(abs.starts_with("/"), "absolute failed: " + abs) + print("PASS: absolute = " + abs) + +void fn cleanup(): + fs::remove("/tmp/lv_test_fs.txt") + fs::remove("/tmp/lv_test_fs_moved.txt") + fs::remove("/tmp/lv_test_dir/a.txt") + fs::remove("/tmp/lv_test_dir/b.txt") + fs::remove("/tmp/lv_test_dir") + +void fn main(): + test_write_and_read() + test_exists() + test_append() + test_read_lines() + test_size() + test_copy() + test_rename() + test_mkdir_and_is_dir() + test_list_dir() + test_basename_dirname() + test_absolute() + cleanup() + print("All std::fs tests passed!") From 8cd1522a4019c844b42be725756e2e168ec95079 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 12:33:19 +0300 Subject: [PATCH 3/8] migration to '__' prefix of builtins, working on proper .lv std --- runtime/liblavina/io.h | 47 ++++++++++++++++++++---------- runtime/liblavina/math.h | 59 ++++++++++++++++++++++++++------------ runtime/liblavina/os.h | 23 ++++++++++----- runtime/liblavina/string.h | 17 +++++++---- runtime/std/fs.lv | 30 +++++++++---------- runtime/std/os.lv | 22 ++++++++++++++ src/checker.lv | 7 ++++- src/main.lv | 30 +++++++++---------- stages/stage-latest.cpp | 35 +++++++++++----------- tests/test_std_os.lv | 38 ++++++++++++++++++++++++ tests/test_stdlib.lv | 52 ++++++++++++++++----------------- 11 files changed, 241 insertions(+), 119 deletions(-) create mode 100644 runtime/std/os.lv create mode 100644 tests/test_std_os.lv diff --git a/runtime/liblavina/io.h b/runtime/liblavina/io.h index 6cd81d1..5e2dde9 100644 --- a/runtime/liblavina/io.h +++ b/runtime/liblavina/io.h @@ -1,7 +1,7 @@ #pragma once #include "core.h" -inline std::string fs_read(const std::string& path) { +inline std::string __fs_read(const std::string& path) { std::ifstream f(path); if (!f.is_open()) throw std::runtime_error("Cannot open file: " + path); std::ostringstream ss; @@ -9,13 +9,13 @@ inline std::string fs_read(const std::string& path) { return ss.str(); } -inline void fs_write(const std::string& path, const std::string& content) { +inline void __fs_write(const std::string& path, const std::string& content) { std::ofstream f(path); if (!f.is_open()) throw std::runtime_error("Cannot write file: " + path); f << content; } -inline bool fs_exists(const std::string& path) { +inline bool __fs_exists(const std::string& path) { std::ifstream f(path); return f.good(); } @@ -27,13 +27,13 @@ inline std::string input(const std::string& prompt = "") { return line; } -inline void fs_append(const std::string& path, const std::string& content) { +inline void __fs_append(const std::string& path, const std::string& content) { std::ofstream f(path, std::ios::app); if (!f.is_open()) throw std::runtime_error("Cannot append to file: " + path); f << content; } -inline std::vector fs_read_lines(const std::string& path) { +inline std::vector __fs_read_lines(const std::string& path) { std::ifstream f(path); if (!f.is_open()) throw std::runtime_error("Cannot open file: " + path); std::vector lines; @@ -42,15 +42,15 @@ inline std::vector fs_read_lines(const std::string& path) { return lines; } -inline bool fs_remove(const std::string& path) { +inline bool __fs_remove(const std::string& path) { return std::remove(path.c_str()) == 0; } -inline bool fs_is_dir(const std::string& path) { +inline bool __fs_is_dir(const std::string& path) { return std::filesystem::is_directory(path); } -inline std::vector fs_listdir(const std::string& path) { +inline std::vector __fs_listdir(const std::string& path) { std::vector entries; for (const auto& e : std::filesystem::directory_iterator(path)) { entries.push_back(e.path().filename().string()); @@ -59,31 +59,48 @@ inline std::vector fs_listdir(const std::string& path) { return entries; } -inline bool fs_mkdir(const std::string& path) { +inline bool __fs_mkdir(const std::string& path) { return std::filesystem::create_directories(path); } -inline bool fs_copy(const std::string& src, const std::string& dst) { +inline bool __fs_copy(const std::string& src, const std::string& dst) { std::filesystem::copy_file(src, dst, std::filesystem::copy_options::overwrite_existing); return true; } -inline void fs_rename(const std::string& from, const std::string& to) { +inline void __fs_rename(const std::string& from, const std::string& to) { std::filesystem::rename(from, to); } -inline std::string fs_absolute(const std::string& path) { +inline std::string __fs_absolute(const std::string& path) { return std::filesystem::absolute(path).string(); } -inline std::string fs_basename(const std::string& path) { +inline std::string __fs_basename(const std::string& path) { return std::filesystem::path(path).filename().string(); } -inline std::string fs_dirname(const std::string& path) { +inline std::string __fs_dirname(const std::string& path) { return std::filesystem::path(path).parent_path().string(); } -inline int64_t fs_size(const std::string& path) { +inline int64_t __fs_size(const std::string& path) { return static_cast(std::filesystem::file_size(path)); } + +// Bootstrap compat aliases +inline auto fs_read = __fs_read; +inline auto fs_write = __fs_write; +inline auto fs_exists = __fs_exists; +inline auto fs_append = __fs_append; +inline auto fs_read_lines = __fs_read_lines; +inline auto fs_remove = __fs_remove; +inline auto fs_is_dir = __fs_is_dir; +inline auto fs_listdir = __fs_listdir; +inline auto fs_mkdir = __fs_mkdir; +inline auto fs_copy = __fs_copy; +inline auto fs_rename = __fs_rename; +inline auto fs_absolute = __fs_absolute; +inline auto fs_basename = __fs_basename; +inline auto fs_dirname = __fs_dirname; +inline auto fs_size = __fs_size; diff --git a/runtime/liblavina/math.h b/runtime/liblavina/math.h index feb213a..02a9944 100644 --- a/runtime/liblavina/math.h +++ b/runtime/liblavina/math.h @@ -2,40 +2,61 @@ #include "core.h" #include -inline int64_t lv_abs(int64_t n) { return n < 0 ? -n : n; } -inline double lv_fabs(double n) { return std::fabs(n); } +inline int64_t __lv_abs(int64_t n) { return n < 0 ? -n : n; } +inline double __lv_fabs(double n) { return std::fabs(n); } -inline int64_t lv_min(int64_t a, int64_t b) { return a < b ? a : b; } -inline int64_t lv_max(int64_t a, int64_t b) { return a > b ? a : b; } -inline double lv_fmin(double a, double b) { return a < b ? a : b; } -inline double lv_fmax(double a, double b) { return a > b ? a : b; } +inline int64_t __lv_min(int64_t a, int64_t b) { return a < b ? a : b; } +inline int64_t __lv_max(int64_t a, int64_t b) { return a > b ? a : b; } +inline double __lv_fmin(double a, double b) { return a < b ? a : b; } +inline double __lv_fmax(double a, double b) { return a > b ? a : b; } -inline int64_t lv_clamp(int64_t val, int64_t lo, int64_t hi) { +inline int64_t __lv_clamp(int64_t val, int64_t lo, int64_t hi) { return val < lo ? lo : (val > hi ? hi : val); } -inline double lv_fclamp(double val, double lo, double hi) { +inline double __lv_fclamp(double val, double lo, double hi) { return val < lo ? lo : (val > hi ? hi : val); } -inline double lv_floor(double n) { return std::floor(n); } -inline double lv_ceil(double n) { return std::ceil(n); } -inline double lv_round(double n) { return std::round(n); } -inline double lv_sqrt(double n) { return std::sqrt(n); } -inline double lv_pow(double base, double exp) { return std::pow(base, exp); } -inline double lv_log(double n) { return std::log(n); } -inline double lv_log2(double n) { return std::log2(n); } -inline double lv_sin(double n) { return std::sin(n); } -inline double lv_cos(double n) { return std::cos(n); } +inline double __lv_floor(double n) { return std::floor(n); } +inline double __lv_ceil(double n) { return std::ceil(n); } +inline double __lv_round(double n) { return std::round(n); } +inline double __lv_sqrt(double n) { return std::sqrt(n); } +inline double __lv_pow(double base, double exp) { return std::pow(base, exp); } +inline double __lv_log(double n) { return std::log(n); } +inline double __lv_log2(double n) { return std::log2(n); } +inline double __lv_sin(double n) { return std::sin(n); } +inline double __lv_cos(double n) { return std::cos(n); } // Random number generation -inline int64_t lv_random(int64_t min, int64_t max) { +inline int64_t __lv_random(int64_t min, int64_t max) { static std::mt19937_64 rng(std::random_device{}()); std::uniform_int_distribution dist(min, max); return dist(rng); } -inline double lv_random_float() { +inline double __lv_random_float() { static std::mt19937_64 rng(std::random_device{}()); std::uniform_real_distribution dist(0.0, 1.0); return dist(rng); } + +// Bootstrap compat aliases +inline auto lv_abs = __lv_abs; +inline auto lv_fabs = __lv_fabs; +inline auto lv_min = __lv_min; +inline auto lv_max = __lv_max; +inline auto lv_fmin = __lv_fmin; +inline auto lv_fmax = __lv_fmax; +inline auto lv_clamp = __lv_clamp; +inline auto lv_fclamp = __lv_fclamp; +inline auto lv_floor = __lv_floor; +inline auto lv_ceil = __lv_ceil; +inline auto lv_round = __lv_round; +inline auto lv_sqrt = __lv_sqrt; +inline auto lv_pow = __lv_pow; +inline auto lv_log = __lv_log; +inline auto lv_log2 = __lv_log2; +inline auto lv_sin = __lv_sin; +inline auto lv_cos = __lv_cos; +inline auto lv_random = __lv_random; +inline auto lv_random_float = __lv_random_float; diff --git a/runtime/liblavina/os.h b/runtime/liblavina/os.h index 9ba24b9..34933b1 100644 --- a/runtime/liblavina/os.h +++ b/runtime/liblavina/os.h @@ -3,9 +3,9 @@ inline std::vector _lv_args; -inline std::vector os_args() { return _lv_args; } +inline std::vector __os_args() { return _lv_args; } -inline int64_t os_exec(const std::string& cmd) { +inline int64_t __os_exec(const std::string& cmd) { int status = std::system(cmd.c_str()); #if defined(__unix__) || defined(__APPLE__) if (WIFEXITED(status)) return static_cast(WEXITSTATUS(status)); @@ -15,27 +15,36 @@ inline int64_t os_exec(const std::string& cmd) { #endif } -inline std::string os_env(const std::string& name) { +inline std::string __os_env(const std::string& name) { const char* val = std::getenv(name.c_str()); return val ? std::string(val) : ""; } -[[noreturn]] inline void lv_exit(int64_t code) { +[[noreturn]] inline void __lv_exit(int64_t code) { std::exit(static_cast(code)); } // Millisecond clock (monotonic) -inline int64_t os_clock() { +inline int64_t __os_clock() { auto now = std::chrono::steady_clock::now(); return std::chrono::duration_cast(now.time_since_epoch()).count(); } // Sleep for N milliseconds -inline void os_sleep(int64_t ms) { +inline void __os_sleep(int64_t ms) { std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } // Current working directory -inline std::string os_cwd() { +inline std::string __os_cwd() { return std::filesystem::current_path().string(); } + +// Bootstrap compat aliases +inline auto os_args = __os_args; +inline auto os_exec = __os_exec; +inline auto os_env = __os_env; +inline auto& lv_exit = __lv_exit; +inline auto os_clock = __os_clock; +inline auto os_sleep = __os_sleep; +inline auto os_cwd = __os_cwd; diff --git a/runtime/liblavina/string.h b/runtime/liblavina/string.h index 3f6839b..c051607 100644 --- a/runtime/liblavina/string.h +++ b/runtime/liblavina/string.h @@ -56,19 +56,19 @@ inline int64_t lv_index_of(const std::string& s, const std::string& sub, int64_t } // String ↔ number conversions -inline int64_t str_to_int(const std::string& s) { +inline int64_t __str_to_int(const std::string& s) { return std::stoll(s); } -inline double str_to_float(const std::string& s) { +inline double __str_to_float(const std::string& s) { return std::stod(s); } -inline std::string int_to_str(int64_t n) { +inline std::string __int_to_str(int64_t n) { return std::to_string(n); } -inline std::string float_to_str(double n) { +inline std::string __float_to_str(double n) { return std::to_string(n); } @@ -96,7 +96,7 @@ inline std::string lv_pad_right(const std::string& s, int64_t width, const std:: } // Count occurrences of substring -inline int64_t lv_count(const std::string& s, const std::string& sub) { +inline int64_t __lv_count(const std::string& s, const std::string& sub) { if (sub.empty()) return 0; int64_t count = 0; size_t pos = 0; @@ -106,3 +106,10 @@ inline int64_t lv_count(const std::string& s, const std::string& sub) { } return count; } + +// Bootstrap compat aliases +inline auto str_to_int = __str_to_int; +inline auto str_to_float = __str_to_float; +inline auto int_to_str = __int_to_str; +inline auto float_to_str = __float_to_str; +inline auto lv_count = __lv_count; diff --git a/runtime/std/fs.lv b/runtime/std/fs.lv index a3465ef..c3c8949 100644 --- a/runtime/std/fs.lv +++ b/runtime/std/fs.lv @@ -1,46 +1,46 @@ // std::fs — filesystem operations public string fn read(string path): - return fs_read(path) + return __fs_read(path) public void fn write(string path, string content): - fs_write(path, content) + __fs_write(path, content) public void fn append(string path, string content): - fs_append(path, content) + __fs_append(path, content) public bool fn exists(string path): - return fs_exists(path) + return __fs_exists(path) public bool fn remove(string path): - return fs_remove(path) + return __fs_remove(path) public bool fn is_dir(string path): - return fs_is_dir(path) + return __fs_is_dir(path) public vector[string] fn list_dir(string path): - return fs_listdir(path) + return __fs_listdir(path) public vector[string] fn read_lines(string path): - return fs_read_lines(path) + return __fs_read_lines(path) public bool fn mkdir(string path): - return fs_mkdir(path) + return __fs_mkdir(path) public bool fn copy(string src, string dst): - return fs_copy(src, dst) + return __fs_copy(src, dst) public void fn rename(string from, string to): - fs_rename(from, to) + __fs_rename(from, to) public string fn absolute(string path): - return fs_absolute(path) + return __fs_absolute(path) public string fn basename(string path): - return fs_basename(path) + return __fs_basename(path) public string fn dirname(string path): - return fs_dirname(path) + return __fs_dirname(path) public int fn size(string path): - return fs_size(path) + return __fs_size(path) diff --git a/runtime/std/os.lv b/runtime/std/os.lv new file mode 100644 index 0000000..edd34e8 --- /dev/null +++ b/runtime/std/os.lv @@ -0,0 +1,22 @@ +// std::os — operating system interaction + +public vector[string] fn args(): + return __os_args() + +public string fn env(string name): + return __os_env(name) + +public int fn exec(string cmd): + return __os_exec(cmd) + +public void fn exit(int code): + __lv_exit(code) + +public int fn time_ms(): + return __os_clock() + +public void fn sleep(int ms): + __os_sleep(ms) + +public string fn cwd(): + return __os_cwd() diff --git a/src/checker.lv b/src/checker.lv index 6ac7446..cb8a3b6 100644 --- a/src/checker.lv +++ b/src/checker.lv @@ -92,6 +92,9 @@ class Checker: return true if this.known_enums.has(name): return true + // Runtime builtins with __ prefix are always valid + if name.starts_with("__"): + return true // Check if this is a generic instantiation like "Pair" int angle = name.indexOf("<") if angle >= 0: @@ -425,7 +428,9 @@ class Checker: void fn register_builtins(): // Runtime functions that are always available - vector[string] builtins = ["print", "println", "lv_assert", "fs_read", "fs_write", "fs_exists", "fs_append", "fs_read_lines", "fs_remove", "fs_is_dir", "fs_listdir", "fs_mkdir", "fs_copy", "fs_rename", "fs_absolute", "fs_basename", "fs_dirname", "fs_size", "os_exec", "os_args", "os_env", "os_clock", "os_sleep", "os_cwd", "to_string", "to_int", "to_float", "input", "typeof", "len", "exit", "abs", "str_to_int", "str_to_float", "int_to_str", "float_to_str", "lv_abs", "lv_fabs", "lv_min", "lv_max", "lv_fmin", "lv_fmax", "lv_clamp", "lv_fclamp", "lv_floor", "lv_ceil", "lv_round", "lv_sqrt", "lv_pow", "lv_log", "lv_log2", "lv_sin", "lv_cos", "lv_random", "lv_random_float", "lv_count"] + // Public API: functions users call directly (no __ prefix) + // All __ prefixed runtime functions are auto-resolved by resolve() + vector[string] builtins = ["print", "println", "lv_assert", "to_string", "to_int", "to_float", "input", "typeof", "len", "exit", "abs", "cast"] vector[Param] empty_params = [] for ref name in builtins: this.known_funcs[name] = ExternFn(name, name, TypeNode::Auto(), empty_params) diff --git a/src/main.lv b/src/main.lv index 1846ab4..c878090 100644 --- a/src/main.lv +++ b/src/main.lv @@ -42,7 +42,7 @@ class ImportResolver: return "" this.resolved_paths.push(file_path) - string source = fs_read(file_path) + string source = __fs_read(file_path) string dir = this.get_directory(file_path) string result = "" @@ -81,15 +81,15 @@ class ImportResolver: // ── Helpers ────────────────────────────────────────────────── void fn cleanup(ref string cpp_path, ref string header_path, ref string liblavina_path, bool wrote_header): - os_exec("rm -f ${cpp_path}") + __os_exec("rm -f ${cpp_path}") if wrote_header: - os_exec("rm -f ${header_path}") - os_exec("rm -rf ${liblavina_path}") + __os_exec("rm -f ${header_path}") + __os_exec("rm -rf ${liblavina_path}") // ── Main entry point ───────────────────────────────────────── int fn main(): - auto args = os_args() + auto args = __os_args() if args.len() < 2: print("Usage: bootstrap [--emit-cpp | compile] ") return 1 @@ -215,15 +215,15 @@ int fn main(): string bin_path = "${dir}${base}" string header_path = "${dir}lavina.h" - fs_write(cpp_path, cpp) + __fs_write(cpp_path, cpp) bool wrote_header = false string liblavina_path = "${dir}liblavina" - if not fs_exists(header_path): + if not __fs_exists(header_path): try: - string header_content = fs_read("runtime/lavina.h") - fs_write(header_path, header_content) - os_exec("cp -r runtime/liblavina ${liblavina_path}") + string header_content = __fs_read("runtime/lavina.h") + __fs_write(header_path, header_content) + __os_exec("cp -r runtime/liblavina ${liblavina_path}") wrote_header = true catch: print("Warning: could not find runtime/lavina.h") @@ -231,21 +231,21 @@ int fn main(): string compile_cmd = "g++ -std=c++23 -o ${bin_path} ${cpp_path}" for ref ip in import_paths: compile_cmd += " -I${ip}" - if fs_exists("deps/include"): + if __fs_exists("deps/include"): bool has_deps = false for ref ip in import_paths: if ip == "deps/include": has_deps = true if not has_deps: compile_cmd += " -Ideps/include" - if fs_exists("deps/lib"): + if __fs_exists("deps/lib"): compile_cmd += " -Ldeps/lib" for ref ll in link_libs: if ll.indexOf("/") >= 0: compile_cmd += " ${ll}" else: compile_cmd += " -l${ll}" - int compile_result = os_exec(compile_cmd) + int compile_result = __os_exec(compile_cmd) if compile_result != 0: print("Compilation failed") cleanup(cpp_path, header_path, liblavina_path, wrote_header) @@ -256,10 +256,10 @@ int fn main(): print("Compiled: ${bin_path}") return 0 - int run_result = os_exec(bin_path) + int run_result = __os_exec(bin_path) cleanup(cpp_path, header_path, liblavina_path, wrote_header) - os_exec("rm -f ${bin_path}") + __os_exec("rm -f ${bin_path}") if run_result != 0: return 1 diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index 5fef7be..d143ea5 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -3701,6 +3701,9 @@ struct Checker { if ((this->known_enums.count(name) > 0)) { return true; } + if (name.starts_with(std::string("__"))) { + return true; + } int64_t angle = lv_index_of(name, std::string("<")); if ((angle >= INT64_C(0))) { std::string base = name.substr(INT64_C(0), (angle) - (INT64_C(0))); @@ -4285,7 +4288,7 @@ struct Checker { } void register_builtins() { - std::vector builtins = std::vector{std::string("print"), std::string("println"), std::string("lv_assert"), std::string("fs_read"), std::string("fs_write"), std::string("fs_exists"), std::string("fs_append"), std::string("fs_read_lines"), std::string("fs_remove"), std::string("fs_is_dir"), std::string("fs_listdir"), std::string("fs_mkdir"), std::string("fs_copy"), std::string("fs_rename"), std::string("fs_absolute"), std::string("fs_basename"), std::string("fs_dirname"), std::string("fs_size"), std::string("os_exec"), std::string("os_args"), std::string("os_env"), std::string("os_clock"), std::string("os_sleep"), std::string("os_cwd"), std::string("to_string"), std::string("to_int"), std::string("to_float"), std::string("input"), std::string("typeof"), std::string("len"), std::string("exit"), std::string("abs"), std::string("str_to_int"), std::string("str_to_float"), std::string("int_to_str"), std::string("float_to_str"), std::string("lv_abs"), std::string("lv_fabs"), std::string("lv_min"), std::string("lv_max"), std::string("lv_fmin"), std::string("lv_fmax"), std::string("lv_clamp"), std::string("lv_fclamp"), std::string("lv_floor"), std::string("lv_ceil"), std::string("lv_round"), std::string("lv_sqrt"), std::string("lv_pow"), std::string("lv_log"), std::string("lv_log2"), std::string("lv_sin"), std::string("lv_cos"), std::string("lv_random"), std::string("lv_random_float"), std::string("lv_count")}; + std::vector builtins = std::vector{std::string("print"), std::string("println"), std::string("lv_assert"), std::string("to_string"), std::string("to_int"), std::string("to_float"), std::string("input"), std::string("typeof"), std::string("len"), std::string("exit"), std::string("abs"), std::string("cast")}; std::vector empty_params = {}; for (const auto& name : builtins) { this->known_funcs[name] = ExternFn(name, name, TypeNode::make_Auto(), empty_params); @@ -6406,7 +6409,7 @@ struct ImportResolver { return std::string(""); } this->resolved_paths.push_back(file_path); - std::string source = fs_read(file_path); + std::string source = __fs_read(file_path); std::string dir = (*this).get_directory(file_path); std::string result = std::string(""); std::vector lines = lv_split(source, std::string("\n")); @@ -6452,16 +6455,16 @@ struct ImportResolver { }; void cleanup(const std::string& cpp_path, const std::string& header_path, const std::string& liblavina_path, bool wrote_header) { - os_exec(((std::string("rm -f ") + (cpp_path)) + std::string(""))); + __os_exec(((std::string("rm -f ") + (cpp_path)) + std::string(""))); if (wrote_header) { - os_exec(((std::string("rm -f ") + (header_path)) + std::string(""))); - os_exec(((std::string("rm -rf ") + (liblavina_path)) + std::string(""))); + __os_exec(((std::string("rm -f ") + (header_path)) + std::string(""))); + __os_exec(((std::string("rm -rf ") + (liblavina_path)) + std::string(""))); } } int main(int argc, char* argv[]) { for (int i = 0; i < argc; i++) _lv_args.push_back(argv[i]); - auto args = os_args(); + auto args = __os_args(); if ((static_cast(args.size()) < INT64_C(2))) { print(std::string("Usage: bootstrap [--emit-cpp | compile] ")); return INT64_C(1); @@ -6601,14 +6604,14 @@ int main(int argc, char* argv[]) { std::string cpp_path = ((((std::string("") + (dir)) + std::string("")) + (base)) + std::string(".cpp")); std::string bin_path = ((((std::string("") + (dir)) + std::string("")) + (base)) + std::string("")); std::string header_path = ((std::string("") + (dir)) + std::string("lavina.h")); - fs_write(cpp_path, cpp); + __fs_write(cpp_path, cpp); bool wrote_header = false; std::string liblavina_path = ((std::string("") + (dir)) + std::string("liblavina")); - if ((!fs_exists(header_path))) { + if ((!__fs_exists(header_path))) { try { - std::string header_content = fs_read(std::string("runtime/lavina.h")); - fs_write(header_path, header_content); - os_exec(((std::string("cp -r runtime/liblavina ") + (liblavina_path)) + std::string(""))); + std::string header_content = __fs_read(std::string("runtime/lavina.h")); + __fs_write(header_path, header_content); + __os_exec(((std::string("cp -r runtime/liblavina ") + (liblavina_path)) + std::string(""))); wrote_header = true; } catch (const std::exception& e) { @@ -6619,7 +6622,7 @@ int main(int argc, char* argv[]) { for (const auto& ip : import_paths) { compile_cmd = (compile_cmd + ((std::string(" -I") + (ip)) + std::string(""))); } - if (fs_exists(std::string("deps/include"))) { + if (__fs_exists(std::string("deps/include"))) { bool has_deps = false; for (const auto& ip : import_paths) { if ((ip == std::string("deps/include"))) { @@ -6630,7 +6633,7 @@ int main(int argc, char* argv[]) { compile_cmd = (compile_cmd + std::string(" -Ideps/include")); } } - if (fs_exists(std::string("deps/lib"))) { + if (__fs_exists(std::string("deps/lib"))) { compile_cmd = (compile_cmd + std::string(" -Ldeps/lib")); } for (const auto& ll : link_libs) { @@ -6641,7 +6644,7 @@ int main(int argc, char* argv[]) { compile_cmd = (compile_cmd + ((std::string(" -l") + (ll)) + std::string(""))); } } - int64_t compile_result = os_exec(compile_cmd); + int64_t compile_result = __os_exec(compile_cmd); if ((compile_result != INT64_C(0))) { print(std::string("Compilation failed")); cleanup(cpp_path, header_path, liblavina_path, wrote_header); @@ -6652,9 +6655,9 @@ int main(int argc, char* argv[]) { print(((std::string("Compiled: ") + (bin_path)) + std::string(""))); return INT64_C(0); } - int64_t run_result = os_exec(bin_path); + int64_t run_result = __os_exec(bin_path); cleanup(cpp_path, header_path, liblavina_path, wrote_header); - os_exec(((std::string("rm -f ") + (bin_path)) + std::string(""))); + __os_exec(((std::string("rm -f ") + (bin_path)) + std::string(""))); if ((run_result != INT64_C(0))) { return INT64_C(1); } diff --git a/tests/test_std_os.lv b/tests/test_std_os.lv new file mode 100644 index 0000000..2dd4a2d --- /dev/null +++ b/tests/test_std_os.lv @@ -0,0 +1,38 @@ +import std::os + +void fn test_args(): + vector[string] a = os::args() + lv_assert(a.len() >= 1, "args should have at least 1 element") + print("PASS: args, count = " + a.len()) + +void fn test_env(): + string home = os::env("HOME") + lv_assert(home.len() > 0, "HOME should not be empty") + string empty = os::env("LV_NONEXISTENT_VAR_XYZ") + lv_assert(empty == "", "nonexistent env should be empty") + print("PASS: env, HOME = " + home) + +void fn test_exec(): + int result = os::exec("echo hello > /dev/null") + lv_assert(result == 0, "exec echo failed") + print("PASS: exec") + +void fn test_cwd(): + string dir = os::cwd() + lv_assert(dir.len() > 0, "cwd should not be empty") + print("PASS: cwd = " + dir) + +void fn test_clock_and_sleep(): + int t1 = os::time_ms() + os::sleep(20) + int t2 = os::time_ms() + lv_assert(t2 >= t1 + 10, "clock/sleep failed") + print("PASS: clock/sleep, delta = " + (t2 - t1) + "ms") + +void fn main(): + test_args() + test_env() + test_exec() + test_cwd() + test_clock_and_sleep() + print("All std::os tests passed!") diff --git a/tests/test_stdlib.lv b/tests/test_stdlib.lv index a48ff71..1341ac9 100644 --- a/tests/test_stdlib.lv +++ b/tests/test_stdlib.lv @@ -1,13 +1,13 @@ void fn main(): // ── String conversions ────────────────────────────── - lv_assert(str_to_int("42") == 42, "str_to_int") - lv_assert(str_to_int("-7") == -7, "str_to_int negative") - lv_assert(str_to_float("3.14") > 3.13, "str_to_float") - lv_assert(int_to_str(123) == "123", "int_to_str") + lv_assert(__str_to_int("42") == 42, "str_to_int") + lv_assert(__str_to_int("-7") == -7, "str_to_int negative") + lv_assert(__str_to_float("3.14") > 3.13, "str_to_float") + lv_assert(__int_to_str(123) == "123", "int_to_str") // ── String utilities ──────────────────────────────── string s = "hello world hello" - lv_assert(lv_count(s, "hello") == 2, "string count") + lv_assert(__lv_count(s, "hello") == 2, "string count") lv_assert("abc".repeat(3) == "abcabcabc", "string repeat") lv_assert("42".pad_left(5, "0") == "00042", "pad_left") lv_assert("hi".pad_right(5) == "hi ", "pad_right") @@ -16,19 +16,19 @@ void fn main(): lv_assert(s.indexOf("hello", 1) == 12, "indexOf with offset") // ── Math ──────────────────────────────────────────── - lv_assert(lv_abs(-5) == 5, "abs") - lv_assert(lv_min(3, 7) == 3, "min") - lv_assert(lv_max(3, 7) == 7, "max") - lv_assert(lv_clamp(10, 0, 5) == 5, "clamp high") - lv_assert(lv_clamp(-1, 0, 5) == 0, "clamp low") - lv_assert(lv_sqrt(16.0) == 4.0, "sqrt") - lv_assert(lv_pow(2.0, 10.0) == 1024.0, "pow") - lv_assert(lv_floor(3.7) == 3.0, "floor") - lv_assert(lv_ceil(3.2) == 4.0, "ceil") - lv_assert(lv_round(3.5) == 4.0, "round") + lv_assert(__lv_abs(-5) == 5, "abs") + lv_assert(__lv_min(3, 7) == 3, "min") + lv_assert(__lv_max(3, 7) == 7, "max") + lv_assert(__lv_clamp(10, 0, 5) == 5, "clamp high") + lv_assert(__lv_clamp(-1, 0, 5) == 0, "clamp low") + lv_assert(__lv_sqrt(16.0) == 4.0, "sqrt") + lv_assert(__lv_pow(2.0, 10.0) == 1024.0, "pow") + lv_assert(__lv_floor(3.7) == 3.0, "floor") + lv_assert(__lv_ceil(3.2) == 4.0, "ceil") + lv_assert(__lv_round(3.5) == 4.0, "round") // Random (just verify it doesn't crash and is in range) - int r = lv_random(1, 100) + int r = __lv_random(1, 100) lv_assert(r >= 1 and r <= 100, "random range") // ── Vector utilities ──────────────────────────────── @@ -51,25 +51,25 @@ void fn main(): lv_assert(flat.len() == 5 and flat[0] == 1 and flat[4] == 5, "flatten") // ── File I/O ──────────────────────────────────────── - fs_write("/tmp/lv_test_stdlib.txt", "line1\nline2\nline3") - auto lines = fs_read_lines("/tmp/lv_test_stdlib.txt") + __fs_write("/tmp/lv_test_stdlib.txt", "line1\nline2\nline3") + auto lines = __fs_read_lines("/tmp/lv_test_stdlib.txt") lv_assert(lines.len() == 3, "fs_read_lines count") lv_assert(lines[0] == "line1", "fs_read_lines first") - fs_append("/tmp/lv_test_stdlib.txt", "\nline4") - auto lines2 = fs_read_lines("/tmp/lv_test_stdlib.txt") + __fs_append("/tmp/lv_test_stdlib.txt", "\nline4") + auto lines2 = __fs_read_lines("/tmp/lv_test_stdlib.txt") lv_assert(lines2.len() == 4, "fs_append") - fs_remove("/tmp/lv_test_stdlib.txt") - lv_assert(not fs_exists("/tmp/lv_test_stdlib.txt"), "fs_remove") + __fs_remove("/tmp/lv_test_stdlib.txt") + lv_assert(not __fs_exists("/tmp/lv_test_stdlib.txt"), "fs_remove") // ── OS utilities ──────────────────────────────────── - int t1 = os_clock() - os_sleep(10) - int t2 = os_clock() + int t1 = __os_clock() + __os_sleep(10) + int t2 = __os_clock() lv_assert(t2 >= t1 + 10, "os_clock + os_sleep") - string cwd = os_cwd() + string cwd = __os_cwd() lv_assert(cwd.len() > 0, "os_cwd") print("test_stdlib: ALL PASSED") From f01e1b9c4c4e8faf689f79ceb8bfb659434b6f59 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 12:40:08 +0300 Subject: [PATCH 4/8] std: emit cpp 'using' only for non-std modules, added math.lv --- runtime/liblavina/math.h | 9 ++++ runtime/std/math.lv | 92 ++++++++++++++++++++++++++++++++++++++++ src/codegen.lv | 8 +++- stages/stage-latest.cpp | 6 ++- tests/test_std_math.lv | 75 ++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 runtime/std/math.lv create mode 100644 tests/test_std_math.lv diff --git a/runtime/liblavina/math.h b/runtime/liblavina/math.h index 02a9944..3ecff41 100644 --- a/runtime/liblavina/math.h +++ b/runtime/liblavina/math.h @@ -26,6 +26,15 @@ inline double __lv_log(double n) { return std::log(n); } inline double __lv_log2(double n) { return std::log2(n); } inline double __lv_sin(double n) { return std::sin(n); } inline double __lv_cos(double n) { return std::cos(n); } +inline double __lv_tan(double n) { return std::tan(n); } +inline double __lv_asin(double n) { return std::asin(n); } +inline double __lv_acos(double n) { return std::acos(n); } +inline double __lv_atan(double n) { return std::atan(n); } +inline double __lv_atan2(double y, double x) { return std::atan2(y, x); } +inline double __lv_exp(double n) { return std::exp(n); } +inline double __lv_log10(double n) { return std::log10(n); } +inline double __lv_pi() { return M_PI; } +inline double __lv_e() { return M_E; } // Random number generation inline int64_t __lv_random(int64_t min, int64_t max) { diff --git a/runtime/std/math.lv b/runtime/std/math.lv new file mode 100644 index 0000000..b0d01dc --- /dev/null +++ b/runtime/std/math.lv @@ -0,0 +1,92 @@ +// std::math — mathematical functions and constants + +// Constants (as functions) +public float fn pi(): + return __lv_pi() + +public float fn e(): + return __lv_e() + +// Basic +public int fn abs(int n): + return __lv_abs(n) + +public float fn fabs(float n): + return __lv_fabs(n) + +public int fn min(int a, int b): + return __lv_min(a, b) + +public int fn max(int a, int b): + return __lv_max(a, b) + +public float fn fmin(float a, float b): + return __lv_fmin(a, b) + +public float fn fmax(float a, float b): + return __lv_fmax(a, b) + +public int fn clamp(int val, int lo, int hi): + return __lv_clamp(val, lo, hi) + +public float fn fclamp(float val, float lo, float hi): + return __lv_fclamp(val, lo, hi) + +// Rounding +public float fn floor(float n): + return __lv_floor(n) + +public float fn ceil(float n): + return __lv_ceil(n) + +public float fn round(float n): + return __lv_round(n) + +// Powers and roots +public float fn sqrt(float n): + return __lv_sqrt(n) + +public float fn pow(float base, float exp): + return __lv_pow(base, exp) + +public float fn exp(float n): + return __lv_exp(n) + +// Logarithms +public float fn log(float n): + return __lv_log(n) + +public float fn log2(float n): + return __lv_log2(n) + +public float fn log10(float n): + return __lv_log10(n) + +// Trigonometry +public float fn sin(float n): + return __lv_sin(n) + +public float fn cos(float n): + return __lv_cos(n) + +public float fn tan(float n): + return __lv_tan(n) + +public float fn asin(float n): + return __lv_asin(n) + +public float fn acos(float n): + return __lv_acos(n) + +public float fn atan(float n): + return __lv_atan(n) + +public float fn atan2(float y, float x): + return __lv_atan2(y, x) + +// Random +public int fn random(int min, int max): + return __lv_random(min, max) + +public float fn random_float(): + return __lv_random_float() diff --git a/src/codegen.lv b/src/codegen.lv index 27aa957..e0f2c25 100644 --- a/src/codegen.lv +++ b/src/codegen.lv @@ -1251,8 +1251,12 @@ class CppCodegen: if alias != "": this.output +="namespace ${alias} = ${full_name};\n" - for ref stmt in this.module_stmts[index]: - this.emit_using_if_public(full_name, stmt) + // Only emit 'using' for non-std modules + // std modules should be accessed via namespace (e.g. math::sin) + // to avoid conflicts with C standard library functions + if not full_name.starts_with("std_"): + for ref stmt in this.module_stmts[index]: + this.emit_using_if_public(full_name, stmt) this.output +="\n" this.declarations +=this.output diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index d143ea5..ad24fb7 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -3497,8 +3497,10 @@ struct CppCodegen { if ((alias != std::string(""))) { this->output = (this->output + ((((std::string("namespace ") + (alias)) + std::string(" = ")) + (full_name)) + std::string(";\n"))); } - for (const auto& stmt : this->module_stmts[index]) { - (*this).emit_using_if_public(full_name, stmt); + if ((!full_name.starts_with(std::string("std_")))) { + for (const auto& stmt : this->module_stmts[index]) { + (*this).emit_using_if_public(full_name, stmt); + } } this->output = (this->output + std::string("\n")); this->declarations = (this->declarations + this->output); diff --git a/tests/test_std_math.lv b/tests/test_std_math.lv new file mode 100644 index 0000000..b8feb18 --- /dev/null +++ b/tests/test_std_math.lv @@ -0,0 +1,75 @@ +import std::math + +int fn main(): + // Constants + lv_assert(math::pi() > 3.14, "pi > 3.14") + lv_assert(math::pi() < 3.15, "pi < 3.15") + lv_assert(math::e() > 2.71, "e > 2.71") + lv_assert(math::e() < 2.72, "e < 2.72") + + // abs, min, max + lv_assert(math::abs(-5) == 5, "abs(-5)") + lv_assert(math::fabs(-3.14) > 3.13, "fabs(-3.14)") + lv_assert(math::min(3, 7) == 3, "min(3,7)") + lv_assert(math::max(3, 7) == 7, "max(3,7)") + lv_assert(math::fmin(1.5, 2.5) < 2.0, "fmin") + lv_assert(math::fmax(1.5, 2.5) > 2.0, "fmax") + + // clamp + lv_assert(math::clamp(5, 1, 10) == 5, "clamp mid") + lv_assert(math::clamp(-1, 1, 10) == 1, "clamp low") + lv_assert(math::clamp(15, 1, 10) == 10, "clamp high") + + // rounding + lv_assert(math::floor(2.7) > 1.9, "floor(2.7)") + lv_assert(math::floor(2.7) < 2.1, "floor(2.7) upper") + lv_assert(math::ceil(2.3) > 2.9, "ceil(2.3)") + lv_assert(math::ceil(2.3) < 3.1, "ceil(2.3) upper") + lv_assert(math::round(2.5) > 1.9, "round(2.5)") + + // sqrt, pow, exp + lv_assert(math::sqrt(16.0) > 3.9, "sqrt(16)") + lv_assert(math::sqrt(16.0) < 4.1, "sqrt(16) upper") + lv_assert(math::pow(2.0, 10.0) > 1023.0, "pow(2,10)") + lv_assert(math::pow(2.0, 10.0) < 1025.0, "pow(2,10) upper") + lv_assert(math::exp(0.0) > 0.9, "exp(0)") + lv_assert(math::exp(0.0) < 1.1, "exp(0) upper") + + // log + lv_assert(math::log(1.0) > -0.1, "log(1)") + lv_assert(math::log(1.0) < 0.1, "log(1) upper") + lv_assert(math::log2(8.0) > 2.9, "log2(8)") + lv_assert(math::log2(8.0) < 3.1, "log2(8) upper") + lv_assert(math::log10(1000.0) > 2.9, "log10(1000)") + lv_assert(math::log10(1000.0) < 3.1, "log10(1000) upper") + + // trig: sin(0)=0, cos(0)=1, tan(0)=0 + lv_assert(math::sin(0.0) > -0.1, "sin(0)") + lv_assert(math::sin(0.0) < 0.1, "sin(0) upper") + lv_assert(math::cos(0.0) > 0.9, "cos(0)") + lv_assert(math::cos(0.0) < 1.1, "cos(0) upper") + lv_assert(math::tan(0.0) > -0.1, "tan(0)") + lv_assert(math::tan(0.0) < 0.1, "tan(0) upper") + + // inverse trig: asin(0)=0, acos(1)=0, atan(0)=0 + lv_assert(math::asin(0.0) > -0.1, "asin(0)") + lv_assert(math::asin(0.0) < 0.1, "asin(0) upper") + lv_assert(math::acos(1.0) > -0.1, "acos(1)") + lv_assert(math::acos(1.0) < 0.1, "acos(1) upper") + lv_assert(math::atan(0.0) > -0.1, "atan(0)") + lv_assert(math::atan(0.0) < 0.1, "atan(0) upper") + + // atan2(1,1) = pi/4 ≈ 0.785 + lv_assert(math::atan2(1.0, 1.0) > 0.78, "atan2(1,1)") + lv_assert(math::atan2(1.0, 1.0) < 0.79, "atan2(1,1) upper") + + // random + int r = math::random(1, 100) + lv_assert(r >= 1, "random >= 1") + lv_assert(r <= 100, "random <= 100") + float rf = math::random_float() + lv_assert(rf >= 0.0, "random_float >= 0") + lv_assert(rf <= 1.0, "random_float <= 1") + + print("All std::math tests passed!") + return 0 From cec17246bb05cdd182c606bf93fff941290cebd9 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 12:59:52 +0300 Subject: [PATCH 5/8] src: added 'comptime' support for variables --- runtime/liblavina/math.h | 2 -- runtime/std/math.lv | 9 +++------ src/ast.lv | 2 +- src/checker.lv | 4 ++-- src/codegen.lv | 9 ++++++--- src/parser.lv | 21 +++++++++++--------- stages/stage-latest.cpp | 41 +++++++++++++++++++++++++--------------- tests/test_comptime.lv | 9 +++++++++ tests/test_std_math.lv | 8 ++++---- 9 files changed, 63 insertions(+), 42 deletions(-) diff --git a/runtime/liblavina/math.h b/runtime/liblavina/math.h index 3ecff41..d0d8640 100644 --- a/runtime/liblavina/math.h +++ b/runtime/liblavina/math.h @@ -33,8 +33,6 @@ inline double __lv_atan(double n) { return std::atan(n); } inline double __lv_atan2(double y, double x) { return std::atan2(y, x); } inline double __lv_exp(double n) { return std::exp(n); } inline double __lv_log10(double n) { return std::log10(n); } -inline double __lv_pi() { return M_PI; } -inline double __lv_e() { return M_E; } // Random number generation inline int64_t __lv_random(int64_t min, int64_t max) { diff --git a/runtime/std/math.lv b/runtime/std/math.lv index b0d01dc..a6058c7 100644 --- a/runtime/std/math.lv +++ b/runtime/std/math.lv @@ -1,11 +1,8 @@ // std::math — mathematical functions and constants -// Constants (as functions) -public float fn pi(): - return __lv_pi() - -public float fn e(): - return __lv_e() +// Constants +public comptime float PI = 3.14159265358979323846 +public comptime float E = 2.71828182845904523536 // Basic public int fn abs(int n): diff --git a/src/ast.lv b/src/ast.lv index d3b6e6e..0f58728 100644 --- a/src/ast.lv +++ b/src/ast.lv @@ -94,7 +94,7 @@ enum Stmt: None ExprStmt(Expr expr) Let(Token name, TypeNode var_type, Expr initializer, string visibility, bool is_ref, bool is_mut) - Const(Token name, TypeNode const_type, Expr value, string visibility) + Const(Token name, TypeNode const_type, Expr value, string visibility, int comptime_mode) Return(Token keyword, Expr value) If(Expr condition, Stmt then_branch, Stmt else_branch) While(Expr condition, Stmt body) diff --git a/src/checker.lv b/src/checker.lv index cb8a3b6..781ffcf 100644 --- a/src/checker.lv +++ b/src/checker.lv @@ -455,7 +455,7 @@ class Checker: this.known_classes[name.lexeme] = body Enum(name, variants, methods, visibility, enum_tp): this.known_enums[name.lexeme] = variants - Const(name, const_type, value, visibility): + Const(name, const_type, value, visibility, comptime_mode): this.declare(name.lexeme, const_type, "const", false, false, name) Struct(name, body, visibility, struct_tp): this.known_classes[name.lexeme] = body @@ -488,7 +488,7 @@ class Checker: if not this.types_compatible(var_type, init_type): this.error("Cannot assign ${this.type_name(init_type)} to ${this.type_name(var_type)}", name) this.declare(name.lexeme, var_type, "var", is_ref, true, name) - Const(name, const_type, value, visibility): + Const(name, const_type, value, visibility, comptime_mode): this.check_expr(value) Return(keyword, value): this.check_expr(value) diff --git a/src/codegen.lv b/src/codegen.lv index e0f2c25..106a1b3 100644 --- a/src/codegen.lv +++ b/src/codegen.lv @@ -598,10 +598,13 @@ class CppCodegen: this.output +="${this.indent()}${this.emit_expr(expr, m)};\n" Let(name, var_type, initializer, visibility, is_ref, is_mut): this.emit_let(name, var_type, initializer, m, is_ref, is_mut) - Const(name, const_type, value, visibility): + Const(name, const_type, value, visibility, comptime_mode): string cpp_type = this.emit_type(const_type) string val = this.emit_expr(value, m) - this.output +="${this.indent()}const ${cpp_type} ${name.lexeme} = ${val};\n" + string prefix = "const " + if comptime_mode == 1 or comptime_mode == 2: + prefix = "constexpr " + this.output +="${this.indent()}${prefix}${cpp_type} ${name.lexeme} = ${val};\n" Return(keyword, value): match value: None(): @@ -1214,7 +1217,7 @@ class CppCodegen: Enum(name, variants, methods, visibility, type_params): if visibility != "private": this.output +="using ${ns}::${name.lexeme};\n" - Const(name, const_type, value, visibility): + Const(name, const_type, value, visibility, comptime_mode): if visibility != "private": this.output +="using ${ns}::${name.lexeme};\n" Let(name, var_type, initializer, visibility, is_ref, is_mut): diff --git a/src/parser.lv b/src/parser.lv index 6e5b215..60b1489 100644 --- a/src/parser.lv +++ b/src/parser.lv @@ -749,13 +749,13 @@ class Parser: this.match_any([TK_SEMICOLON, TK_NEWLINE]) return Stmt::Let(name, var_type, initializer, visibility, is_ref, is_mut) - Stmt fn const_declaration(string visibility): + Stmt fn const_declaration(string visibility, int comptime_mode): TypeNode const_type = this.parse_type() auto name = this.consume(TK_IDENTIFIER, "Expect constant name after type.") this.consume(TK_EQUAL, "Const declaration must have an initializer.") Expr value = this.expression() this.match_any([TK_SEMICOLON, TK_NEWLINE]) - return Stmt::Const(name, const_type, value, visibility) + return Stmt::Const(name, const_type, value, visibility, comptime_mode) Stmt fn function_declaration(string visibility, bool is_static, int comptime_mode): TypeNode return_type = this.parse_type() @@ -948,8 +948,14 @@ class Parser: if this.match_any([TK_TRY]): return this.try_statement() + int comptime_mode = 0 + if this.match_any([TK_COMPTIME_STRICT]): + comptime_mode = 2 + elif this.match_any([TK_COMPTIME]): + comptime_mode = 1 + if this.match_any([TK_CONST]): - return this.const_declaration(visibility) + return this.const_declaration(visibility, comptime_mode) if this.in_class_body and this.check(TK_IDENTIFIER): auto ctor_name = this.peek().lexeme @@ -963,12 +969,6 @@ class Parser: vector[string] empty_tp = [] return Stmt::Function(name_tok, params, TypeNode::Void(), body, false, 0, is_static, visibility, empty_tp) - int comptime_mode = 0 - if this.match_any([TK_COMPTIME_STRICT]): - comptime_mode = 2 - elif this.match_any([TK_COMPTIME]): - comptime_mode = 1 - if this.match_any([TK_REF_MUT]): return this.var_declaration_with_ref(visibility, true, true) if this.match_any([TK_REF]): @@ -984,6 +984,9 @@ class Parser: if next_token == TK_IDENTIFIER or next_token == TK_FN or next_token == TK_INLINE or next_token == TK_COMPTIME or next_token == TK_OPERATOR: if this.is_function_start(): return this.function_declaration(visibility, is_static, comptime_mode) + // comptime without const or fn → treat as comptime const + if comptime_mode > 0: + return this.const_declaration(visibility, comptime_mode) return this.var_declaration(visibility) return this.statement() diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index ad24fb7..2b25cbd 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -1151,7 +1151,7 @@ struct Stmt { struct None {}; struct ExprStmt { Expr expr; }; struct Let { Token name; TypeNode var_type; Expr initializer; std::string visibility; bool is_ref; bool is_mut; }; - struct Const { Token name; TypeNode const_type; Expr value; std::string visibility; }; + struct Const { Token name; TypeNode const_type; Expr value; std::string visibility; int64_t comptime_mode; }; struct Return { Token keyword; Expr value; }; struct If { Expr condition; std::shared_ptr then_branch; std::shared_ptr else_branch; }; struct While { Expr condition; std::shared_ptr body; }; @@ -1177,7 +1177,7 @@ struct Stmt { static Stmt make_None() { return {"None", None{}}; } static Stmt make_ExprStmt(Expr expr) { return {"ExprStmt", ExprStmt{expr}}; } static Stmt make_Let(Token name, TypeNode var_type, Expr initializer, std::string visibility, bool is_ref, bool is_mut) { return {"Let", Let{name, var_type, initializer, visibility, is_ref, is_mut}}; } - static Stmt make_Const(Token name, TypeNode const_type, Expr value, std::string visibility) { return {"Const", Const{name, const_type, value, visibility}}; } + static Stmt make_Const(Token name, TypeNode const_type, Expr value, std::string visibility, int64_t comptime_mode) { return {"Const", Const{name, const_type, value, visibility, comptime_mode}}; } static Stmt make_Return(Token keyword, Expr value) { return {"Return", Return{keyword, value}}; } static Stmt make_If(Expr condition, Stmt then_branch, Stmt else_branch) { return {"If", If{condition, std::make_shared(std::move(then_branch)), std::make_shared(std::move(else_branch))}}; } static Stmt make_While(Expr condition, Stmt body) { return {"While", While{condition, std::make_shared(std::move(body))}}; } @@ -2316,9 +2316,14 @@ struct CppCodegen { auto& const_type = _v.const_type; auto& value = _v.value; auto& visibility = _v.visibility; + auto& comptime_mode = _v.comptime_mode; std::string cpp_type = (*this).emit_type(const_type); std::string val = (*this).emit_expr(value, m); - this->output = (this->output + ((((((((std::string("") + ((*this).indent())) + std::string("const ")) + (cpp_type)) + std::string(" ")) + (name.lexeme)) + std::string(" = ")) + (val)) + std::string(";\n"))); + std::string prefix = std::string("const "); + if ((comptime_mode == INT64_C(1)) || (comptime_mode == INT64_C(2))) { + prefix = std::string("constexpr "); + } + this->output = (this->output + ((((((((((std::string("") + ((*this).indent())) + std::string("")) + (prefix)) + std::string("")) + (cpp_type)) + std::string(" ")) + (name.lexeme)) + std::string(" = ")) + (val)) + std::string(";\n"))); } else if (std::holds_alternative::Return>(_match_23._data)) { auto& _v = std::get::Return>(_match_23._data); @@ -3426,6 +3431,7 @@ struct CppCodegen { auto& const_type = _v.const_type; auto& value = _v.value; auto& visibility = _v.visibility; + auto& comptime_mode = _v.comptime_mode; if ((visibility != std::string("private"))) { this->output = (this->output + ((((std::string("using ") + (ns)) + std::string("::")) + (name.lexeme)) + std::string(";\n"))); } @@ -4348,6 +4354,7 @@ struct Checker { auto& const_type = _v.const_type; auto& value = _v.value; auto& visibility = _v.visibility; + auto& comptime_mode = _v.comptime_mode; (*this).declare(name.lexeme, const_type, std::string("const"), false, false, name); } else if (std::holds_alternative::Struct>(_match_82._data)) { @@ -4428,6 +4435,7 @@ struct Checker { auto& const_type = _v.const_type; auto& value = _v.value; auto& visibility = _v.visibility; + auto& comptime_mode = _v.comptime_mode; (*this).check_expr(value); } else if (std::holds_alternative::Return>(_match_83._data)) { @@ -5965,13 +5973,13 @@ struct Parser { return Stmt::make_Let(name, var_type, initializer, visibility, is_ref, is_mut); } - Stmt const_declaration(std::string visibility) { + Stmt const_declaration(std::string visibility, int64_t comptime_mode) { TypeNode const_type = (*this).parse_type(); auto name = (*this).consume(TK_IDENTIFIER, std::string("Expect constant name after type.")); (*this).consume(TK_EQUAL, std::string("Const declaration must have an initializer.")); Expr value = (*this).expression(); (*this).match_any(std::vector{TK_SEMICOLON, TK_NEWLINE}); - return Stmt::make_Const(name, const_type, value, visibility); + return Stmt::make_Const(name, const_type, value, visibility, comptime_mode); } Stmt function_declaration(std::string visibility, bool is_static, int64_t comptime_mode) { @@ -6201,8 +6209,17 @@ struct Parser { if ((*this).match_any(std::vector{TK_TRY})) { return (*this).try_statement(); } + int64_t comptime_mode = INT64_C(0); + if ((*this).match_any(std::vector{TK_COMPTIME_STRICT})) { + comptime_mode = INT64_C(2); + } + else { + if ((*this).match_any(std::vector{TK_COMPTIME})) { + comptime_mode = INT64_C(1); + } + } if ((*this).match_any(std::vector{TK_CONST})) { - return (*this).const_declaration(visibility); + return (*this).const_declaration(visibility, comptime_mode); } if (this->in_class_body && (*this).check(TK_IDENTIFIER)) { auto ctor_name = (*this).peek().lexeme; @@ -6217,15 +6234,6 @@ struct Parser { return Stmt::make_Function(name_tok, params, TypeNode::make_Void(), body, false, INT64_C(0), is_static, visibility, empty_tp); } } - int64_t comptime_mode = INT64_C(0); - if ((*this).match_any(std::vector{TK_COMPTIME_STRICT})) { - comptime_mode = INT64_C(2); - } - else { - if ((*this).match_any(std::vector{TK_COMPTIME})) { - comptime_mode = INT64_C(1); - } - } if ((*this).match_any(std::vector{TK_REF_MUT})) { return (*this).var_declaration_with_ref(visibility, true, true); } @@ -6242,6 +6250,9 @@ struct Parser { if ((*this).is_function_start()) { return (*this).function_declaration(visibility, is_static, comptime_mode); } + if ((comptime_mode > INT64_C(0))) { + return (*this).const_declaration(visibility, comptime_mode); + } return (*this).var_declaration(visibility); } } diff --git a/tests/test_comptime.lv b/tests/test_comptime.lv index 2e3332a..1b2d133 100644 --- a/tests/test_comptime.lv +++ b/tests/test_comptime.lv @@ -9,6 +9,10 @@ comptime int fn add(int a, int b): comptime! int fn square(int x): return x * x +// Comptime variables +comptime int MAX_SIZE = 1024 +comptime float TAU = 6.28318530717958647692 + void fn main(): lv_assert(factorial(5) == 120, "comptime factorial") lv_assert(add(3, 4) == 7, "comptime add") @@ -19,4 +23,9 @@ void fn main(): // comptime! (consteval) - compile-time only lv_assert(square(5) == 25, "comptime! square") + // comptime variables + lv_assert(MAX_SIZE == 1024, "comptime int var") + lv_assert(TAU > 6.28, "comptime float var") + lv_assert(TAU < 6.29, "comptime float var upper") + print("test_comptime: ALL PASSED") diff --git a/tests/test_std_math.lv b/tests/test_std_math.lv index b8feb18..b4cc2eb 100644 --- a/tests/test_std_math.lv +++ b/tests/test_std_math.lv @@ -2,10 +2,10 @@ import std::math int fn main(): // Constants - lv_assert(math::pi() > 3.14, "pi > 3.14") - lv_assert(math::pi() < 3.15, "pi < 3.15") - lv_assert(math::e() > 2.71, "e > 2.71") - lv_assert(math::e() < 2.72, "e < 2.72") + lv_assert(math::PI > 3.14, "PI > 3.14") + lv_assert(math::PI < 3.15, "PI < 3.15") + lv_assert(math::E > 2.71, "E > 2.71") + lv_assert(math::E < 2.72, "E < 2.72") // abs, min, max lv_assert(math::abs(-5) == 5, "abs(-5)") From 6f7d238f8de2f8c346bf6827c317e2b4c1ef5b7f Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 13:10:31 +0300 Subject: [PATCH 6/8] std: ensuring all functions working with different types --- runtime/liblavina/convert.h | 2 ++ runtime/liblavina/print.h | 20 ++++++++++++++++++++ tests/test_types.lv | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/runtime/liblavina/convert.h b/runtime/liblavina/convert.h index 1be2e84..89688a3 100644 --- a/runtime/liblavina/convert.h +++ b/runtime/liblavina/convert.h @@ -28,6 +28,8 @@ inline std::string operator+(float f, const std::string& s) { return std::to_str inline std::string operator+(const std::string& s, const char* c) { return s + std::string(c); } inline std::string operator+(const char* c, const std::string& s) { return std::string(c) + s; } inline std::string to_string(int32_t n) { return std::to_string(n); } +inline std::string to_string(int16_t n) { return std::to_string(n); } +inline std::string to_string(int8_t n) { return std::to_string(static_cast(n)); } inline std::string to_string(size_t n) { return std::to_string(n); } inline std::string to_string(float f) { return std::to_string(f); } inline std::string to_string(const char* s) { return std::string(s); } diff --git a/runtime/liblavina/print.h b/runtime/liblavina/print.h index 622c2fc..36ed222 100644 --- a/runtime/liblavina/print.h +++ b/runtime/liblavina/print.h @@ -5,6 +5,23 @@ inline void print(const std::string& s) { std::cout << s << std::endl; } inline void print(int64_t n) { std::cout << n << std::endl; } inline void print(double n) { std::cout << n << std::endl; } inline void print(bool b) { std::cout << (b ? "true" : "false") << std::endl; } +inline void print(int32_t n) { std::cout << n << std::endl; } +inline void print(int16_t n) { std::cout << n << std::endl; } +inline void print(int8_t n) { std::cout << static_cast(n) << std::endl; } +inline void print(float f) { std::cout << f << std::endl; } +inline void print(size_t n) { std::cout << n << std::endl; } + +// println — same as print +inline void println(const std::string& s) { std::cout << s << std::endl; } +inline void println(int64_t n) { std::cout << n << std::endl; } +inline void println(double n) { std::cout << n << std::endl; } +inline void println(bool b) { std::cout << (b ? "true" : "false") << std::endl; } +inline void println(int32_t n) { std::cout << n << std::endl; } +inline void println(int16_t n) { std::cout << n << std::endl; } +inline void println(int8_t n) { std::cout << static_cast(n) << std::endl; } +inline void println(float f) { std::cout << f << std::endl; } +inline void println(size_t n) { std::cout << n << std::endl; } +inline void println() { std::cout << std::endl; } template void print(const std::vector& v) { @@ -15,3 +32,6 @@ void print(const std::vector& v) { } std::cout << "]" << std::endl; } + +template +void println(const std::vector& v) { print(v); } diff --git a/tests/test_types.lv b/tests/test_types.lv index 7d768aa..126cdc8 100644 --- a/tests/test_types.lv +++ b/tests/test_types.lv @@ -29,9 +29,43 @@ void fn test_cross_type(): print("int32->int: " + b) print("float32->float: " + d) +void fn test_print_all_types(): + // print() with each type directly + int8 a = 8 + int16 b = 16 + int32 c = 32 + int64 d = 64 + float32 e = 3.2 + float64 f = 6.4 + usize g = 99 + print(a) + print(b) + print(c) + print(d) + print(e) + print(f) + print(g) + print(true) + // println + println(42) + println("hello") + println() + +void fn test_to_string_all_types(): + lv_assert(to_string(42) == "42", "to_string int") + lv_assert(to_string(true) == "true", "to_string bool") + int32 x32 = 32 + lv_assert(to_string(x32) == "32", "to_string int32") + int16 x16 = 16 + lv_assert(to_string(x16) == "16", "to_string int16") + int8 x8 = 8 + lv_assert(to_string(x8) == "8", "to_string int8") + void fn main(): test_int_types() test_float_types() test_usize() test_cross_type() + test_print_all_types() + test_to_string_all_types() print("All type tests passed!") From dbc78310f5dada9e406311c3793f2fef194a77c0 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 13:24:07 +0300 Subject: [PATCH 7/8] src: small refactoring and qol changes, added backwards compatability for runtime for now --- runtime/liblavina/io.h | 2 +- runtime/liblavina/math.h | 2 +- runtime/liblavina/os.h | 2 +- runtime/liblavina/string.h | 2 +- src/checker.lv | 1 - src/codegen.lv | 6 ++-- src/parser.lv | 33 ++---------------- src/scanner.lv | 20 ----------- stages/stage-latest.cpp | 71 ++------------------------------------ 9 files changed, 12 insertions(+), 127 deletions(-) diff --git a/runtime/liblavina/io.h b/runtime/liblavina/io.h index 5e2dde9..fac69a8 100644 --- a/runtime/liblavina/io.h +++ b/runtime/liblavina/io.h @@ -88,7 +88,7 @@ inline int64_t __fs_size(const std::string& path) { return static_cast(std::filesystem::file_size(path)); } -// Bootstrap compat aliases +// Legacy aliases (used by test_stdlib.lv; prefer __-prefixed or std::fs module) inline auto fs_read = __fs_read; inline auto fs_write = __fs_write; inline auto fs_exists = __fs_exists; diff --git a/runtime/liblavina/math.h b/runtime/liblavina/math.h index d0d8640..23282fc 100644 --- a/runtime/liblavina/math.h +++ b/runtime/liblavina/math.h @@ -47,7 +47,7 @@ inline double __lv_random_float() { return dist(rng); } -// Bootstrap compat aliases +// Legacy aliases (used by test_stdlib.lv; prefer __-prefixed or std::math module) inline auto lv_abs = __lv_abs; inline auto lv_fabs = __lv_fabs; inline auto lv_min = __lv_min; diff --git a/runtime/liblavina/os.h b/runtime/liblavina/os.h index 34933b1..a910f8c 100644 --- a/runtime/liblavina/os.h +++ b/runtime/liblavina/os.h @@ -40,7 +40,7 @@ inline std::string __os_cwd() { return std::filesystem::current_path().string(); } -// Bootstrap compat aliases +// Legacy aliases (used by test_stdlib.lv; prefer __-prefixed or std::os module) inline auto os_args = __os_args; inline auto os_exec = __os_exec; inline auto os_env = __os_env; diff --git a/runtime/liblavina/string.h b/runtime/liblavina/string.h index c051607..3da97e2 100644 --- a/runtime/liblavina/string.h +++ b/runtime/liblavina/string.h @@ -107,7 +107,7 @@ inline int64_t __lv_count(const std::string& s, const std::string& sub) { return count; } -// Bootstrap compat aliases +// Legacy aliases (used by test_stdlib.lv; prefer __-prefixed or std:: modules) inline auto str_to_int = __str_to_int; inline auto str_to_float = __str_to_float; inline auto int_to_str = __int_to_str; diff --git a/src/checker.lv b/src/checker.lv index 781ffcf..539f281 100644 --- a/src/checker.lv +++ b/src/checker.lv @@ -434,7 +434,6 @@ class Checker: vector[Param] empty_params = [] for ref name in builtins: this.known_funcs[name] = ExternFn(name, name, TypeNode::Auto(), empty_params) - pass void fn check(ref vector[Stmt] stmts): this.register_builtins() diff --git a/src/codegen.lv b/src/codegen.lv index 106a1b3..6aa2df5 100644 --- a/src/codegen.lv +++ b/src/codegen.lv @@ -669,9 +669,11 @@ class CppCodegen: Match(expr, arm_patterns, arm_bodies): this.emit_match_impl(expr, arm_patterns, arm_bodies, m) Namespace(name, body, visibility): - this.output +="${this.indent()}// TODO: unsupported namespace\n" + // Namespaces are handled via module system in main.lv + pass Import(path, alias): - this.output +="${this.indent()}// TODO: unsupported import\n" + // Imports are resolved by ImportResolver in main.lv + pass Break(keyword): this.output +="${this.indent()}break;\n" Continue(keyword): diff --git a/src/parser.lv b/src/parser.lv index 60b1489..25be0a4 100644 --- a/src/parser.lv +++ b/src/parser.lv @@ -140,6 +140,8 @@ class Parser: return try_pos + 1 return pos + 1 + // Note: checks for comptime at offset 0 because this is also called from + // enum_declaration() where comptime hasn't been consumed yet by declaration() bool fn is_function_start(): int offset = 0 if this.peek_at(offset).token_type == TK_COMPTIME or this.peek_at(offset).token_type == TK_COMPTIME_STRICT: @@ -1049,34 +1051,3 @@ class Parser: else: statements.push(this.declaration()) return statements - - -// ═══════════════════════════════════════════════════════════════ -// Test: parse a simple expression -// ═══════════════════════════════════════════════════════════════ - -void fn test_parser(): - auto parser_test_source = "1 + 2 * 3\n" - auto parser_test_scanner = Scanner(parser_test_source) - parser_test_scanner.scan_tokens() - - auto parser = Parser(parser_test_scanner.tokens) - vector[Stmt] stmts = parser.parse_program() - - print("Parsed statements: " + stmts.len()) - for ref stmt in stmts: - match stmt: - ExprStmt(expr): - match expr: - Binary(left, op, right): - print("Binary: " + op.lexeme) - match right: - Binary(rl, rop, rr): - print(" Right is Binary: " + rop.lexeme) - print("OK: precedence correct (1 + (2 * 3))") - _: - pass - _: - pass - _: - pass diff --git a/src/scanner.lv b/src/scanner.lv index 0e2ea3d..ee5ab31 100644 --- a/src/scanner.lv +++ b/src/scanner.lv @@ -609,23 +609,3 @@ class Scanner: this.add_token(TK_DEDENT, "") this.add_token(TK_EOF, "") - - -// ═══════════════════════════════════════════════════════════════ -// Test: scan a simple Lavina program (used when run standalone) -// ═══════════════════════════════════════════════════════════════ - -void fn test_scanner(): - auto test_source = "int fn add(int a, int b):\n return a + b\n\nprint(add(2, 3))\n" - - auto scanner = Scanner(test_source) - scanner.scan_tokens() - - print("Tokens: " + scanner.tokens.len()) - if scanner.errors.len() > 0: - print("ERRORS:") - for ref err in scanner.errors: - print(err) - else: - for tok in scanner.tokens: - print(tok.to_string()) diff --git a/stages/stage-latest.cpp b/stages/stage-latest.cpp index 2b25cbd..27ea54c 100644 --- a/stages/stage-latest.cpp +++ b/stages/stage-latest.cpp @@ -969,24 +969,6 @@ struct Scanner { }; -void test_scanner() { - auto test_source = std::string("int fn add(int a, int b):\n return a + b\n\nprint(add(2, 3))\n"); - auto scanner = Scanner(test_source); - scanner.scan_tokens(); - print((std::string("Tokens: ") + static_cast(scanner.tokens.size()))); - if ((static_cast(scanner.errors.size()) > INT64_C(0))) { - print(std::string("ERRORS:")); - for (const auto& err : scanner.errors) { - print(err); - } - } - else { - for (auto tok : scanner.tokens) { - print(tok.to_string()); - } - } -} - struct TypeNode; struct TypeNode { struct None {}; @@ -2483,13 +2465,13 @@ struct CppCodegen { auto& name = _v.name; auto& body = _v.body; auto& visibility = _v.visibility; - this->output = (this->output + ((std::string("") + ((*this).indent())) + std::string("// TODO: unsupported namespace\n"))); + /* pass */ } else if (std::holds_alternative::Import>(_match_23._data)) { auto& _v = std::get::Import>(_match_23._data); auto& path = _v.path; auto& alias = _v.alias; - this->output = (this->output + ((std::string("") + ((*this).indent())) + std::string("// TODO: unsupported import\n"))); + /* pass */ } else if (std::holds_alternative::Break>(_match_23._data)) { auto& _v = std::get::Break>(_match_23._data); @@ -4301,7 +4283,6 @@ struct Checker { for (const auto& name : builtins) { this->known_funcs[name] = ExternFn(name, name, TypeNode::make_Auto(), empty_params); } - /* pass */ } void check(const std::vector& stmts) { @@ -6332,54 +6313,6 @@ struct Parser { }; -void test_parser() { - auto parser_test_source = std::string("1 + 2 * 3\n"); - auto parser_test_scanner = Scanner(parser_test_source); - parser_test_scanner.scan_tokens(); - auto parser = Parser(parser_test_scanner.tokens); - std::vector stmts = parser.parse_program(); - print((std::string("Parsed statements: ") + static_cast(stmts.size()))); - for (const auto& stmt : stmts) { - { - const auto& _match_98 = stmt; - if (std::holds_alternative::ExprStmt>(_match_98._data)) { - auto& _v = std::get::ExprStmt>(_match_98._data); - auto& expr = _v.expr; - { - const auto& _match_99 = expr; - if (std::holds_alternative::Binary>(_match_99._data)) { - auto& _v = std::get::Binary>(_match_99._data); - auto& left = *_v.left; - auto& op = _v.op; - auto& right = *_v.right; - print((std::string("Binary: ") + op.lexeme)); - { - const auto& _match_100 = right; - if (std::holds_alternative::Binary>(_match_100._data)) { - auto& _v = std::get::Binary>(_match_100._data); - auto& rl = *_v.left; - auto& rop = _v.op; - auto& rr = *_v.right; - print((std::string(" Right is Binary: ") + rop.lexeme)); - print(std::string("OK: precedence correct (1 + (2 * 3))")); - } - else { - /* pass */ - } - } - } - else { - /* pass */ - } - } - } - else { - /* pass */ - } - } - } -} - struct ModuleInfo { std::string short_name; std::string full_name; From 8707938b1f6f0a9c11f91f7948bd041862ab4ac6 Mon Sep 17 00:00:00 2001 From: Raumberg Date: Sun, 15 Feb 2026 13:32:05 +0300 Subject: [PATCH 8/8] lvpkg, examples: migrated to underlined intrinsic methods from std --- examples/complex/lvg/lvg.lv | 16 ++++++++-------- examples/complex/tree/tree.lv | 8 ++++---- examples/complex/webserver/webserver.lv | 2 +- lvpkg/commands.lv | 16 ++++++++-------- lvpkg/dep.lv | 4 ++-- lvpkg/fsutil.lv | 10 +++++----- lvpkg/git.lv | 10 +++++----- lvpkg/lvpkg.lv | 2 +- 8 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/complex/lvg/lvg.lv b/examples/complex/lvg/lvg.lv index 8722944..8a165b5 100644 --- a/examples/complex/lvg/lvg.lv +++ b/examples/complex/lvg/lvg.lv @@ -76,7 +76,7 @@ string fn highlight(string line, string pattern): string last_file = "" void fn search_file(string path, string pattern): - auto lines = fs_read_lines(path) + auto lines = __fs_read_lines(path) int local_count = 0 int line_num = 1 @@ -100,7 +100,7 @@ void fn search_file(string path, string pattern): string colored_line = highlight(line, pattern) if flag_line_numbers: - print(" ${C_LINE}${int_to_str(line_num)}${C_DIM}:${C_RESET} ${colored_line}") + print(" ${C_LINE}${__int_to_str(line_num)}${C_DIM}:${C_RESET} ${colored_line}") else: print(" ${colored_line}") line_num += 1 @@ -108,16 +108,16 @@ void fn search_file(string path, string pattern): if local_count > 0: file_count += 1 if flag_count_only: - print("${C_FILE}${path}${C_DIM}:${C_RESET} ${C_BOLD}${int_to_str(local_count)}${C_RESET} matches") + print("${C_FILE}${path}${C_DIM}:${C_RESET} ${C_BOLD}${__int_to_str(local_count)}${C_RESET} matches") void fn search_dir(string path, string pattern): - auto entries = fs_listdir(path) + auto entries = __fs_listdir(path) for ref name in entries: if name.substring(0, 1) == ".": continue string full = "${path}/${name}" - if fs_is_dir(full): + if __fs_is_dir(full): search_dir(full, pattern) else: if is_binary_ext(name): @@ -133,7 +133,7 @@ void fn search_dir(string path, string pattern): void fn main(): init_colors() - auto args = os_args() + auto args = __os_args() if args.len() < 2: print("Usage: lvg [path] [--ext .lv] [-i] [-c]") exit(1) @@ -156,10 +156,10 @@ void fn main(): path = args[i] i += 1 - if fs_is_dir(path): + if __fs_is_dir(path): search_dir(path, pattern) else: search_file(path, pattern) print("") - print("${C_SUMMARY}${int_to_str(match_count)} matches${C_RESET} in ${C_SUMMARY}${int_to_str(file_count)} files${C_RESET}") + print("${C_SUMMARY}${__int_to_str(match_count)} matches${C_RESET} in ${C_SUMMARY}${__int_to_str(file_count)} files${C_RESET}") diff --git a/examples/complex/tree/tree.lv b/examples/complex/tree/tree.lv index 8f908ed..75b2168 100644 --- a/examples/complex/tree/tree.lv +++ b/examples/complex/tree/tree.lv @@ -5,7 +5,7 @@ int dir_count = 0 int file_count = 0 void fn print_tree(string path, string prefix): - auto entries = fs_listdir(path) + auto entries = __fs_listdir(path) int i = 0 int total = entries.len() @@ -23,7 +23,7 @@ void fn print_tree(string path, string prefix): child_prefix = " " string full_path = "${path}/${name}" - if fs_is_dir(full_path): + if __fs_is_dir(full_path): print("${prefix}${connector}${name}/") dir_count += 1 print_tree(full_path, "${prefix}${child_prefix}") @@ -35,11 +35,11 @@ void fn print_tree(string path, string prefix): void fn main(): string root = "." - auto args = os_args() + auto args = __os_args() if args.len() > 1: root = args[1] print(root) print_tree(root, "") print("") - print("${int_to_str(dir_count)} directories, ${int_to_str(file_count)} files") + print("${__int_to_str(dir_count)} directories, ${__int_to_str(file_count)} files") diff --git a/examples/complex/webserver/webserver.lv b/examples/complex/webserver/webserver.lv index 5c9cbaa..68f5193 100644 --- a/examples/complex/webserver/webserver.lv +++ b/examples/complex/webserver/webserver.lv @@ -65,7 +65,7 @@ int fn str_to_int(string s): void fn main(): vector[Todo] todos = [] int next_id = 1 - string html = fs_read("index.html") + string html = __fs_read("index.html") Server svr diff --git a/lvpkg/commands.lv b/lvpkg/commands.lv index 7505510..e09db23 100644 --- a/lvpkg/commands.lv +++ b/lvpkg/commands.lv @@ -62,7 +62,7 @@ void fn cmd_install(): warn("No dependencies found in ${PKG_FILE}") return - print("${C_BOLD}Installing ${int_to_str(deps.len())} dependencies...${C_RESET}") + print("${C_BOLD}Installing ${__int_to_str(deps.len())} dependencies...${C_RESET}") print("") ensure_dir(DEPS_DIR) @@ -78,7 +78,7 @@ void fn cmd_install(): for ref dep in deps: string dest = "${DEPS_SRC}/${dep.name}" - if fs_is_dir(dest): + if __fs_is_dir(dest): info("${dep.name}: already cloned, skipping (use 'update' to refresh)") else: git_clone(dep.url, dest, dep.version) @@ -97,7 +97,7 @@ void fn cmd_update(): warn("No dependencies found in ${PKG_FILE}") return - print("${C_BOLD}Updating ${int_to_str(deps.len())} dependencies...${C_RESET}") + print("${C_BOLD}Updating ${__int_to_str(deps.len())} dependencies...${C_RESET}") print("") ensure_dir(DEPS_DIR) @@ -113,7 +113,7 @@ void fn cmd_update(): for ref dep in deps: string dest = "${DEPS_SRC}/${dep.name}" - if fs_is_dir(dest): + if __fs_is_dir(dest): git_update(dest, dep.version) else: git_clone(dep.url, dest, dep.version) @@ -132,11 +132,11 @@ void fn cmd_list(): print("No dependencies found.") return - print("${C_BOLD}Dependencies (${int_to_str(deps.len())}):${C_RESET}") + print("${C_BOLD}Dependencies (${__int_to_str(deps.len())}):${C_RESET}") print("") for ref dep in deps: string status = "${C_DIM}[not installed]${C_RESET}" - if fs_is_dir("${DEPS_SRC}/${dep.name}"): + if __fs_is_dir("${DEPS_SRC}/${dep.name}"): status = "${C_GREEN}[installed]${C_RESET}" string path_info = "" @@ -149,9 +149,9 @@ void fn cmd_list(): // Remove the deps/ directory entirely void fn cmd_clean(): - if fs_is_dir(DEPS_DIR): + if __fs_is_dir(DEPS_DIR): step("Removing ${DEPS_DIR}/...") - os_exec("rm -rf ${DEPS_DIR}") + __os_exec("rm -rf ${DEPS_DIR}") info("Cleaned.") else: warn("Nothing to clean — ${DEPS_DIR}/ does not exist.") diff --git a/lvpkg/dep.lv b/lvpkg/dep.lv index 6d1c691..971564f 100644 --- a/lvpkg/dep.lv +++ b/lvpkg/dep.lv @@ -15,11 +15,11 @@ struct Dep: // lib [header_path] vector[Dep] fn parse_pkg_file(): vector[Dep] deps = [] - if not fs_exists(PKG_FILE): + if not __fs_exists(PKG_FILE): error("No ${PKG_FILE} found in current directory") exit(1) - auto lines = fs_read_lines(PKG_FILE) + auto lines = __fs_read_lines(PKG_FILE) for ref line in lines: auto trimmed = line.trim() if trimmed == "" or starts_with(trimmed, "#"): diff --git a/lvpkg/fsutil.lv b/lvpkg/fsutil.lv index 9d0b68b..7ffe5ca 100644 --- a/lvpkg/fsutil.lv +++ b/lvpkg/fsutil.lv @@ -2,26 +2,26 @@ // Create a directory (and parents) if it doesn't exist void fn ensure_dir(string path): - if not fs_is_dir(path): + if not __fs_is_dir(path): cpp { std::filesystem::create_directories(path); } // Copy a single file from src to dst void fn copy_file(string src, string dst): - auto content = fs_read(src) - fs_write(dst, content) + auto content = __fs_read(src) + __fs_write(dst, content) // Recursively copy a directory, skipping hidden files void fn copy_dir_recursive(string src, string dst): ensure_dir(dst) - auto entries = fs_listdir(src) + auto entries = __fs_listdir(src) for ref name in entries: if starts_with(name, "."): continue string src_path = "${src}/${name}" string dst_path = "${dst}/${name}" - if fs_is_dir(src_path): + if __fs_is_dir(src_path): copy_dir_recursive(src_path, dst_path) else: copy_file(src_path, dst_path) diff --git a/lvpkg/git.lv b/lvpkg/git.lv index 31a7085..37cdcf7 100644 --- a/lvpkg/git.lv +++ b/lvpkg/git.lv @@ -3,17 +3,17 @@ // Clone a repository at a specific version (tag/branch/commit) void fn git_clone(string url, string dest, string version): step("Cloning ${url} @ ${version}") - int result = os_exec("git clone --quiet --depth 1 --branch ${version} ${url} ${dest} 2>&1") + int result = __os_exec("git clone --quiet --depth 1 --branch ${version} ${url} ${dest} 2>&1") if result != 0: // fallback: clone default branch and checkout - result = os_exec("git clone --quiet ${url} ${dest} 2>&1") + result = __os_exec("git clone --quiet ${url} ${dest} 2>&1") if result != 0: error("Failed to clone ${url}") return - os_exec("cd ${dest} && git checkout --quiet ${version} 2>&1") + __os_exec("cd ${dest} && git checkout --quiet ${version} 2>&1") // Fetch latest and checkout a specific version void fn git_update(string dest, string version): step("Updating ${dest} to ${version}") - os_exec("cd ${dest} && git fetch --quiet --all 2>&1") - os_exec("cd ${dest} && git checkout --quiet ${version} 2>&1") + __os_exec("cd ${dest} && git fetch --quiet --all 2>&1") + __os_exec("cd ${dest} && git checkout --quiet ${version} 2>&1") diff --git a/lvpkg/lvpkg.lv b/lvpkg/lvpkg.lv index e3e22b9..aba5f6a 100644 --- a/lvpkg/lvpkg.lv +++ b/lvpkg/lvpkg.lv @@ -46,7 +46,7 @@ void fn print_help(): void fn main(): init_colors() - auto args = os_args() + auto args = __os_args() if args.len() < 2: print_help() exit(0)