First usable generator source.

Still issues with usage rules and error reporting, but functional.
This commit is contained in:
2019-09-28 16:01:40 +01:00
parent 54de004ede
commit 60d0446cf0
24 changed files with 1605 additions and 0 deletions

View 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

View 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

View 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';
}

View 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

View 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

View 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;
}
}