You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1326 lines
32 KiB
1326 lines
32 KiB
/**
|
|
* @file option.cpp
|
|
* Parses the options from the config file.
|
|
*
|
|
* @author Ben Gardner
|
|
* @author Guy Maurel October 2015, 2021
|
|
* @author Matthew Woehlke since version 0.67
|
|
* @license GPL v2+
|
|
*/
|
|
|
|
#include "option.h"
|
|
|
|
#include "keywords.h"
|
|
#include "language_names.h"
|
|
#include "uncrustify.h"
|
|
#include "uncrustify_version.h"
|
|
|
|
#include <fstream>
|
|
#include <unordered_map>
|
|
|
|
#include <cctype> // to get std::tolower
|
|
#include <cstdarg> // to get va_start, va_end
|
|
|
|
|
|
namespace uncrustify
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
static const char *DOC_TEXT_END = u8R"___(
|
|
# Meaning of the settings:
|
|
# Ignore - do not do any changes
|
|
# Add - makes sure there is 1 or more space/brace/newline/etc
|
|
# Force - makes sure there is exactly 1 space/brace/newline/etc,
|
|
# behaves like Add in some contexts
|
|
# Remove - removes space/brace/newline/etc
|
|
#
|
|
#
|
|
# - Token(s) can be treated as specific type(s) with the 'set' option:
|
|
# `set tokenType tokenString [tokenString...]`
|
|
#
|
|
# Example:
|
|
# `set BOOL __AND__ __OR__`
|
|
#
|
|
# tokenTypes are defined in src/token_enum.h, use them without the
|
|
# 'CT_' prefix: 'CT_BOOL' => 'BOOL'
|
|
#
|
|
#
|
|
# - Token(s) can be treated as type(s) with the 'type' option.
|
|
# `type tokenString [tokenString...]`
|
|
#
|
|
# Example:
|
|
# `type int c_uint_8 Rectangle`
|
|
#
|
|
# This can also be achieved with `set TYPE int c_uint_8 Rectangle`
|
|
#
|
|
#
|
|
# To embed whitespace in tokenStrings use the '\' escape character, or quote
|
|
# the tokenStrings. These quotes are supported: "'`
|
|
#
|
|
#
|
|
# - Support for the auto detection of languages through the file ending can be
|
|
# added using the 'file_ext' command.
|
|
# `file_ext langType langString [langString..]`
|
|
#
|
|
# Example:
|
|
# `file_ext CPP .ch .cxx .cpp.in`
|
|
#
|
|
# langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use
|
|
# them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP'
|
|
#
|
|
#
|
|
# - Custom macro-based indentation can be set up using 'macro-open',
|
|
# 'macro-else' and 'macro-close'.
|
|
# `(macro-open | macro-else | macro-close) tokenString`
|
|
#
|
|
# Example:
|
|
# `macro-open BEGIN_TEMPLATE_MESSAGE_MAP`
|
|
# `macro-open BEGIN_MESSAGE_MAP`
|
|
# `macro-close END_MESSAGE_MAP`
|
|
#
|
|
#
|
|
)___";
|
|
|
|
|
|
std::vector<OptionGroup> option_groups;
|
|
std::unordered_map<std::string, GenericOption *> option_map;
|
|
|
|
#define LOG_CONFIG(...) \
|
|
log_config(); LOG_FMT(LNOTE, __VA_ARGS__);
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
constexpr int option_level(int major, int minor, int patch = 0)
|
|
{
|
|
return((major << 20) | (minor << 10) | (patch << 0));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void log_config()
|
|
{
|
|
// Print the name of the configuration file only once
|
|
static bool config_name_logged = false;
|
|
|
|
if (!config_name_logged)
|
|
{
|
|
LOG_FMT(LNOTE, "log_config: the configuration file is: %s\n",
|
|
cpd.filename.c_str());
|
|
config_name_logged = true;
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// This identity function exists so that all Option<T>::str can simply call
|
|
// to_string(m_val); this function will be used by Option<string>
|
|
std::string to_string(const std::string &in)
|
|
{
|
|
return(in);
|
|
}
|
|
|
|
using std::to_string;
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::string to_lower(const char *in, std::string::size_type size = 0)
|
|
{
|
|
std::string out;
|
|
|
|
if (size > 0)
|
|
{
|
|
out.reserve(size);
|
|
}
|
|
|
|
while (*in)
|
|
{
|
|
out += static_cast<char>(std::tolower(*in));
|
|
++in;
|
|
}
|
|
return(out);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::string to_lower(const std::string &in)
|
|
{
|
|
return(to_lower(in.data(), in.size()));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool is_arg_sep(int ch)
|
|
{
|
|
return( isspace(ch)
|
|
|| ch == ','
|
|
|| ch == '=');
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool is_varg_sep(int ch)
|
|
{
|
|
return(ch == '.');
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
std::vector<std::string> split_args(std::string in, const char *filename,
|
|
bool (*is_sep)(int))
|
|
{
|
|
std::vector<std::string> out;
|
|
std::string::size_type n = 0;
|
|
std::string::size_type k = in.size();
|
|
|
|
// Parse input string
|
|
while (n < k)
|
|
{
|
|
// Skip leading space
|
|
while ( n < k
|
|
&& is_sep(in[n]))
|
|
{
|
|
++n;
|
|
}
|
|
|
|
// Detect comments or trailing space
|
|
if ( n >= k
|
|
|| in[n] == '#')
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Detect and extract quoted string
|
|
if (const auto *quote = strchr("\'\"`", in[n]))
|
|
{
|
|
const auto start = ++n;
|
|
|
|
for ((void)n; in[n] != *quote; ++n)
|
|
{
|
|
if ( n < k
|
|
&& in[n] == '\\')
|
|
{
|
|
in.erase(n, 1);
|
|
--k;
|
|
}
|
|
|
|
if (n >= k)
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("found unterminated quoted-string");
|
|
return{};
|
|
}
|
|
}
|
|
|
|
out.push_back(in.substr(start, n - start));
|
|
|
|
if ( ++n < k
|
|
&& !is_sep(in[n]))
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("unexpected text following quoted-string");
|
|
return{};
|
|
}
|
|
continue;
|
|
}
|
|
// Extract anything else
|
|
const auto start = n;
|
|
|
|
for ((void)n;
|
|
( n < k
|
|
&& !is_sep(in[n]));
|
|
++n)
|
|
{
|
|
if (in[n] == '\\')
|
|
{
|
|
in.erase(n, 1);
|
|
--k;
|
|
}
|
|
|
|
if (n >= k)
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("found unterminated quoted-string");
|
|
return{};
|
|
}
|
|
}
|
|
|
|
out.push_back(in.substr(start, n - start));
|
|
}
|
|
return(out);
|
|
} // split_args
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool is_path_relative(const std::string &path)
|
|
{
|
|
assert(!path.empty());
|
|
|
|
#ifdef WIN32
|
|
// Check for partition labels as indication for an absolute path
|
|
// 'X:\path\to\file' style absolute disk path
|
|
if ( path.size() > 1
|
|
&& isalpha(path[0])
|
|
&& path[1] == ':')
|
|
{
|
|
return(false);
|
|
}
|
|
|
|
// Check for double backslashes as indication for a network path
|
|
// '\\server\path\to\file style' absolute UNC path
|
|
if ( path.size() > 1
|
|
&& path[0] == '\\'
|
|
&& path[1] == '\\')
|
|
{
|
|
return(false);
|
|
}
|
|
#endif
|
|
|
|
// Check for a slash as indication for a filename with leading path
|
|
// '/path/to/file' style absolute path
|
|
return(path[0] != '/');
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void print_description(FILE *pfile, std::string description,
|
|
const char *eol_marker)
|
|
{
|
|
// Descriptions always start with a '\n', so skip the first character
|
|
for (std::string::size_type start = 1, length = description.length();
|
|
( start != std::string::npos
|
|
&& start < length);
|
|
++start)
|
|
{
|
|
// Check for empty line so we can squelch trailing whitespace
|
|
if (description[start] == '\n')
|
|
{
|
|
fprintf(pfile, "#%s", eol_marker);
|
|
}
|
|
else
|
|
{
|
|
const auto end = description.find('\n', start);
|
|
fprintf(pfile, "# %s%s",
|
|
description.substr(start, end - start).c_str(), eol_marker);
|
|
start = end;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool process_option_line_compat_0_68(const std::string &cmd,
|
|
const std::vector<std::string> &args,
|
|
const char *filename)
|
|
{
|
|
if (cmd == "sp_cpp_lambda_paren")
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; use '%s' instead",
|
|
cmd.c_str(), options::sp_cpp_lambda_square_paren.name());
|
|
|
|
UNUSED(options::sp_cpp_lambda_square_paren.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_68
|
|
|
|
|
|
bool process_option_line_compat_0_70(const std::string &cmd, const char *filename)
|
|
{
|
|
if (cmd == "sp_word_brace") // Issue #2428
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; did you want to use '%s' instead?",
|
|
cmd.c_str(), options::sp_type_brace_init_lst.name());
|
|
|
|
//UNUSED(options::sp_type_brace_init_lst.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_70
|
|
|
|
|
|
bool process_option_line_compat_0_73(const std::string &cmd, const char *filename)
|
|
{
|
|
if (cmd == "indent_sing_line_comments") // Issue #3249
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; did you want to use '%s' instead?",
|
|
cmd.c_str(), options::indent_single_line_comments_before.name());
|
|
|
|
//UNUSED(options::indent_single_line_comments_before.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
|
|
if (cmd == "sp_before_tr_emb_cmt") // Issue #3339
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; did you want to use '%s' instead?",
|
|
cmd.c_str(), options::sp_before_tr_cmt.name());
|
|
|
|
//UNUSED(options::sp_before_tr_cmt.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
|
|
if (cmd == "sp_num_before_tr_emb_cmt") // Issue #3339
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; did you want to use '%s' instead?",
|
|
cmd.c_str(), options::sp_num_before_tr_cmt.name());
|
|
|
|
//UNUSED(options::sp_num_before_tr_cmt.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_73
|
|
|
|
|
|
bool process_option_line_compat_0_74(const std::string &cmd, const char *filename)
|
|
{
|
|
if (cmd == "sp_type_question") // PR #3638
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; did you want to use '%s' instead?",
|
|
cmd.c_str(), options::sp_before_ptr_star.name());
|
|
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_74
|
|
|
|
|
|
bool process_option_line_compat_0_75(const std::string &cmd, const char *filename)
|
|
{
|
|
if (cmd == "pp_space")
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; it has been replaced by '%s'.",
|
|
cmd.c_str(), options::pp_space_after.name());
|
|
|
|
return(true);
|
|
}
|
|
|
|
if (cmd == "pp_space_before")
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; it was a temporary option used\n"
|
|
"during the development of version 0.76. Use '%s' and '%s' instead.",
|
|
cmd.c_str(), options::pp_indent.name(), options::pp_indent_count.name());
|
|
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_75
|
|
|
|
|
|
bool process_option_line_compat_0_76(const std::string &cmd, const std::vector<std::string> &args, const char *filename)
|
|
{
|
|
if (cmd == "nl_func_var_def_blk")
|
|
{
|
|
OptionWarning w{ filename, OptionWarning::MINOR };
|
|
w("option '%s' is deprecated; it has been replaced by '%s'.\n"
|
|
"You can also use '%s' for additional functionality",
|
|
cmd.c_str(), options::nl_var_def_blk_end_func_top.name(),
|
|
options::nl_var_def_blk_end.name());
|
|
|
|
UNUSED(options::nl_var_def_blk_end_func_top.read(args[1].c_str()));
|
|
return(true);
|
|
}
|
|
return(false);
|
|
} // process_option_line_compat_0_76
|
|
|
|
} // namespace
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<T> and helpers
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
OptionWarning::OptionWarning(const char *filename, Severity severity)
|
|
{
|
|
UNUSED(severity);
|
|
|
|
if (cpd.line_number != 0)
|
|
{
|
|
fprintf(stderr, "%s:%u: ", filename, cpd.line_number);
|
|
}
|
|
else
|
|
{
|
|
fprintf(stderr, "%s: ", filename);
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
OptionWarning::OptionWarning(const GenericOption *opt, Severity severity)
|
|
{
|
|
UNUSED(severity);
|
|
|
|
fprintf(stderr, "Option<%s>: at %s:%d: ", to_string(opt->type()),
|
|
cpd.filename.c_str(), cpd.line_number);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
OptionWarning::~OptionWarning()
|
|
{
|
|
fprintf(stderr, "\n");
|
|
log_flush(true);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void OptionWarning::operator()(const char *fmt, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, fmt);
|
|
vfprintf(stderr, fmt, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GenericOption::warnUnexpectedValue(const char *actual) const
|
|
{
|
|
OptionWarning w{ this };
|
|
|
|
auto values = possibleValues();
|
|
|
|
if (values[1] == nullptr)
|
|
{
|
|
w("Expected %s ", *values);
|
|
}
|
|
else
|
|
{
|
|
w("Expected one of ");
|
|
|
|
while (*values)
|
|
{
|
|
w("'%s'", *values);
|
|
|
|
if (*(++values))
|
|
{
|
|
w(", ");
|
|
}
|
|
}
|
|
}
|
|
w(", for '%s'; got '%s'", name(), actual);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void GenericOption::warnIncompatibleReference(const GenericOption *ref) const
|
|
{
|
|
OptionWarning w{ this };
|
|
|
|
w("%s references option %s with incompatible type %s",
|
|
name(), ref->name(), to_string(ref->type()));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<typename T>
|
|
bool read_enum(const char *in, Option<T> &out)
|
|
{
|
|
assert(in);
|
|
|
|
if (convert_string(in, out.m_val))
|
|
{
|
|
return(true);
|
|
}
|
|
|
|
if (const auto *const opt = find_option(in))
|
|
{
|
|
if (opt->type() != out.type())
|
|
{
|
|
out.warnIncompatibleReference(opt);
|
|
return(false);
|
|
}
|
|
auto &topt = *static_cast<const Option<T> *>(opt);
|
|
out.m_val = topt();
|
|
return(true);
|
|
}
|
|
out.warnUnexpectedValue(in);
|
|
return(false);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<typename T>
|
|
bool read_number(const char *in, Option<T> &out)
|
|
{
|
|
assert(in);
|
|
|
|
char *c;
|
|
const auto val = std::strtol(in, &c, 10);
|
|
|
|
if ( *c == 0
|
|
&& out.validate(val))
|
|
{
|
|
out.m_val = static_cast<T>(val);
|
|
return(true);
|
|
}
|
|
bool invert = false;
|
|
|
|
if (strchr("-", in[0]))
|
|
{
|
|
invert = true;
|
|
++in;
|
|
}
|
|
|
|
if (const auto *const opt = find_option(in))
|
|
{
|
|
LOG_CONFIG("%s(%d): line_number is %d, option(%s) %s, ref(%s) %s\n",
|
|
__func__, __LINE__, cpd.line_number,
|
|
to_string(out.type()), out.name(),
|
|
to_string(opt->type()), opt->name());
|
|
|
|
long tval;
|
|
|
|
if (opt->type() == OT_NUM)
|
|
{
|
|
auto &sopt = *static_cast<const Option<signed> *>(opt);
|
|
tval = static_cast<long>(sopt());
|
|
}
|
|
else if (opt->type() == OT_UNUM)
|
|
{
|
|
auto &uopt = *static_cast<const Option<unsigned> *>(opt);
|
|
tval = static_cast<long>(uopt());
|
|
}
|
|
else
|
|
{
|
|
out.warnIncompatibleReference(opt);
|
|
return(false);
|
|
}
|
|
const auto rval = (invert ? -tval : tval);
|
|
|
|
if (out.validate(rval))
|
|
{
|
|
out.m_val = static_cast<T>(rval);
|
|
return(true);
|
|
}
|
|
return(false);
|
|
}
|
|
out.warnUnexpectedValue(in);
|
|
return(false);
|
|
} // read_number
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<typename T>
|
|
void Option<T>::reset()
|
|
{
|
|
m_val = m_default;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<typename T>
|
|
std::string Option<T>::str() const
|
|
{
|
|
return(to_string(m_val));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<typename T>
|
|
std::string Option<T>::defaultStr() const
|
|
{
|
|
return(m_default != T{} ? to_string(m_default) : std::string{});
|
|
}
|
|
|
|
// Explicit instantiations
|
|
template class Option<bool>;
|
|
template class Option<iarf_e>;
|
|
template class Option<line_end_e>;
|
|
template class Option<token_pos_e>;
|
|
template class Option<signed>;
|
|
template class Option<unsigned>;
|
|
template class Option<std::string>;
|
|
|
|
//END Option<T> and helpers
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<bool>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<bool>::type() const
|
|
{
|
|
return(OT_BOOL);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<bool>::possibleValues() const
|
|
{
|
|
static char const *values[] = { "true", "false", nullptr };
|
|
|
|
return(values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<bool>::read(const char *in)
|
|
{
|
|
assert(in);
|
|
|
|
if (convert_string(in, m_val))
|
|
{
|
|
return(true);
|
|
}
|
|
bool invert = false;
|
|
|
|
if (strchr("~!-", in[0]))
|
|
{
|
|
invert = true;
|
|
++in;
|
|
}
|
|
|
|
if (const auto *const opt = find_option(in))
|
|
{
|
|
if (opt->type() != OT_BOOL)
|
|
{
|
|
warnIncompatibleReference(opt);
|
|
return(false);
|
|
}
|
|
auto &bopt = *static_cast<const Option<bool> *>(opt);
|
|
m_val = (invert ? !bopt() : bopt());
|
|
return(true);
|
|
}
|
|
warnUnexpectedValue(in);
|
|
return(false);
|
|
}
|
|
|
|
//END Option<bool>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<iarf_e>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<iarf_e>::type() const
|
|
{
|
|
return(OT_IARF);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<iarf_e>::possibleValues() const
|
|
{
|
|
return(iarf_values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<iarf_e>::read(const char *in)
|
|
{
|
|
return(read_enum(in, *this));
|
|
}
|
|
|
|
//END Option<iarf_e>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<line_end_e>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<line_end_e>::type() const
|
|
{
|
|
return(OT_LINEEND);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<line_end_e>::possibleValues() const
|
|
{
|
|
return(line_end_values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<line_end_e>::read(const char *in)
|
|
{
|
|
return(read_enum(in, *this));
|
|
}
|
|
|
|
//END Option<line_end_e>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<token_pos_e>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<token_pos_e>::type() const
|
|
{
|
|
return(OT_TOKENPOS);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<token_pos_e>::possibleValues() const
|
|
{
|
|
return(token_pos_values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<token_pos_e>::read(const char *in)
|
|
{
|
|
return(read_enum(in, *this));
|
|
}
|
|
|
|
//END Option<token_pos_e>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<signed>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<signed>::type() const
|
|
{
|
|
return(OT_NUM);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<signed>::possibleValues() const
|
|
{
|
|
static char const *values[] = { "number", nullptr };
|
|
|
|
return(values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<signed>::read(const char *in)
|
|
{
|
|
return(read_number(in, *this));
|
|
}
|
|
|
|
//END Option<signed>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<unsigned>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<unsigned>::type() const
|
|
{
|
|
return(OT_UNUM);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<unsigned>::possibleValues() const
|
|
{
|
|
static char const *values[] = { "unsigned number", nullptr };
|
|
|
|
return(values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<unsigned>::read(const char *in)
|
|
{
|
|
return(read_number(in, *this));
|
|
}
|
|
|
|
//END Option<unsigned>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN Option<string>
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
option_type_e Option<std::string>::type() const
|
|
{
|
|
return(OT_STRING);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
const char *const *Option<std::string>::possibleValues() const
|
|
{
|
|
static char const *values[] = { "string", nullptr };
|
|
|
|
return(values);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
template<>
|
|
bool Option<std::string>::read(const char *in)
|
|
{
|
|
m_val = in;
|
|
return(true);
|
|
}
|
|
|
|
//END Option<string>
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
//BEGIN global functions for options
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void begin_option_group(const char *description)
|
|
{
|
|
auto g = OptionGroup{ description, {} };
|
|
|
|
option_groups.push_back(g);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void register_option(GenericOption *option)
|
|
{
|
|
assert(!option_groups.empty());
|
|
|
|
option_groups.back().options.push_back(option);
|
|
option_map.emplace(option->name(), option);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
uncrustify::GenericOption *find_option(const char *name)
|
|
{
|
|
const auto iter = option_map.find(to_lower(name));
|
|
|
|
if (iter != option_map.end())
|
|
{
|
|
return(iter->second);
|
|
}
|
|
return(nullptr);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
OptionGroup *get_option_group(size_t i)
|
|
{
|
|
if (i >= option_groups.size())
|
|
{
|
|
return(nullptr);
|
|
}
|
|
return(&option_groups[i]);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
size_t get_option_count()
|
|
{
|
|
return(option_map.size());
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void process_option_line(const std::string &config_line, const char *filename,
|
|
int &compat_level)
|
|
{
|
|
// Split line into arguments, and punt if no arguments are present
|
|
auto args = split_args(config_line, filename, is_arg_sep);
|
|
|
|
if (args.empty())
|
|
{
|
|
return;
|
|
}
|
|
// Check for necessary arguments
|
|
const auto &cmd = to_lower(args.front());
|
|
|
|
if ( cmd == "set"
|
|
|| cmd == "file_ext")
|
|
{
|
|
if (args.size() < 3)
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("%s requires at least three arguments", cmd.c_str());
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (args.size() < 2)
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("%s requires at least two arguments", cmd.c_str());
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (cmd == "type")
|
|
{
|
|
for (size_t i = 1; i < args.size(); ++i)
|
|
{
|
|
add_keyword(args[i], CT_TYPE);
|
|
}
|
|
}
|
|
else if (cmd == "macro-open")
|
|
{
|
|
add_keyword(args[1], CT_MACRO_OPEN);
|
|
}
|
|
else if (cmd == "macro-close")
|
|
{
|
|
add_keyword(args[1], CT_MACRO_CLOSE);
|
|
}
|
|
else if (cmd == "macro-else")
|
|
{
|
|
add_keyword(args[1], CT_MACRO_ELSE);
|
|
}
|
|
else if (cmd == "set")
|
|
{
|
|
const auto token = find_token_name(args[1].c_str());
|
|
|
|
if (token != CT_NONE)
|
|
{
|
|
LOG_FMT(LNOTE, "%s:%d set '%s':",
|
|
filename, cpd.line_number, args[1].c_str());
|
|
|
|
for (size_t i = 2; i < args.size(); ++i)
|
|
{
|
|
LOG_FMT(LNOTE, " '%s'", args[i].c_str());
|
|
add_keyword(args[i], token);
|
|
}
|
|
|
|
LOG_FMT(LNOTE, "\n");
|
|
}
|
|
else
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("%s: unknown type '%s'", cmd.c_str(), args[1].c_str());
|
|
}
|
|
}
|
|
#ifndef EMSCRIPTEN
|
|
else if (cmd == "include")
|
|
{
|
|
auto this_line_number = cpd.line_number;
|
|
const auto &include_path = args[1];
|
|
|
|
if (include_path.empty())
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("include: path cannot be empty");
|
|
}
|
|
else if (is_path_relative(include_path))
|
|
{
|
|
// include is a relative path to the current config file
|
|
UncText ut = std::string{ filename };
|
|
ut.resize(static_cast<unsigned>(path_dirname_len(filename)));
|
|
ut.append(include_path);
|
|
UNUSED(load_option_file(ut.c_str(), compat_level));
|
|
}
|
|
else
|
|
{
|
|
// include is an absolute path
|
|
UNUSED(load_option_file(include_path.c_str(), compat_level));
|
|
}
|
|
cpd.line_number = this_line_number;
|
|
}
|
|
#endif
|
|
else if (cmd == "file_ext")
|
|
{
|
|
auto *const lang_arg = args[1].c_str();
|
|
|
|
for (size_t i = 2; i < args.size(); ++i)
|
|
{
|
|
auto *const lang_name = extension_add(args[i].c_str(), lang_arg);
|
|
|
|
if (lang_name)
|
|
{
|
|
LOG_FMT(LNOTE, "%s:%d file_ext '%s' => '%s'\n",
|
|
filename, cpd.line_number, args[i].c_str(), lang_name);
|
|
}
|
|
else
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("file_ext: unknown language '%s'", lang_arg);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if (cmd == "using")
|
|
{
|
|
auto vargs = split_args(args[1], filename, is_varg_sep);
|
|
|
|
if (vargs.size() == 2)
|
|
{
|
|
compat_level = option_level(std::stoi(vargs[0]), std::stoi(vargs[1]));
|
|
}
|
|
else if (vargs.size() == 3)
|
|
{
|
|
compat_level = option_level(std::stoi(vargs[0]),
|
|
std::stoi(vargs[1]),
|
|
std::stoi(vargs[2]));
|
|
}
|
|
else
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("%s requires a version number in the form MAJOR.MINOR[.PATCH]",
|
|
cmd.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Must be a regular option = value
|
|
if (compat_level < option_level(0, 69))
|
|
{
|
|
if (process_option_line_compat_0_68(cmd, args, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (compat_level < option_level(0, 71))
|
|
{
|
|
if (process_option_line_compat_0_70(cmd, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (compat_level < option_level(0, 74))
|
|
{
|
|
if (process_option_line_compat_0_73(cmd, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (compat_level < option_level(0, 75))
|
|
{
|
|
if (process_option_line_compat_0_74(cmd, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (compat_level < option_level(0, 76))
|
|
{
|
|
if (process_option_line_compat_0_75(cmd, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (compat_level < option_level(0, 77))
|
|
{
|
|
if (process_option_line_compat_0_76(cmd, args, filename))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
const auto oi = option_map.find(cmd);
|
|
|
|
if (oi == option_map.end())
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("unknown option '%s'", args[0].c_str());
|
|
}
|
|
else
|
|
{
|
|
UNUSED(oi->second->read(args[1].c_str()));
|
|
}
|
|
}
|
|
} // process_option_line
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool load_option_file(const char *filename, int compat_level)
|
|
{
|
|
cpd.line_number = 0;
|
|
|
|
#ifdef WIN32
|
|
// "/dev/null" not understood by "fopen" in Windows
|
|
if (strcasecmp(filename, "/dev/null") == 0)
|
|
{
|
|
return(true);
|
|
}
|
|
#endif
|
|
|
|
std::ifstream in;
|
|
in.open(filename, std::ifstream::in);
|
|
|
|
if (!in.good())
|
|
{
|
|
OptionWarning w{ filename };
|
|
w("file could not be opened: %s (%d)\n",
|
|
strerror(errno), errno);
|
|
exit(EX_SOFTWARE);
|
|
}
|
|
// Read in the file line by line
|
|
std::string line;
|
|
|
|
while (std::getline(in, line))
|
|
{
|
|
// check all characters of the line
|
|
size_t howmany = line.length();
|
|
int ch;
|
|
|
|
for (size_t n = 0; n < howmany; n++)
|
|
{
|
|
ch = line[n];
|
|
|
|
// do not check characters in comment part of line
|
|
if ('#' == ch)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// ch >= 0 && ch <= 255
|
|
if ( ch < 0
|
|
|| ch > 255)
|
|
{
|
|
// error
|
|
// related to PR #3298
|
|
fprintf(stderr, "%s: line %u: Character at position %zu, is not printable.\n", filename, cpd.line_number + 1, n + 1);
|
|
exit(EX_SOFTWARE);
|
|
}
|
|
}
|
|
|
|
++cpd.line_number;
|
|
process_option_line(line, filename, compat_level);
|
|
}
|
|
return(true);
|
|
} // load_option_file
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
const char *get_eol_marker()
|
|
{
|
|
static char eol[3] = { 0x0A, 0x00, 0x00 };
|
|
|
|
const auto &lines = cpd.newline.get();
|
|
|
|
for (size_t i = 0; i < lines.size(); ++i)
|
|
{
|
|
eol[i] = static_cast<char>(lines[i]);
|
|
}
|
|
|
|
return(eol);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void save_option_file(FILE *pfile, bool with_doc, bool minimal)
|
|
{
|
|
int non_default_values = 0;
|
|
const char *eol_marker = get_eol_marker();
|
|
|
|
fprintf(pfile, "# %s%s", UNCRUSTIFY_VERSION, eol_marker);
|
|
|
|
// Print the options by group
|
|
for (auto &og : option_groups)
|
|
{
|
|
bool first = true;
|
|
|
|
for (auto *option : og.options)
|
|
{
|
|
const auto val = option->str();
|
|
|
|
if (!option->isDefault())
|
|
{
|
|
++non_default_values;
|
|
}
|
|
else if (minimal)
|
|
{
|
|
continue;
|
|
}
|
|
//....................................................................
|
|
|
|
if (with_doc)
|
|
{
|
|
assert(option->description() != nullptr);
|
|
assert(*option->description() != 0);
|
|
|
|
if (first)
|
|
{
|
|
fprintf(pfile, "%s#%s", eol_marker, eol_marker);
|
|
print_description(pfile, og.description, eol_marker);
|
|
fprintf(pfile, "#%s", eol_marker);
|
|
}
|
|
fprintf(pfile, "%s", eol_marker);
|
|
print_description(pfile, option->description(), eol_marker);
|
|
|
|
const auto ds = option->defaultStr();
|
|
|
|
if (!ds.empty())
|
|
{
|
|
fprintf(pfile, "#%s# Default: %s%s",
|
|
eol_marker, ds.c_str(), eol_marker);
|
|
}
|
|
}
|
|
first = false;
|
|
|
|
const int name_len = static_cast<int>(strlen(option->name()));
|
|
const int pad = name_len < uncrustify::limits::MAX_OPTION_NAME_LEN
|
|
? (uncrustify::limits::MAX_OPTION_NAME_LEN - name_len)
|
|
: 1;
|
|
|
|
fprintf(pfile, "%s%*.s= ", option->name(), pad, " ");
|
|
|
|
if (option->type() == OT_STRING)
|
|
{
|
|
fprintf(pfile, "\"%s\"", val.c_str());
|
|
}
|
|
else
|
|
{
|
|
fprintf(pfile, "%s", val.c_str());
|
|
}
|
|
|
|
if (with_doc)
|
|
{
|
|
const int val_len = static_cast<int>(val.length());
|
|
fprintf(pfile, "%*.s # ", 8 - val_len, " ");
|
|
|
|
for (auto pv = option->possibleValues(); *pv; ++pv)
|
|
{
|
|
fprintf(pfile, "%s%s", *pv, pv[1] ? "/" : "");
|
|
}
|
|
}
|
|
fputs(eol_marker, pfile);
|
|
}
|
|
}
|
|
|
|
if (with_doc)
|
|
{
|
|
fprintf(pfile, "%s", DOC_TEXT_END);
|
|
}
|
|
print_custom_keywords(pfile); // Print custom keywords
|
|
print_extensions(pfile); // Print custom file extensions
|
|
|
|
fprintf(pfile, "# option(s) with 'not default' value: %d%s#%s",
|
|
non_default_values, eol_marker, eol_marker);
|
|
} // save_option_file
|
|
|
|
} // namespace uncrustify
|