First usable generator source.
Still issues with usage rules and error reporting, but functional.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
.ccls-cache
|
||||
.ccls
|
||||
*.o
|
||||
.tup
|
||||
|
||||
19
examples/dc/dc.argspec
Normal file
19
examples/dc/dc.argspec
Normal file
@@ -0,0 +1,19 @@
|
||||
Program:
|
||||
program dc
|
||||
version "dc (GNU bc 1.07.1) 1.4.1\n\nCopyright 1994, 1997, 1998, 2000, 2001, 2003-2006, 2008, 2010, 2012-2017 Free Software Foundation, Inc."
|
||||
license "This is free software; see the source for copying conditions.\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law."
|
||||
help "Email bug reports to: bug-dc@gnu.org"
|
||||
|
||||
Usage:
|
||||
|
||||
dc
|
||||
dc OPTIONS
|
||||
|
||||
OPTIONS = --expression | --file
|
||||
|
||||
Arguments:
|
||||
|
||||
--help, -h, , "shows this help message"
|
||||
--version, -V, , "prints the version of the program"
|
||||
--expression, -e, EXPR, "evaluate expression"
|
||||
--file, -f, FILE, "evaluate contents of file"
|
||||
18
examples/dc/main.cpp
Normal file
18
examples/dc/main.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#include "src/dcArgGrammarDriver.hpp"
|
||||
#include <sstream>
|
||||
|
||||
using Result = dcArgGrammar::Driver::Result;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
dcArgGrammar::Driver driver{ argc, argv };
|
||||
auto res = driver.parse();
|
||||
|
||||
if (res != Result::success) {
|
||||
if (res == Result::completedAction)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
2
generator/.gitignore
vendored
Normal file
2
generator/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
fcap
|
||||
generated/
|
||||
20
generator/Tupfile
Normal file
20
generator/Tupfile
Normal file
@@ -0,0 +1,20 @@
|
||||
LEXER = flex
|
||||
PARSER = bison
|
||||
CXX = g++
|
||||
CXXFLAGS = -pedantic -std=c++17 -Wall -Wno-unused-parameter -Wno-reorder -Wno-sign-compare -Wno-address -Wno-noexcept-type -Wno-unknown-attributes -Wno-unknown-warning-option
|
||||
CPPFLAGS =
|
||||
LDFLAGS =
|
||||
LIBS = -lmstch
|
||||
|
||||
SRC_DIR = src
|
||||
GEN_DIR = generated
|
||||
TEMPL_DIR = templates
|
||||
BUILD_DIR = build
|
||||
PROG = fcap
|
||||
|
||||
: foreach $(SRC_DIR)/*.yy |> $(PARSER) -b "$(GEN_DIR)/%B" "%f" |> "$(GEN_DIR)/%B.tab.cc" | "$(GEN_DIR)/%B.tab.hh"
|
||||
: foreach $(SRC_DIR)/*.ll | $(GEN_DIR)/*.hh |> $(LEXER) -o "%o" "%f" |> "$(GEN_DIR)/%B.yy.cc"
|
||||
: foreach $(TEMPL_DIR)/*.* |> ld --relocatable --format=binary --output="%o" "%f" |> "$(GEN_DIR)/template-%b.data"
|
||||
: foreach $(GEN_DIR)/*.data |> objcopy --rename-section .data=.rodata,alloc,load,readonly,data,contents "%f" "%o" |> "$(BUILD_DIR)/%B.o"
|
||||
: foreach $(GEN_DIR)/*.cc $(SRC_DIR)/*.cpp | $(GEN_DIR)/*.hh |> $(CXX) $(CPPFLAGS) $(CXXFLAGS) -I "$(SRC_DIR)" -I "$(GEN_DIR)" -c "%f" -o "%o" |> "$(BUILD_DIR)/%B.o"
|
||||
: $(BUILD_DIR)/*.o |> $(CXX) %f $(LDFLAGS) $(LIBS) -o "%o" |> "$(PROG)"
|
||||
31
generator/demo/README.md
Normal file
31
generator/demo/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Demonstration of templates being re-written for a specific argument specification
|
||||
|
||||
## Requirements
|
||||
|
||||
* mustache
|
||||
* bison (to compile)
|
||||
* flex (to compile)
|
||||
|
||||
## How to re-write
|
||||
|
||||
```sh
|
||||
mustache demo.yml ../templates/<file>
|
||||
```
|
||||
|
||||
To save the result to a file:
|
||||
|
||||
```sh
|
||||
mustache demo.yml ../templates/<file> /path/to/output/file
|
||||
```
|
||||
|
||||
## How to run
|
||||
|
||||
Run `mustache` on all the templates in `../templates/`, and on _`main.cpp`_ (in this folder) to an empty output directory (`/path/to/output`).
|
||||
Then:
|
||||
|
||||
```sh
|
||||
bison /path/to/output/*.yy
|
||||
flex /path/to/output/*.ll
|
||||
g++ /path/to/output/*.cpp /path/to/output*.cc -o demo
|
||||
/path/to/output/demo
|
||||
```
|
||||
53
generator/demo/demo.yml
Normal file
53
generator/demo/demo.yml
Normal file
@@ -0,0 +1,53 @@
|
||||
---
|
||||
argspec: "dc"
|
||||
any_parameters: true
|
||||
|
||||
argument_tokens:
|
||||
- argument: "expression"
|
||||
usage: "evaluates an expression"
|
||||
short_argument: "e"
|
||||
clean_token: "EXPRESSION"
|
||||
has_parameters: true
|
||||
parameters:
|
||||
- index: 1
|
||||
name: "expression"
|
||||
- argument: "file"
|
||||
usage: "evaluates the contents of a file"
|
||||
short_argument: "f"
|
||||
clean_token: "FILE"
|
||||
has_parameters: true
|
||||
parameters:
|
||||
- index: 1
|
||||
name: "file"
|
||||
help_token:
|
||||
- usage: false
|
||||
short_argument: "h"
|
||||
|
||||
version_token:
|
||||
- usage: false
|
||||
short_argument: false
|
||||
|
||||
license_token:
|
||||
- usage: false
|
||||
short_argument: false
|
||||
|
||||
argument_explanation: false
|
||||
|
||||
usage:
|
||||
- flags:
|
||||
- "OPTIONS"
|
||||
positional: false
|
||||
|
||||
usage_rules:
|
||||
- rule_name: "OPTIONS"
|
||||
options:
|
||||
- option: "EXPRESSION"
|
||||
has_next: true
|
||||
- option: "FILE"
|
||||
|
||||
version: "dc (GNU bc 1.07.1) 1.4.1\\n\\nCopyright 1994, 1997, 1998, 2000, 2001, 2003-2006, 2008, 2010, 2012-2017 Free Software Foundation, Inc."
|
||||
|
||||
license: "This is free software; see the source for copying conditions.\\nThere is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, to the extent permitted by law."
|
||||
|
||||
help_addendum: "Email bug reports to: bug-dc@gnu.org"
|
||||
---
|
||||
19
generator/demo/main.cpp
Normal file
19
generator/demo/main.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
{{=@@ @@=}}
|
||||
#include "@@argspec@@ArgGrammarDriver.hpp"
|
||||
#include <sstream>
|
||||
|
||||
using Result = @@argspec@@ArgGrammar::Driver::Result;
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
@@argspec@@ArgGrammar::Driver driver{ argc, argv };
|
||||
auto res = driver.parse();
|
||||
|
||||
if (res != Result::success) {
|
||||
if (res == Result::completedAction)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
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;
|
||||
};
|
||||
80
generator/templates/ArgGrammarDriver.cpp
Normal file
80
generator/templates/ArgGrammarDriver.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
{{=@@ @@=}}
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
|
||||
#include "@@{argspec}@@ArgGrammarDriver.hpp"
|
||||
#include "@@{argspec}@@ArgGrammarScanner.hpp"
|
||||
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
Driver::Driver(int argc, char* argv[]) : argc{ argc }, argv{ argv }, scanner{}, parser{} {
|
||||
assert(argc > 0 && "Arguments must include program invocation");
|
||||
}
|
||||
|
||||
Driver::~Driver() {
|
||||
delete scanner;
|
||||
delete parser;
|
||||
}
|
||||
|
||||
Driver::Result Driver::parse() {
|
||||
if (scanner)
|
||||
delete scanner;
|
||||
|
||||
try {
|
||||
scanner = new Scanner(argc, argv, *this);
|
||||
} catch (const std::bad_alloc& ba) {
|
||||
std::cerr << "Failed to allocate scanner: \"" << ba.what() << "\". Exiting!\n";
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (parser)
|
||||
delete parser;
|
||||
try {
|
||||
parser = new 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 result;
|
||||
}
|
||||
|
||||
void Driver::addArg(const FlagArg flag) {
|
||||
flagArguments.insert(flag);
|
||||
}@@#any_parameters@@
|
||||
|
||||
void Driver::addArg(const ParamArg flag, std::vector<std::string> parameters) {
|
||||
paramArguments.insert_or_assign(flag, parameters);
|
||||
}@@/any_parameters@@
|
||||
|
||||
bool Driver::getArg(const FlagArg flag) const {
|
||||
return (flagArguments.find(flag) != flagArguments.end());
|
||||
}@@#any_parameters@@
|
||||
|
||||
std::optional<std::vector<std::string>> Driver::getArg(const ParamArg flag) const {
|
||||
try {
|
||||
return std::make_optional(paramArguments.at(flag));
|
||||
} catch (const std::out_of_range& ignored) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}@@/any_parameters@@
|
||||
|
||||
void Driver::setResult(Driver::Result result) {
|
||||
this->result = result;
|
||||
}
|
||||
|
||||
const std::set<FlagArg>& Driver::getFlagArgs() const {
|
||||
return flagArguments;
|
||||
}@@#any_parameters@@
|
||||
|
||||
const std::map<ParamArg, std::vector<std::string>>& Driver::getParamArgs() const {
|
||||
return paramArguments;
|
||||
}@@/any_parameters@@
|
||||
|
||||
char** Driver::getArgv() {
|
||||
return argv;
|
||||
}
|
||||
} // namespace trueArgGrammar
|
||||
58
generator/templates/ArgGrammarDriver.hpp
Normal file
58
generator/templates/ArgGrammarDriver.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
{{=@@ @@=}}
|
||||
#pragma once
|
||||
|
||||
#include "@@{argspec}@@ArgGrammarParser.tab.hh"
|
||||
#include <iostream>
|
||||
#include <set>@@#any_parameters@@
|
||||
#include <map>
|
||||
#include <optional>@@/any_parameters@@
|
||||
#include <string>
|
||||
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
enum class FlagArg { @@#argument_tokens@@@@^has_parameters@@@@{clean_token}@@, @@/has_parameters@@@@/argument_tokens@@ };@@#any_parameters@@
|
||||
enum class ParamArg { @@#argument_tokens@@@@#has_parameters@@@@{clean_token}@@, @@/has_parameters@@@@/argument_tokens@@ };@@/any_parameters@@
|
||||
|
||||
class Scanner;
|
||||
|
||||
class Driver {
|
||||
public:
|
||||
enum class Result { success, completedAction, wrongArgument };
|
||||
|
||||
Driver(int argc, char* argv[]);
|
||||
virtual ~Driver();
|
||||
|
||||
Result parse();
|
||||
|
||||
void addArg(const FlagArg flag);@@#any_parameters@@
|
||||
void addArg(const ParamArg flag, const std::vector<std::string> arguments);@@/any_parameters@@
|
||||
|
||||
bool getArg(const FlagArg flag) const;@@#any_parameters@@
|
||||
std::optional<std::vector<std::string>> getArg(const ParamArg flag) const;@@/any_parameters@@
|
||||
void setResult(Result result);
|
||||
|
||||
const std::set<FlagArg>& getFlagArgs() const;@@#any_parameters@@
|
||||
const std::map<ParamArg, std::vector<std::string>>& getParamArgs() const;@@/any_parameters@@
|
||||
|
||||
std::ostream& print(std::ostream& oss);
|
||||
|
||||
char** getArgv();
|
||||
|
||||
private:
|
||||
int argc;
|
||||
char** argv;
|
||||
|
||||
Result result = Result::success;
|
||||
|
||||
void parseHelper(std::istream& iss);
|
||||
|
||||
@@{argspec}@@ArgGrammar::Parser* parser = nullptr;
|
||||
@@{argspec}@@ArgGrammar::Scanner* scanner = nullptr;
|
||||
|
||||
/*
|
||||
* specialised data store
|
||||
*/
|
||||
|
||||
std::set<FlagArg> flagArguments;@@#any_parameters@@
|
||||
std::map<ParamArg, std::vector<std::string>> paramArguments;@@/any_parameters@@
|
||||
};
|
||||
} // namespace trueArgGrammar
|
||||
94
generator/templates/ArgGrammarParser.yy
Normal file
94
generator/templates/ArgGrammarParser.yy
Normal file
@@ -0,0 +1,94 @@
|
||||
{{=@@ @@=}}
|
||||
%skeleton "lalr1.cc"
|
||||
%require "3.2"
|
||||
%debug
|
||||
%defines
|
||||
%define api.namespace {@@{argspec}@@ArgGrammar}
|
||||
%define api.parser.class {Parser}
|
||||
%file-prefix "@@{argspec}@@ArgGrammarParser"
|
||||
|
||||
%code requires{
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
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 <string>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "@@{argspec}@@ArgGrammarDriver.hpp"
|
||||
#include "@@{argspec}@@ArgGrammarScanner.hpp"
|
||||
|
||||
#undef yylex
|
||||
#define yylex scanner.yylex
|
||||
}
|
||||
|
||||
%define api.value.type variant
|
||||
%define parse.assert
|
||||
|
||||
%token <std::string> POSITIONAL_ARGUMENT@@#argument_tokens@@
|
||||
%token ARGUMENT_@@{clean_token}@@@@/argument_tokens@@
|
||||
|
||||
%start ARGUMENTS
|
||||
|
||||
%%
|
||||
|
||||
ARGUMENTS
|
||||
: ARGUMENT_help {
|
||||
std::cout
|
||||
<< "Usage:\n"
|
||||
<< "\n"@@#usage@@
|
||||
<< driver.getArgv()[0] << "@@#flags@@ @@{.}@@@@/flags@@@@#positional@@ <@@{.}@@>@@/positional@@\n"@@/usage@@
|
||||
<< driver.getArgv()[0] << " --help\n"
|
||||
<< driver.getArgv()[0] << " --version\n"
|
||||
<< driver.getArgv()[0] << " --license\n"
|
||||
<< "\n"
|
||||
<< "Arguments:\n"
|
||||
<< "\n"@@#argument_explanations@@
|
||||
<< "@@{.}@@\n"
|
||||
<< "\n"@@/argument_explanations@@@@#argument_tokens@@
|
||||
<< " @@#short_argument@@-@@{short_argument}@@,@@/short_argument@@@@^short_argument@@ @@/short_argument@@ --@@{argument}@@@@{parameter_align_spacing}@@@@#parameters@@ <@@{name}@@>@@#next_state@@,@@/next_state@@@@/parameters@@@@^parameters@@ @@/parameters@@ @@{explain_align_spacing}@@@@{usage}@@\n"@@/argument_tokens@@
|
||||
<< "\n"
|
||||
<< "@@{help_addendum}@@"@@/help_addendum@@
|
||||
<< std::endl;
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::FlagArg::help);
|
||||
driver.setResult(Driver::Result::completedAction);
|
||||
}
|
||||
| ARGUMENT_version {
|
||||
std::cout << "@@{version}@@" << std::endl;
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::FlagArg::version);
|
||||
driver.setResult(Driver::Result::completedAction);
|
||||
}
|
||||
| ARGUMENT_license {
|
||||
std::cout << "@@{license}@@" << std::endl;
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::FlagArg::license);
|
||||
driver.setResult(Driver::Result::completedAction);
|
||||
} @@#usage@@
|
||||
|@@#flags@@ ARGUMENT_@@{.}@@@@/flags@@@@#positional@@ @@{.}@@@@/positional@@@@^flags@@@@^positional@@ %empty@@/positional@@@@/flags@@@@/usage@@
|
||||
;@@#usage_rules@@
|
||||
|
||||
ARGUMENT_@@{rule_name}@@
|
||||
:@@#options@@ ARGUMENT_@@{option}@@@@#has_next@@
|
||||
|@@/has_next@@@@/options@@
|
||||
;@@/usage_rules@@
|
||||
|
||||
%%
|
||||
|
||||
void @@{argspec}@@ArgGrammar::Parser::error(const std::string& err_message)
|
||||
{
|
||||
std::cerr << "Error: " << err_message << '\n';
|
||||
}
|
||||
31
generator/templates/ArgGrammarScanner.cpp
Normal file
31
generator/templates/ArgGrammarScanner.cpp
Normal file
@@ -0,0 +1,31 @@
|
||||
{{=@@ @@=}}
|
||||
#include "@@{argspec}@@ArgGrammarScanner.hpp"
|
||||
#include "@@{argspec}@@ArgGrammarDriver.hpp"
|
||||
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
Scanner::Scanner(int argc, char* argv[], Driver& driver)
|
||||
: yyFlexLexer{}, argc{ argc }, argv{ argv }, argi{ 1 }, streamInput{}, resetVal{}, driver{
|
||||
driver
|
||||
} {
|
||||
if (argi < argc)
|
||||
streamInput << argv[argi];
|
||||
|
||||
switch_streams(&streamInput);
|
||||
}
|
||||
|
||||
int Scanner::yywrap() {
|
||||
++argi;
|
||||
bool more = (argi < argc);
|
||||
|
||||
if (more) {
|
||||
streamInput << argv[argi];
|
||||
resetOnWrap();
|
||||
}
|
||||
|
||||
return more ? 0 : 1;
|
||||
}
|
||||
|
||||
void Scanner::setResult(Scanner::Result result) {
|
||||
driver.setResult(result);
|
||||
}
|
||||
} // namespace @@{argspec}@@ArgGrammar
|
||||
38
generator/templates/ArgGrammarScanner.hpp
Normal file
38
generator/templates/ArgGrammarScanner.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
{{=@@ @@=}}
|
||||
#pragma once
|
||||
|
||||
#if !defined(yyFlexLexerOnce)
|
||||
# include <FlexLexer.h>
|
||||
#endif
|
||||
|
||||
#include "@@{argspec}@@ArgGrammarDriver.hpp"
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
class Scanner : public yyFlexLexer {
|
||||
public:
|
||||
using Result = Driver::Result;
|
||||
|
||||
Scanner(int argc, char* argv[], Driver& driver);
|
||||
virtual ~Scanner() = default;
|
||||
|
||||
using FlexLexer::yylex;
|
||||
|
||||
virtual int yylex(Parser::semantic_type* const lval);
|
||||
void resetOnWrap();
|
||||
void setResult(Result result);
|
||||
|
||||
private:
|
||||
int argc, argi;
|
||||
char** argv;
|
||||
Driver& driver;
|
||||
std::stringstream streamInput;
|
||||
std::optional<int> resetVal;
|
||||
Parser::semantic_type* yyval = nullptr;
|
||||
|
||||
protected:
|
||||
int yywrap() override;
|
||||
};
|
||||
} // namespace trueArgGrammar
|
||||
123
generator/templates/ArgGrammarScanner.ll
Normal file
123
generator/templates/ArgGrammarScanner.ll
Normal file
@@ -0,0 +1,123 @@
|
||||
{{=@@ @@=}}
|
||||
%{
|
||||
#include <stdexcept>
|
||||
#include "@@{argspec}@@ArgGrammarParser.tab.hh"
|
||||
|
||||
#include "@@{argspec}@@ArgGrammarScanner.hpp"
|
||||
#undef YY_DECL
|
||||
#define YY_DECL int @@{argspec}@@ArgGrammar::Scanner::yylex(@@{argspec}@@ArgGrammar::Parser::semantic_type* const lval)
|
||||
|
||||
using token = @@{argspec}@@ArgGrammar::Parser::token;
|
||||
%}
|
||||
|
||||
%option debug
|
||||
%option nodefault
|
||||
%option yyclass="@@{argspec}@@ArgGrammar::Scanner"
|
||||
%option noyywrap
|
||||
%option c++
|
||||
%option outfile="@@{argspec}@@ArgGrammarScannerDef.cpp"
|
||||
|
||||
%x SHORT_ARGUMENTS ARGUMENT_VALUE POSITIONAL_ARGUMENTS@@#argument_tokens@@@@#parameters@@ @@{clean_token}@@_PARAMETER_@@{index}@@@@/parameters@@@@/argument_tokens@@
|
||||
|
||||
ANYTHING .|\n
|
||||
LONGARG [[:alnum:]][[:alnum:]\-]*
|
||||
|
||||
%%
|
||||
|
||||
%{ /** Code executed at the beginning of yylex **/
|
||||
yyval = lval;
|
||||
static std::vector<std::string> parameters;
|
||||
%}@@#argument_tokens@@@@#has_parameters@@
|
||||
|
||||
<@@#parameters@@@@{clean_token}@@_PARAMETER_@@{index}@@@@#next_state@@,@@/next_state@@@@/parameters@@>-- { /** invalid usage of --@@{argument}@@ flag **/
|
||||
BEGIN(POSITIONAL_ARGUMENTS);
|
||||
std::cerr << "--@@{argument}@@ has not been given sufficient parameters:\n";
|
||||
|
||||
for (const auto& parameter : parameters)
|
||||
std::cerr << "\t- \'" << parameter << "\'\n";
|
||||
|
||||
setResult(Result::wrongArgument);
|
||||
}@@/has_parameters@@@@/argument_tokens@@
|
||||
|
||||
<*>-- { /* end of arguments */
|
||||
BEGIN(POSITIONAL_ARGUMENTS);
|
||||
}
|
||||
|
||||
/** Arguments **/
|
||||
@@#argument_tokens@@
|
||||
|
||||
<INITIAL>--@@{argument}@@ {@@#has_parameters@@
|
||||
BEGIN(@@{clean_token}@@_PARAMETER_1);
|
||||
parameters = {};@@/has_parameters@@@@^has_parameters@@
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::FlagArg::@@{clean_token}@@);
|
||||
return token::ARGUMENT_@@{clean_token}@@;@@/has_parameters@@
|
||||
}@@#short_argument@@
|
||||
|
||||
<SHORT_ARGUMENTS>@@{short_argument}@@ {@@#has_parameters@@
|
||||
BEGIN(@@{clean_token}@@_PARAMETER_1);
|
||||
resetVal = std::nullopt;
|
||||
parameters = {};@@/has_parameters@@@@^has_parameters@@
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::FlagArg::@@{clean_token}@@);
|
||||
return token::ARGUMENT_@@{clean_token}@@;@@/has_parameters@@
|
||||
}@@/short_argument@@@@#parameters@@
|
||||
|
||||
<@@{clean_token}@@_PARAMETER_@@{index}@@>{ANYTHING}+ {@@#next_state@@
|
||||
BEGIN(@@{clean_token}@@_PARAMETER_@@{.}@@);@@/next_state@@@@^next_state@@
|
||||
BEGIN(INITIAL);@@/next_state@@
|
||||
parameters.push_back(yytext);@@^next_state@@
|
||||
driver.addArg(@@{argspec}@@ArgGrammar::ParamArg::@@{clean_token}@@, parameters);
|
||||
return token::ARGUMENT_@@{clean_token}@@;@@/next_state@@
|
||||
}@@/parameters@@
|
||||
@@/argument_tokens@@
|
||||
|
||||
/** Default (error) arguments */
|
||||
|
||||
<INITIAL>--{LONGARG} {
|
||||
std::cerr << "Unknown argument: \'" << yytext << "\' found\n";
|
||||
setResult(Result::wrongArgument);
|
||||
}
|
||||
|
||||
<INITIAL>--{ANYTHING}+ { /* show common error for invalid option */
|
||||
std::cerr << "Invalid argument: \'" << yytext << "\' found\n";
|
||||
setResult(Result::wrongArgument);
|
||||
}
|
||||
|
||||
<SHORT_ARGUMENTS>{ANYTHING} {
|
||||
std::cerr << "Unknown argument \'-" << yytext << "\' found\n";
|
||||
setResult(Result::wrongArgument);
|
||||
}
|
||||
|
||||
/** Check for short argument **/
|
||||
|
||||
<INITIAL>-/{ANYTHING}+ {
|
||||
BEGIN(SHORT_ARGUMENTS);
|
||||
resetVal = INITIAL;
|
||||
}
|
||||
|
||||
/** Check for possible unknown arguments **/
|
||||
|
||||
<POSITIONAL_ARGUMENTS>{ANYTHING}+ {
|
||||
/* std::cerr << "Unknown positional argument \'" << yytext << "\' found\n";
|
||||
setResult(Result::wrongArgument); */
|
||||
yyval->build<std::string>(yytext);
|
||||
return token::POSITIONAL_ARGUMENT;
|
||||
}
|
||||
|
||||
<INITIAL>{ANYTHING}* { /* unknown positional argument */
|
||||
BEGIN(POSITIONAL_ARGUMENTS);
|
||||
/* std::cerr << "Unknown positional argument \'" << yytext << "\' found\n";
|
||||
setResult(Result::wrongArgument); */
|
||||
yyval->build<std::string>(yytext);
|
||||
return token::POSITIONAL_ARGUMENT;
|
||||
}
|
||||
|
||||
%%
|
||||
|
||||
namespace @@{argspec}@@ArgGrammar {
|
||||
void Scanner::resetOnWrap() {
|
||||
if (resetVal)
|
||||
BEGIN(*resetVal);
|
||||
|
||||
resetVal = INITIAL;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user