Bash++
Bash++ compiler internal documentation
parse_arguments.h
Go to the documentation of this file.
1
7#pragma once
8
9#include <iostream>
10#include <string>
11#include <cstring>
12#include <vector>
13#include <optional>
14#include <filesystem>
15#include <memory>
16#include <unistd.h>
17
18#include <include/BashVersion.h>
19#include <include/xgetopt.h>
20
21#include <version.h>
22#include <updated_year.h>
23
24constexpr const char* program_name = "Bash++";
25
26constexpr const char* copyright = "Copyright (C) 2024-"
28 " Andrew S. Rightenburg\n\n"
29 "This program is free software; you can redistribute it and/or modify\n"
30 "it under the terms of the GNU General Public License as published by\n"
31 "the Free Software Foundation; either version 3 of the License, or\n"
32 "(at your option) any later version.\n\n"
33 "This program is distributed in the hope that it will be useful,\n"
34 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
35 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
36 "GNU General Public License for more details.\n\n"
37 "You should have received a copy of the GNU General Public License\n"
38 "along with this program. If not, see http://www.gnu.org/licenses/.\n";
39
40constexpr const char* help_intro =
41 "Usage: bpp [options] [file] ...\n"
42 "If no file is specified, read from stdin\n"
43 "All arguments after the file are passed to the compiled program\n"
44 "Options:\n";
45
46constexpr XGetOpt::OptionParser<
47 XGetOpt::Option<'o', "output", "Specify output file (default: run on exit)", XGetOpt::RequiredArgument, "file">,
48 XGetOpt::Option<'b', "target-bash", "Compile to Bash version (default: 5.2)", XGetOpt::RequiredArgument, "version">,
49 XGetOpt::Option<'s', "no-warnings", "Suppress warnings", XGetOpt::NoArgument>,
50 XGetOpt::Option<'I', "include", "Add directory to include path", XGetOpt::RequiredArgument, "directory">,
51 XGetOpt::Option<'t', "tokens", "Display tokens from lexer (do not compile program)", XGetOpt::NoArgument>,
52 XGetOpt::Option<'p', "parse-tree", "Display parse tree (do not compile program)", XGetOpt::NoArgument>,
53 XGetOpt::Option<'v', "version", "Display version information and exit", XGetOpt::NoArgument>,
54 XGetOpt::Option<'h', "help", "Display this help message and exit", XGetOpt::NoArgument>
56
66class Arguments {
67 private:
68 std::vector<char*> m_program_arguments;
69 std::optional<std::string> m_input_file;
70 std::optional<std::string> m_output_file;
71 BashVersion m_target_bash_version = {5, 2}; // Default to Bash 5.2
72 std::shared_ptr<std::vector<std::string>> m_include_paths = std::make_shared<std::vector<std::string>>();
73 bool f_suppress_warnings = false;
74 bool f_display_tokens = false;
76 bool f_run_on_exit = true;
77 bool f_exit_early = false; // Exit early if the request is just -h/--help or -v/--version
78
79 public:
92 void set_program_arguments(int argc, char** argv) {
93 auto arguments_span = std::span<char*>{argv, static_cast<size_t>(argc)};
94 this->m_program_arguments = std::vector<char*>(arguments_span.begin(), arguments_span.end());
95 }
96 const std::vector<char*>& program_arguments() const {
97 return this->m_program_arguments;
98 }
99
109 void set_input_file(std::string_view input_file) {
110 // Verify that the file exists and is a regular file
111 if (!std::filesystem::exists(input_file)) {
112 throw std::runtime_error("Source file '" + std::string(input_file) + "' does not exist");
113 }
114 if (!std::filesystem::is_regular_file(input_file)) {
115 throw std::runtime_error("Source file '" + std::string(input_file) + "' is not a regular file");
116 }
117 this->m_input_file = input_file;
118 }
119 const std::optional<std::string>& input_file() const {
120 return this->m_input_file;
121 }
122
133 void set_output_file(std::string_view output_file_arg) {
134 if (output_file_arg == "-") {
135 this->m_output_file = output_file_arg;
136 return;
137 }
138
139 std::string parsed_output_file;
140
141 {
142 std::filesystem::path output_path(output_file_arg);
143 if (output_path.is_absolute()) {
144 parsed_output_file = output_path.string();
145 } else {
146 parsed_output_file = std::filesystem::current_path() / output_path;
147 }
148 }
149
150 // Check if we have permission to write to the specified output file
151 // If the file exists, verify write access; if it doesn't, verify write access on its parent directory
152 try {
153 if (std::filesystem::exists(parsed_output_file)) {
154 if (access(parsed_output_file.c_str(), W_OK) != 0) {
155 throw std::runtime_error("No write permission for output file '" + parsed_output_file + "'");
156 }
157 } else {
158 std::filesystem::path parent_path = std::filesystem::path(parsed_output_file).parent_path();
159 if (!std::filesystem::exists(parent_path) || !std::filesystem::is_directory(parent_path)) {
160 throw std::runtime_error("Parent directory of output file '" + parsed_output_file + "' does not exist or is not a directory");
161 }
162 if (access(parent_path.c_str(), W_OK) != 0) {
163 throw std::runtime_error("No write permission for parent directory of output file '" + parsed_output_file + "'");
164 }
165 }
166 } catch (const std::exception& e) {
167 throw std::runtime_error(std::string("Could not verify write permission for output file '") + parsed_output_file + "': " + e.what());
168 }
169
170 this->m_output_file = parsed_output_file;
171 }
172 const std::optional<std::string>& output_file() const {
173 return this->m_output_file;
174 }
175
185 void set_target_bash_version(std::string_view version_arg) {
186 this->m_target_bash_version = BashVersion(version_arg);
187 }
189 return this->m_target_bash_version;
190 }
191
203 void add_include_path(std::string_view path) {
204 if (!std::filesystem::is_directory(path)) {
205 throw std::runtime_error("Include path '" + std::string(path) + "' is not a directory");
206 }
207 this->m_include_paths->emplace_back(path);
208 }
209 const std::shared_ptr<std::vector<std::string>>& include_paths() const {
210 return this->m_include_paths;
211 }
212
213 void set_suppress_warnings(bool suppress) {
214 this->f_suppress_warnings = suppress;
215 }
216 bool suppress_warnings() const {
217 return this->f_suppress_warnings;
218 }
219
220 void set_display_tokens(bool display) {
221 this->f_display_tokens = display;
222 }
223 bool display_tokens() const {
224 return this->f_display_tokens;
225 }
226
227 void set_display_parse_tree(bool display) {
228 this->f_display_parse_tree = display;
229 }
230 bool display_parse_tree() const {
231 return this->f_display_parse_tree;
232 }
233
234 void set_exit_early(bool exit) {
235 this->f_exit_early = exit;
236 }
237 bool exit_early() const {
238 return this->f_exit_early;
239 }
240
242 this->f_run_on_exit = run_on_exit;
243 }
244 bool run_on_exit() const {
245 return this->f_run_on_exit;
246 }
247
248 bool output_to_stdout() const {
249 return this->m_output_file == "-";
250 }
251
252 bool output_to_file() const {
253 return this->m_output_file.has_value() && this->m_output_file.value() != "-";
254 }
255
256 bool input_from_stdin() const {
257 return !this->m_input_file.has_value();
258 }
259};
260
261inline Arguments parse_arguments(int argc, char* argv[]) {
262 Arguments args;
263
264 // Will throw if invalid arguments are provided
265 auto [compiler_arguments, program_arguments]
267
268 args.set_program_arguments(program_arguments.argc, program_arguments.argv);
269
270 if (compiler_arguments.getNonOptionArguments().size() > 0) {
271 args.set_input_file(compiler_arguments.getNonOptionArguments()[0]);
272 }
273
274 for (const auto& arg : compiler_arguments) {
275 switch (arg.getShortOpt()) {
276 default:
277 // This should never happen since XGetOpt should throw on unrecognized options
278 std::cerr << program_name << ": Warning: Unhandled option '" << static_cast<char>(arg.getShortOpt()) << "'" << std::endl;
279 break;
280 case 'b':
281 args.set_target_bash_version(arg.getArgument());
282 break;
283 case 'h':
284 std::cout << program_name << " " << bpp_compiler_version << std::endl
285 << help_intro << OptionParser.getHelpString();
286 args.set_exit_early(true);
287 return args;
288 break;
289 case 'I':
290 args.add_include_path(arg.getArgument());
291 break;
292 case 'o':
293 args.set_run_on_exit(false);
294 args.set_output_file(arg.getArgument());
295 break;
296 case 'p':
297 args.set_display_parse_tree(true);
298 break;
299 case 's':
300 args.set_suppress_warnings(true);
301 break;
302 case 't':
303 args.set_display_tokens(true);
304 break;
305 case 'v':
306 std::cout << program_name << " " << bpp_compiler_version << std::endl << copyright;
307 args.set_exit_early(true);
308 return args;
309 break;
310 }
311 }
312
313 return args;
314}
Represents the parsed command-line arguments given to the compiler.
Definition parse_arguments.h:66
void set_suppress_warnings(bool suppress)
Definition parse_arguments.h:213
void set_run_on_exit(bool run_on_exit)
Definition parse_arguments.h:241
void add_include_path(std::string_view path)
Adds a directory to the include paths.
Definition parse_arguments.h:203
void set_display_parse_tree(bool display)
Definition parse_arguments.h:227
bool run_on_exit() const
Definition parse_arguments.h:244
bool display_parse_tree() const
Definition parse_arguments.h:230
std::vector< char * > m_program_arguments
Definition parse_arguments.h:68
bool exit_early() const
Definition parse_arguments.h:237
const std::optional< std::string > & output_file() const
Definition parse_arguments.h:172
bool f_exit_early
Definition parse_arguments.h:77
std::optional< std::string > m_input_file
Definition parse_arguments.h:69
void set_program_arguments(int argc, char **argv)
Sets the program arguments to be passed to the compiled program.
Definition parse_arguments.h:92
bool suppress_warnings() const
Definition parse_arguments.h:216
void set_input_file(std::string_view input_file)
Sets the input file for the compiler.
Definition parse_arguments.h:109
const std::vector< char * > & program_arguments() const
Definition parse_arguments.h:96
bool f_display_parse_tree
Definition parse_arguments.h:75
void set_display_tokens(bool display)
Definition parse_arguments.h:220
bool f_display_tokens
Definition parse_arguments.h:74
bool output_to_file() const
Definition parse_arguments.h:252
void set_target_bash_version(std::string_view version_arg)
Parses a given target Bash version argument and updates the Arguments object accordingly.
Definition parse_arguments.h:185
const std::optional< std::string > & input_file() const
Definition parse_arguments.h:119
bool input_from_stdin() const
Definition parse_arguments.h:256
std::shared_ptr< std::vector< std::string > > m_include_paths
Definition parse_arguments.h:72
const BashVersion & target_bash_version() const
Definition parse_arguments.h:188
void set_output_file(std::string_view output_file_arg)
Parses a given output_file argument and updates the Arguments object accordingly.
Definition parse_arguments.h:133
std::optional< std::string > m_output_file
Definition parse_arguments.h:70
bool f_run_on_exit
Definition parse_arguments.h:76
bool f_suppress_warnings
Definition parse_arguments.h:73
void set_exit_early(bool exit)
Definition parse_arguments.h:234
const std::shared_ptr< std::vector< std::string > > & include_paths() const
Definition parse_arguments.h:209
bool display_tokens() const
Definition parse_arguments.h:223
bool output_to_stdout() const
Definition parse_arguments.h:248
BashVersion m_target_bash_version
Definition parse_arguments.h:71
Parses command-line arguments based on a set of defined options.
Definition xgetopt.h:548
@ NoArgument
Definition xgetopt.h:187
@ RequiredArgument
Definition xgetopt.h:188
@ AfterFirstNonOptionArgument
Definition xgetopt.h:532
Arguments parse_arguments(int argc, char *argv[])
Definition parse_arguments.h:261
constexpr XGetOpt::OptionParser< XGetOpt::Option< 'o', "output", "Specify output file (default: run on exit)", XGetOpt::RequiredArgument, "file">, XGetOpt::Option< 'b', "target-bash", "Compile to Bash version (default: 5.2)", XGetOpt::RequiredArgument, "version">, XGetOpt::Option< 's', "no-warnings", "Suppress warnings", XGetOpt::NoArgument >, XGetOpt::Option< 'I', "include", "Add directory to include path", XGetOpt::RequiredArgument, "directory">, XGetOpt::Option< 't', "tokens", "Display tokens from lexer (do not compile program)", XGetOpt::NoArgument >, XGetOpt::Option< 'p', "parse-tree", "Display parse tree (do not compile program)", XGetOpt::NoArgument >, XGetOpt::Option< 'v', "version", "Display version information and exit", XGetOpt::NoArgument >, XGetOpt::Option< 'h', "help", "Display this help message and exit", XGetOpt::NoArgument > > OptionParser
Definition parse_arguments.h:55
constexpr const char * help_intro
Definition parse_arguments.h:40
constexpr const char * copyright
Definition parse_arguments.h:26
constexpr const char * program_name
Definition parse_arguments.h:24
Represents a Bash version to target for code generation.
Definition BashVersion.h:22
Compile-time representation of a command-line option.
Definition xgetopt.h:208
#define bpp_compiler_updated_year
Definition updated_year.h:1
#define bpp_compiler_version
Definition version.h:1