diff --git a/c-programming/strings/portable_basename.c b/c-programming/strings/portable_basename.c new file mode 100644 index 0000000..2b44c8c --- /dev/null +++ b/c-programming/strings/portable_basename.c @@ -0,0 +1,258 @@ +/* + * portable_basename.c + * + * Author: Intel A80486DX2-66 + * License: Unlicense + */ + +#include "portable_basename.h" + +static char* correct_slashes(const char* path) { + char* new_path = strdup(path); + if (new_path == NULL) + return NULL; + + char* ptr = new_path; + while (*ptr != '\0') { + if (*ptr == '\\') + *ptr = '/'; + ptr++; + } + + return new_path; +} + +char* portable_basename(const char* raw_path) { + char* path = correct_slashes(raw_path); + if (path == NULL) + return NULL; + + char* base = malloc(FILENAME_MAX * sizeof(char)); + if (base == NULL) { + free(path); + return NULL; + } + + size_t fname_len = strlen(path); + + const char* last_slash = strrchr(path, '/'); + if (last_slash != NULL) + fname_len = strlen(last_slash + 1); + + memcpy(base, last_slash + 1, fname_len); + base[fname_len] = '\0'; + + return base; +} + +#ifdef TEST +# include +# include +# include +# include + +// system identification +# ifdef _WIN32 +# define SYS_NT +# include +# endif + +# if defined(__unix__) || defined(__APPLE__) +# define SYS_UNIX +# endif + +// macros +# if defined(SYS_UNIX) && !defined(CLOCK_MONOTONIC_RAW) +# define CLOCK_MONOTONIC_RAW CLOCK_MONOTONIC +# endif + +// function prototypes +long double clock_hr_microsec(void); +# ifdef SYS_NT +void WinAPI_perror(const char* s); +# endif +static void func_expect(const char* path, const char* expected_output, + bool expect_to_succeed); + +long double clock_hr_microsec(void) { +# ifdef SYS_NT + // TODO: FIXME: less accurate than Unix + + LARGE_INTEGER counter; + if (!QueryPerformanceCounter(&counter)) { + WinAPI_perror("QueryPerformanceCounter"); + exit(EXIT_FAILURE); + } + return (long double) (counter.QuadPart * 1000000ULL); +# elif SYS_UNIX + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + return ((long double) (ts.tv_sec * 1000000000ULL + ts.tv_nsec)) / 1000.l; +# else + return (long double) (clock() * 1000000ULL); +# endif +} + +// macros +# ifdef SYS_NT +# define WinAPI_perror_COMMON_MACRO \ + fprintf(stderr, "%s: %lu, %s", s, errorCode, errorString) + +// function implementations +void WinAPI_perror(const char* s) { + DWORD errorCode = GetLastError(); + LPSTR errorString = NULL; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&errorString, + 0, + NULL + ); + + if (errorString == NULL) { + errorString = "???\n"; + WinAPI_perror_COMMON_MACRO; + } else { + WinAPI_perror_COMMON_MACRO; + LocalFree(errorString); + } +} +# endif + +// macros +# if defined(SYS_NT) +LONGLONG QPF_frequency; +# define CLOCK_HR_DIFFTIME_MS(t2, t1) \ + ((long double) ((t2) - (t1))) / ((long double) QPF_frequency) +# elif defined(SYS_UNIX) +# define CLOCK_HR_DIFFTIME_MS(t2, t1) \ + ((t2) - (t1)) +# else +# define CLOCK_HR_DIFFTIME_MS(t2, t1) \ + ((long double) ((t2) - (t1)) * 1000000.L) / \ + ((long double) CLOCKS_PER_SEC) +# endif + +// global variables +uintmax_t tests_failed = 0; + +// function implementations +static void func_expect(const char* path, const char* expected_output, + bool expect_to_succeed) { + long double t1 = 0, t2 = 0; + + t1 = clock_hr_microsec(); + char* output = portable_basename(path); + t2 = clock_hr_microsec(); + if (output == NULL) { + perror("portable_basename"); + exit(EXIT_FAILURE); + } + + const char* caption = expect_to_succeed ? "Expected output " + : "Unexpected output"; + long double t = CLOCK_HR_DIFFTIME_MS(t2, t1); + printf("Input path : '%s'\n" + "%s: '%s'\n" + "Actual output : '%s'\n" + "Time, clock_t : %.3Lf microsec\n" + "Test result : ", path, caption, expected_output, output, t); + if ((bool) (strcmp(output, expected_output)) == expect_to_succeed) { + tests_failed++; + free(output); + + puts("Failed!\n"); + return; + } + puts("Passed\n"); + + free(output); + fflush(stdout); +} + +// macros +#define STRINGIZE(x) #x +#define INT2STR(x) STRINGIZE(x) + +#define TIMES_TO_CALL 65535 +#include +int main(void) { +# ifdef SYS_NT + // set console output code page to the native one + SetConsoleOutputCP(GetACP()); + + // get CPU frequency + LARGE_INTEGER freq; + if (!QueryPerformanceFrequency(&freq)) { + WinAPI_perror("QueryPerformanceFrequency"); + return EXIT_FAILURE; + } + + QPF_frequency = freq.QuadPart; +# endif + + // positive tests + func_expect("/", "", true); + func_expect("/usr/include/stdlib.h", "stdlib.h", true); + func_expect("C:\\Windows\\Fonts\\consola.ttf", "consola.ttf", true); + func_expect("\\..\\..\\directory\\.e.x.e.c.u.t.a.b.l.e.", + ".e.x.e.c.u.t.a.b.l.e.", true); + + // negative tests + func_expect("/a/.b.c.", "b.c", false); + func_expect("/a/.b.c.", ".b.c", false); + func_expect("/a/.b.c.", "b.c.", false); + func_expect("/a/.b.c.\\", ".b.c.", false); + + printf("Failed tests: %" PRIuMAX "\n", tests_failed); + + // timing test + puts("\nBenchmarking (" INT2STR(TIMES_TO_CALL) " calls):"); + fflush(stdout); + +#define BENCHMARKING_STRING_SET \ + "a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r\\s\\t\\u\\v\\w\\x\\y\\z\\.\\file", \ + "/mnt/cdrom/usr/bin/include/library/test_files/test.h" + + const char* string_set[] = { + BENCHMARKING_STRING_SET, + // HACK: try to prevent caching by using different pointers + BENCHMARKING_STRING_SET, + BENCHMARKING_STRING_SET, + BENCHMARKING_STRING_SET + }; + + const size_t string_set_len = sizeof(string_set) / sizeof(char*); + + long double t_beginning = 0, t_end = 0; + long double t_total = 0.l; + + for (uint_fast16_t i = 0; i < TIMES_TO_CALL; i++) { + const char* selected_string = string_set[i % string_set_len]; + + t_beginning = clock_hr_microsec(); + char* output = portable_basename(selected_string); + t_end = clock_hr_microsec(); + + if (output == NULL) { + perror("portable_basename"); + return EXIT_FAILURE; + } + + free(output); + + t_total += CLOCK_HR_DIFFTIME_MS(t_end, t_beginning); + } + + printf("Total time: %.3Lf microsec, avg. %.3Lf microsec\n", t_total, + t_total / ((long double) TIMES_TO_CALL)); + + return (tests_failed > 0 ? EXIT_FAILURE : EXIT_SUCCESS); +} + +#endif /* TEST */ diff --git a/c-programming/strings/portable_basename.h b/c-programming/strings/portable_basename.h new file mode 100644 index 0000000..2a326e9 --- /dev/null +++ b/c-programming/strings/portable_basename.h @@ -0,0 +1,18 @@ +/* + * portable_basename.h + * + * Author: Intel A80486DX2-66 + * License: Unlicense + */ + +#ifndef _PORTABLE_BASENAME_H +#define _PORTABLE_BASENAME_H + +#include +#include +#include + +static char* correct_slashes(const char* path); +char* portable_basename(const char* raw_path); + +#endif /* _PORTABLE_BASENAME_H */