First usable generator source.
Still issues with usage rules and error reporting, but functional.
This commit is contained in:
100
generator/src/argument.cpp
Normal file
100
generator/src/argument.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "argument.hpp"
|
||||
#include <numeric>
|
||||
|
||||
Argument::Argument(std::string argument, std::optional<char> shortArgument,
|
||||
std::string usage)
|
||||
: argument{argument}, usage{usage}, shortArgument{shortArgument} {}
|
||||
|
||||
Argument::~Argument() {}
|
||||
|
||||
mstch::map Argument::render() const {
|
||||
mstch::map result{
|
||||
{std::string{"argument"}, argument},
|
||||
{"usage", usage},
|
||||
{"short_argument", shortArgument
|
||||
? mstch::node{std::string{*shortArgument}}
|
||||
: mstch::node{false}},
|
||||
{"clean_token", cleanToken()},
|
||||
{"has_parameters", hasParameters()},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Argument::cleanToken(const std::string &token) {
|
||||
std::string result(token.size(), static_cast<char>(NULL));
|
||||
std::transform(token.begin(), token.end(), result.begin(),
|
||||
[](char character) {
|
||||
if (isalpha(character) || isdigit(character))
|
||||
return character;
|
||||
else
|
||||
return '_';
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Argument::cleanToken() const { return cleanToken(argument); }
|
||||
|
||||
#include <iostream>
|
||||
|
||||
bool Argument::operator==(const Argument &other) const {
|
||||
return this->argument == other.argument;
|
||||
}
|
||||
|
||||
bool Argument::operator<(const Argument &other) const {
|
||||
return this->argument < other.argument;
|
||||
}
|
||||
|
||||
bool Argument::operator>(const Argument &other) const {
|
||||
return this->argument > other.argument;
|
||||
}
|
||||
|
||||
size_t Argument::argStrLength() const { return argument.size(); }
|
||||
|
||||
FlagArgument::FlagArgument(std::string argument,
|
||||
std::optional<char> shortArgument, std::string usage)
|
||||
: Argument{argument, shortArgument, usage} {}
|
||||
|
||||
mstch::map FlagArgument::render() const {
|
||||
auto result = Argument::render();
|
||||
result.insert({"parameters", mstch::array{}});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool FlagArgument::hasParameters() const { return false; }
|
||||
|
||||
size_t FlagArgument::paramStrLength() const { return 0; }
|
||||
|
||||
ParameterArgument::ParameterArgument(std::string argument,
|
||||
std::optional<char> shortArgument,
|
||||
std::vector<std::string> parameters,
|
||||
std::string usage)
|
||||
: Argument{argument, shortArgument, usage}, parameters{parameters} {}
|
||||
|
||||
mstch::map ParameterArgument::render() const {
|
||||
auto result = Argument::render();
|
||||
mstch::array resultParameters{};
|
||||
|
||||
for (int i = 0; i < parameters.size(); i++) {
|
||||
mstch::map resultParameter{{"index", i + 1}, {"name", parameters[i]}};
|
||||
|
||||
if (i + 1 < parameters.size())
|
||||
resultParameter.insert({"next", i + 2});
|
||||
|
||||
resultParameters.push_back(resultParameter);
|
||||
}
|
||||
|
||||
result.insert({"parameters", resultParameters});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ParameterArgument::hasParameters() const { return true; }
|
||||
|
||||
size_t ParameterArgument::paramStrLength() const {
|
||||
size_t totalSize = std::accumulate(
|
||||
parameters.begin(), parameters.end(), 0,
|
||||
[](size_t count, const std::string &str) { return count + str.size(); });
|
||||
|
||||
return totalSize + (parameters.size() - 1);
|
||||
}
|
||||
52
generator/src/argument.hpp
Normal file
52
generator/src/argument.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <mstch/mstch.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
class Argument {
|
||||
public:
|
||||
Argument(std::string argument, std::optional<char> shortArgument,
|
||||
std::string usage);
|
||||
virtual ~Argument() = 0;
|
||||
|
||||
virtual mstch::map render() const = 0;
|
||||
static std::string cleanToken(const std::string &longToken);
|
||||
std::string cleanToken() const;
|
||||
virtual bool hasParameters() const = 0;
|
||||
virtual size_t argStrLength() const;
|
||||
virtual size_t paramStrLength() const = 0;
|
||||
|
||||
bool operator==(const Argument &other) const;
|
||||
bool operator<(const Argument &other) const;
|
||||
bool operator>(const Argument &other) const;
|
||||
|
||||
public:
|
||||
std::string argument, usage;
|
||||
std::optional<char> shortArgument;
|
||||
};
|
||||
|
||||
class FlagArgument : public Argument {
|
||||
public:
|
||||
FlagArgument(std::string argument, std::optional<char> shortArgument,
|
||||
std::string usage);
|
||||
~FlagArgument() = default;
|
||||
|
||||
mstch::map render() const override;
|
||||
bool hasParameters() const override;
|
||||
size_t paramStrLength() const override;
|
||||
|
||||
bool operator<(const FlagArgument &other) const;
|
||||
};
|
||||
|
||||
class ParameterArgument : public Argument {
|
||||
public:
|
||||
ParameterArgument(std::string argument, std::optional<char> shortArgument,
|
||||
std::vector<std::string> parameters, std::string usage);
|
||||
|
||||
mstch::map render() const override;
|
||||
bool hasParameters() const override;
|
||||
size_t paramStrLength() const override;
|
||||
|
||||
public:
|
||||
std::vector<std::string> parameters;
|
||||
};
|
||||
221
generator/src/driver.cpp
Normal file
221
generator/src/driver.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <numeric>
|
||||
|
||||
#include "driver.hpp"
|
||||
|
||||
Grammar::Driver::Driver()
|
||||
: arguments{ArgumentComparator{}}, maxArgLength{0}, maxParamLength{0} {
|
||||
addArg(std::make_unique<FlagArgument>("help", std::nullopt,
|
||||
"shows this help message"));
|
||||
addArg(std::make_unique<FlagArgument>("version", std::nullopt,
|
||||
"shows the version of this software"));
|
||||
addArg(std::make_unique<FlagArgument>("license", std::nullopt,
|
||||
"shows the license of this software"));
|
||||
}
|
||||
|
||||
Grammar::Driver::~Driver() {
|
||||
delete (scanner);
|
||||
delete (parser);
|
||||
}
|
||||
|
||||
void Grammar::Driver::parse(const char *const filename) {
|
||||
assert(filename != nullptr);
|
||||
std::ifstream iss{filename};
|
||||
|
||||
if (!iss)
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
parse_helper(iss);
|
||||
}
|
||||
|
||||
void Grammar::Driver::parse(std::istream &iss) {
|
||||
if (!iss.good() && iss.eof())
|
||||
return;
|
||||
|
||||
parse_helper(iss);
|
||||
}
|
||||
|
||||
void Grammar::Driver::parse_helper(std::istream &iss) {
|
||||
delete scanner;
|
||||
try {
|
||||
scanner = new Grammar::Scanner(&iss);
|
||||
} catch (const std::bad_alloc &ba) {
|
||||
std::cerr << "Failed to allocate scanner: \"" << ba.what()
|
||||
<< "\". Exiting!\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
delete parser;
|
||||
try {
|
||||
parser = new Grammar::Parser(*scanner, *this);
|
||||
} catch (const std::bad_alloc &ba) {
|
||||
std::cerr << "Failed to allocate parser: \"" << ba.what()
|
||||
<< "\". Exiting!\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (parser->parse() != 0) {
|
||||
std::cerr << "Parsing failure!\n";
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void Grammar::Driver::setProgramName(std::string programName) {
|
||||
this->programName = programName;
|
||||
}
|
||||
|
||||
void Grammar::Driver::setVersion(std::string version) {
|
||||
this->version = version;
|
||||
}
|
||||
|
||||
void Grammar::Driver::setLicense(std::string license) {
|
||||
this->license = license;
|
||||
}
|
||||
|
||||
void Grammar::Driver::setHelpAddendum(std::string addendum) {
|
||||
this->helpAddendum = addendum;
|
||||
}
|
||||
|
||||
void Grammar::Driver::addArg(std::unique_ptr<Argument> argument) {
|
||||
maxArgLength = std::max(maxArgLength, argument->argStrLength());
|
||||
maxParamLength = std::max(maxParamLength, argument->paramStrLength());
|
||||
const auto prevVal = arguments.find(argument);
|
||||
|
||||
if (prevVal != arguments.end()) {
|
||||
arguments.erase(prevVal);
|
||||
}
|
||||
|
||||
arguments.insert(std::move(argument));
|
||||
}
|
||||
|
||||
void Grammar::Driver::addUsage(Usage usage) { usages.push_back(usage); }
|
||||
|
||||
void Grammar::Driver::addRule(std::string ruleName,
|
||||
std::vector<std::string> options) {
|
||||
rules.push_back({ruleName, options});
|
||||
}
|
||||
|
||||
mstch::map Grammar::Driver::getContext() const {
|
||||
return mstch::map{{"argspec", getSafeName()},
|
||||
{"any_parameters", usesAnyParameters()},
|
||||
{"argument_tokens", generateArgumentTokens()},
|
||||
{"argument_explanations", generateArgumentExplanation()},
|
||||
{"usage", generateUsageList()},
|
||||
{"usage_rules", generateUsageRuleList()},
|
||||
{"version", version},
|
||||
{"license", license},
|
||||
{"help_addendum", getHelpAddendum()}};
|
||||
}
|
||||
|
||||
std::string Grammar::Driver::getSafeName() const {
|
||||
std::string safeName{programName};
|
||||
safeName.erase(std::remove_if(safeName.begin(), safeName.end(),
|
||||
[](char letter) { return !isalpha(letter); }),
|
||||
safeName.end());
|
||||
|
||||
return safeName;
|
||||
}
|
||||
|
||||
bool Grammar::Driver::usesAnyParameters() const {
|
||||
for (auto &argument : arguments) {
|
||||
if (argument->hasParameters())
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
mstch::array Grammar::Driver::generateArgumentTokens() const {
|
||||
mstch::array tokens{};
|
||||
|
||||
for (auto &argument : arguments) {
|
||||
tokens.push_back(alignArg(*argument));
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
mstch::array Grammar::Driver::generateArgumentExplanation() const {
|
||||
mstch::array argumentExplanations{};
|
||||
|
||||
for (const auto &rule : rules)
|
||||
argumentExplanations.push_back(explainRule(rule.first, rule.second));
|
||||
|
||||
return argumentExplanations;
|
||||
}
|
||||
|
||||
mstch::array Grammar::Driver::generateUsageList() const {
|
||||
mstch::array usageList{};
|
||||
|
||||
for (const auto &usage : usages) {
|
||||
const mstch::array flags{usage.flags.begin(), usage.flags.end()};
|
||||
const mstch::array positional{usage.positional.begin(),
|
||||
usage.positional.end()};
|
||||
|
||||
usageList.push_back(
|
||||
mstch::map{{"flags", flags}, {"positional", positional}});
|
||||
}
|
||||
|
||||
return usageList;
|
||||
}
|
||||
|
||||
mstch::array Grammar::Driver::generateUsageRuleList() const {
|
||||
mstch::array usageRuleList{};
|
||||
|
||||
for (const auto &rulePair : rules) {
|
||||
mstch::map usageRule{{"rule_name", Argument::cleanToken(rulePair.first)}};
|
||||
|
||||
mstch::array ruleOptions{};
|
||||
|
||||
for (size_t i = 0; i < rulePair.second.size(); i++) {
|
||||
mstch::map ruleOption{
|
||||
{"option", Argument::cleanToken(rulePair.second[i])}};
|
||||
|
||||
if (i + 1 < rulePair.second.size())
|
||||
ruleOption.insert({"has_next", true});
|
||||
|
||||
ruleOptions.push_back(ruleOption);
|
||||
}
|
||||
|
||||
usageRule.insert({"options", ruleOptions});
|
||||
usageRuleList.push_back(usageRule);
|
||||
}
|
||||
|
||||
return usageRuleList;
|
||||
}
|
||||
|
||||
mstch::node Grammar::Driver::getHelpAddendum() const {
|
||||
return helpAddendum ? mstch::node{*helpAddendum} : mstch::node{false};
|
||||
}
|
||||
|
||||
std::string
|
||||
Grammar::Driver::explainRule(const std::string &ruleName,
|
||||
const std::vector<std::string> &ruleOptions) {
|
||||
return ruleName + " can be " +
|
||||
std::accumulate(std::next(ruleOptions.begin()), ruleOptions.end(),
|
||||
"--" + ruleOptions[0],
|
||||
[](const std::string &left, const std::string &right) {
|
||||
return left + " or --" + right;
|
||||
});
|
||||
}
|
||||
|
||||
std::string Grammar::Driver::spaceN(size_t spaceCount) {
|
||||
std::string spaces(spaceCount, ' ');
|
||||
return spaces;
|
||||
}
|
||||
|
||||
mstch::map Grammar::Driver::alignArg(Argument &arg) const {
|
||||
auto result = arg.render();
|
||||
result.insert(
|
||||
{"parameter_align_spacing", spaceN(maxArgLength - arg.argStrLength())});
|
||||
result.insert(
|
||||
{"explain_align_spacing", spaceN(maxParamLength - arg.paramStrLength())});
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Grammar::Driver::ArgumentComparator::
|
||||
operator()(const std::unique_ptr<Argument> &left,
|
||||
const std::unique_ptr<Argument> &right) const {
|
||||
return *left < *right;
|
||||
}
|
||||
74
generator/src/driver.hpp
Normal file
74
generator/src/driver.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "argument.hpp"
|
||||
#include "parser.tab.hh"
|
||||
#include "scanner.hpp"
|
||||
#include "usage.hpp"
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace Grammar {
|
||||
class Driver {
|
||||
public:
|
||||
Driver();
|
||||
virtual ~Driver();
|
||||
|
||||
void parse(const char *const filename);
|
||||
void parse(std::istream &iss);
|
||||
|
||||
/*
|
||||
* functions to store data retrieved by grammar / parser
|
||||
*/
|
||||
|
||||
void setProgramName(std::string programName);
|
||||
void setVersion(std::string version);
|
||||
void setLicense(std::string license);
|
||||
void setHelpAddendum(std::string addendum);
|
||||
void addUsage(Usage usage);
|
||||
void addRule(std::string ruleName, std::vector<std::string> options);
|
||||
void addArg(std::unique_ptr<Argument> argument);
|
||||
|
||||
mstch::map getContext() const;
|
||||
std::string getSafeName() const;
|
||||
|
||||
private:
|
||||
void parse_helper(std::istream &iss);
|
||||
|
||||
Grammar::Parser *parser = nullptr;
|
||||
Grammar::Scanner *scanner = nullptr;
|
||||
|
||||
bool usesAnyParameters() const;
|
||||
mstch::array generateArgumentTokens() const;
|
||||
mstch::map generateHelpToken() const;
|
||||
mstch::map generateVersionToken() const;
|
||||
mstch::map generateLicenseToken() const;
|
||||
mstch::array generateArgumentExplanation() const;
|
||||
mstch::array generateUsageList() const;
|
||||
mstch::array generateUsageRuleList() const;
|
||||
mstch::node getHelpAddendum() const;
|
||||
|
||||
static std::string explainRule(const std::string &ruleName,
|
||||
const std::vector<std::string> &ruleOptions);
|
||||
static std::string spaceN(size_t spaceCount);
|
||||
mstch::map alignArg(Argument &arg) const;
|
||||
|
||||
struct ArgumentComparator {
|
||||
bool operator()(const std::unique_ptr<Argument> &left,
|
||||
const std::unique_ptr<Argument> &right) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* specialised data store
|
||||
*/
|
||||
|
||||
friend Argument;
|
||||
std::string programName, version, license;
|
||||
std::set<std::unique_ptr<Argument>, ArgumentComparator> arguments;
|
||||
std::optional<std::string> helpAddendum;
|
||||
std::vector<Usage> usages;
|
||||
std::vector<std::pair<std::string, std::vector<std::string>>> rules;
|
||||
size_t maxArgLength, maxParamLength;
|
||||
};
|
||||
} // namespace Grammar
|
||||
295
generator/src/lexer.ll
Normal file
295
generator/src/lexer.ll
Normal file
@@ -0,0 +1,295 @@
|
||||
/* Simply lexes arguments */
|
||||
%{
|
||||
#include <stdexcept>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <vector>
|
||||
#include "parser.tab.hh"
|
||||
#include "scanner.hpp"
|
||||
#include "argument.hpp"
|
||||
|
||||
#undef YY_DECL
|
||||
#define YY_DECL int Grammar::Scanner::yylex(Grammar::Parser::semantic_type* const lval, Grammar::Parser::location_type* lloc)
|
||||
#define YY_USER_ACTION lloc->step(); lloc->columns(yyleng);
|
||||
|
||||
using token = Grammar::Parser::token;
|
||||
|
||||
#define LOG std::clog
|
||||
%}
|
||||
|
||||
%option debug
|
||||
%option nodefault
|
||||
%option yyclass="Grammar::Scanner"
|
||||
%option noyywrap
|
||||
%option c++
|
||||
|
||||
%x PROGRAM_SECTION PROGRAM_NAME PROGRAM_VALUE PROGRAM_QUOTED_VALUE PROGRAM_QUOTED_VALUE_END USAGE_SECTION USAGE_DETAILS_BEGIN USAGE_DETAILS USAGE_RULE_BEGIN USAGE_RULE ARGUMENTS_SECTION ARGUMENTS_LONGOPT ARGUMENTS_SHORTOPT ARGUMENTS_PARAMETERS ARGUMENTS_DESCRIPTION_BEGIN ARGUMENTS_DESCRIPTION ARGUMENTS_DESCRIPTION_END
|
||||
|
||||
NAME [^[:space:]]+
|
||||
SL_SPACE [[:space:]]{-}[\n]
|
||||
LONGOPT [[:alpha:]][[:alnum:]\-]*
|
||||
SHORTOPT [^[:space:]]
|
||||
PARAM [[:alpha:]][[:alnum:]]*
|
||||
RULE_TOKEN [[:alpha:]][[:alpha:]_-]*
|
||||
|
||||
%%
|
||||
|
||||
%{ /** Code executed at the beginning of yylex **/
|
||||
yyval = lval;
|
||||
static std::string quotedVal{};
|
||||
static std::string programName{};
|
||||
static std::vector<std::string> arguments{};
|
||||
%}
|
||||
|
||||
<INITIAL>"Program:" {
|
||||
BEGIN(PROGRAM_SECTION);
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>"Usage:" {
|
||||
BEGIN(USAGE_SECTION);
|
||||
}
|
||||
|
||||
<USAGE_SECTION>"Arguments:" {
|
||||
BEGIN(ARGUMENTS_SECTION);
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>\n {
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>program {
|
||||
BEGIN(PROGRAM_NAME);
|
||||
return token::PROGRAM;
|
||||
}
|
||||
|
||||
<PROGRAM_NAME>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<PROGRAM_NAME>{NAME} {
|
||||
programName = yytext;
|
||||
yyval->build<std::string>(programName);
|
||||
return token::VALUE;
|
||||
}
|
||||
|
||||
<PROGRAM_NAME>\n {
|
||||
BEGIN(PROGRAM_SECTION);
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>version {
|
||||
BEGIN(PROGRAM_VALUE);
|
||||
return token::VERSION;
|
||||
}
|
||||
|
||||
<PROGRAM_VALUE>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<PROGRAM_VALUE>\n {
|
||||
BEGIN(PROGRAM_SECTION);
|
||||
throw std::runtime_error{ "Error: no value set on line " + std::to_string(lloc->begin.line) };
|
||||
}
|
||||
|
||||
<PROGRAM_VALUE>\" {
|
||||
BEGIN(PROGRAM_QUOTED_VALUE);
|
||||
quotedVal = "";
|
||||
}
|
||||
|
||||
<PROGRAM_QUOTED_VALUE>[^\"\\]+ {
|
||||
quotedVal += yytext;
|
||||
}
|
||||
|
||||
<PROGRAM_QUOTED_VALUE>\\. {
|
||||
quotedVal += yytext;
|
||||
}
|
||||
|
||||
<PROGRAM_QUOTED_VALUE>\" {
|
||||
BEGIN(PROGRAM_QUOTED_VALUE_END);
|
||||
yyval->build<std::string>(quotedVal);
|
||||
quotedVal = "";
|
||||
return token::VALUE;
|
||||
}
|
||||
|
||||
<PROGRAM_QUOTED_VALUE_END>[^[:space:]]+$ {
|
||||
/* received more text at end of line, after quoted value */
|
||||
throw std::runtime_error{ "Unknown text after value: \'" + std::string{ yytext } + "\'" };
|
||||
}
|
||||
|
||||
<PROGRAM_QUOTED_VALUE_END>\n {
|
||||
BEGIN(PROGRAM_SECTION);
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>license {
|
||||
BEGIN(PROGRAM_VALUE);
|
||||
return token::LICENSE;
|
||||
}
|
||||
|
||||
<PROGRAM_SECTION>help {
|
||||
BEGIN(PROGRAM_VALUE);
|
||||
return token::HELP;
|
||||
}
|
||||
|
||||
<USAGE_SECTION>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<USAGE_SECTION>\n {
|
||||
/* ignored */
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<USAGE_SECTION>{NAME} {
|
||||
if (yytext != programName) {
|
||||
BEGIN(USAGE_RULE_BEGIN);
|
||||
yyless(0);
|
||||
} else {
|
||||
BEGIN(USAGE_DETAILS_BEGIN);
|
||||
}
|
||||
}
|
||||
|
||||
<USAGE_DETAILS_BEGIN>{SL_SPACE}+ {
|
||||
/* ignore */
|
||||
BEGIN(USAGE_DETAILS);
|
||||
}
|
||||
|
||||
<USAGE_DETAILS_BEGIN>\n {
|
||||
BEGIN(USAGE_SECTION);
|
||||
yyval->build<std::string>();
|
||||
lloc->lines();
|
||||
|
||||
return token::USAGE;
|
||||
}
|
||||
|
||||
<USAGE_DETAILS>[^[:space:]].*$ {
|
||||
BEGIN(USAGE_SECTION);
|
||||
const std::string_view usage{ yytext };
|
||||
auto trimEndIter = std::find_if_not(usage.rend(),
|
||||
usage.rbegin(),
|
||||
[](char charac){ return std::isspace(charac); });
|
||||
const std::string trimUsage{ usage.begin(), trimEndIter.base() };
|
||||
|
||||
yyval->build<std::string>(trimUsage);
|
||||
|
||||
return token::USAGE;
|
||||
}
|
||||
|
||||
<USAGE_RULE_BEGIN>{RULE_TOKEN} {
|
||||
BEGIN(USAGE_RULE);
|
||||
yyval->build<std::string>(yytext);
|
||||
return token::RULE_NAME;
|
||||
}
|
||||
|
||||
<USAGE_RULE_BEGIN>{NAME} {
|
||||
throw std::runtime_error{ "Invalid rule name: " + std::string{ yytext } };
|
||||
}
|
||||
|
||||
<USAGE_RULE>{SL_SPACE} {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<USAGE_RULE>= {
|
||||
return token::RULE_EQUALS;
|
||||
}
|
||||
|
||||
<USAGE_RULE>--{LONGOPT} {
|
||||
yyval->build<std::string>(yytext + 2);
|
||||
return token::RULE_TOKEN;
|
||||
}
|
||||
|
||||
<USAGE_RULE>{RULE_TOKEN} {
|
||||
yyval->build<std::string>(yytext);
|
||||
return token::RULE_TOKEN;
|
||||
}
|
||||
|
||||
<USAGE_RULE>"|" {
|
||||
return token::RULE_OR;
|
||||
}
|
||||
|
||||
<USAGE_RULE>\n {
|
||||
BEGIN(USAGE_SECTION);
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
|
||||
<ARGUMENTS_SECTION>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<ARGUMENTS_SECTION>\n {
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<ARGUMENTS_SECTION>--{LONGOPT} {
|
||||
yyval->build<std::string>(yytext + 2);
|
||||
return token::LONGOPT;
|
||||
}
|
||||
|
||||
<ARGUMENTS_SECTION>, {
|
||||
BEGIN(ARGUMENTS_LONGOPT);
|
||||
}
|
||||
|
||||
<ARGUMENTS_LONGOPT>-{SHORTOPT} {
|
||||
yyval->build<char>(*(yytext + 1));
|
||||
arguments = {};
|
||||
return token::SHORTOPT;
|
||||
}
|
||||
|
||||
<ARGUMENTS_LONGOPT>, {
|
||||
BEGIN(ARGUMENTS_SHORTOPT);
|
||||
}
|
||||
|
||||
<ARGUMENTS_LONGOPT,ARGUMENTS_SHORTOPT,ARGUMENTS_PARAMETERS,ARGUMENTS_DESCRIPTION_BEGIN>{SL_SPACE}+ {
|
||||
/* ignored */
|
||||
}
|
||||
|
||||
<ARGUMENTS_SHORTOPT,ARGUMENTS_PARAMETERS>{PARAM} {
|
||||
BEGIN(ARGUMENTS_PARAMETERS);
|
||||
arguments.push_back({ yytext });
|
||||
}
|
||||
|
||||
<ARGUMENTS_SHORTOPT,ARGUMENTS_PARAMETERS>, {
|
||||
BEGIN(ARGUMENTS_DESCRIPTION_BEGIN);
|
||||
if (!arguments.empty()) {
|
||||
yyval->build<std::vector<std::string>>(arguments);
|
||||
arguments = {};
|
||||
return token::PARAMETERS;
|
||||
}
|
||||
}
|
||||
|
||||
<ARGUMENTS_DESCRIPTION_BEGIN>\" {
|
||||
BEGIN(ARGUMENTS_DESCRIPTION);
|
||||
quotedVal = "";
|
||||
}
|
||||
|
||||
<ARGUMENTS_DESCRIPTION>[^\\\"]+ {
|
||||
quotedVal += yytext;
|
||||
}
|
||||
|
||||
<ARGUMENTS_DESCRIPTION>\\. {
|
||||
quotedVal += yytext;
|
||||
}
|
||||
|
||||
<ARGUMENTS_DESCRIPTION>\" {
|
||||
BEGIN(ARGUMENTS_DESCRIPTION_END);
|
||||
yyval->build<std::string>(quotedVal);
|
||||
quotedVal = "";
|
||||
return token::DESCRIPTION;
|
||||
}
|
||||
|
||||
<ARGUMENTS_DESCRIPTION_END>\n {
|
||||
BEGIN(ARGUMENTS_SECTION);
|
||||
lloc->lines();
|
||||
}
|
||||
|
||||
<*>. {
|
||||
std::cerr << "Scanning error in state: " << YY_START << std::endl;
|
||||
throw std::runtime_error{ "Invalid input: \'" + std::string{ yytext } + "\' on line " + std::to_string(lloc->begin.line) };
|
||||
}
|
||||
|
||||
%%
|
||||
42
generator/src/main.cpp
Normal file
42
generator/src/main.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "driver.hpp"
|
||||
#include "templates.hpp"
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <mstch/mstch.hpp>
|
||||
|
||||
std::string symbolNameToOutputFile(const std::string &symbolName);
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
std::cerr << "Usage: " << argv[0] << " <filename-to-parse>\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
Grammar::Driver driver{};
|
||||
std::ifstream specFile{argv[1]};
|
||||
driver.parse(specFile);
|
||||
const auto context = driver.getContext();
|
||||
|
||||
for (const auto &templateFile : templateFiles) {
|
||||
std::string strTemplate{templateFile.contents};
|
||||
std::ofstream outputFile{driver.getSafeName() +
|
||||
symbolNameToOutputFile(templateFile.symbolName)};
|
||||
|
||||
outputFile << mstch::render(strTemplate, context);
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string symbolNameToOutputFile(const std::string &symbolName) {
|
||||
std::string outputFileName(symbolName.size(), static_cast<char>(NULL));
|
||||
std::transform(symbolName.begin(), symbolName.end(), outputFileName.begin(),
|
||||
[](char letter) {
|
||||
if (letter == '_')
|
||||
return '.';
|
||||
return letter;
|
||||
});
|
||||
|
||||
return outputFileName;
|
||||
}
|
||||
170
generator/src/parser.yy
Normal file
170
generator/src/parser.yy
Normal file
@@ -0,0 +1,170 @@
|
||||
/* Parses an argument specification */
|
||||
%skeleton "lalr1.cc"
|
||||
%require "3.2"
|
||||
%debug
|
||||
%defines
|
||||
%define api.namespace {Grammar}
|
||||
%define api.parser.class {Parser}
|
||||
|
||||
%code requires{
|
||||
# include <optional>
|
||||
|
||||
namespace Grammar {
|
||||
class Driver;
|
||||
class Scanner;
|
||||
}
|
||||
|
||||
# ifndef YY_NULLPTR
|
||||
# if defined __cplusplus && 201103L <= __cplusplus
|
||||
# define YY_NULLPTR nullptr
|
||||
# else
|
||||
# define YY_NULLPTR 0
|
||||
# endif
|
||||
# endif
|
||||
}
|
||||
|
||||
%parse-param { Scanner& scanner }
|
||||
%parse-param { Driver& driver }
|
||||
|
||||
%code{
|
||||
# include "driver.hpp"
|
||||
# include <string>
|
||||
# include <vector>
|
||||
# include <utility>
|
||||
# include <memory>
|
||||
|
||||
|
||||
# undef yylex
|
||||
# define yylex scanner.yylex
|
||||
}
|
||||
|
||||
%define api.value.type variant
|
||||
%define parse.assert
|
||||
%locations
|
||||
%define api.location.file none
|
||||
|
||||
%token PROGRAM
|
||||
%token VERSION
|
||||
%token LICENSE
|
||||
%token HELP
|
||||
%token <std::string> VALUE
|
||||
%token <std::string> USAGE
|
||||
%token <std::string> LONGOPT
|
||||
%token <char> SHORTOPT
|
||||
%token <std::vector<std::string>> PARAMETERS
|
||||
%token <std::string> DESCRIPTION
|
||||
%token <std::string> RULE_NAME
|
||||
%token RULE_EQUALS
|
||||
%token RULE_OR
|
||||
%token <std::string> RULE_TOKEN
|
||||
|
||||
%type <std::optional<char>> OPTIONAL_SHORTOPT
|
||||
%type <std::optional<std::vector<std::string>>> OPTIONAL_PARAMETERS
|
||||
%type <std::vector<std::string>> RULE_OPTIONS
|
||||
|
||||
%start ARGSPEC
|
||||
|
||||
%%
|
||||
|
||||
ARGSPEC
|
||||
: PROGRAM_DETAILS
|
||||
USAGE_DETAILS
|
||||
ARGUMENTS_DETAILS
|
||||
;
|
||||
|
||||
PROGRAM_DETAILS
|
||||
: PROGRAM VALUE
|
||||
VERSION VALUE
|
||||
LICENSE VALUE
|
||||
OPTIONAL_HELP {
|
||||
driver.setProgramName($2);
|
||||
driver.setVersion($4);
|
||||
driver.setLicense($6);
|
||||
}
|
||||
;
|
||||
|
||||
OPTIONAL_HELP
|
||||
: %empty
|
||||
| HELP VALUE {
|
||||
driver.setHelpAddendum($2);
|
||||
}
|
||||
;
|
||||
|
||||
USAGE_DETAILS
|
||||
: USAGE_DETAIL
|
||||
| USAGE_DETAILS USAGE_DETAIL
|
||||
;
|
||||
|
||||
USAGE_DETAIL
|
||||
: USAGE {
|
||||
if ($1.empty())
|
||||
driver.addUsage(Usage{ {}, {} });
|
||||
else
|
||||
driver.addUsage(Usage{ { $1 }, {} });
|
||||
}
|
||||
| RULE_NAME RULE_EQUALS RULE_OPTIONS {
|
||||
driver.addRule($1, $3);
|
||||
}
|
||||
;
|
||||
|
||||
RULE_OPTIONS
|
||||
: RULE_TOKEN {
|
||||
$$ = std::vector{ $1 };
|
||||
}
|
||||
| RULE_OPTIONS RULE_OR RULE_TOKEN {
|
||||
$$ = $1;
|
||||
$$.push_back($3);
|
||||
}
|
||||
;
|
||||
|
||||
ARGUMENTS_DETAILS
|
||||
: %empty
|
||||
| ARGUMENTS_DETAILS ARGUMENT_DETAILS
|
||||
;
|
||||
|
||||
ARGUMENT_DETAILS
|
||||
: LONGOPT
|
||||
OPTIONAL_SHORTOPT OPTIONAL_PARAMETERS
|
||||
DESCRIPTION {
|
||||
if ($1 == "help" && $3) {
|
||||
std::cerr << "--help cannot take parameters\n";
|
||||
throw std::runtime_error{ "Invalid rule for --help" };
|
||||
} else if ($1 == "version" && $3) {
|
||||
std::cerr << "--version cannot take parameters\n";
|
||||
throw std::runtime_error{ "Invalid rule for --version" };
|
||||
} else if ($1 == "license" && $3) {
|
||||
std::cerr << "--license cannot take parameters\n";
|
||||
throw std::runtime_error{ "Invalid rule for --license" };
|
||||
}
|
||||
|
||||
if ($3) /* if parameters exist */
|
||||
driver.addArg(std::make_unique<ParameterArgument>($1, $2, *$3, $4));
|
||||
else
|
||||
driver.addArg(std::make_unique<FlagArgument>($1, $2, $4));
|
||||
}
|
||||
;
|
||||
|
||||
OPTIONAL_SHORTOPT
|
||||
: %empty {
|
||||
$$ = std::nullopt;
|
||||
}
|
||||
| SHORTOPT {
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
OPTIONAL_PARAMETERS
|
||||
: %empty {
|
||||
$$ = std::nullopt;
|
||||
}
|
||||
| PARAMETERS {
|
||||
$$ = $1;
|
||||
}
|
||||
;
|
||||
|
||||
%%
|
||||
|
||||
void Grammar::Parser::error(const location_type& loc, const std::string& err_message)
|
||||
{
|
||||
std::cerr << "Error: \'" << err_message << "\' at " << loc << '\n';
|
||||
}
|
||||
25
generator/src/scanner.hpp
Normal file
25
generator/src/scanner.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#if !defined(yyFlexLexerOnce)
|
||||
#include <FlexLexer.h>
|
||||
#endif
|
||||
|
||||
#include "parser.tab.hh"
|
||||
|
||||
namespace Grammar {
|
||||
class Scanner : public yyFlexLexer {
|
||||
public:
|
||||
Scanner(std::istream *in) : yyFlexLexer{in} {};
|
||||
|
||||
virtual ~Scanner(){};
|
||||
|
||||
using FlexLexer::yylex;
|
||||
|
||||
virtual int yylex(Grammar::Parser::semantic_type *const lval,
|
||||
Grammar::Parser::location_type *lloc);
|
||||
|
||||
private:
|
||||
Grammar::Parser::semantic_type *yyval = nullptr;
|
||||
Grammar::Parser::location_type *loc = nullptr;
|
||||
};
|
||||
} // namespace Grammar
|
||||
32
generator/src/templates.hpp
Normal file
32
generator/src/templates.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#define PREFIX(filename) _binary_templates_ArgGrammar##filename
|
||||
#define DECLARE_TEMPLATE_FILE(filename) \
|
||||
extern const char PREFIX(filename##_start); \
|
||||
extern const char PREFIX(filename##_end);
|
||||
|
||||
DECLARE_TEMPLATE_FILE(Driver_cpp)
|
||||
DECLARE_TEMPLATE_FILE(Driver_hpp)
|
||||
DECLARE_TEMPLATE_FILE(Scanner_ll)
|
||||
DECLARE_TEMPLATE_FILE(Parser_yy)
|
||||
DECLARE_TEMPLATE_FILE(Scanner_cpp)
|
||||
DECLARE_TEMPLATE_FILE(Scanner_hpp)
|
||||
|
||||
struct TemplateFile {
|
||||
std::string symbolName;
|
||||
std::string_view contents;
|
||||
};
|
||||
|
||||
#define STRING_TEMPLATE(filename) \
|
||||
std::string_view(&PREFIX(filename##_start), \
|
||||
&PREFIX(filename##_end) - &PREFIX(filename##_start))
|
||||
|
||||
#define FILE_TEMPLATE(filename) \
|
||||
TemplateFile { "ArgGrammar" #filename, STRING_TEMPLATE(filename) }
|
||||
|
||||
const TemplateFile templateFiles[] = {
|
||||
FILE_TEMPLATE(Driver_cpp), FILE_TEMPLATE(Driver_hpp),
|
||||
FILE_TEMPLATE(Scanner_ll), FILE_TEMPLATE(Parser_yy),
|
||||
FILE_TEMPLATE(Scanner_cpp), FILE_TEMPLATE(Scanner_hpp)};
|
||||
7
generator/src/usage.hpp
Normal file
7
generator/src/usage.hpp
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct Usage {
|
||||
std::vector<std::string> flags, positional;
|
||||
};
|
||||
Reference in New Issue
Block a user