0

I want to get the name of the compiled file without extension as a constant in PROGMEM. Partially the name can be cleared like this:

#define __FILENAME__ strrchr(__FILE__, '\\') + 1

Okay, it's easy. Next I need to delete the file extension, I can do this with the code:

char *dot = strrchr(__FILENAME__, '.'); *dot = '\0'

But I can't get all-in-one inside #define because of step-by-step actions sequence and an redundant variable.

Is there a pretty solution? Length of the filename is unknown.

The option of clearing the name in the program body is an extra expense of code and memory.

N_A
  • 3
  • 2
  • Does this answer your question? [Why use apparently meaningless do-while and if-else statements in macros?](https://stackoverflow.com/questions/154136/why-use-apparently-meaningless-do-while-and-if-else-statements-in-macros) – IMSoP May 09 '23 at 09:17
  • No, it won't help me, I can't collapse this to expression with filename return. Perhaps using parentheses is a good idea and I can apply it as a scope (so as not to save the *dot exactly), but I don't understand what to do next with it yet. – N_A May 09 '23 at 09:45
  • The C preprocessor wasn't really designed to do heavy string editing like this. You might figure out something using C++ and `constexpr`, but that's a different language. Could you just write `#define FILENAME "foo.c"` at the top of every compilation unit and have a Ruby or Python script that helps you keep those definitions up-to-date? (Or perhaps you define the macro on the commandline with `-D`, but defining macros with double quotes on the command line is incredibly hard.) – David Grayson May 10 '23 at 23:01

1 Answers1

0

How to use #define for get filename without file extension

It is not possible to use C preprocessor to get a filename with file extension - C preprocessor has no capability of parsing strings.

#define __FILENAME__

Defining identifiers starting with double __ is invalid. They are reserved, you can't define such your own identifiers.

Your code will fail miserably when the __FILE__ does not contain a slash or a dot.

Next I need to delete the file extension, I can do this with the code:

The code you presented is invalid. You can't modify __FILE__, it is a constant. If you are using runtime, you have to malloc the required memory or allocate the memory on stack.

Potentially, you could execute a function on program startup to fix the value, or just call the function right after main. The happy assumption here is that filename without extension will always be smaller than __FILE__, so we can overallocate memory.

static char file_we[] = __FILE__;

__attribute__((__constructor__))
void _fix_file_we_() {
    // modify file_we
    char *const slash = strrchr(__FILE__, '\\');
    if (slash) {
        strmove(file_we, slash + 1);
    }
    chat *const dot = strrchr(file_we, '.');
    if (dot) {
         *dot = '\0';
    }
}

With GCC, there are__attribute__((__constructor__)) and __FILE_NAME__ extension you might be interested in.

and store it to PROGMEM

It is impossible to get a string literal of a filename without extension in C programming language. You have to use an external program or an extension.

With the above approach and using GCC, you can just instead compute only the length of file_we at runtime and store __FILE_NAME__ in a constant static variable. You will lose 2 bytes for .c, but that is really not much.

#include <stdio.h>
#include <string.h>
static const char file_name[] = __FILE_NAME__;
int main() {
   printf("%.*s\n", (int)strcspn(file_name, "."), file_name);
}

In this case, we might precompute the dot position with a constant expression, given the string is not long. The compilation time with longer strings when writing such long ternary expressions might increase.

#include <stdio.h>
#include <limits.h>

#define FORRANGE_0(f, ...)  f(0, __VA_ARGS__)
#define FORRANGE_1(f, ...)  FORRANGE_0(f, __VA_ARGS__) f(1, __VA_ARGS__)
#define FORRANGE_2(f, ...)  FORRANGE_1(f, __VA_ARGS__) f(2, __VA_ARGS__)
#define FORRANGE_3(f, ...)  FORRANGE_2(f, __VA_ARGS__) f(3, __VA_ARGS__)
#define FORRANGE_4(f, ...)  FORRANGE_3(f, __VA_ARGS__) f(4, __VA_ARGS__)
#define FORRANGE_5(f, ...)  FORRANGE_4(f, __VA_ARGS__) f(5, __VA_ARGS__)
#define FORRANGE_6(f, ...)  FORRANGE_5(f, __VA_ARGS__) f(6, __VA_ARGS__)
#define FORRANGE_7(f, ...)  FORRANGE_6(f, __VA_ARGS__) f(7, __VA_ARGS__)
#define FORRANGE_8(f, ...)  FORRANGE_7(f, __VA_ARGS__) f(8, __VA_ARGS__)
#define FORRANGE_9(f, ...)  FORRANGE_8(f, __VA_ARGS__) f(9, __VA_ARGS__)
#define FORRANGE(f, n, ...) FORRANGE_##n(f, __VA_ARGS__)

#define DOT_POS_CASE(n, f)  f[n]=='.' ? n :
#define DOT_POS(f)  ( \
    !f[0] ? INT_MAX : \
    !f[1] ? FORRANGE(DOT_POS_CASE, 0, f) INT_MAX : \
    !f[2] ? FORRANGE(DOT_POS_CASE, 1, f) INT_MAX : \
    !f[3] ? FORRANGE(DOT_POS_CASE, 2, f) INT_MAX : \
    !f[4] ? FORRANGE(DOT_POS_CASE, 3, f) INT_MAX : \
    !f[5] ? FORRANGE(DOT_POS_CASE, 4, f) INT_MAX : \
    !f[6] ? FORRANGE(DOT_POS_CASE, 5, f) INT_MAX : \
    !f[7] ? FORRANGE(DOT_POS_CASE, 6, f) INT_MAX : \
    !f[8] ? FORRANGE(DOT_POS_CASE, 7, f) INT_MAX : \
    !f[9] ? FORRANGE(DOT_POS_CASE, 8, f) INT_MAX : \
    /* TODO: add more to handle longer strings */ \
    INT_MAX)
            
static const char file_name[] = __FILE_NAME__;
static int file_name_dot_pos = DOT_POS(__FILE_NAME__);

int main() {
    printf("%.*s\n", file_name_dot_pos, file_name);
}

In real life, typically, the build system is configured to precompute the value and pass it using compiler options for each file separately, like -DFILE_WE="something".

For example, the following CMake script:

cmake_minimum_required(VERSION 3.11)
project(test)
add_executable(tgt main.c)

get_property(srcs TARGET tgt PROPERTY SOURCES)
foreach(src IN LISTS srcs)
  get_filename_component(file_we ${src} NAME_WE)
  set_property(
    SOURCE ${src}
    APPEND PROPERTY COMPILE_DEFINITIONS
    "FILE_WE=\"${file_we}\""
  )
endforeach()

include(CTest)
add_test(NAME tgt COMMAND tgt)
set_tests_properties(tgt PROPERTIES PASS_REGULAR_EXPRESSION main)

Compiles a program:

#include <stdio.h>
int main() { printf("%s\n", FILE_WE); }

With:

 /usr/bin/cc -DFILE_WE=\"main\"  .....main.c

Or you can use a better preprocessor like m4 or jinja2 or php.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111