#include #include #include #include "kernel/yosys_common.h" #ifndef YOSYS_IO_H #define YOSYS_IO_H YOSYS_NAMESPACE_BEGIN inline std::string vstringf(const char *fmt, va_list ap) { // For the common case of strings shorter than 128, save a heap // allocation by using a stack allocated buffer. const int kBufSize = 128; char buf[kBufSize]; buf[0] = '\0'; va_list apc; va_copy(apc, ap); int n = vsnprintf(buf, kBufSize, fmt, apc); va_end(apc); if (n < kBufSize) return std::string(buf); std::string string; char *str = NULL; #if defined(_WIN32) || defined(__CYGWIN__) int sz = 2 * kBufSize, rc; while (1) { va_copy(apc, ap); str = (char *)realloc(str, sz); rc = vsnprintf(str, sz, fmt, apc); va_end(apc); if (rc >= 0 && rc < sz) break; sz *= 2; } if (str != NULL) { string = str; free(str); } return string; #else if (vasprintf(&str, fmt, ap) < 0) str = NULL; if (str != NULL) { string = str; free(str); } return string; #endif } enum ConversionSpecifier : uint8_t { CONVSPEC_NONE, // Specifier not understood/supported CONVSPEC_ERROR, // Consumes a "long long" CONVSPEC_SIGNED_INT, // Consumes a "unsigned long long" CONVSPEC_UNSIGNED_INT, // Consumes a "double" CONVSPEC_DOUBLE, // Consumes a "const char*" CONVSPEC_CHAR_PTR, // Consumes a "void*" CONVSPEC_VOID_PTR, }; constexpr ConversionSpecifier parse_conversion_specifier(char ch, char prev_ch) { switch (ch) { case 'd': case 'i': return CONVSPEC_SIGNED_INT; case 'o': case 'u': case 'x': case 'X': case 'm': return CONVSPEC_UNSIGNED_INT; case 'c': if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { // wchar not supported return CONVSPEC_ERROR; } return CONVSPEC_UNSIGNED_INT; case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A': return CONVSPEC_DOUBLE; case 's': if (prev_ch == 'l' || prev_ch == 'q' || prev_ch == 'L') { // wchar not supported return CONVSPEC_ERROR; } return CONVSPEC_CHAR_PTR; case 'p': return CONVSPEC_VOID_PTR; case '$': // positional parameters case 'n': case 'S': return CONVSPEC_ERROR; default: return CONVSPEC_NONE; } } enum class DynamicIntCount : uint8_t { NONE = 0, ONE = 1, TWO = 2, }; // Describes a printf-style format conversion specifier found in a format string. struct FoundFormatSpec { // The start offset of the conversion spec in the format string. int start; // The end offset of the conversion spec in the format string. int end; ConversionSpecifier spec; // Number of int args consumed by '*' dynamic width/precision args. DynamicIntCount num_dynamic_ints; }; // Ensure there is no format spec. constexpr void ensure_no_format_spec(std::string_view fmt, int index, bool *has_escapes) { int fmt_size = static_cast(fmt.size()); // A trailing '%' is not a format spec. while (index + 1 < fmt_size) { if (fmt[index] != '%') { ++index; continue; } if (fmt[index + 1] != '%') { YOSYS_ABORT("More format conversion specifiers than arguments"); } *has_escapes = true; index += 2; } } // Returns the next format conversion specifier (starting with '%'). // Returns CONVSPEC_NONE if there isn't a format conversion specifier. constexpr FoundFormatSpec find_next_format_spec(std::string_view fmt, int fmt_start, bool *has_escapes) { int index = fmt_start; int fmt_size = static_cast(fmt.size()); while (index < fmt_size) { if (fmt[index] != '%') { ++index; continue; } int p = index + 1; uint8_t num_dynamic_ints = 0; while (true) { if (p == fmt_size) { return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; } if (fmt[p] == '%') { *has_escapes = true; index = p + 1; break; } if (fmt[p] == '*') { if (num_dynamic_ints >= 2) { return {0, 0, CONVSPEC_ERROR, DynamicIntCount::NONE}; } ++num_dynamic_ints; } ConversionSpecifier spec = parse_conversion_specifier(fmt[p], fmt[p - 1]); if (spec != CONVSPEC_NONE) { return {index, p + 1, spec, static_cast(num_dynamic_ints)}; } ++p; } } return {0, 0, CONVSPEC_NONE, DynamicIntCount::NONE}; } template constexpr typename std::enable_if::type check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec*, DynamicIntCount) { ensure_no_format_spec(fmt, fmt_start, has_escapes); } // Check that the format string `fmt.substr(fmt_start)` is valid for the given type arguments. // Fills `specs` with the FoundFormatSpecs found in the format string. // `int_args_consumed` is the number of int arguments already consumed to satisfy the // dynamic width/precision args for the next format conversion specifier. template constexpr void check_format(std::string_view fmt, int fmt_start, bool *has_escapes, FoundFormatSpec* specs, DynamicIntCount int_args_consumed) { FoundFormatSpec found = find_next_format_spec(fmt, fmt_start, has_escapes); if (found.num_dynamic_ints > int_args_consumed) { // We need to consume at least one more int for the dynamic // width/precision of this format conversion specifier. if constexpr (!std::is_convertible_v) { YOSYS_ABORT("Expected dynamic int argument"); } check_format(fmt, fmt_start, has_escapes, specs, static_cast(static_cast(int_args_consumed) + 1)); return; } switch (found.spec) { case CONVSPEC_NONE: YOSYS_ABORT("Expected format conversion specifier for argument"); break; case CONVSPEC_ERROR: YOSYS_ABORT("Found unsupported format conversion specifier"); break; case CONVSPEC_SIGNED_INT: if constexpr (!std::is_convertible_v) { YOSYS_ABORT("Expected type convertible to signed integer"); } *specs = found; break; case CONVSPEC_UNSIGNED_INT: if constexpr (!std::is_convertible_v) { YOSYS_ABORT("Expected type convertible to unsigned integer"); } *specs = found; break; case CONVSPEC_DOUBLE: if constexpr (!std::is_convertible_v) { YOSYS_ABORT("Expected type convertible to double"); } *specs = found; break; case CONVSPEC_CHAR_PTR: if constexpr (!std::is_convertible_v && !std::is_convertible_v && !std::is_convertible_v) { YOSYS_ABORT("Expected type convertible to char *"); } *specs = found; break; case CONVSPEC_VOID_PTR: if constexpr (!std::is_convertible_v) { YOSYS_ABORT("Expected pointer type"); } *specs = found; break; } check_format(fmt, found.end, has_escapes, specs + 1, DynamicIntCount::NONE); } // Emit the string representation of `arg` that has been converted to a `long long'. void format_emit_long_long(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, long long arg); // Emit the string representation of `arg` that has been converted to a `unsigned long long'. void format_emit_unsigned_long_long(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, unsigned long long arg); // Emit the string representation of `arg` that has been converted to a `double'. void format_emit_double(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, double arg); // Emit the string representation of `arg` that has been converted to a `const char*'. void format_emit_char_ptr(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, const char *arg); // Emit the string representation of `arg` that has been converted to a `std::string'. void format_emit_string(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, const std::string &arg); // Emit the string representation of `arg` that has been converted to a `std::string_view'. void format_emit_string_view(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, std::string_view arg); // Emit the string representation of `arg` that has been converted to a `double'. void format_emit_void_ptr(std::string &result, std::string_view spec, int *dynamic_ints, DynamicIntCount num_dynamic_ints, const void *arg); // Emit the string representation of `arg` according to the given `FoundFormatSpec`, // appending it to `result`. template inline void format_emit_one(std::string &result, std::string_view fmt, const FoundFormatSpec &ffspec, int *dynamic_ints, const Arg& arg) { std::string_view spec = fmt.substr(ffspec.start, ffspec.end - ffspec.start); DynamicIntCount num_dynamic_ints = ffspec.num_dynamic_ints; switch (ffspec.spec) { case CONVSPEC_SIGNED_INT: if constexpr (std::is_convertible_v) { long long s = arg; format_emit_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); return; } break; case CONVSPEC_UNSIGNED_INT: if constexpr (std::is_convertible_v) { unsigned long long s = arg; format_emit_unsigned_long_long(result, spec, dynamic_ints, num_dynamic_ints, s); return; } break; case CONVSPEC_DOUBLE: if constexpr (std::is_convertible_v) { double s = arg; format_emit_double(result, spec, dynamic_ints, num_dynamic_ints, s); return; } break; case CONVSPEC_CHAR_PTR: if constexpr (std::is_convertible_v) { const char *s = arg; format_emit_char_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); return; } if constexpr (std::is_convertible_v) { const std::string &s = arg; format_emit_string(result, spec, dynamic_ints, num_dynamic_ints, s); return; } if constexpr (std::is_convertible_v) { const std::string_view &s = arg; format_emit_string_view(result, spec, dynamic_ints, num_dynamic_ints, s); return; } break; case CONVSPEC_VOID_PTR: if constexpr (std::is_convertible_v) { const void *s = arg; format_emit_void_ptr(result, spec, dynamic_ints, num_dynamic_ints, s); return; } break; default: break; } YOSYS_ABORT("Internal error"); } // Append the format string `fmt` to `result`, assuming there are no format conversion // specifiers other than "%%" and therefore no arguments. Unescape "%%". void format_emit_unescaped(std::string &result, std::string_view fmt); std::string unescape_format_string(std::string_view fmt); inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, bool has_escapes, const FoundFormatSpec*, int*, DynamicIntCount) { fmt = fmt.substr(fmt_start); if (has_escapes) { format_emit_unescaped(result, fmt); } else { result += fmt; } } // Format `args` according to `fmt` (starting at `fmt_start`) and `specs` and append to `result`. // `num_dynamic_ints` in `dynamic_ints[]` have already been collected to provide as // dynamic width/precision args for the next format conversion specifier. template inline void format_emit(std::string &result, std::string_view fmt, int fmt_start, bool has_escapes, const FoundFormatSpec* specs, int *dynamic_ints, DynamicIntCount num_dynamic_ints, const Arg &arg, const Args &... args) { if (specs->num_dynamic_ints > num_dynamic_ints) { // Collect another int for the dynamic width precision/args // for the next format conversion specifier. if constexpr (std::is_convertible_v) { dynamic_ints[static_cast(num_dynamic_ints)] = arg; } else { YOSYS_ABORT("Internal error"); } format_emit(result, fmt, fmt_start, has_escapes, specs, dynamic_ints, static_cast(static_cast(num_dynamic_ints) + 1), args...); return; } std::string_view str = fmt.substr(fmt_start, specs->start - fmt_start); if (has_escapes) { format_emit_unescaped(result, str); } else { result += str; } format_emit_one(result, fmt, *specs, dynamic_ints, arg); format_emit(result, fmt, specs->end, has_escapes, specs + 1, dynamic_ints, DynamicIntCount::NONE, args...); } template inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec* specs, const Args &... args) { std::string result; int dynamic_ints[2] = { 0, 0 }; format_emit(result, fmt, 0, has_escapes, specs, dynamic_ints, DynamicIntCount::NONE, args...); return result; } template <> inline std::string format_emit_toplevel(std::string_view fmt, bool has_escapes, const FoundFormatSpec*) { if (!has_escapes) { return std::string(fmt); } return unescape_format_string(fmt); } // This class parses format strings to build a list of `FoundFormatSpecs` in `specs`. // When the compiler supports `consteval` (C++20), this parsing happens at compile time and // type errors will be reported at compile time. Otherwise the parsing happens at // runtime and type errors will trigger an `abort()` at runtime. template class FmtString { public: // Implicit conversion from const char * means that users can pass // C string constants which are automatically parsed. YOSYS_CONSTEVAL FmtString(const char *p) : fmt(p) { check_format(fmt, 0, &has_escapes, specs, DynamicIntCount::NONE); } std::string format(const Args &... args) { return format_emit_toplevel(fmt, has_escapes, specs, args...); } private: std::string_view fmt; bool has_escapes = false; FoundFormatSpec specs[sizeof...(Args)] = {}; }; template struct WrapType { using type = T; }; template using TypeIdentity = typename WrapType::type; template inline std::string stringf(FmtString...> fmt, Args... args) { return fmt.format(args...); } int readsome(std::istream &f, char *s, int n); std::string next_token(std::string &text, const char *sep = " \t\r\n", bool long_strings = false); std::vector split_tokens(const std::string &text, const char *sep = " \t\r\n"); bool patmatch(const char *pattern, const char *string); #if !defined(YOSYS_DISABLE_SPAWN) int run_command(const std::string &command, std::function process_line = std::function()); #endif std::string get_base_tmpdir(); std::string make_temp_file(std::string template_str = get_base_tmpdir() + "/yosys_XXXXXX"); std::string make_temp_dir(std::string template_str = get_base_tmpdir() + "/yosys_XXXXXX"); bool check_file_exists(const std::string& filename, bool is_exec = false); bool check_directory_exists(const std::string& dirname, bool is_exec = false); bool is_absolute_path(std::string filename); void remove_directory(std::string dirname); bool create_directory(const std::string& dirname); std::string escape_filename_spaces(const std::string& filename); YOSYS_NAMESPACE_END #endif // YOSYS_IO_H