diff --git a/book/pages/03_log.md b/book/pages/03_log.md index 795fa25..8472e96 100644 --- a/book/pages/03_log.md +++ b/book/pages/03_log.md @@ -28,7 +28,7 @@ And want something of the form: :::{dropdown} Details about the markdown syntax :icon: ellipsis :color: info -Because a program may output thousands up on thousands of log lines when running, it might be preferable not to print characters that have no effect. +Because a program may output thousands upon thousands of log lines when running, it might be preferable not to print characters that have no effect. In our instance, there is no need for spaces around `|`, and the two outer `|` can be omitted, so what we actually want to be output is: ```{code-block} Markdown @@ -49,11 +49,14 @@ Mardown table rendered The simplest and most direct way to achieve the desired output is to print it literally: ```{code-block} C -:caption: Step 0: just print it ! +:caption: Step 0 - just print it ! printf("DEBUG|`folder/file.c`|`main`|12|Hello world !\n"); printf("ERROR|`folder/file.c`|`foobar`|42|Failed to open `%s`: %s\n", file_name, strerror(errno)); ``` +Starting from this, let's use increasingly andvanced tricks to end-up with the desired interface, +while remaining functionnaly equivalent to the simplest solution. + ## Concatenate string literals at compile-time The first trick we can use is not specific to macros: we have seen, in the list of [phases of translation](00_compilation.md#phases-of-translation), that during phase 6, "Adjacent string literals are concatenated". @@ -71,26 +74,98 @@ const char help[] = This allows us to split the boiler-plate from the "variable" parts of the string: ```{code-block} C -:caption: Step 1: Isolate the user-provided string -printf("DEBUG" "|`" "folder/file.c" "`|`main`|" "12" "|" "Hello world !" "\n"); -printf("ERROR" "|`" "folder/file.c" "`|`foobar`|" "42" "|" "Failed to open `%s`: %s" "\n", file_name, strerror(errno)); +:caption: Step 1 - Isolate the user-provided string +printf("DEBUG" "|`" "folder/file.c" "`|`" "main" "`|" "12" "|" "Hello world !" "\n"); +printf("ERROR" "|`" "folder/file.c" "`|`" "foobar" "`|" "42" "|" "Failed to open `%s`: %s" "\n", file_name, strerror(errno)); ``` ## Current file name In addition to the user-provided string, a variable part of the log output is the name of the file the log is from. -We don't want the user to have to provide it, as it would then have no guarantee of beiing up to date and therefore would be useless. +We don't want the user to have to provide it, as it would then have no guarantee of beiing up to date and would therefore be useless. Luckily, the compiler can provide this information, as we have seen in the [last chapter](02_use.md#debugging-constants). -Most conviniently, it is provided as a string literal, allowing us to concatenate it with the rest of the string at compile-time: +Most conveniently, it is provided as a string literal, allowing us to concatenate it with the rest of the string at compile-time: ```{code-block} C -:caption: Step 2: Ask the compiler for the file name -printf("DEBUG" "|`" __FILE__ "`|`main`|" "12" "|" "Hello world !" "\n"); -printf("ERROR" "|`" __FILE__ "`|`foobar`|" "42" "|" "Failed to open `%s`: %s" "\n", file_name, strerror(errno)); +:caption: Step 2 - Ask the compiler for the file name +printf("DEBUG" "|`" __FILE__ "`|`" "main" "`|" "12" "|" "Hello world !" "\n"); +printf("ERROR" "|`" __FILE__ "`|`" "foobar" "`|" "42" "|" "Failed to open `%s`: %s" "\n", file_name, strerror(errno)); ``` :::{note} In this instance, we use `__FILE__` and not `__FILE_NAME__`, to be able to differentiate files that have the same name in different directories, like `arrays/push.c` and `deque/push.c`. ::: +## Format a string at run-time + +OK, the file name was easy, but what about the function name ? + +As seen in the [last chapter](02_use.md#debugging-constants), the name of the function is provided by a magic constant `__func__`. +But as it is not a string literal, it cannot be concatenated at compile-time. + +So we have to do that at run-time, and we are already using `printf`, so let's use the `%s` flag: +```{code-block} C +:caption: Step 3 - Ask the compiler for the function name +printf("DEBUG" "|`" __FILE__ "`|`%s`|" "12" "|" "Hello world !" "\n", __func__); +printf("ERROR" "|`" __FILE__ "`|`%s`|" "42" "|" "Failed to open `%s`: %s" "\n", __func__, file_name, strerror(errno)); +``` + +As you can see, it is slightly less trivial than the previous step, as it involves adding an argument to `printf`, before the user provided arguments, if any. + +:::{note} +`__func__` and `__FUNCTION__` are two names of the exact same variable, so they can be used interchangably. But the lower case `__func__` emphasize the fact that it is not a compile-time macro, as opposed to `__FILE__`. +::: + +Next, we have the line number to display. By using the same trick as the function name, we can include it at run-time: +```{code-block} C +:caption: Step 4 - Ask the compiler for the line number +printf("DEBUG" "|`" __FILE__ "`|`%s`|%i|" "Hello world !" "\n", __func__, __LINE__); +printf("ERROR" "|`" __FILE__ "`|`%s`|%i|" "Failed to open `%s`: %s" "\n", __func__, __LINE__, file_name, strerror(errno)); +``` + +## Convert the line number to a string literal + +In the previous step, we started to use `__LINE__` to retrieve the line number, and placed it in the log line at run-time. + +But why do it at run-time ? `__LINE__` is a macro that expands to an integer literal, meaning its value is accessible at compile-time. It just isn't a string, so we cannot concatenate it as-is. + +This is where [preprocessing operators](01_preprocessor.md#the-operators) come in: the `#` operator changes the type of tokens. + +::::{dropdown} Illustration +:color: info +:icon: paintbrush +:open: +Consider the following line of code: +```C +printf("arg = %i\n", 42); +``` +It would be [tokenized](00_compilation.md#tokenizing) like this: +:::{card} +Preprocessing input +^^^ +{bdg-primary-line}`printf` {bdg-dark-line}`(` +{bdg-success-line}`arg = %i\n` {bdg-dark-line}`,` {material-regular}`space_bar` {bdg-danger-line}`42` +{bdg-dark-line}`)` {bdg-dark-line}`;` {material-regular}`keyboard_return` +::: + +What the `#` operator can do is transform the integer literal {bdg-danger-line}`42` into the string literal {bdg-success-line}`42`, just as if the code was: +```C +printf("arg = %i\n", "42"); +``` + +Once that's done, we no longer need printf to format the integer in the string +:::: + +```{code-block} C +:caption: Step 5 - Concatenate the line number at compile-time +printf("DEBUG" "|`" __FILE__ "`|`%s`|%i|" "Hello world !" "\n", __func__, __LINE__); +printf("ERROR" "|`" __FILE__ "`|`%s`|%i|" "Failed to open `%s`: %s" "\n", __func__, __LINE__, file_name, strerror(errno)); +``` + +## Define a macro + +We can now place the boiler-plate in a macro to be reused and not repeated: +```{code-block} C +:caption: Step 6 +```