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-cache
|
||||||
|
.ccls
|
||||||
*.o
|
*.o
|
||||||
.tup
|
.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