blob: 9a93055e6061645672d63c8b98ad4eab960925e3 [file] [log] [blame]
// Copyright 2020 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#ifdef __cplusplus
#include <array>
#include <cstddef>
#include <cstdint>
#else
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#endif // __cplusplus
#include "pw_preprocessor/arguments.h"
#include "pw_preprocessor/compiler.h"
#include "pw_preprocessor/concat.h"
#include "pw_preprocessor/util.h"
#include "pw_tokenizer/internal/argument_types.h"
#include "pw_tokenizer/internal/tokenize_string.h"
// The type of the token used in place of a format string. Also available as
// pw::tokenizer::Token.
typedef uint32_t pw_tokenizer_Token;
// Strings may optionally be tokenized to a domain. Strings in different domains
// can be processed separately by the token database tools. Each domain in use
// must have a corresponding section declared in the linker script. See
// pw_tokenizer_linker_sections.ld for more details.
//
// If no domain is specified, this default is used.
#define PW_TOKENIZER_DEFAULT_DOMAIN "default"
// Tokenizes a string and converts it to a pw_tokenizer_Token. In C++, the
// string may be a literal or a constexpr char array. In C, the argument must be
// a string literal.
//
// This expression can be assigned to a local or global variable, but cannot be
// used in another expression. For example:
//
// constexpr uint32_t global = PW_TOKENIZE_STRING("Wow!"); // This works.
//
// void SomeFunction() {
// constexpr uint32_t token = PW_TOKENIZE_STRING("Cool!"); // This works.
//
// DoSomethingElse(PW_TOKENIZE_STRING("Lame!")); // This does NOT work.
// }
//
#define PW_TOKENIZE_STRING(string_literal) \
PW_TOKENIZE_STRING_DOMAIN(PW_TOKENIZER_DEFAULT_DOMAIN, string_literal)
// Same as PW_TOKENIZE_STRING, but tokenizes to the specified domain.
#define PW_TOKENIZE_STRING_DOMAIN(domain, string_literal) \
/* assign to a variable */ PW_TOKENIZER_STRING_TOKEN(string_literal); \
\
_PW_TOKENIZER_RECORD_ORIGINAL_STRING(domain, string_literal)
// Encodes a tokenized string and arguments to the provided buffer. The size of
// the buffer is passed via a pointer to a size_t. After encoding is complete,
// the size_t is set to the number of bytes written to the buffer.
//
// The macro's arguments are equivalent to the following function signature:
//
// TokenizeToBuffer(void* buffer,
// size_t* buffer_size_pointer,
// const char* format,
// ...); /* printf-style arguments */
//
// For example, the following encodes a tokenized string with a temperature to a
// buffer. The buffer is passed to a function to send the message over a UART.
//
// uint8_t buffer[32];
// size_t size_bytes = sizeof(buffer);
// PW_TOKENIZE_TO_BUFFER(
// buffer, &size_bytes, "Temperature (C): %0.2f", temperature_c);
// MyProject_EnqueueMessageForUart(buffer, size);
//
#define PW_TOKENIZE_TO_BUFFER(buffer, buffer_size_pointer, format, ...) \
PW_TOKENIZE_TO_BUFFER_DOMAIN(PW_TOKENIZER_DEFAULT_DOMAIN, \
buffer, \
buffer_size_pointer, \
format, \
__VA_ARGS__)
// Same as PW_TOKENIZE_TO_BUFFER, but tokenizes to the specified domain.
#define PW_TOKENIZE_TO_BUFFER_DOMAIN( \
domain, buffer, buffer_size_pointer, format, ...) \
do { \
_PW_TOKENIZE_FORMAT_STRING(domain, format, __VA_ARGS__); \
_pw_tokenizer_ToBuffer(buffer, \
buffer_size_pointer, \
_pw_tokenizer_token, \
PW_TOKENIZER_ARG_TYPES(__VA_ARGS__) \
PW_COMMA_ARGS(__VA_ARGS__)); \
} while (0)
// Encodes a tokenized string and arguments to a buffer on the stack. The
// provided callback is called with the encoded data. The size of the
// stack-allocated argument encoding buffer is set with the
// PW_TOKENIZER_CFG_ENCODING_BUFFER_SIZE_BYTES option.
//
// The macro's arguments are equivalent to the following function signature:
//
// TokenizeToCallback(void (*callback)(const uint8_t* data, size_t size),
// const char* format,
// ...); /* printf-style arguments */
//
// For example, the following encodes a tokenized string with a sensor name and
// floating point data. The encoded message is passed directly to the
// MyProject_EnqueueMessageForUart function, which the caller provides as a
// callback.
//
// void MyProject_EnqueueMessageForUart(const uint8_t* buffer,
// size_t size_bytes) {
// uart_queue_write(uart_instance, buffer, size_bytes);
// }
//
// void LogSensorValue(const char* sensor_name, float value) {
// PW_TOKENIZE_TO_CALLBACK(MyProject_EnqueueMessageForUart,
// "%s: %f",
// sensor_name,
// value);
// }
//
#define PW_TOKENIZE_TO_CALLBACK(callback, format, ...) \
PW_TOKENIZE_TO_CALLBACK_DOMAIN( \
PW_TOKENIZER_DEFAULT_DOMAIN, callback, format, __VA_ARGS__)
#define PW_TOKENIZE_TO_CALLBACK_DOMAIN(domain, callback, format, ...) \
do { \
_PW_TOKENIZE_FORMAT_STRING(domain, format, __VA_ARGS__); \
_pw_tokenizer_ToCallback(callback, \
_pw_tokenizer_token, \
PW_TOKENIZER_ARG_TYPES(__VA_ARGS__) \
PW_COMMA_ARGS(__VA_ARGS__)); \
} while (0)
PW_EXTERN_C_START
// These functions encode the tokenized strings. These should not be called
// directly. Instead, use the corresponding PW_TOKENIZE_TO_* macros above.
void _pw_tokenizer_ToBuffer(void* buffer,
size_t* buffer_size_bytes, // input and output arg
pw_tokenizer_Token token,
_pw_tokenizer_ArgTypes types,
...);
void _pw_tokenizer_ToCallback(void (*callback)(const uint8_t* encoded_message,
size_t size_bytes),
pw_tokenizer_Token token,
_pw_tokenizer_ArgTypes types,
...);
// This empty function allows the compiler to check the format string.
static inline void pw_tokenizer_CheckFormatString(const char* format, ...)
PW_PRINTF_FORMAT(1, 2);
static inline void pw_tokenizer_CheckFormatString(const char* format, ...) {
PW_UNUSED(format);
}
PW_EXTERN_C_END
// These macros implement string tokenization. They should not be used directly;
// use one of the PW_TOKENIZE_* macros above instead.
// This macro takes a printf-style format string and corresponding arguments. It
// checks that the arguments are correct, stores the format string in a special
// section, and calculates the string's token at compile time.
// clang-format off
#define _PW_TOKENIZE_FORMAT_STRING(domain, format, ...) \
if (0) { /* Do not execute to prevent double evaluation of the arguments. */ \
pw_tokenizer_CheckFormatString(format PW_COMMA_ARGS(__VA_ARGS__)); \
} \
\
/* Check that the macro is invoked with a supported number of arguments. */ \
static_assert( \
PW_FUNCTION_ARG_COUNT(__VA_ARGS__) <= PW_TOKENIZER_MAX_SUPPORTED_ARGS, \
"Tokenized strings cannot have more than " \
PW_STRINGIFY(PW_TOKENIZER_MAX_SUPPORTED_ARGS) " arguments; " \
PW_STRINGIFY(PW_FUNCTION_ARG_COUNT(__VA_ARGS__)) \
" arguments were used for " #format " (" #__VA_ARGS__ ")"); \
\
/* Tokenize the string to a pw_tokenizer_Token at compile time. */ \
_PW_TOKENIZER_CONST pw_tokenizer_Token _pw_tokenizer_token = \
PW_TOKENIZE_STRING_DOMAIN(domain, format)
// clang-format on
#ifdef __cplusplus // use constexpr for C++
#define _PW_TOKENIZER_CONST constexpr
namespace pw {
namespace tokenizer {
using Token = ::pw_tokenizer_Token;
} // namespace tokenizer
} // namespace pw
#else // use const for C
#define _PW_TOKENIZER_CONST const
#endif // __cplusplus
// _PW_TOKENIZER_SECTION places the format string in a special .pw_tokenized
// linker section. Host-side decoding tools read the strings from this section
// to build a database of tokenized strings.
//
// This section should be declared as type INFO so that it is excluded from the
// final binary. To declare the section, as well as the .pw_tokenizer_info
// metadata section, add the following to the linker script's SECTIONS command:
//
// .pw_tokenizer_info 0x0 (INFO) :
// {
// KEEP(*(.pw_atokenizer_info))
// }
//
// .pw_tokenized.default 0x0 (INFO) :
// {
// KEEP(*(.pw_tokenized.default.*))
// }
//
//
// If custom tokenization domains are used, a section must be declared for each
// domain:
//
// .pw_tokenized.YOUR_CUSTOM_TOKENIZATION_DOMAIN 0x0 (INFO) :
// {
// KEEP(*(.pw_tokenized.YOUR_CUSTOM_TOKENIZATION_DOMAIN.*))
// }
//
// A linker script snippet that provides these sections is provided in the file
// pw_tokenizer_linker_sections.ld. This file may be directly included into
// existing linker scripts.
//
// The tokenized string sections can also be managed without linker script
// modifications, though this is not recommended. The section can be extracted
// and removed from the ELF with objcopy:
//
// objcopy --only-section .pw_tokenize* <ORIGINAL_ELF> <OUTPUT_ELF>
// objcopy --remove-section .pw_tokenize* <ORIGINAL_ELF>
//
// OUTPUT_ELF will be an ELF with only the tokenized strings, and the original
// ELF file will have the sections removed.
//
// Without the above linker script modifications, the section garbage collection
// option (--gc-sections) removes the tokenized string sections. To avoid
// editing the target linker script, a separate metadata ELF can be linked
// without --gc-sections to preserve the tokenized data.
//
// pw_tokenizer is intended for use with ELF files only. Mach-O files (macOS
// executables) do not support section names longer than 16 characters, so a
// short, dummy section name is used on macOS.
#ifdef __APPLE__
#define _PW_TOKENIZER_SECTION(unused_domain) \
PW_KEEP_IN_SECTION(".pw." PW_STRINGIFY(__LINE__))
#else
#define _PW_TOKENIZER_SECTION(domain) \
PW_KEEP_IN_SECTION(".pw_tokenized." domain "." PW_STRINGIFY(__LINE__))
#endif // __APPLE__
// Declare the format string as an array in the special tokenized string
// section, which should be excluded from the final binary. Use __COUNTER__
// to create unique names for the section and variable, which avoids
// compiler warnings.
#ifdef __cplusplus
// In C++, use std::to_array to support tokenizing string literals or constexpr
// char arrays.
#define _PW_TOKENIZER_RECORD_ORIGINAL_STRING(domain, string) \
static constexpr std::array<char, sizeof(string)> PW_CONCAT( \
_pw_tokenizer_string_literal_DO_NOT_USE_, __COUNTER__) \
_PW_TOKENIZER_SECTION(domain) = std::to_array<const char>(string)
#else // In C, only string literals may be tokenized.
#define _PW_TOKENIZER_RECORD_ORIGINAL_STRING(domain, string_literal) \
static const char PW_CONCAT(_pw_tokenizer_string_literal_DO_NOT_USE_, \
__COUNTER__)[] _PW_TOKENIZER_SECTION(domain) = \
string_literal
#endif // __cplusplus