From e955d92a93a9ef7bc2da4b24478b91419b197312 Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Sun, 6 Sep 2026 00:20:46 +0200 Subject: [PATCH 1/6] initial Pathname implementation --- ListTalk/ListTalk.h | 1 + ListTalk/classes/Pathname.h | 23 ++++++ meson.build | 1 + src/classes/ByteVector.c | 15 ++-- src/classes/Pathname.c | 130 +++++++++++++++++++++++++++++++++ src/classes/RandomAccessFile.c | 3 +- src/classes/Stream.c | 2 +- src/classes/String.c | 17 +++-- src/modules/ini.c | 4 +- src/modules/os.c | 2 +- src/modules/zlib.c | 4 +- src/vm/base_env/base_env.c | 1 + src/vm/base_env/primitives.c | 6 +- tests/c_api_test.c | 17 +++++ tests/eval-objects.lt | 10 +++ 15 files changed, 210 insertions(+), 26 deletions(-) create mode 100644 ListTalk/classes/Pathname.h create mode 100644 src/classes/Pathname.c diff --git a/ListTalk/ListTalk.h b/ListTalk/ListTalk.h index d63b575..d83b5bd 100644 --- a/ListTalk/ListTalk.h +++ b/ListTalk/ListTalk.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include diff --git a/ListTalk/classes/Pathname.h b/ListTalk/classes/Pathname.h new file mode 100644 index 0000000..3574e31 --- /dev/null +++ b/ListTalk/classes/Pathname.h @@ -0,0 +1,23 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) 2023 - 2026 Ales Hakl + */ +#ifndef H__ListTalk__Pathname__ +#define H__ListTalk__Pathname__ + +#include +#include + +LT__BEGIN_DECLS + +LT_DECLARE_CLASS(LT_Pathname); + +LT_Pathname* LT_Pathname_new(char* pathname); +LT_Pathname* LT_Pathname_from_string(LT_String* string); +LT_String* LT_Pathname_as_string(LT_Pathname* pathname); +char* LT_Pathname_value_cstr(LT_Pathname* pathname); +char* LT_Pathname_like_value_cstr(LT_Value value); +LT_String* LT_Pathname_like_as_string(LT_Value value); + +LT__END_DECLS +#endif diff --git a/meson.build b/meson.build index 999f175..aa02ed2 100644 --- a/meson.build +++ b/meson.build @@ -165,6 +165,7 @@ vm_lib = library( 'src/classes/Float.c', 'src/classes/List.c', 'src/classes/String.c', + 'src/classes/Pathname.c', 'src/classes/Vector.c', 'src/classes/BitVector.c', 'src/classes/ByteVector.c', diff --git a/src/classes/ByteVector.c b/src/classes/ByteVector.c index d299cc1..8953538 100644 --- a/src/classes/ByteVector.c +++ b/src/classes/ByteVector.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -305,17 +306,17 @@ LT_DEFINE_PRIMITIVE( ){ LT_Value cursor = arguments; LT_Value self; - LT_String* filename; + LT_Value filename; uint8_t* bytes; size_t length; (void)tail_call_unwind_marker; LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_OBJECT_ARG(cursor, filename); LT_ARG_END(cursor); (void)self; - bytes = read_file_bytes(LT_String_value_cstr(filename), &length); + bytes = read_file_bytes(LT_Pathname_like_value_cstr(filename), &length); return (LT_Value)(uintptr_t)LT_ByteVector_new(bytes, length); } @@ -367,19 +368,19 @@ LT_DEFINE_PRIMITIVE( ){ LT_Value cursor = arguments; LT_ByteVector* bytevector; - LT_String* filename; + LT_Value filename; (void)tail_call_unwind_marker; LT_GENERIC_ARG(cursor, bytevector, LT_ByteVector*, LT_ByteVector_from_value); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_OBJECT_ARG(cursor, filename); LT_ARG_END(cursor); LT_write_file_bytes_atomically( - LT_String_value_cstr(filename), + LT_Pathname_like_value_cstr(filename), LT_ByteVector_bytes(bytevector), LT_ByteVector_length(bytevector) ); - return (LT_Value)(uintptr_t)filename; + return filename; } LT_DEFINE_PRIMITIVE( diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c new file mode 100644 index 0000000..435bbaf --- /dev/null +++ b/src/classes/Pathname.c @@ -0,0 +1,130 @@ +/* + * SPDX-License-Identifier: MIT + * Copyright (c) 2023 - 2026 Ales Hakl + */ +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +struct LT_Pathname_s { + LT_Object base; + char* pathname; +}; + +static void Pathname_debugPrintOn(LT_Value value, FILE* stream){ + fprintf(stream, "#", + LT_Pathname_value_cstr(LT_Pathname_from_value(value))); +} + +LT_DEFINE_PRIMITIVE( + pathname_class_method_from_string, + "Pathname class>>fromString:", + "(self string)", + "Create a pathname from a string that contains no NUL bytes." +){ + LT_Value cursor = arguments; + LT_Value self; + LT_String* string; + (void)tail_call_unwind_marker; + LT_OBJECT_ARG(cursor, self); + LT_GENERIC_ARG(cursor, string, LT_String*, LT_String_from_value); + LT_ARG_END(cursor); + if (self != LT_STATIC_CLASS(LT_Pathname)){ + LT_error("fromString: is only supported on Pathname"); + } + return (LT_Value)(uintptr_t)LT_Pathname_from_string(string); +} + +LT_DEFINE_PRIMITIVE( + pathname_method_as_string, + "Pathname>>asString", + "(self)", + "Return the pathname as a string." +){ + LT_Value cursor = arguments; + LT_Pathname* pathname; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_Pathname_as_string(pathname); +} + +static LT_Method_Descriptor Pathname_methods[] = { + {"asString", &pathname_method_as_string}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +static LT_Method_Descriptor Pathname_class_methods[] = { + {"fromString:", &pathname_class_method_from_string}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +LT_DEFINE_CLASS(LT_Pathname) { + .superclass = <_Object_class, + .metaclass_superclass = <_Class_class, + .name = "Pathname", + .documentation = "Immutable NUL-terminated UTF-8 filesystem pathname.", + .instance_size = sizeof(LT_Pathname), + .class_flags = LT_CLASS_FLAG_IMMUTABLE | LT_CLASS_FLAG_SCALAR, + .debugPrintOn = Pathname_debugPrintOn, + .methods = Pathname_methods, + .class_methods = Pathname_class_methods, +}; + +LT_Pathname* LT_Pathname_new(char* pathname){ + return LT_Pathname_from_string(LT_String_new_cstr(pathname)); +} + +LT_Pathname* LT_Pathname_from_string(LT_String* string){ + LT_Pathname* pathname; + size_t length = LT_String_byte_length(string); + char* bytes; + + if (strlen(LT_String_value_cstr(string)) != length){ + LT_error("Pathname cannot contain NUL bytes"); + } + bytes = GC_MALLOC_ATOMIC(length + 1); + memcpy(bytes, LT_String_value_cstr(string), length + 1); + pathname = LT_Class_ALLOC(LT_Pathname); + pathname->pathname = bytes; + return pathname; +} + +LT_String* LT_Pathname_as_string(LT_Pathname* pathname){ + return LT_String_new_cstr(pathname->pathname); +} + +char* LT_Pathname_value_cstr(LT_Pathname* pathname){ + return pathname->pathname; +} + +char* LT_Pathname_like_value_cstr(LT_Value value){ + if (LT_Pathname_p(value)){ + return LT_Pathname_value_cstr(LT_Pathname_from_value(value)); + } + if (LT_String_p(value)){ + LT_String* string = LT_String_from_value(value); + if (strlen(LT_String_value_cstr(string)) != LT_String_byte_length(string)){ + LT_error("Pathname cannot contain NUL bytes"); + } + return (char*)LT_String_value_cstr(string); + } + LT_error("Expected Pathname or String"); + return NULL; +} + +LT_String* LT_Pathname_like_as_string(LT_Value value){ + if (LT_Pathname_p(value)){ + return LT_Pathname_as_string(LT_Pathname_from_value(value)); + } + (void)LT_Pathname_like_value_cstr(value); + return LT_String_from_value(value); +} diff --git a/src/classes/RandomAccessFile.c b/src/classes/RandomAccessFile.c index e8016f2..380a911 100644 --- a/src/classes/RandomAccessFile.c +++ b/src/classes/RandomAccessFile.c @@ -6,6 +6,7 @@ #include "BigInteger_internal.h" #include +#include #include #include @@ -191,7 +192,7 @@ static LT_Value random_access_file_constructor(LT_Value arguments, LT_String* filename; LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_GENERIC_ARG(cursor, filename, LT_String*, LT_Pathname_like_as_string); LT_ARG_END(cursor); if (self != (LT_Value)(uintptr_t)<_RandomAccessFile_class){ LT_error( diff --git a/src/classes/Stream.c b/src/classes/Stream.c index b96a622..8325a3d 100644 --- a/src/classes/Stream.c +++ b/src/classes/Stream.c @@ -1043,7 +1043,7 @@ static LT_Value filename_arg(LT_Value arguments){ LT_String* filename; LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_GENERIC_ARG(cursor, filename, LT_String*, LT_Pathname_like_as_string); LT_ARG_END(cursor); (void)self; return (LT_Value)(uintptr_t)filename; diff --git a/src/classes/String.c b/src/classes/String.c index e24ace6..f7a71ac 100644 --- a/src/classes/String.c +++ b/src/classes/String.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -1269,15 +1270,17 @@ LT_DEFINE_PRIMITIVE( ){ LT_Value cursor = arguments; LT_Value self; - LT_String* filename; + LT_Value filename; (void)tail_call_unwind_marker; LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_OBJECT_ARG(cursor, filename); LT_ARG_END(cursor); (void)self; - return (LT_Value)(uintptr_t)read_file_string(LT_String_value_cstr(filename)); + return (LT_Value)(uintptr_t)read_file_string( + LT_Pathname_like_value_cstr(filename) + ); } LT_DEFINE_PRIMITIVE( @@ -1288,19 +1291,19 @@ LT_DEFINE_PRIMITIVE( ){ LT_Value cursor = arguments; LT_String* string; - LT_String* filename; + LT_Value filename; (void)tail_call_unwind_marker; LT_GENERIC_ARG(cursor, string, LT_String*, LT_String_from_value); - LT_GENERIC_ARG(cursor, filename, LT_String*, LT_String_from_value); + LT_OBJECT_ARG(cursor, filename); LT_ARG_END(cursor); LT_write_file_bytes_atomically( - LT_String_value_cstr(filename), + LT_Pathname_like_value_cstr(filename), LT_String_value_cstr(string), LT_String_byte_length(string) ); - return (LT_Value)(uintptr_t)filename; + return filename; } LT_DEFINE_PRIMITIVE( diff --git a/src/modules/ini.c b/src/modules/ini.c index 7747cf5..8d9b0e1 100644 --- a/src/modules/ini.c +++ b/src/modules/ini.c @@ -152,7 +152,7 @@ LT_DEFINE_PRIMITIVE( (void)invocation_context_data; LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, path, LT_String*, LT_String_from_value); + LT_GENERIC_ARG(cursor, path, LT_String*, LT_Pathname_like_as_string); LT_ARG_END(cursor); if (self != LT_STATIC_CLASS(LT_INIReader)){ LT_error("readFile: class method is only supported on INIReader"); @@ -209,7 +209,7 @@ LT_DEFINE_PRIMITIVE( (void)invocation_context_data; LT_GENERIC_ARG(cursor, self, LT_INIReader*, LT_INIReader_from_value); - LT_GENERIC_ARG(cursor, path, LT_String*, LT_String_from_value); + LT_GENERIC_ARG(cursor, path, LT_String*, LT_Pathname_like_as_string); LT_ARG_END(cursor); ini = LT_INI_parseFile( diff --git a/src/modules/os.c b/src/modules/os.c index 0467e6d..62435bb 100644 --- a/src/modules/os.c +++ b/src/modules/os.c @@ -179,7 +179,7 @@ static void bind_os_primitive(LT_Environment* environment, } #define OS_STRING_ARG(cursor, name) \ - LT_GENERIC_ARG(cursor, name, LT_String*, LT_String_from_value) + LT_GENERIC_ARG(cursor, name, LT_String*, LT_Pathname_like_as_string) #define OS_STAT_ARG(cursor, name) \ LT_GENERIC_ARG(cursor, name, LT_OS_Stat*, LT_OS_Stat_from_value) diff --git a/src/modules/zlib.c b/src/modules/zlib.c index 17e562e..419f8fd 100644 --- a/src/modules/zlib.c +++ b/src/modules/zlib.c @@ -653,7 +653,7 @@ LT_DEFINE_PRIMITIVE( (void)tail_call_unwind_marker; \ (void)invocation_context_kind; \ (void)invocation_context_data; \ - LT_GENERIC_ARG(cursor, path, LT_String*, LT_String_from_value); \ + LT_GENERIC_ARG(cursor, path, LT_String*, LT_Pathname_like_as_string); \ LT_ARG_END(cursor); \ return gzip_stream_new(path, mode); \ } @@ -694,7 +694,7 @@ DEFINE_GZIP_OPEN_PRIMITIVE( (void)invocation_context_kind; \ (void)invocation_context_data; \ LT_OBJECT_ARG(cursor, self); \ - LT_GENERIC_ARG(cursor, path, LT_String*, LT_String_from_value); \ + LT_GENERIC_ARG(cursor, path, LT_String*, LT_Pathname_like_as_string); \ LT_ARG_END(cursor); \ (void)self; \ return gzip_stream_new(path, mode); \ diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index a43d4f8..9e3b337 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -154,6 +154,7 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"XoshiroRNG", <_XoshiroRNG_class}, {"AsconRNG", <_AsconRNG_class}, {"String", <_String_class}, + {"Pathname", <_Pathname_class}, {"StringIterator", <_StringIterator_class}, {"Symbol", <_Symbol_class}, {"Package", <_Package_class}, diff --git a/src/vm/base_env/primitives.c b/src/vm/base_env/primitives.c index 353f7f0..8c0bb75 100644 --- a/src/vm/base_env/primitives.c +++ b/src/vm/base_env/primitives.c @@ -779,11 +779,7 @@ LT_DEFINE_PRIMITIVE( LT_OBJECT_ARG(cursor, path); LT_ARG_END(cursor); - if (!LT_String_p(path)){ - LT_type_error(path, <_String_class); - } - - path_cstr = LT_String_value_cstr(LT_String_from_value(path)); + path_cstr = LT_Pathname_like_value_cstr(path); file = fopen(path_cstr, "r"); if (file == NULL){ LT_system_error("Could not open file for reading", errno); diff --git a/tests/c_api_test.c b/tests/c_api_test.c index 4847e75..38cdd02 100644 --- a/tests/c_api_test.c +++ b/tests/c_api_test.c @@ -3612,6 +3612,22 @@ static int test_string_utf8_helpers_replace_invalid_sequences(void){ ); } +static int test_pathname_c_api_round_trips_utf8(void){ + LT_Pathname* pathname = LT_Pathname_new("dir/\xce\xbb.txt"); + LT_String* string = LT_Pathname_as_string(pathname); + + if (expect( + strcmp(LT_Pathname_value_cstr(pathname), "dir/\xce\xbb.txt") == 0, + "LT_Pathname_new and LT_Pathname_value_cstr round-trip UTF-8" + )){ + return 1; + } + return expect( + strcmp(LT_String_value_cstr(string), "dir/\xce\xbb.txt") == 0, + "LT_Pathname_as_string converts to String" + ); +} + static int test_string_append_and_substring_c_api_use_codepoint_indexes(void){ LT_String* left = LT_String_new_cstr("a\xce\xbb"); LT_String* right = LT_String_new_cstr("\xf0\x9f\x98\x80" "z"); @@ -4617,6 +4633,7 @@ int main(void){ RUN_TEST(test_character_api_uses_unicode_codepoints); RUN_TEST(test_string_api_uses_unicode_codepoints); RUN_TEST(test_string_utf8_helpers_replace_invalid_sequences); + RUN_TEST(test_pathname_c_api_round_trips_utf8); RUN_TEST(test_string_append_and_substring_c_api_use_codepoint_indexes); RUN_TEST(test_string_search_c_api_uses_codepoint_indexes); RUN_TEST(test_string_format_c_api); diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index f0e4e07..2df7ae4 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1564,6 +1564,16 @@ "RegularExpression>>substitute:with: treats unmatched captures as empty") ) +(define-test "Pathname" +;;; Pathname + +(let ((path [Pathname fromString: "dir/\u03bb.txt"])) + (check (eq? (type-of path) Pathname) + "Pathname class>>fromString: creates Pathname") + (check (equal? [path asString] "dir/\u03bb.txt") + "Pathname>>asString returns pathname text")) +) + (define-test "make-class and make-instance" ;;; make-class and make-instance From 4bf27cb1de0adb30c50f17e3dcda1d3e6a32a68a Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Sun, 6 Sep 2026 00:54:55 +0200 Subject: [PATCH 2/6] split Pathname into two concrete implementations --- ListTalk/classes/Pathname.h | 27 ++- src/classes/Pathname.c | 349 +++++++++++++++++++++++++++++++++--- src/vm/base_env/base_env.c | 2 + src/vm/reader.c | 26 +++ tests/c_api_test.c | 55 +++++- tests/eval-objects.lt | 46 ++++- tests/reader_test.c | 49 +++++ 7 files changed, 519 insertions(+), 35 deletions(-) diff --git a/ListTalk/classes/Pathname.h b/ListTalk/classes/Pathname.h index 3574e31..25196d1 100644 --- a/ListTalk/classes/Pathname.h +++ b/ListTalk/classes/Pathname.h @@ -10,12 +10,37 @@ LT__BEGIN_DECLS -LT_DECLARE_CLASS(LT_Pathname); +typedef struct LT_Pathname_s LT_Pathname; +typedef struct LT_RelativePathname_s LT_RelativePathname; +typedef struct LT_AbsolutePathname_s LT_AbsolutePathname; + +extern LT_Class LT_Pathname_class; +extern LT_Class LT_Pathname_class_class; +LT_DECLARE_CLASS(LT_RelativePathname); +LT_DECLARE_CLASS(LT_AbsolutePathname); + +static inline int LT_Pathname_p(LT_Value value){ + return LT_Value_is_instance_of(value, LT_STATIC_CLASS(LT_Pathname)); +} + +static inline LT_Pathname* LT_Pathname_from_value(LT_Value value){ + if (!LT_Pathname_p(value)){ + LT_type_error(value, <_Pathname_class); + } + return (LT_Pathname*)LT_VALUE_POINTER_VALUE(value); +} LT_Pathname* LT_Pathname_new(char* pathname); LT_Pathname* LT_Pathname_from_string(LT_String* string); +LT_RelativePathname* LT_RelativePathname_new(char* pathname); +LT_RelativePathname* LT_RelativePathname_from_string(LT_String* string); +LT_AbsolutePathname* LT_AbsolutePathname_new(char* pathname); +LT_AbsolutePathname* LT_AbsolutePathname_from_string(LT_String* string); +LT_Pathname* LT_Pathname_append(LT_Pathname* left, LT_Pathname* right); LT_String* LT_Pathname_as_string(LT_Pathname* pathname); char* LT_Pathname_value_cstr(LT_Pathname* pathname); +int LT_Pathname_absolute_p(LT_Pathname* pathname); +int LT_Pathname_relative_p(LT_Pathname* pathname); char* LT_Pathname_like_value_cstr(LT_Value value); LT_String* LT_Pathname_like_as_string(LT_Value value); diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c index 435bbaf..0516288 100644 --- a/src/classes/Pathname.c +++ b/src/classes/Pathname.c @@ -19,28 +19,228 @@ struct LT_Pathname_s { char* pathname; }; +struct LT_RelativePathname_s { + LT_Pathname base; +}; + +struct LT_AbsolutePathname_s { + LT_Pathname base; +}; + +static void Pathname_check_string(LT_String* string){ + if (strlen(LT_String_value_cstr(string)) != LT_String_byte_length(string)){ + LT_error("Pathname cannot contain NUL bytes"); + } +} + +static char* Pathname_normalize(const char* input, int absolute){ + size_t input_length = strlen(input); + char* copy = GC_MALLOC_ATOMIC(input_length + 1); + char** segments = GC_MALLOC(sizeof(char*) * (input_length + 1)); + size_t count = 0; + char* cursor; + char* output = GC_MALLOC_ATOMIC(input_length + 3); + char* output_cursor = output; + size_t index; + + memcpy(copy, input, input_length + 1); + cursor = copy; + while (*cursor != '\0'){ + char* segment; + + while (*cursor == '/'){ + cursor++; + } + if (*cursor == '\0'){ + break; + } + segment = cursor; + while (*cursor != '\0' && *cursor != '/'){ + cursor++; + } + if (*cursor != '\0'){ + *cursor++ = '\0'; + } + if (strcmp(segment, ".") == 0){ + continue; + } + if (strcmp(segment, "..") == 0){ + if (count > 0 && strcmp(segments[count - 1], "..") != 0){ + count--; + } else if (absolute){ + LT_error("Absolute pathname cannot resolve above root"); + } else { + segments[count++] = segment; + } + } else { + segments[count++] = segment; + } + } + + if (absolute){ + *output_cursor++ = '/'; + } else if (count == 0){ + *output_cursor++ = '.'; + } else if (strcmp(segments[0], "..") != 0){ + *output_cursor++ = '.'; + *output_cursor++ = '/'; + } + for (index = 0; index < count; index++){ + size_t length = strlen(segments[index]); + + if (index > 0){ + *output_cursor++ = '/'; + } + memcpy(output_cursor, segments[index], length); + output_cursor += length; + } + *output_cursor = '\0'; + return output; +} + +static LT_Pathname* Pathname_allocate(LT_Class* klass, char* normalized){ + LT_Pathname* pathname = LT_Class_alloc(klass); + + pathname->pathname = normalized; + return pathname; +} + +static size_t Pathname_hash(LT_Value value){ + const unsigned char* cursor = (const unsigned char*)LT_Pathname_value_cstr( + LT_Pathname_from_value(value) + ); + uint32_t hash = UINT32_C(0x811c9dc5); + + while (*cursor != '\0'){ + hash ^= *cursor++; + hash *= UINT32_C(0x01000193); + } + return (size_t)hash; +} + +static int Pathname_equal_p(LT_Value left, LT_Value right){ + return LT_Pathname_p(right) + && strcmp( + LT_Pathname_value_cstr(LT_Pathname_from_value(left)), + LT_Pathname_value_cstr(LT_Pathname_from_value(right)) + ) == 0; +} + static void Pathname_debugPrintOn(LT_Value value, FILE* stream){ - fprintf(stream, "#", - LT_Pathname_value_cstr(LT_Pathname_from_value(value))); + LT_String* string = LT_Pathname_as_string(LT_Pathname_from_value(value)); + + fputs("#p", stream); + LT_Value_debugPrintOn((LT_Value)(uintptr_t)string, stream); +} + +typedef LT_Pathname* (*Pathname_StringConstructor)(LT_String* string); + +static LT_Pathname* RelativePathname_from_string_as_pathname(LT_String* string){ + return (LT_Pathname*)LT_RelativePathname_from_string(string); +} + +static LT_Pathname* AbsolutePathname_from_string_as_pathname(LT_String* string){ + return (LT_Pathname*)LT_AbsolutePathname_from_string(string); +} + +static LT_Value Pathname_class_from_string(LT_Value arguments, + LT_Class* expected_class, + Pathname_StringConstructor constructor){ + LT_Value cursor = arguments; + LT_Value self; + LT_String* string; + + LT_OBJECT_ARG(cursor, self); + LT_GENERIC_ARG(cursor, string, LT_String*, LT_String_from_value); + LT_ARG_END(cursor); + if (self != (LT_Value)(uintptr_t)expected_class){ + LT_error("fromString: is not supported on this class"); + } + return (LT_Value)(uintptr_t)constructor(string); } LT_DEFINE_PRIMITIVE( pathname_class_method_from_string, "Pathname class>>fromString:", "(self string)", - "Create a pathname from a string that contains no NUL bytes." + "Create an absolute or relative pathname according to the string's shape." +){ + (void)tail_call_unwind_marker; + return Pathname_class_from_string( + arguments, <_Pathname_class, LT_Pathname_from_string + ); +} + +LT_DEFINE_PRIMITIVE( + relative_pathname_class_method_from_string, + "RelativePathname class>>fromString:", + "(self string)", + "Create a relative pathname; reject a leading slash." +){ + (void)tail_call_unwind_marker; + return Pathname_class_from_string( + arguments, + <_RelativePathname_class, + RelativePathname_from_string_as_pathname + ); +} + +LT_DEFINE_PRIMITIVE( + absolute_pathname_class_method_from_string, + "AbsolutePathname class>>fromString:", + "(self string)", + "Create an absolute pathname, adding a leading slash when absent." +){ + (void)tail_call_unwind_marker; + return Pathname_class_from_string( + arguments, + <_AbsolutePathname_class, + AbsolutePathname_from_string_as_pathname + ); +} + +LT_DEFINE_PRIMITIVE( + pathname_method_absolute_p, + "Pathname>>absolute?", + "(self)", + "Return true when the pathname is absolute." ){ LT_Value cursor = arguments; - LT_Value self; - LT_String* string; + LT_Pathname* pathname; (void)tail_call_unwind_marker; - LT_OBJECT_ARG(cursor, self); - LT_GENERIC_ARG(cursor, string, LT_String*, LT_String_from_value); + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); LT_ARG_END(cursor); - if (self != LT_STATIC_CLASS(LT_Pathname)){ - LT_error("fromString: is only supported on Pathname"); - } - return (LT_Value)(uintptr_t)LT_Pathname_from_string(string); + return LT_Pathname_absolute_p(pathname) ? LT_TRUE : LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + pathname_method_relative_p, + "Pathname>>relative?", + "(self)", + "Return true when the pathname is relative." +){ + LT_Value cursor = arguments; + LT_Pathname* pathname; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return LT_Pathname_relative_p(pathname) ? LT_TRUE : LT_FALSE; +} + +LT_DEFINE_PRIMITIVE( + pathname_method_append, + "Pathname>>/", + "(self pathname)", + "Append a relative pathname to the receiver and normalize the result." +){ + LT_Value cursor = arguments; + LT_Pathname* left; + LT_Pathname* right; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, left, LT_Pathname*, LT_Pathname_from_value); + LT_GENERIC_ARG(cursor, right, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_Pathname_append(left, right); } LT_DEFINE_PRIMITIVE( @@ -59,6 +259,9 @@ LT_DEFINE_PRIMITIVE( static LT_Method_Descriptor Pathname_methods[] = { {"asString", &pathname_method_as_string}, + {"absolute?", &pathname_method_absolute_p}, + {"relative?", &pathname_method_relative_p}, + {"/", &pathname_method_append}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; @@ -67,35 +270,121 @@ static LT_Method_Descriptor Pathname_class_methods[] = { LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; +static LT_Method_Descriptor RelativePathname_class_methods[] = { + {"fromString:", &relative_pathname_class_method_from_string}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +static LT_Method_Descriptor AbsolutePathname_class_methods[] = { + {"fromString:", &absolute_pathname_class_method_from_string}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + LT_DEFINE_CLASS(LT_Pathname) { .superclass = <_Object_class, .metaclass_superclass = <_Class_class, .name = "Pathname", - .documentation = "Immutable NUL-terminated UTF-8 filesystem pathname.", + .documentation = "Abstract normalized UTF-8 filesystem pathname.", .instance_size = sizeof(LT_Pathname), - .class_flags = LT_CLASS_FLAG_IMMUTABLE | LT_CLASS_FLAG_SCALAR, + .class_flags = LT_CLASS_FLAG_ABSTRACT | LT_CLASS_FLAG_IMMUTABLE + | LT_CLASS_FLAG_SCALAR, + .hash = Pathname_hash, + .equal_p = Pathname_equal_p, .debugPrintOn = Pathname_debugPrintOn, .methods = Pathname_methods, .class_methods = Pathname_class_methods, }; +LT_DEFINE_CLASS(LT_RelativePathname) { + .superclass = <_Pathname_class, + .metaclass_superclass = <_Class_class, + .name = "RelativePathname", + .documentation = "Normalized relative filesystem pathname.", + .instance_size = sizeof(LT_RelativePathname), + .class_flags = LT_CLASS_FLAG_FINAL | LT_CLASS_FLAG_IMMUTABLE + | LT_CLASS_FLAG_SCALAR, + .debugPrintOn = Pathname_debugPrintOn, + .class_methods = RelativePathname_class_methods, +}; + +LT_DEFINE_CLASS(LT_AbsolutePathname) { + .superclass = <_Pathname_class, + .metaclass_superclass = <_Class_class, + .name = "AbsolutePathname", + .documentation = "Normalized absolute filesystem pathname.", + .instance_size = sizeof(LT_AbsolutePathname), + .class_flags = LT_CLASS_FLAG_FINAL | LT_CLASS_FLAG_IMMUTABLE + | LT_CLASS_FLAG_SCALAR, + .debugPrintOn = Pathname_debugPrintOn, + .class_methods = AbsolutePathname_class_methods, +}; + LT_Pathname* LT_Pathname_new(char* pathname){ - return LT_Pathname_from_string(LT_String_new_cstr(pathname)); + return pathname[0] == '/' + ? (LT_Pathname*)LT_AbsolutePathname_new(pathname) + : (LT_Pathname*)LT_RelativePathname_new(pathname); } LT_Pathname* LT_Pathname_from_string(LT_String* string){ - LT_Pathname* pathname; - size_t length = LT_String_byte_length(string); - char* bytes; + Pathname_check_string(string); + return LT_String_value_cstr(string)[0] == '/' + ? (LT_Pathname*)LT_AbsolutePathname_from_string(string) + : (LT_Pathname*)LT_RelativePathname_from_string(string); +} - if (strlen(LT_String_value_cstr(string)) != length){ - LT_error("Pathname cannot contain NUL bytes"); +LT_RelativePathname* LT_RelativePathname_new(char* pathname){ + return LT_RelativePathname_from_string(LT_String_new_cstr(pathname)); +} + +LT_RelativePathname* LT_RelativePathname_from_string(LT_String* string){ + Pathname_check_string(string); + if (LT_String_value_cstr(string)[0] == '/'){ + LT_error("Relative pathname cannot start with slash"); } - bytes = GC_MALLOC_ATOMIC(length + 1); - memcpy(bytes, LT_String_value_cstr(string), length + 1); - pathname = LT_Class_ALLOC(LT_Pathname); - pathname->pathname = bytes; - return pathname; + return (LT_RelativePathname*)Pathname_allocate( + <_RelativePathname_class, + Pathname_normalize(LT_String_value_cstr(string), 0) + ); +} + +LT_AbsolutePathname* LT_AbsolutePathname_new(char* pathname){ + return LT_AbsolutePathname_from_string(LT_String_new_cstr(pathname)); +} + +LT_AbsolutePathname* LT_AbsolutePathname_from_string(LT_String* string){ + Pathname_check_string(string); + return (LT_AbsolutePathname*)Pathname_allocate( + <_AbsolutePathname_class, + Pathname_normalize(LT_String_value_cstr(string), 1) + ); +} + +LT_Pathname* LT_Pathname_append(LT_Pathname* left, LT_Pathname* right){ + const char* left_bytes = LT_Pathname_value_cstr(left); + const char* right_bytes = LT_Pathname_value_cstr(right); + const char* right_suffix; + size_t left_length; + size_t right_length; + char* combined; + + if (LT_Pathname_absolute_p(right)){ + LT_error("Cannot append an absolute pathname"); + } + right_suffix = strcmp(right_bytes, ".") == 0 + ? "" + : (right_bytes[0] == '.' && right_bytes[1] == '/' + ? right_bytes + 2 + : right_bytes); + if (strcmp(left_bytes, ".") == 0){ + return LT_Pathname_new((char*)(*right_suffix == '\0' ? "." : right_suffix)); + } + left_length = strlen(left_bytes); + right_length = strlen(right_suffix); + combined = GC_MALLOC_ATOMIC(left_length + right_length + 2); + memcpy(combined, left_bytes, left_length); + combined[left_length] = '/'; + memcpy(combined + left_length + 1, right_suffix, right_length + 1); + return LT_Pathname_new(combined); } LT_String* LT_Pathname_as_string(LT_Pathname* pathname){ @@ -106,15 +395,21 @@ char* LT_Pathname_value_cstr(LT_Pathname* pathname){ return pathname->pathname; } +int LT_Pathname_absolute_p(LT_Pathname* pathname){ + return LT_AbsolutePathname_p((LT_Value)(uintptr_t)pathname); +} + +int LT_Pathname_relative_p(LT_Pathname* pathname){ + return LT_RelativePathname_p((LT_Value)(uintptr_t)pathname); +} + char* LT_Pathname_like_value_cstr(LT_Value value){ if (LT_Pathname_p(value)){ return LT_Pathname_value_cstr(LT_Pathname_from_value(value)); } if (LT_String_p(value)){ LT_String* string = LT_String_from_value(value); - if (strlen(LT_String_value_cstr(string)) != LT_String_byte_length(string)){ - LT_error("Pathname cannot contain NUL bytes"); - } + Pathname_check_string(string); return (char*)LT_String_value_cstr(string); } LT_error("Expected Pathname or String"); diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 9e3b337..9f91309 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -155,6 +155,8 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"AsconRNG", <_AsconRNG_class}, {"String", <_String_class}, {"Pathname", <_Pathname_class}, + {"RelativePathname", <_RelativePathname_class}, + {"AbsolutePathname", <_AbsolutePathname_class}, {"StringIterator", <_StringIterator_class}, {"Symbol", <_Symbol_class}, {"Package", <_Package_class}, diff --git a/src/vm/reader.c b/src/vm/reader.c index a420a6d..79b3dec 100644 --- a/src/vm/reader.c +++ b/src/vm/reader.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,10 @@ static LT_Value read_dictionary_dispatch_literal( LT_Reader* reader, LT_ReaderStream* stream ); +static LT_Value read_pathname_dispatch_literal( + LT_Reader* reader, + LT_ReaderStream* stream +); static LT_Value read_number_token_with_radix( LT_Reader* reader, LT_ReaderStream* stream, @@ -1173,6 +1178,23 @@ static LT_Value read_bytevector_dispatch_literal( return read_bytevector_literal(reader, stream); } +static LT_Value read_pathname_dispatch_literal( + LT_Reader* reader, + LT_ReaderStream* stream +){ + int ch = reader_getc(reader, stream); + LT_String* string; + + if (ch == EOF){ + reader_incomplete_input(reader, "#p expects string syntax"); + } + if (ch != '"'){ + reader_error(reader, "#p expects string syntax"); + } + string = read_string_literal(reader, stream); + return (LT_Value)(uintptr_t)LT_Pathname_from_string(string); +} + static LT_Value read_dispatch_macro( LT_Reader* reader, LT_ReaderStream* stream @@ -1249,6 +1271,10 @@ static LT_Value read_dispatch_macro( case 'D': ensure_dispatch_argument_unused(reader, argument); return read_dictionary_dispatch_literal(reader, stream); + case 'p': + case 'P': + ensure_dispatch_argument_unused(reader, argument); + return read_pathname_dispatch_literal(reader, stream); case 'u': case 'U': ensure_dispatch_argument_unused(reader, argument); diff --git a/tests/c_api_test.c b/tests/c_api_test.c index 38cdd02..4569268 100644 --- a/tests/c_api_test.c +++ b/tests/c_api_test.c @@ -3615,15 +3615,64 @@ static int test_string_utf8_helpers_replace_invalid_sequences(void){ static int test_pathname_c_api_round_trips_utf8(void){ LT_Pathname* pathname = LT_Pathname_new("dir/\xce\xbb.txt"); LT_String* string = LT_Pathname_as_string(pathname); + LT_Pathname* absolute = LT_Pathname_new("/a//b/../c"); + LT_Pathname* appended = LT_Pathname_append( + absolute, + LT_Pathname_new("../d") + ); + LT_RelativePathname* relative = LT_RelativePathname_new("x/y"); + LT_AbsolutePathname* forced_absolute = LT_AbsolutePathname_new("x/y"); if (expect( - strcmp(LT_Pathname_value_cstr(pathname), "dir/\xce\xbb.txt") == 0, - "LT_Pathname_new and LT_Pathname_value_cstr round-trip UTF-8" + strcmp(LT_Pathname_value_cstr(pathname), "./dir/\xce\xbb.txt") == 0, + "LT_Pathname_new normalizes and preserves UTF-8" + )){ + return 1; + } + if (expect( + LT_Pathname_relative_p(pathname) && !LT_Pathname_absolute_p(pathname), + "relative and absolute pathname predicates distinguish relative paths" + )){ + return 1; + } + if (expect( + LT_RelativePathname_p((LT_Value)(uintptr_t)pathname), + "LT_Pathname_new dispatches to RelativePathname" + )){ + return 1; + } + if (expect( + LT_Pathname_absolute_p(absolute) && !LT_Pathname_relative_p(absolute), + "relative and absolute pathname predicates distinguish absolute paths" + )){ + return 1; + } + if (expect( + LT_AbsolutePathname_p((LT_Value)(uintptr_t)absolute), + "LT_Pathname_new dispatches to AbsolutePathname" + )){ + return 1; + } + if (expect( + strcmp(LT_Pathname_value_cstr((LT_Pathname*)relative), "./x/y") == 0, + "LT_RelativePathname_new constructs a relative pathname" + )){ + return 1; + } + if (expect( + strcmp(LT_Pathname_value_cstr((LT_Pathname*)forced_absolute), "/x/y") == 0, + "LT_AbsolutePathname_new adds a leading slash" + )){ + return 1; + } + if (expect( + strcmp(LT_Pathname_value_cstr(appended), "/a/d") == 0, + "LT_Pathname_append combines and normalizes paths" )){ return 1; } return expect( - strcmp(LT_String_value_cstr(string), "dir/\xce\xbb.txt") == 0, + strcmp(LT_String_value_cstr(string), "./dir/\xce\xbb.txt") == 0, "LT_Pathname_as_string converts to String" ); } diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 2df7ae4..6171dbc 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1568,10 +1568,48 @@ ;;; Pathname (let ((path [Pathname fromString: "dir/\u03bb.txt"])) - (check (eq? (type-of path) Pathname) - "Pathname class>>fromString: creates Pathname") - (check (equal? [path asString] "dir/\u03bb.txt") - "Pathname>>asString returns pathname text")) + (check (eq? (type-of path) RelativePathname) + "Pathname class>>fromString: dispatches to RelativePathname") + (check (equal? [path asString] "./dir/\u03bb.txt") + "Pathname>>asString returns normalized pathname text") + (check [path relative?] "Pathname>>relative? identifies relative paths") + (check (not [path absolute?]) "relative path is not absolute")) + +(let ((path [Pathname fromString: "///a//./b/../c/"])) + (check (eq? (type-of path) AbsolutePathname) + "Pathname class>>fromString: dispatches to AbsolutePathname") + (check (equal? [path asString] "/a/c") + "Pathname removes repeated slashes, dot segments, and resolved parent segments") + (check [path absolute?] "Pathname>>absolute? identifies absolute paths") + (check (not [path relative?]) "absolute path is not relative")) + +(check (equal? [[RelativePathname fromString: "a/./b"] asString] "./a/b") + "RelativePathname has a language-level constructor") +(check-error-message "Relative pathname cannot start with slash" + "RelativePathname rejects a leading slash" + [RelativePathname fromString: "/a"]) +(let ((path [AbsolutePathname fromString: "a/b"])) + (check (eq? (type-of path) AbsolutePathname) + "AbsolutePathname has a language-level constructor") + (check (equal? [path asString] "/a/b") + "AbsolutePathname quietly adds a leading slash")) + +(check (equal? [[Pathname fromString: "a/b"] + / [Pathname fromString: "../c"]] + [Pathname fromString: "a/c"]) + "Pathname>>/ appends and normalizes relative paths") +(check (equal? [[Pathname fromString: "/a/b"] + / [Pathname fromString: "../../c"]] + [Pathname fromString: "/c"]) + "Pathname>>/ resolves parents in absolute paths") +(check (equal? [[Pathname fromString: "../../a/../b"] asString] "../../b") + "relative parent segments remain only at the beginning") +(check-error-message "Absolute pathname cannot resolve above root" + "absolute pathname rejects unresolved parent segments" + [Pathname fromString: "/a/../../b"]) +(check-error-message "Cannot append an absolute pathname" + "Pathname>>/ rejects an absolute right operand" + [[Pathname fromString: "/a"] / [Pathname fromString: "/b"]]) ) (define-test "make-class and make-instance" diff --git a/tests/reader_test.c b/tests/reader_test.c index 5da5895..5d373f2 100644 --- a/tests/reader_test.c +++ b/tests/reader_test.c @@ -600,6 +600,54 @@ static int test_dispatch_boolean_true(void){ return expect(value == LT_TRUE, "dispatch #t"); } +static int test_dispatch_pathname_literal(void){ + LT_Value relative = read_one("#p\"foo//./bar\""); + LT_Value absolute = read_one("#P\"/foo/../bar\""); + char* relative_printed; + char* absolute_printed; + LT_Value relative_reparsed; + LT_Value absolute_reparsed; + + if (expect(LT_RelativePathname_p(relative), + "#p relative literal dispatches to RelativePathname")){ + return 1; + } + if (expect(LT_AbsolutePathname_p(absolute), + "#p absolute literal dispatches to AbsolutePathname")){ + return 1; + } + if (expect( + strcmp(LT_Pathname_value_cstr(LT_Pathname_from_value(relative)), + "./foo/bar") == 0, + "#p literal uses shared pathname normalization" + )){ + return 1; + } + relative_printed = debug_string_for_value(relative); + absolute_printed = debug_string_for_value(absolute); + if (relative_printed == NULL || absolute_printed == NULL){ + free(relative_printed); + free(absolute_printed); + return 1; + } + if (expect(strcmp(relative_printed, "#p\"./foo/bar\"") == 0, + "RelativePathname prints as #p string syntax") + || expect(strcmp(absolute_printed, "#p\"/bar\"") == 0, + "AbsolutePathname prints as #p string syntax")){ + free(relative_printed); + free(absolute_printed); + return 1; + } + relative_reparsed = read_one(relative_printed); + absolute_reparsed = read_one(absolute_printed); + free(relative_printed); + free(absolute_printed); + return expect(LT_Value_equal_p(relative, relative_reparsed), + "printed RelativePathname reads back equivalently") + + expect(LT_Value_equal_p(absolute, absolute_reparsed), + "printed AbsolutePathname reads back equivalently"); +} + static int test_dispatch_boolean_false(void){ LT_Value value = read_one("#false"); return expect(value == LT_FALSE, "dispatch #false"); @@ -2331,6 +2379,7 @@ int main(void){ failures += test_quoted_dynamic_variable_symbol_does_not_expand(); failures += test_single_star_symbol_does_not_expand_to_dynamic_ref(); failures += test_dispatch_boolean_true(); + failures += test_dispatch_pathname_literal(); failures += test_dispatch_boolean_false(); failures += test_dispatch_nil(); failures += test_dispatch_nil_short(); From 695aab9556db9efefd223f61fee4c918219ad70f Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Sun, 6 Sep 2026 01:04:22 +0200 Subject: [PATCH 3/6] basic path traversal --- ListTalk/classes/Pathname.h | 5 +++ src/classes/Pathname.c | 74 +++++++++++++++++++++++++++++++++++++ tests/c_api_test.c | 13 +++++++ tests/eval-objects.lt | 23 ++++++++++++ 4 files changed, 115 insertions(+) diff --git a/ListTalk/classes/Pathname.h b/ListTalk/classes/Pathname.h index 25196d1..29757f2 100644 --- a/ListTalk/classes/Pathname.h +++ b/ListTalk/classes/Pathname.h @@ -37,6 +37,11 @@ LT_RelativePathname* LT_RelativePathname_from_string(LT_String* string); LT_AbsolutePathname* LT_AbsolutePathname_new(char* pathname); LT_AbsolutePathname* LT_AbsolutePathname_from_string(LT_String* string); LT_Pathname* LT_Pathname_append(LT_Pathname* left, LT_Pathname* right); +LT_Pathname* LT_Pathname_parent(LT_Pathname* pathname); +LT_Pathname* LT_AbsolutePathname_rooted_at( + LT_AbsolutePathname* pathname, + LT_Pathname* root +); LT_String* LT_Pathname_as_string(LT_Pathname* pathname); char* LT_Pathname_value_cstr(LT_Pathname* pathname); int LT_Pathname_absolute_p(LT_Pathname* pathname); diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c index 0516288..7b03f14 100644 --- a/src/classes/Pathname.c +++ b/src/classes/Pathname.c @@ -257,11 +257,43 @@ LT_DEFINE_PRIMITIVE( return (LT_Value)(uintptr_t)LT_Pathname_as_string(pathname); } +LT_DEFINE_PRIMITIVE( + pathname_method_parent, + "Pathname>>parent", + "(self)", + "Return the pathname with its final segment removed." +){ + LT_Value cursor = arguments; + LT_Pathname* pathname; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_Pathname_parent(pathname); +} + +LT_DEFINE_PRIMITIVE( + absolute_pathname_method_rooted_at, + "AbsolutePathname>>rootedAt:", + "(self root)", + "Interpret the absolute pathname relative to root." +){ + LT_Value cursor = arguments; + LT_AbsolutePathname* pathname; + LT_Pathname* root; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_AbsolutePathname*, + LT_AbsolutePathname_from_value); + LT_GENERIC_ARG(cursor, root, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_AbsolutePathname_rooted_at(pathname, root); +} + static LT_Method_Descriptor Pathname_methods[] = { {"asString", &pathname_method_as_string}, {"absolute?", &pathname_method_absolute_p}, {"relative?", &pathname_method_relative_p}, {"/", &pathname_method_append}, + {"parent", &pathname_method_parent}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; @@ -280,6 +312,11 @@ static LT_Method_Descriptor AbsolutePathname_class_methods[] = { LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; +static LT_Method_Descriptor AbsolutePathname_methods[] = { + {"rootedAt:", &absolute_pathname_method_rooted_at}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + LT_DEFINE_CLASS(LT_Pathname) { .superclass = <_Object_class, .metaclass_superclass = <_Class_class, @@ -316,6 +353,7 @@ LT_DEFINE_CLASS(LT_AbsolutePathname) { .class_flags = LT_CLASS_FLAG_FINAL | LT_CLASS_FLAG_IMMUTABLE | LT_CLASS_FLAG_SCALAR, .debugPrintOn = Pathname_debugPrintOn, + .methods = AbsolutePathname_methods, .class_methods = AbsolutePathname_class_methods, }; @@ -387,6 +425,42 @@ LT_Pathname* LT_Pathname_append(LT_Pathname* left, LT_Pathname* right){ return LT_Pathname_new(combined); } +LT_Pathname* LT_Pathname_parent(LT_Pathname* pathname){ + const char* bytes = LT_Pathname_value_cstr(pathname); + const char* slash; + size_t length; + char* parent; + + if (strcmp(bytes, ".") == 0 || strcmp(bytes, "/") == 0){ + LT_error("Pathname has no parent"); + } + slash = strrchr(bytes, '/'); + if (slash == NULL){ + return LT_Pathname_new("."); + } + if (slash == bytes){ + return LT_Pathname_new("/"); + } + length = (size_t)(slash - bytes); + if (length == 1 && bytes[0] == '.'){ + return LT_Pathname_new("."); + } + parent = GC_MALLOC_ATOMIC(length + 1); + memcpy(parent, bytes, length); + parent[length] = '\0'; + return LT_Pathname_new(parent); +} + +LT_Pathname* LT_AbsolutePathname_rooted_at(LT_AbsolutePathname* pathname, + LT_Pathname* root){ + const char* suffix = LT_Pathname_value_cstr((LT_Pathname*)pathname) + 1; + LT_RelativePathname* relative = LT_RelativePathname_new( + (char*)(*suffix == '\0' ? "." : suffix) + ); + + return LT_Pathname_append(root, (LT_Pathname*)relative); +} + LT_String* LT_Pathname_as_string(LT_Pathname* pathname){ return LT_String_new_cstr(pathname->pathname); } diff --git a/tests/c_api_test.c b/tests/c_api_test.c index 4569268..7330eb6 100644 --- a/tests/c_api_test.c +++ b/tests/c_api_test.c @@ -3622,6 +3622,11 @@ static int test_pathname_c_api_round_trips_utf8(void){ ); LT_RelativePathname* relative = LT_RelativePathname_new("x/y"); LT_AbsolutePathname* forced_absolute = LT_AbsolutePathname_new("x/y"); + LT_Pathname* parent = LT_Pathname_parent(LT_Pathname_new("/a/b")); + LT_Pathname* rooted = LT_AbsolutePathname_rooted_at( + LT_AbsolutePathname_new("/a/b"), + LT_Pathname_new("/root") + ); if (expect( strcmp(LT_Pathname_value_cstr(pathname), "./dir/\xce\xbb.txt") == 0, @@ -3671,6 +3676,14 @@ static int test_pathname_c_api_round_trips_utf8(void){ )){ return 1; } + if (expect(strcmp(LT_Pathname_value_cstr(parent), "/a") == 0, + "LT_Pathname_parent removes the final segment")){ + return 1; + } + if (expect(strcmp(LT_Pathname_value_cstr(rooted), "/root/a/b") == 0, + "LT_AbsolutePathname_rooted_at roots an absolute pathname")){ + return 1; + } return expect( strcmp(LT_String_value_cstr(string), "./dir/\xce\xbb.txt") == 0, "LT_Pathname_as_string converts to String" diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 6171dbc..93b5939 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1610,6 +1610,29 @@ (check-error-message "Cannot append an absolute pathname" "Pathname>>/ rejects an absolute right operand" [[Pathname fromString: "/a"] / [Pathname fromString: "/b"]]) +(check (equal? [[Pathname fromString: "a/b/c"] parent] + [Pathname fromString: "a/b"]) + "Pathname>>parent removes the final relative segment") +(check (equal? [[Pathname fromString: "/a"] parent] + [Pathname fromString: "/"]) + "Pathname>>parent preserves an absolute root") +(check (equal? [[Pathname fromString: "../a"] parent] + [Pathname fromString: ".."]) + "Pathname>>parent preserves leading parent segments") +(check-error-message "Pathname has no parent" + "current-directory pathname has no parent" + [[Pathname fromString: "."] parent]) +(check-error-message "Pathname has no parent" + "root pathname has no parent" + [[Pathname fromString: "/"] parent]) +(check (equal? [[AbsolutePathname fromString: "/usr/local"] + rootedAt: [Pathname fromString: "/sandbox"]] + [Pathname fromString: "/sandbox/usr/local"]) + "AbsolutePathname>>rootedAt: interprets the receiver beneath an absolute root") +(check (equal? [[AbsolutePathname fromString: "/usr/local"] + rootedAt: [Pathname fromString: "sandbox"]] + [Pathname fromString: "sandbox/usr/local"]) + "AbsolutePathname>>rootedAt: also accepts a relative root") ) (define-test "make-class and make-instance" From 17371bb821f2d6301a1640c4e0c8db00a4535098 Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Sun, 6 Sep 2026 01:48:19 +0200 Subject: [PATCH 4/6] pathname OS predicates. --- ListTalk/classes/Pathname.h | 14 ++ src/classes/Pathname.c | 295 ++++++++++++++++++++++++++++++++++++ src/vm/base_env/base_env.c | 1 + tests/c_api_test.c | 15 ++ tests/eval-objects.lt | 31 ++++ 5 files changed, 356 insertions(+) diff --git a/ListTalk/classes/Pathname.h b/ListTalk/classes/Pathname.h index 29757f2..6375dcd 100644 --- a/ListTalk/classes/Pathname.h +++ b/ListTalk/classes/Pathname.h @@ -18,6 +18,7 @@ extern LT_Class LT_Pathname_class; extern LT_Class LT_Pathname_class_class; LT_DECLARE_CLASS(LT_RelativePathname); LT_DECLARE_CLASS(LT_AbsolutePathname); +LT_DECLARE_CLASS(LT_PathnameStat); static inline int LT_Pathname_p(LT_Value value){ return LT_Value_is_instance_of(value, LT_STATIC_CLASS(LT_Pathname)); @@ -46,6 +47,19 @@ LT_String* LT_Pathname_as_string(LT_Pathname* pathname); char* LT_Pathname_value_cstr(LT_Pathname* pathname); int LT_Pathname_absolute_p(LT_Pathname* pathname); int LT_Pathname_relative_p(LT_Pathname* pathname); +int LT_Pathname_exists_p(LT_Pathname* pathname); +int LT_Pathname_directory_p(LT_Pathname* pathname); +int LT_Pathname_regular_file_p(LT_Pathname* pathname); +int LT_Pathname_symbolic_link_p(LT_Pathname* pathname); +int LT_Pathname_fifo_p(LT_Pathname* pathname); +int LT_Pathname_socket_p(LT_Pathname* pathname); +int LT_Pathname_character_device_p(LT_Pathname* pathname); +int LT_Pathname_block_device_p(LT_Pathname* pathname); +int LT_Pathname_readable_p(LT_Pathname* pathname); +int LT_Pathname_writable_p(LT_Pathname* pathname); +int LT_Pathname_executable_p(LT_Pathname* pathname); +LT_PathnameStat* LT_Pathname_stat(LT_Pathname* pathname); +LT_PathnameStat* LT_Pathname_lstat(LT_Pathname* pathname); char* LT_Pathname_like_value_cstr(LT_Value value); LT_String* LT_Pathname_like_as_string(LT_Value value); diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c index 7b03f14..50dca4e 100644 --- a/src/classes/Pathname.c +++ b/src/classes/Pathname.c @@ -4,15 +4,21 @@ */ #include #include +#include +#include +#include #include #include #include #include #include +#include #include #include #include +#include +#include struct LT_Pathname_s { LT_Object base; @@ -27,6 +33,41 @@ struct LT_AbsolutePathname_s { LT_Pathname base; }; +struct LT_PathnameStat_s { + LT_Object base; + LT_Value pathname; + struct stat status; +}; + +static LT_Value Pathname_boolean(int value){ + return value ? LT_TRUE : LT_FALSE; +} + +static int Pathname_stat_buffer(LT_Pathname* pathname, + struct stat* status, + int follow_links, + int missing_is_false){ + int result = follow_links + ? stat(LT_Pathname_value_cstr(pathname), status) + : lstat(LT_Pathname_value_cstr(pathname), status); + + if (result == 0){ + return 1; + } + if (missing_is_false && (errno == ENOENT || errno == ENOTDIR)){ + return 0; + } + LT_system_error("Could not stat pathname", errno); + return 0; +} + +static LT_Value Pathname_instant(time_t seconds){ + return LT_Instant_new(LT_Number_multiply2( + LT_Integer_from_intmax((intmax_t)seconds), + LT_SmallInteger_new(1000000) + )); +} + static void Pathname_check_string(LT_String* string){ if (strlen(LT_String_value_cstr(string)) != LT_String_byte_length(string)){ LT_error("Pathname cannot contain NUL bytes"); @@ -288,12 +329,158 @@ LT_DEFINE_PRIMITIVE( return (LT_Value)(uintptr_t)LT_AbsolutePathname_rooted_at(pathname, root); } +#define DEFINE_PATHNAME_PREDICATE_METHOD(c_name, selector, function, description) \ + LT_DEFINE_PRIMITIVE( \ + c_name, \ + "Pathname>>" selector, \ + "(self)", \ + description \ + ){ \ + LT_Value cursor = arguments; \ + LT_Pathname* pathname; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); \ + LT_ARG_END(cursor); \ + return Pathname_boolean(function(pathname)); \ + } + +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_exists_p, "exists?", + LT_Pathname_exists_p, "Return true when the pathname exists.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_directory_p, "isDirectory?", + LT_Pathname_directory_p, "Return true when the pathname names a directory.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_regular_file_p, "isRegularFile?", + LT_Pathname_regular_file_p, "Return true when the pathname names a regular file.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_symbolic_link_p, "isSymbolicLink?", + LT_Pathname_symbolic_link_p, "Return true when the pathname names a symbolic link.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_fifo_p, "isFIFO?", + LT_Pathname_fifo_p, "Return true when the pathname names a FIFO.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_socket_p, "isSocket?", + LT_Pathname_socket_p, "Return true when the pathname names a socket.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_character_device_p, + "isCharacterDevice?", LT_Pathname_character_device_p, + "Return true when the pathname names a character device.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_block_device_p, "isBlockDevice?", + LT_Pathname_block_device_p, "Return true when the pathname names a block device.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_readable_p, "readable?", + LT_Pathname_readable_p, "Return true when the pathname is readable.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_writable_p, "writable?", + LT_Pathname_writable_p, "Return true when the pathname is writable.") +DEFINE_PATHNAME_PREDICATE_METHOD(pathname_method_executable_p, "executable?", + LT_Pathname_executable_p, "Return true when the pathname is executable.") + +LT_DEFINE_PRIMITIVE( + pathname_method_stat, + "Pathname>>stat", + "(self)", + "Return a snapshot of POSIX metadata for the pathname." +){ + LT_Value cursor = arguments; + LT_Pathname* pathname; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_Pathname_stat(pathname); +} + +LT_DEFINE_PRIMITIVE( + pathname_method_lstat, + "Pathname>>lstat", + "(self)", + "Return a POSIX metadata snapshot without following the final symlink." +){ + LT_Value cursor = arguments; + LT_Pathname* pathname; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, pathname, LT_Pathname*, LT_Pathname_from_value); + LT_ARG_END(cursor); + return (LT_Value)(uintptr_t)LT_Pathname_lstat(pathname); +} + +#define DEFINE_PATHNAME_STAT_PREDICATE(c_name, selector, predicate) \ + LT_DEFINE_PRIMITIVE(c_name, "PathnameStat>>" selector, "(self)", \ + "Inspect the snapshotted POSIX file kind."){ \ + LT_Value cursor = arguments; \ + LT_PathnameStat* self; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, self, LT_PathnameStat*, LT_PathnameStat_from_value); \ + LT_ARG_END(cursor); \ + return Pathname_boolean(predicate(self->status.st_mode)); \ + } + +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_directory_p, + "isDirectory?", S_ISDIR) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_regular_file_p, + "isRegularFile?", S_ISREG) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_symbolic_link_p, + "isSymbolicLink?", S_ISLNK) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_fifo_p, "isFIFO?", S_ISFIFO) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_socket_p, "isSocket?", S_ISSOCK) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_character_device_p, + "isCharacterDevice?", S_ISCHR) +DEFINE_PATHNAME_STAT_PREDICATE(pathname_stat_method_block_device_p, + "isBlockDevice?", S_ISBLK) + +#define DEFINE_PATHNAME_STAT_INTEGER(c_name, selector, field, signedness) \ + LT_DEFINE_PRIMITIVE(c_name, "PathnameStat>>" selector, "(self)", \ + "Return a snapshotted POSIX metadata field."){ \ + LT_Value cursor = arguments; \ + LT_PathnameStat* self; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, self, LT_PathnameStat*, LT_PathnameStat_from_value); \ + LT_ARG_END(cursor); \ + return LT_Integer_from_##signedness((signedness##_t)self->status.field); \ + } + +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_size, "size", st_size, intmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_mode, "mode", st_mode, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_uid, "uid", st_uid, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_gid, "gid", st_gid, uintmax) + +LT_DEFINE_PRIMITIVE(pathname_stat_method_pathname, "PathnameStat>>pathname", + "(self)", "Return the pathname used for this snapshot."){ + LT_Value cursor = arguments; + LT_PathnameStat* self; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, self, LT_PathnameStat*, LT_PathnameStat_from_value); + LT_ARG_END(cursor); + return self->pathname; +} + +#define DEFINE_PATHNAME_STAT_TIME(c_name, selector, field) \ + LT_DEFINE_PRIMITIVE(c_name, "PathnameStat>>" selector, "(self)", \ + "Return a snapshotted POSIX timestamp."){ \ + LT_Value cursor = arguments; \ + LT_PathnameStat* self; \ + (void)tail_call_unwind_marker; \ + LT_GENERIC_ARG(cursor, self, LT_PathnameStat*, LT_PathnameStat_from_value); \ + LT_ARG_END(cursor); \ + return Pathname_instant(self->status.field); \ + } + +DEFINE_PATHNAME_STAT_TIME(pathname_stat_method_accessed_at, "accessedAt", st_atime) +DEFINE_PATHNAME_STAT_TIME(pathname_stat_method_modified_at, "modifiedAt", st_mtime) +DEFINE_PATHNAME_STAT_TIME(pathname_stat_method_status_changed_at, + "statusChangedAt", st_ctime) + static LT_Method_Descriptor Pathname_methods[] = { {"asString", &pathname_method_as_string}, {"absolute?", &pathname_method_absolute_p}, {"relative?", &pathname_method_relative_p}, {"/", &pathname_method_append}, {"parent", &pathname_method_parent}, + {"exists?", &pathname_method_exists_p}, + {"isDirectory?", &pathname_method_directory_p}, + {"isRegularFile?", &pathname_method_regular_file_p}, + {"isSymbolicLink?", &pathname_method_symbolic_link_p}, + {"isFIFO?", &pathname_method_fifo_p}, + {"isSocket?", &pathname_method_socket_p}, + {"isCharacterDevice?", &pathname_method_character_device_p}, + {"isBlockDevice?", &pathname_method_block_device_p}, + {"readable?", &pathname_method_readable_p}, + {"writable?", &pathname_method_writable_p}, + {"executable?", &pathname_method_executable_p}, + {"stat", &pathname_method_stat}, + {"lstat", &pathname_method_lstat}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; @@ -317,6 +504,25 @@ static LT_Method_Descriptor AbsolutePathname_methods[] = { LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; +static LT_Method_Descriptor PathnameStat_methods[] = { + {"pathname", &pathname_stat_method_pathname}, + {"isDirectory?", &pathname_stat_method_directory_p}, + {"isRegularFile?", &pathname_stat_method_regular_file_p}, + {"isSymbolicLink?", &pathname_stat_method_symbolic_link_p}, + {"isFIFO?", &pathname_stat_method_fifo_p}, + {"isSocket?", &pathname_stat_method_socket_p}, + {"isCharacterDevice?", &pathname_stat_method_character_device_p}, + {"isBlockDevice?", &pathname_stat_method_block_device_p}, + {"size", &pathname_stat_method_size}, + {"mode", &pathname_stat_method_mode}, + {"uid", &pathname_stat_method_uid}, + {"gid", &pathname_stat_method_gid}, + {"accessedAt", &pathname_stat_method_accessed_at}, + {"modifiedAt", &pathname_stat_method_modified_at}, + {"statusChangedAt", &pathname_stat_method_status_changed_at}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + LT_DEFINE_CLASS(LT_Pathname) { .superclass = <_Object_class, .metaclass_superclass = <_Class_class, @@ -357,6 +563,16 @@ LT_DEFINE_CLASS(LT_AbsolutePathname) { .class_methods = AbsolutePathname_class_methods, }; +LT_DEFINE_CLASS(LT_PathnameStat) { + .superclass = <_Object_class, + .metaclass_superclass = <_Class_class, + .name = "PathnameStat", + .documentation = "Immutable snapshot of POSIX pathname metadata.", + .instance_size = sizeof(LT_PathnameStat), + .class_flags = LT_CLASS_FLAG_FINAL | LT_CLASS_FLAG_IMMUTABLE, + .methods = PathnameStat_methods, +}; + LT_Pathname* LT_Pathname_new(char* pathname){ return pathname[0] == '/' ? (LT_Pathname*)LT_AbsolutePathname_new(pathname) @@ -477,6 +693,85 @@ int LT_Pathname_relative_p(LT_Pathname* pathname){ return LT_RelativePathname_p((LT_Value)(uintptr_t)pathname); } +int LT_Pathname_exists_p(LT_Pathname* pathname){ + struct stat status; + + return Pathname_stat_buffer(pathname, &status, 1, 1); +} + +static int Pathname_mode_p(LT_Pathname* pathname, + int follow_links, + int (*predicate)(mode_t)){ + struct stat status; + + return Pathname_stat_buffer(pathname, &status, follow_links, 1) + && predicate(status.st_mode); +} + +static int Pathname_mode_directory(mode_t mode){ return S_ISDIR(mode); } +static int Pathname_mode_regular(mode_t mode){ return S_ISREG(mode); } +static int Pathname_mode_link(mode_t mode){ return S_ISLNK(mode); } +static int Pathname_mode_fifo(mode_t mode){ return S_ISFIFO(mode); } +static int Pathname_mode_socket(mode_t mode){ return S_ISSOCK(mode); } +static int Pathname_mode_character(mode_t mode){ return S_ISCHR(mode); } +static int Pathname_mode_block(mode_t mode){ return S_ISBLK(mode); } + +int LT_Pathname_directory_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_directory); +} + +int LT_Pathname_regular_file_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_regular); +} + +int LT_Pathname_symbolic_link_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 0, Pathname_mode_link); +} + +int LT_Pathname_fifo_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_fifo); +} + +int LT_Pathname_socket_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_socket); +} + +int LT_Pathname_character_device_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_character); +} + +int LT_Pathname_block_device_p(LT_Pathname* pathname){ + return Pathname_mode_p(pathname, 1, Pathname_mode_block); +} + +int LT_Pathname_readable_p(LT_Pathname* pathname){ + return access(LT_Pathname_value_cstr(pathname), R_OK) == 0; +} + +int LT_Pathname_writable_p(LT_Pathname* pathname){ + return access(LT_Pathname_value_cstr(pathname), W_OK) == 0; +} + +int LT_Pathname_executable_p(LT_Pathname* pathname){ + return access(LT_Pathname_value_cstr(pathname), X_OK) == 0; +} + +LT_PathnameStat* LT_Pathname_stat(LT_Pathname* pathname){ + LT_PathnameStat* result = LT_Class_ALLOC(LT_PathnameStat); + + Pathname_stat_buffer(pathname, &result->status, 1, 0); + result->pathname = (LT_Value)(uintptr_t)pathname; + return result; +} + +LT_PathnameStat* LT_Pathname_lstat(LT_Pathname* pathname){ + LT_PathnameStat* result = LT_Class_ALLOC(LT_PathnameStat); + + Pathname_stat_buffer(pathname, &result->status, 0, 0); + result->pathname = (LT_Value)(uintptr_t)pathname; + return result; +} + char* LT_Pathname_like_value_cstr(LT_Value value){ if (LT_Pathname_p(value)){ return LT_Pathname_value_cstr(LT_Pathname_from_value(value)); diff --git a/src/vm/base_env/base_env.c b/src/vm/base_env/base_env.c index 9f91309..329b422 100644 --- a/src/vm/base_env/base_env.c +++ b/src/vm/base_env/base_env.c @@ -157,6 +157,7 @@ static const struct LT_NativeClassBinding native_class_bindings[] = { {"Pathname", <_Pathname_class}, {"RelativePathname", <_RelativePathname_class}, {"AbsolutePathname", <_AbsolutePathname_class}, + {"PathnameStat", <_PathnameStat_class}, {"StringIterator", <_StringIterator_class}, {"Symbol", <_Symbol_class}, {"Package", <_Package_class}, diff --git a/tests/c_api_test.c b/tests/c_api_test.c index 7330eb6..4aa4555 100644 --- a/tests/c_api_test.c +++ b/tests/c_api_test.c @@ -3627,6 +3627,7 @@ static int test_pathname_c_api_round_trips_utf8(void){ LT_AbsolutePathname_new("/a/b"), LT_Pathname_new("/root") ); + LT_Pathname* source_file = LT_Pathname_new("build.ninja"); if (expect( strcmp(LT_Pathname_value_cstr(pathname), "./dir/\xce\xbb.txt") == 0, @@ -3684,6 +3685,20 @@ static int test_pathname_c_api_round_trips_utf8(void){ "LT_AbsolutePathname_rooted_at roots an absolute pathname")){ return 1; } + if (expect(LT_Pathname_exists_p(source_file), + "LT_Pathname_exists_p recognizes an existing path")){ + return 1; + } + if (expect(LT_Pathname_regular_file_p(source_file), + "LT_Pathname_regular_file_p recognizes a regular file")){ + return 1; + } + if (expect(LT_PathnameStat_p( + (LT_Value)(uintptr_t)LT_Pathname_stat(source_file)), + "LT_Pathname_stat returns PathnameStat" + )){ + return 1; + } return expect( strcmp(LT_String_value_cstr(string), "./dir/\xce\xbb.txt") == 0, "LT_Pathname_as_string converts to String" diff --git a/tests/eval-objects.lt b/tests/eval-objects.lt index 93b5939..57be2c2 100644 --- a/tests/eval-objects.lt +++ b/tests/eval-objects.lt @@ -1633,6 +1633,37 @@ rootedAt: [Pathname fromString: "sandbox"]] [Pathname fromString: "sandbox/usr/local"]) "AbsolutePathname>>rootedAt: also accepts a relative root") + +(let ((directory #p".") + (file #p"build.ninja") + (missing #p".__listtalk_missing_pathname__")) + (check [directory exists?] "Pathname>>exists? recognizes an existing path") + (check [directory isDirectory?] "Pathname>>isDirectory? recognizes a directory") + (check (not [directory isRegularFile?]) + "Pathname>>isRegularFile? rejects a directory") + (check [file isRegularFile?] "Pathname>>isRegularFile? recognizes a file") + (check (not [file isDirectory?]) "Pathname>>isDirectory? rejects a file") + (check [file readable?] "Pathname>>readable? checks POSIX access") + (check (not [missing exists?]) "Pathname>>exists? returns false when missing") + (check (not [missing isDirectory?]) + "file-kind predicates return false when the path is missing") + (let ((status [file stat])) + (check (eq? (type-of status) PathnameStat) + "Pathname>>stat returns PathnameStat") + (check (equal? [status pathname] file) + "PathnameStat retains its source pathname") + (check [status isRegularFile?] + "PathnameStat snapshots the POSIX file kind") + (check (integer? [status size]) "PathnameStat exposes size") + (check (integer? [status mode]) "PathnameStat exposes mode") + (check (integer? [status uid]) "PathnameStat exposes uid") + (check (integer? [status gid]) "PathnameStat exposes gid") + (check (eq? (type-of [status accessedAt]) Instant) + "PathnameStat exposes access time") + (check (eq? (type-of [status modifiedAt]) Instant) + "PathnameStat exposes modification time") + (check (eq? (type-of [status statusChangedAt]) Instant) + "PathnameStat exposes status-change time"))) ) (define-test "make-class and make-instance" From a6796f707552ea1623f24071eb593cfb63915bef Mon Sep 17 00:00:00 2001 From: Ales Hakl Date: Sun, 6 Sep 2026 02:02:37 +0200 Subject: [PATCH 5/6] only one Stat class --- src/classes/Pathname.c | 88 ++++++++-- src/modules/os.c | 299 +--------------------------------- tests/eval-loading-control.lt | 7 +- 3 files changed, 82 insertions(+), 312 deletions(-) diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c index 50dca4e..b74a8f9 100644 --- a/src/classes/Pathname.c +++ b/src/classes/Pathname.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -435,6 +436,24 @@ DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_size, "size", st_size, intmax) DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_mode, "mode", st_mode, uintmax) DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_uid, "uid", st_uid, uintmax) DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_gid, "gid", st_gid, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_dev, "dev", st_dev, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_ino, "ino", st_ino, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_nlink, "nlink", st_nlink, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_rdev, "rdev", st_rdev, uintmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_blksize, "blksize", st_blksize, intmax) +DEFINE_PATHNAME_STAT_INTEGER(pathname_stat_method_blocks, "blocks", st_blocks, intmax) + +LT_DEFINE_PRIMITIVE(pathname_stat_method_device_p, "PathnameStat>>device?", + "(self)", "Return true for a character or block device."){ + LT_Value cursor = arguments; + LT_PathnameStat* self; + (void)tail_call_unwind_marker; + LT_GENERIC_ARG(cursor, self, LT_PathnameStat*, LT_PathnameStat_from_value); + LT_ARG_END(cursor); + return Pathname_boolean( + S_ISCHR(self->status.st_mode) || S_ISBLK(self->status.st_mode) + ); +} LT_DEFINE_PRIMITIVE(pathname_stat_method_pathname, "PathnameStat>>pathname", "(self)", "Return the pathname used for this snapshot."){ @@ -462,6 +481,37 @@ DEFINE_PATHNAME_STAT_TIME(pathname_stat_method_modified_at, "modifiedAt", st_mti DEFINE_PATHNAME_STAT_TIME(pathname_stat_method_status_changed_at, "statusChangedAt", st_ctime) +static LT_PathnameStat* PathnameStat_new_for_value(LT_Value path, + int follow_links){ + LT_PathnameStat* result = LT_Class_ALLOC(LT_PathnameStat); + const char* bytes = LT_Pathname_like_value_cstr(path); + int status = follow_links + ? stat(bytes, &result->status) + : lstat(bytes, &result->status); + + if (status != 0){ + LT_system_error("Could not stat pathname", errno); + } + result->pathname = path; + return result; +} + +LT_DEFINE_PRIMITIVE(pathname_stat_class_method_file, + "PathnameStat class>>file:", "(self path)", + "Return POSIX status for a String or Pathname."){ + LT_Value cursor = arguments; + LT_Value self; + LT_Value path; + (void)tail_call_unwind_marker; + LT_OBJECT_ARG(cursor, self); + LT_OBJECT_ARG(cursor, path); + LT_ARG_END(cursor); + if (self != LT_STATIC_CLASS(LT_PathnameStat)){ + LT_error("file: is only supported on PathnameStat"); + } + return (LT_Value)(uintptr_t)PathnameStat_new_for_value(path, 1); +} + static LT_Method_Descriptor Pathname_methods[] = { {"asString", &pathname_method_as_string}, {"absolute?", &pathname_method_absolute_p}, @@ -520,9 +570,33 @@ static LT_Method_Descriptor PathnameStat_methods[] = { {"accessedAt", &pathname_stat_method_accessed_at}, {"modifiedAt", &pathname_stat_method_modified_at}, {"statusChangedAt", &pathname_stat_method_status_changed_at}, + {"regular-file?", &pathname_stat_method_regular_file_p}, + {"directory?", &pathname_stat_method_directory_p}, + {"pipe?", &pathname_stat_method_fifo_p}, + {"device?", &pathname_stat_method_device_p}, + {"socket?", &pathname_stat_method_socket_p}, + {"dev", &pathname_stat_method_dev}, + {"ino", &pathname_stat_method_ino}, + {"nlink", &pathname_stat_method_nlink}, + {"rdev", &pathname_stat_method_rdev}, + {"blksize", &pathname_stat_method_blksize}, + {"blocks", &pathname_stat_method_blocks}, + {"atime", &pathname_stat_method_accessed_at}, + {"mtime", &pathname_stat_method_modified_at}, + {"ctime", &pathname_stat_method_status_changed_at}, + LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR +}; + +static LT_Method_Descriptor PathnameStat_class_methods[] = { + {"file:", &pathname_stat_class_method_file}, LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR }; +static LT_Slot_Descriptor PathnameStat_slots[] = { + {"path", offsetof(LT_PathnameStat, pathname), <_SlotType_ReadonlyObject}, + LT_NULL_NATIVE_CLASS_SLOT_DESCRIPTOR +}; + LT_DEFINE_CLASS(LT_Pathname) { .superclass = <_Object_class, .metaclass_superclass = <_Class_class, @@ -570,7 +644,9 @@ LT_DEFINE_CLASS(LT_PathnameStat) { .documentation = "Immutable snapshot of POSIX pathname metadata.", .instance_size = sizeof(LT_PathnameStat), .class_flags = LT_CLASS_FLAG_FINAL | LT_CLASS_FLAG_IMMUTABLE, + .slots = PathnameStat_slots, .methods = PathnameStat_methods, + .class_methods = PathnameStat_class_methods, }; LT_Pathname* LT_Pathname_new(char* pathname){ @@ -757,19 +833,11 @@ int LT_Pathname_executable_p(LT_Pathname* pathname){ } LT_PathnameStat* LT_Pathname_stat(LT_Pathname* pathname){ - LT_PathnameStat* result = LT_Class_ALLOC(LT_PathnameStat); - - Pathname_stat_buffer(pathname, &result->status, 1, 0); - result->pathname = (LT_Value)(uintptr_t)pathname; - return result; + return PathnameStat_new_for_value((LT_Value)(uintptr_t)pathname, 1); } LT_PathnameStat* LT_Pathname_lstat(LT_Pathname* pathname){ - LT_PathnameStat* result = LT_Class_ALLOC(LT_PathnameStat); - - Pathname_stat_buffer(pathname, &result->status, 0, 0); - result->pathname = (LT_Value)(uintptr_t)pathname; - return result; + return PathnameStat_new_for_value((LT_Value)(uintptr_t)pathname, 0); } char* LT_Pathname_like_value_cstr(LT_Value value){ diff --git a/src/modules/os.c b/src/modules/os.c index 62435bb..966325a 100644 --- a/src/modules/os.c +++ b/src/modules/os.c @@ -29,16 +29,8 @@ #include #include -LT_DECLARE_CLASS(LT_OS_Stat); - static LT_MutexWord os_environment_lock = LT_MUTEX_INITIALIZER; -struct LT_OS_Stat_s { - LT_Object base; - LT_Value path; - struct stat stat_buffer; -}; - static mode_t mode_from_fixnum(int64_t value){ if (value < 0){ LT_error("Negative mode"); @@ -95,16 +87,6 @@ static void mkdirs(const char* path, mode_t mode){ mkdir_existing_ok(copy, mode); } -static LT_OS_Stat* os_stat_new(LT_String* path){ - LT_OS_Stat* stat_object = LT_Class_ALLOC(LT_OS_Stat); - - stat_object->path = (LT_Value)(uintptr_t)path; - if (stat(LT_String_value_cstr(path), &stat_object->stat_buffer) != 0){ - LT_system_error("Could not stat path", errno); - } - return stat_object; -} - static int mode_is_regular_file(mode_t mode){ return S_ISREG(mode); } @@ -134,13 +116,6 @@ static LT_Value boolean_from_int(int value){ return value ? LT_TRUE : LT_FALSE; } -static LT_Value instant_from_epoch_seconds(intmax_t seconds){ - return LT_Instant_new(LT_Number_multiply2( - LT_Integer_from_intmax(seconds), - LT_SmallInteger_new(1000000) - )); -} - static LT_Value stat_predicate_from_path(LT_String* path, int (*predicate)(mode_t)){ struct stat stat_buffer; @@ -181,9 +156,6 @@ static void bind_os_primitive(LT_Environment* environment, #define OS_STRING_ARG(cursor, name) \ LT_GENERIC_ARG(cursor, name, LT_String*, LT_Pathname_like_as_string) -#define OS_STAT_ARG(cursor, name) \ - LT_GENERIC_ARG(cursor, name, LT_OS_Stat*, LT_OS_Stat_from_value) - struct OS_Flag { const char* name; int value; @@ -287,25 +259,6 @@ static LT_Value os_fnmatch_result(const char* pattern, return LT_FALSE; } -#define DEFINE_OS_STAT_PREDICATE_METHOD(c_name, selector, predicate, description) \ - LT_DEFINE_PRIMITIVE( \ - c_name, \ - "Stat>>" selector, \ - "(self)", \ - description \ - ){ \ - LT_Value cursor = arguments; \ - LT_OS_Stat* self; \ - (void)tail_call_unwind_marker; \ - (void)invocation_context_kind; \ - (void)invocation_context_data; \ - \ - OS_STAT_ARG(cursor, self); \ - LT_ARG_END(cursor); \ - \ - return boolean_from_int(predicate(self->stat_buffer.st_mode)); \ - } - #define DEFINE_OS_PATH_PREDICATE(c_name, primitive_name, predicate, description) \ LT_DEFINE_PRIMITIVE( \ c_name, \ @@ -325,255 +278,6 @@ static LT_Value os_fnmatch_result(const char* pattern, return stat_predicate_from_path(path, predicate); \ } -#define DEFINE_OS_STAT_UINT_FIELD_METHOD(c_name, selector, field, description) \ - LT_DEFINE_PRIMITIVE( \ - c_name, \ - "Stat>>" selector, \ - "(self)", \ - description \ - ){ \ - LT_Value cursor = arguments; \ - LT_OS_Stat* self; \ - (void)tail_call_unwind_marker; \ - (void)invocation_context_kind; \ - (void)invocation_context_data; \ - \ - OS_STAT_ARG(cursor, self); \ - LT_ARG_END(cursor); \ - \ - return LT_Integer_from_uintmax((uintmax_t)self->stat_buffer.field); \ - } - -#define DEFINE_OS_STAT_INT_FIELD_METHOD(c_name, selector, field, description) \ - LT_DEFINE_PRIMITIVE( \ - c_name, \ - "Stat>>" selector, \ - "(self)", \ - description \ - ){ \ - LT_Value cursor = arguments; \ - LT_OS_Stat* self; \ - (void)tail_call_unwind_marker; \ - (void)invocation_context_kind; \ - (void)invocation_context_data; \ - \ - OS_STAT_ARG(cursor, self); \ - LT_ARG_END(cursor); \ - \ - return LT_Integer_from_intmax((intmax_t)self->stat_buffer.field); \ - } - -#define DEFINE_OS_STAT_TIME_FIELD_METHOD(c_name, selector, field, description) \ - LT_DEFINE_PRIMITIVE( \ - c_name, \ - "Stat>>" selector, \ - "(self)", \ - description \ - ){ \ - LT_Value cursor = arguments; \ - LT_OS_Stat* self; \ - (void)tail_call_unwind_marker; \ - (void)invocation_context_kind; \ - (void)invocation_context_data; \ - \ - OS_STAT_ARG(cursor, self); \ - LT_ARG_END(cursor); \ - \ - return instant_from_epoch_seconds((intmax_t)self->stat_buffer.field); \ - } - -LT_DEFINE_PRIMITIVE( - stat_class_method_file, - "Stat class>>file:", - "(self path)", - "Return filesystem status for path." -){ - LT_Value cursor = arguments; - LT_Value self; - LT_String* path; - (void)tail_call_unwind_marker; - (void)invocation_context_kind; - (void)invocation_context_data; - - LT_OBJECT_ARG(cursor, self); - OS_STRING_ARG(cursor, path); - LT_ARG_END(cursor); - (void)self; - - return (LT_Value)(uintptr_t)os_stat_new(path); -} - -DEFINE_OS_STAT_PREDICATE_METHOD( - stat_method_regular_file_p, - "regular-file?", - mode_is_regular_file, - "Return true when this status describes a regular file." -) - -DEFINE_OS_STAT_PREDICATE_METHOD( - stat_method_directory_p, - "directory?", - mode_is_directory, - "Return true when this status describes a directory." -) - -DEFINE_OS_STAT_PREDICATE_METHOD( - stat_method_pipe_p, - "pipe?", - mode_is_pipe, - "Return true when this status describes a pipe." -) - -DEFINE_OS_STAT_PREDICATE_METHOD( - stat_method_device_p, - "device?", - mode_is_device, - "Return true when this status describes a device." -) - -DEFINE_OS_STAT_PREDICATE_METHOD( - stat_method_socket_p, - "socket?", - mode_is_socket, - "Return true when this status describes a socket." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_dev, - "dev", - st_dev, - "Return the device ID." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_ino, - "ino", - st_ino, - "Return the inode number." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_mode, - "mode", - st_mode, - "Return the file mode bits." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_nlink, - "nlink", - st_nlink, - "Return the link count." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_uid, - "uid", - st_uid, - "Return the owner user ID." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_gid, - "gid", - st_gid, - "Return the owner group ID." -) - -DEFINE_OS_STAT_UINT_FIELD_METHOD( - stat_method_rdev, - "rdev", - st_rdev, - "Return the device ID for special files." -) - -DEFINE_OS_STAT_INT_FIELD_METHOD( - stat_method_size, - "size", - st_size, - "Return the file size in bytes." -) - -DEFINE_OS_STAT_INT_FIELD_METHOD( - stat_method_blksize, - "blksize", - st_blksize, - "Return the preferred block size for filesystem I/O." -) - -DEFINE_OS_STAT_INT_FIELD_METHOD( - stat_method_blocks, - "blocks", - st_blocks, - "Return the number of allocated blocks." -) - -DEFINE_OS_STAT_TIME_FIELD_METHOD( - stat_method_atime, - "atime", - st_atime, - "Return the last access time as an Instant." -) - -DEFINE_OS_STAT_TIME_FIELD_METHOD( - stat_method_mtime, - "mtime", - st_mtime, - "Return the last modification time as an Instant." -) - -DEFINE_OS_STAT_TIME_FIELD_METHOD( - stat_method_ctime, - "ctime", - st_ctime, - "Return the last status change time as an Instant." -) - -static LT_Slot_Descriptor Stat_slots[] = { - {"path", offsetof(LT_OS_Stat, path), <_SlotType_ReadonlyObject}, - LT_NULL_NATIVE_CLASS_SLOT_DESCRIPTOR -}; - -static LT_Method_Descriptor Stat_methods[] = { - {"regular-file?", &stat_method_regular_file_p}, - {"directory?", &stat_method_directory_p}, - {"pipe?", &stat_method_pipe_p}, - {"device?", &stat_method_device_p}, - {"socket?", &stat_method_socket_p}, - {"dev", &stat_method_dev}, - {"ino", &stat_method_ino}, - {"mode", &stat_method_mode}, - {"nlink", &stat_method_nlink}, - {"uid", &stat_method_uid}, - {"gid", &stat_method_gid}, - {"rdev", &stat_method_rdev}, - {"size", &stat_method_size}, - {"blksize", &stat_method_blksize}, - {"blocks", &stat_method_blocks}, - {"atime", &stat_method_atime}, - {"mtime", &stat_method_mtime}, - {"ctime", &stat_method_ctime}, - LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR -}; - -static LT_Method_Descriptor Stat_class_methods[] = { - {"file:", &stat_class_method_file}, - LT_NULL_NATIVE_CLASS_METHOD_DESCRIPTOR -}; - -LT_DEFINE_CLASS(LT_OS_Stat) { - .superclass = <_Object_class, - .metaclass_superclass = <_Class_class, - .package = "ListTalk:OS", - .name = "Stat", - .documentation = "Filesystem metadata returned by stat operations.", - .instance_size = sizeof(LT_OS_Stat), - .class_flags = LT_CLASS_FLAG_FINAL, - .slots = Stat_slots, - .methods = Stat_methods, - .class_methods = Stat_class_methods, -}; - LT_DEFINE_PRIMITIVE( primitive_os_exit, "exit", @@ -1010,11 +714,10 @@ LT_DEFINE_PRIMITIVE( void ListTalk_os_load(LT_Environment* environment){ LT_Package* package = LT_Package_new("ListTalk:OS"); - LT_init_native_class(<_OS_Stat_class); LT_Environment_bind( environment, LT_Symbol_new_in(package, "Stat"), - LT_STATIC_CLASS(LT_OS_Stat), + LT_STATIC_CLASS(LT_PathnameStat), LT_ENV_BINDING_FLAG_CONSTANT ); diff --git a/tests/eval-loading-control.lt b/tests/eval-loading-control.lt index 855df0a..0fe1b24 100644 --- a/tests/eval-loading-control.lt +++ b/tests/eval-loading-control.lt @@ -21,10 +21,9 @@ (check (pair? (memq :os ListTalk:%modules)) "native module records provided module") (check (primitive? ListTalk:GC:collect!) "load! loads gc native module primitive") (check (class? ListTalk:cmdopts:Parser) "load! loads cmdopts Parser class") -(let ((stat-name (slot-ref ListTalk:OS:Stat 'name)) - (parser-name (slot-ref ListTalk:cmdopts:Parser 'name))) - (check (eq? [stat-name package] [Package named: "ListTalk:OS"]) - "native OS class name is interned in module package") +(let ((parser-name (slot-ref ListTalk:cmdopts:Parser 'name))) + (check (eq? ListTalk:OS:Stat PathnameStat) + "OS Stat is an alias for the core PathnameStat class") (check (eq? [parser-name package] [Package named: "ListTalk:cmdopts"]) "native cmdopts class name is interned in module package")) (check-error-message "use-package expects :as keyword" From b94adc796e27c0b8e1ca610bd9f7b6e85154f6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ale=C5=A1=20Hakl?= Date: Mon, 7 Sep 2026 17:46:38 +0200 Subject: [PATCH 6/6] Change return type of pathname value functions to const char* --- ListTalk/classes/Pathname.h | 4 ++-- src/classes/Pathname.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ListTalk/classes/Pathname.h b/ListTalk/classes/Pathname.h index 6375dcd..d5e96a9 100644 --- a/ListTalk/classes/Pathname.h +++ b/ListTalk/classes/Pathname.h @@ -44,7 +44,7 @@ LT_Pathname* LT_AbsolutePathname_rooted_at( LT_Pathname* root ); LT_String* LT_Pathname_as_string(LT_Pathname* pathname); -char* LT_Pathname_value_cstr(LT_Pathname* pathname); +const char* LT_Pathname_value_cstr(LT_Pathname* pathname); int LT_Pathname_absolute_p(LT_Pathname* pathname); int LT_Pathname_relative_p(LT_Pathname* pathname); int LT_Pathname_exists_p(LT_Pathname* pathname); @@ -60,7 +60,7 @@ int LT_Pathname_writable_p(LT_Pathname* pathname); int LT_Pathname_executable_p(LT_Pathname* pathname); LT_PathnameStat* LT_Pathname_stat(LT_Pathname* pathname); LT_PathnameStat* LT_Pathname_lstat(LT_Pathname* pathname); -char* LT_Pathname_like_value_cstr(LT_Value value); +const char* LT_Pathname_like_value_cstr(LT_Value value); LT_String* LT_Pathname_like_as_string(LT_Value value); LT__END_DECLS diff --git a/src/classes/Pathname.c b/src/classes/Pathname.c index b74a8f9..ab0086d 100644 --- a/src/classes/Pathname.c +++ b/src/classes/Pathname.c @@ -757,7 +757,7 @@ LT_String* LT_Pathname_as_string(LT_Pathname* pathname){ return LT_String_new_cstr(pathname->pathname); } -char* LT_Pathname_value_cstr(LT_Pathname* pathname){ +const char* LT_Pathname_value_cstr(LT_Pathname* pathname){ return pathname->pathname; } @@ -840,14 +840,14 @@ LT_PathnameStat* LT_Pathname_lstat(LT_Pathname* pathname){ return PathnameStat_new_for_value((LT_Value)(uintptr_t)pathname, 0); } -char* LT_Pathname_like_value_cstr(LT_Value value){ +const char* LT_Pathname_like_value_cstr(LT_Value value){ if (LT_Pathname_p(value)){ return LT_Pathname_value_cstr(LT_Pathname_from_value(value)); } if (LT_String_p(value)){ LT_String* string = LT_String_from_value(value); Pathname_check_string(string); - return (char*)LT_String_value_cstr(string); + return LT_String_value_cstr(string); } LT_error("Expected Pathname or String"); return NULL;