1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2021 Gavin D. Howard and contributors. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * * Redistributions of source code must retain the above copyright notice, this 12 * list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright notice, 15 * this list of conditions and the following disclaimer in the documentation 16 * and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 * ***************************************************************************** 31 * 32 * Code for processing command-line arguments. 33 * 34 */ 35 36 #include <assert.h> 37 #include <ctype.h> 38 #include <stdbool.h> 39 #include <stdlib.h> 40 #include <string.h> 41 42 #ifndef _WIN32 43 #include <unistd.h> 44 #endif // _WIN32 45 46 #include <vector.h> 47 #include <read.h> 48 #include <args.h> 49 #include <opt.h> 50 #include <num.h> 51 52 /** 53 * Adds @a str to the list of expressions to execute later. 54 * @param str The string to add to the list of expressions. 55 */ 56 static void 57 bc_args_exprs(const char* str) 58 { 59 BC_SIG_ASSERT_LOCKED; 60 if (vm.exprs.v == NULL) bc_vec_init(&vm.exprs, sizeof(uchar), BC_DTOR_NONE); 61 bc_vec_concat(&vm.exprs, str); 62 bc_vec_concat(&vm.exprs, "\n"); 63 } 64 65 /** 66 * Adds the contents of @a file to the list of expressions to execute later. 67 * @param file The name of the file whose contents should be added to the list 68 * of expressions to execute. 69 */ 70 static void 71 bc_args_file(const char* file) 72 { 73 char* buf; 74 75 BC_SIG_ASSERT_LOCKED; 76 77 vm.file = file; 78 79 buf = bc_read_file(file); 80 81 assert(buf != NULL); 82 83 bc_args_exprs(buf); 84 free(buf); 85 } 86 87 static BcBigDig 88 bc_args_builtin(const char* arg) 89 { 90 bool strvalid; 91 BcNum n; 92 BcBigDig res; 93 94 strvalid = bc_num_strValid(arg); 95 96 if (BC_ERR(!strvalid)) 97 { 98 bc_verr(BC_ERR_FATAL_ARG, arg); 99 } 100 101 bc_num_init(&n, 0); 102 103 bc_num_parse(&n, arg, 10); 104 105 res = bc_num_bigdig(&n); 106 107 bc_num_free(&n); 108 109 return res; 110 } 111 112 #if BC_ENABLED 113 114 /** 115 * Redefines a keyword, if it exists and is not a POSIX keyword. Otherwise, it 116 * throws a fatal error. 117 * @param keyword The keyword to redefine. 118 */ 119 static void 120 bc_args_redefine(const char* keyword) 121 { 122 size_t i; 123 124 BC_SIG_ASSERT_LOCKED; 125 126 for (i = 0; i < bc_lex_kws_len; ++i) 127 { 128 const BcLexKeyword* kw = bc_lex_kws + i; 129 130 if (!strcmp(keyword, kw->name)) 131 { 132 if (BC_LEX_KW_POSIX(kw)) break; 133 134 vm.redefined_kws[i] = true; 135 136 return; 137 } 138 } 139 140 bc_error(BC_ERR_FATAL_ARG, 0, keyword); 141 } 142 143 #endif // BC_ENABLED 144 145 void 146 bc_args(int argc, char* argv[], bool exit_exprs, BcBigDig scale) 147 { 148 int c; 149 size_t i; 150 bool do_exit = false, version = false; 151 BcOpt opts; 152 BcBigDig newscale = scale, ibase = BC_BASE, obase = BC_BASE; 153 #if BC_ENABLE_EXTRA_MATH 154 char* seed = NULL; 155 #endif // BC_ENABLE_EXTRA_MATH 156 157 BC_SIG_ASSERT_LOCKED; 158 159 bc_opt_init(&opts, argv); 160 161 // This loop should look familiar to anyone who has used getopt() or 162 // getopt_long() in C. 163 while ((c = bc_opt_parse(&opts, bc_args_lopt)) != -1) 164 { 165 switch (c) 166 { 167 case 'e': 168 { 169 // Barf if not allowed. 170 if (vm.no_exprs) 171 { 172 bc_verr(BC_ERR_FATAL_OPTION, "-e (--expression)"); 173 } 174 175 // Add the expressions and set exit. 176 bc_args_exprs(opts.optarg); 177 vm.exit_exprs = (exit_exprs || vm.exit_exprs); 178 179 break; 180 } 181 182 case 'f': 183 { 184 // Figure out if exiting on expressions is disabled. 185 if (!strcmp(opts.optarg, "-")) vm.no_exprs = true; 186 else 187 { 188 // Barf if not allowed. 189 if (vm.no_exprs) 190 { 191 bc_verr(BC_ERR_FATAL_OPTION, "-f (--file)"); 192 } 193 194 // Add the expressions and set exit. 195 bc_args_file(opts.optarg); 196 vm.exit_exprs = (exit_exprs || vm.exit_exprs); 197 } 198 199 break; 200 } 201 202 case 'h': 203 { 204 bc_vm_info(vm.help); 205 do_exit = true; 206 break; 207 } 208 209 case 'i': 210 { 211 vm.flags |= BC_FLAG_I; 212 break; 213 } 214 215 case 'I': 216 { 217 ibase = bc_args_builtin(opts.optarg); 218 break; 219 } 220 221 case 'z': 222 { 223 vm.flags |= BC_FLAG_Z; 224 break; 225 } 226 227 case 'L': 228 { 229 vm.line_len = 0; 230 break; 231 } 232 233 case 'O': 234 { 235 obase = bc_args_builtin(opts.optarg); 236 break; 237 } 238 239 case 'P': 240 { 241 vm.flags &= ~(BC_FLAG_P); 242 break; 243 } 244 245 case 'R': 246 { 247 vm.flags &= ~(BC_FLAG_R); 248 break; 249 } 250 251 case 'S': 252 { 253 newscale = bc_args_builtin(opts.optarg); 254 break; 255 } 256 257 #if BC_ENABLE_EXTRA_MATH 258 case 'E': 259 { 260 if (BC_ERR(!bc_num_strValid(opts.optarg))) 261 { 262 bc_verr(BC_ERR_FATAL_ARG, opts.optarg); 263 } 264 265 seed = opts.optarg; 266 267 break; 268 } 269 #endif // BC_ENABLE_EXTRA_MATH 270 271 #if BC_ENABLED 272 case 'g': 273 { 274 assert(BC_IS_BC); 275 vm.flags |= BC_FLAG_G; 276 break; 277 } 278 279 case 'l': 280 { 281 assert(BC_IS_BC); 282 vm.flags |= BC_FLAG_L; 283 break; 284 } 285 286 case 'q': 287 { 288 assert(BC_IS_BC); 289 vm.flags &= ~(BC_FLAG_Q); 290 break; 291 } 292 293 case 'r': 294 { 295 bc_args_redefine(opts.optarg); 296 break; 297 } 298 299 case 's': 300 { 301 assert(BC_IS_BC); 302 vm.flags |= BC_FLAG_S; 303 break; 304 } 305 306 case 'w': 307 { 308 assert(BC_IS_BC); 309 vm.flags |= BC_FLAG_W; 310 break; 311 } 312 #endif // BC_ENABLED 313 314 case 'V': 315 case 'v': 316 { 317 do_exit = version = true; 318 break; 319 } 320 321 #if DC_ENABLED 322 case 'x': 323 { 324 assert(BC_IS_DC); 325 vm.flags |= DC_FLAG_X; 326 break; 327 } 328 #endif // DC_ENABLED 329 330 #ifndef NDEBUG 331 // We shouldn't get here because bc_opt_error()/bc_error() should 332 // longjmp() out. 333 case '?': 334 case ':': 335 default: 336 { 337 BC_UNREACHABLE 338 abort(); 339 } 340 #endif // NDEBUG 341 } 342 } 343 344 if (version) bc_vm_info(NULL); 345 if (do_exit) 346 { 347 vm.status = (sig_atomic_t) BC_STATUS_QUIT; 348 BC_JMP; 349 } 350 351 // We do not print the banner if expressions are used or dc is used. 352 if (!BC_IS_BC || vm.exprs.len > 1) vm.flags &= ~(BC_FLAG_Q); 353 354 // We need to make sure the files list is initialized. We don't want to 355 // initialize it if there are no files because it's just a waste of memory. 356 if (opts.optind < (size_t) argc && vm.files.v == NULL) 357 { 358 bc_vec_init(&vm.files, sizeof(char*), BC_DTOR_NONE); 359 } 360 361 // Add all the files to the vector. 362 for (i = opts.optind; i < (size_t) argc; ++i) 363 { 364 bc_vec_push(&vm.files, argv + i); 365 } 366 367 #if BC_ENABLE_EXTRA_MATH 368 if (seed != NULL) 369 { 370 BcNum n; 371 372 bc_num_init(&n, strlen(seed)); 373 374 BC_SIG_UNLOCK; 375 376 bc_num_parse(&n, seed, BC_BASE); 377 378 bc_program_assignSeed(&vm.prog, &n); 379 380 BC_SIG_LOCK; 381 382 bc_num_free(&n); 383 } 384 #endif // BC_ENABLE_EXTRA_MATH 385 386 BC_SIG_UNLOCK; 387 388 if (newscale != scale) 389 { 390 bc_program_assignBuiltin(&vm.prog, true, false, newscale); 391 } 392 393 if (obase != BC_BASE) 394 { 395 bc_program_assignBuiltin(&vm.prog, false, true, obase); 396 } 397 398 // This is last to avoid it affecting the value of the others. 399 if (ibase != BC_BASE) 400 { 401 bc_program_assignBuiltin(&vm.prog, false, false, ibase); 402 } 403 404 BC_SIG_LOCK; 405 } 406